当前课程知识点:C++语言程序设计基础 > 第4章 类与对象 > 构造函数 > 复制构造函数
复制构造函数
复制构造函数是一种特殊的构造函数,其形参为本类的对象引用。作用是用一个已存在的对象去初始化同类型的新对象。
class类名 {
public :
类名(形参);//构造函数
类名(const 类名 &对象名);//复制构造函数
// ...
};
类名::类(const 类名&对象名)//复制构造函数的实现
{ 函数体 }
l 如果程序员没有为类声明拷贝初始化构造函数,则编译器自己生成一个隐含的复制构造函数。
l 这个构造函数执行的功能是:用作为初始值的对象的每个数据成员的值,初始化将要建立的对象的对应数据成员。
l 如果不希望对象被复制构造
n C++98做法:将复制构造函数声明为private,并且不提供函数的实现。
n C++11做法:用“=delete”指示编译器不生成默认复制构造函数。
l 例:
class Point { //Point 类的定义
public:
Point(int xx=0, int yy=0) { x = xx; y = yy; } //构造函数,内联
Point(const Point& p) =delete; //指示编译器不生成默认复制构造函数
private:
int x, y; //私有数据
};
l 定义一个对象时,以本类另一个对象作为初始值,发生复制构造;
l 如果函数的形参是类的对象,调用函数时,将使用实参对象初始化形参对象,发生复制构造;
l 如果函数的返回值是类的对象,函数执行完成返回主调函数时,将使用return语句中的对象初始化一个临时无名对象,传递给主调函数,此时发生复制构造。
n 这种情况也可以通过移动构造避免不必要的复制(第6章介绍)
大家好
欢迎回来继续学习
C++语言程序设计
这一节我们来学习复制构造函数
复制构造函数是什么呢
为什么我们需要复制构造函数呢
我们还是首先温故而知新
回忆一下
当我们定义一个
基本类型的变量的时候
是不是经常会用一个已经存在的
已经有值的这个变量
去初始化新定义的变量
我们定义对象的时候
也会经常有这样的需求
就是用一个已经存在的对象
去初始化新对象
这个时候
怎么实现这种初始化呢
像基本类型的数据
怎么样用已经有值的变量
去初始化同类型的新变量
这个编译器它能知道
因为什么
因为基本类型数据
是语法预先定义好的
而我们自定义的类的类型
它该怎么初始化
当然编译器不会默认知道的
这个时候怎么用一个对象
去复制新对象
就需要由我们自己来定义
C++语法为我们提供了一种
特殊的构造函数
叫做复制构造函数
在复制构造函数中
我们可以规定
如何用一个已经存在的对象
去初始化一个新对象
这个已经有了对象
可以用它的引用作为
构造函数的参数
当然了
我们在前面例子中
看到有些类的定义
并没有定义复制构造函数
那么如果我们不定义
复制构造函数会怎么样呢
如果你在定义类的时候
没有定义复制构造函数
编译器也会为我们生成一个
默认的复制构造函数
那么这个默认的复制构造函数
它干事吗
会不会也像默认的构造函数似的
什么初始化工作都不做呢
不是的
默认的复制构造函数
它还真干事
它会实现两个对象的
数据成员之间的一一对应复制
那么这样的一一对应复制
很多时候就已经令人满意了
那么我们就不用写
复制构造函数了
但是在一些时候呢
这个默认的复制构造函数
还不那么令人满意
那么这时候我们就可以利用
复制构造函数这个机制
我们自己来规定
如何进行复制构造
现在我们就来看看
程序中如何自己定义
复制构造函数
复制构造函数它是一种
特殊的构造函数
它的形参必须是本类对象的引用
那么复制构造函数的作用
就是用这个形参构造新的对象
那我们来看
在类里面
我们可以定义普通的构造函数
它的函数名就是类名
有一个形参表
这个形参表
可以是任意格式的形参
任意类型的形参
任意个数的形参都可以
但是接下来我们来看
另一个构造函数就很特殊了
它的参数类型
必须是本类对象的引用
而在这个参数前面呢
又加了一个const来限定
为什么
因为我们在第三章中学过
当我们传递引用作为参数的时候
实际上是可以双向传递数据的
也就是说
接收这个引用参数的函数
它在函数体中
如果对这个形参引用
做了任何修改
那么实参也会被同步修改
显然这不是我们写
复制构造函数的目的
我们写复制构造函数
是希望用形参引用
所指的那个对象
去初始化新对象
但绝不希望在这个初始化过程中
把原有的那个形参对象给修改了
那么怎么办呢
在这加一个const关键字
这就说明了这个引用
是一个常引用
我们只能使用这个引用
去读取它里面的数据
但是不能用这个引用
对它指向的对象进行修改
这样既能够传参数进来
又能够保证实参的安全性
接下来我们看
这个复制构造函数
在类外实现的时候
它跟其他的构造函数
实际上是一样的
前面要有个类名
后面函数名
复制构造函数的函数名也是类名
同样它不允许定义返回值
函数体中也不允许有return语句
除了在构造新对象的时候
用已有的对象做参数
去初始化它
这种情况以外
还有什么情况下
复制构造函数有可能被调用呢
一共有三种情况
是典型的要调用
复制构造函数的情况
我们来看看哪三种情况
首先当然就是我们知道的
定义一个对象的时候
用本类的另外一个对象
作为初始值
这个时候显然是发生了复制构造
第二种情况呢
就是如果一个函数的参数
是类的对象
那形参首先规定
它是一个类的对象
那么调用函数的时候呢
给出的实参
也应该是同类型的对象
这个时候用实参去初始化形参
做形实结合的时候
就发生了复制构造
还有一种情况
就是如果一个函数的返回值
是类的对象
函数执行完了要返回主调函数了
这个时候它有一个return语句
return语句中返回一个对象
给主调函数
那么在这个过程中
也是有可能发生复制构造的
如果我们在定义一个类的时候
没有为它定义复制构造函数
这个时候编译的时候
编译器就会生成一个
默认的复制构造函数
这个默认的复制构造函数的功能
就是用参数引用
所指向的这个对象
去初始化新对象
直接将数据成员一一对应
就这么一一对应着复制
很多时候呢
这样的功能也就够用了
在这一章呢
我们遇到的例题习题都比较简单
大家会直觉地感觉
这个默认的复制构造函数
就挺好用了
那我们还需要自己定义
复制构造函数吗
好
如果现在你写简单程序的时候
觉得自定义复制构造函数
没有必要
用默认就挺好
那你可以先这样用
后面当我们在第六章的时候
我就会给大家看相关的例题
如果类的成员中有指针的时候
很多情况下
默认的复制构造函数
它实现的浅层复制功能
就不够用了
这时候我们就需要做
深层的复制构造
那即使对于简单的程序
我们也可以再想一想
会不会
你有不同的复制构造的需求呢
比如说我们使用复印机的时候
有可能是要缩小复印
放大复印
把彩色的复印成黑白的
或者说覆盖住一部分
复印另一部分 都有可能
这是平常我们
进行复印的时候的经验
在程序中
当我们复制一个对象的时候
是不是也有可能说
我要做特定功能的复制构造
并不希望
原样不动地一一对应复制
而是只复制它的一部分功能
或者复制的时候
给它做一个小小的修改
我们可以说
这是个打引号的复制
只要你使用复制构造函数
就可以按照你自己的意愿
你自己的需求
去定义你认为合适的复制方式
而默认的复制构造函数
就是原样照应
有时候呢
我们不希望对象被复制构造
那这个时候
按照C++98标准的做法
我们就可以将复制构造函数
声明为私有的private类型
并且不提供函数体的实现
在C++11中有更好
更方便的办法
就是用delete关键字
来指示编译器
不生成默认的复制构造函数
现在我们看点类的定义
我们把它简化了
大家看
这里面只有一个构造函数
然后另外在复制构造函数之后
有一个等于delete
这个就是指示编译器
不要生成默认的复制构造函数
-导学
--第1章导学
-计算机系统简介
--计算机系统简介
--计算机系统简介 测试题
-计算机语言和程序设计方法的发展
--计算机语言和程序设计方法的发展 测试题
-面向对象的基本概念
--面向对象的基本概念 测试题
-程序的开发过程
--程序的开发过程
--程序的开发过程 测试题
-信息的表示和储存
--计算机的数字系统
--数据的编码表示
--信息的表示和储存 测试题
-实验指导
-导学
--第二章导学
-C++语言概述
--C++语言概述 测试题
-基本数据类型、常量、变量
--程序举例
--基本数据类型、常量、变量 测试题
-运算与表达式
--运算与表达式 测试题
-实验二:简单程序设计(上)
-数据的输入和输出
--数据的输入和输出
--数据的输入和输出 测试题
-选择结构
--if语句
--switch语句
--选择结构 测试题
-循环结构
--for语句
--循环结构 测试题
-自定义类型
--自定义类型
--自定义类型
-第2章小结
--第二章小结
-实验二:C++简单程序设计(下)
-导学
--导学
-函数定义
--函数定义
--函数定义 测试题
-函数调用
--例3-2
--例3-3
--例3-4
--例3-5
--例3-6
--函数调用 测试题
-嵌套与递归
--例3-9
--例3-10
--嵌套与递归 测试题
-函数的参数传递
--函数的参数传递
--函数的参数传递 测试题
-引用类型
--引用类型 测试题
-含有可变参数的函数
--含有可变参数的函数 测试题
-内联函数
--内联函数 测试题
-constexpr函数
--CONSTEXPR函数课后习题
-带默认参数值的函数
--带默认参数值的函数 测试题
-函数重载
--函数重载 测试题
-C++系统函数
--C++系统函数习题
-第3章小结
--第三章小结
-实验三(上)函数的应用
-实验三(下)函数的应用
-导学
--导学
-面向对象程序的基本特点
--面向对象程序的基本特点 测试题
-类和对象
--类和对象的定义
--类和对象 测试题
-构造函数
--构造函数基本概念
--委托构造函数
--复制构造函数
--构造函数 测试题
-析构函数
--析构函数
--析构函数 测试题
-类的组合
--类的组合
--类的组合程序举例
--前向引用声明
--类的组合 测试题
-UML简介
--UML简介
--UML简介课后习题
-结构体与联合体
--结构体与联合体 测试题
-枚举类
--枚举类
--枚举类 测试题
-第4章小结
--第四章小结
-实验四(上)
--实验四(上)
-实验四(下)
--实验四(下)
-导学
--导学
-标识符的作用域与可见性
--标识符的作用域与可见性 测试题
-对象的生存期
--对象的生存期
--对象的生存期 测试题
-类的静态成员
--类的静态成员 测试题
-类的友元
--类的友元 测试题
-共享数据的保护
--共享数据的保护 测试题
-多文件结构和预编译命令
--多文件结构和预编译命令 测试题
-第5章小结
--小结
-实验五
--实验五
-导学
--导学
-数组的定义与初始化
--数组的定义与使用
--一维数组应用举例
--数组的定义与初始化 测试题
-数组作为函数的参数
--数组作为函数的参数 测试题
-对象数组
--对象数组
--对象数组 测试题
-基于范围的for循环
-指针的定义和运算
--指针的定义和运算 测试题
-综合实例
--综合实例
-实验六(上)
--实验六上
-指针与数组
--指针数组
--指针与数组 测试题
-指针与函数
--指针类型的函数
--指向函数的指针
--指针与函数 测试题
-对象指针
--对象指针
--对象指针 测试题
-动态内存分配
--动态内存分配 测试题
-智能指针
--智能指针
-vector对象
--vector对象
--vector对象 测试题
-对象复制与移动
--移动构造
--对象复制与移动 测试题
-字符串
--C风格字符串
--string类
--字符串 测试题
-第6章小结
--第六章小结
-综合实例
--综合实例
-实验六(下)
--实验六(下)