## 一.对象的赋值
1. **实质操作** 把对象的每个数据成员的值赋值给运算符左边的同类型对象。
2. **操作对象** 数据成员。
>[warning]不会复制成员函数,因为相同类型的对象共用成员函数的代码。
## 二.拷贝构造函数
### 1.作用
+ 指导编译器为对象分配空间
+ 完成对象的初始化操作
### 2.特点
+ **无名函数** 因为类型名不能作为函数名。
+ **不可指定返回类型** 不能指定任何返回类型,即使void也不行。
+ **支持成员初始化列表**
### 3.定义和声明
类似于构造函数,但第一个参数必须为对象的引用。
为支持常对象,一般第一个参数设置为常引用。
```c++
//一般旧对象那里会加上 const ,防止原对象被修改。
//如需多个参数,后面的参数需要加默认值
类名(类名& 旧对象)
{
//code here
}
```
### 4.调用时机
用 **同类型的对象** 拷贝初始化正在被创建的 **同类型的对象** 时。即:
+ 使用一个已存在的同类型对象初始化另一个正在被创建同类型对象。
+ 从函数传递一个对象(而不是对象的引用)。
+ 从函数返回一个对象(而不是对象的引用)。
对于派生类的拷贝构造函数,若类类型成员没有出现在成员初始化列表,则类类型成员执行 **无参构造函数** 。
>[warning] \[17+\]\[17-的编译器可能支持\] 若使用函数返回的对象创建一个新对象(或没有变量接受返回值),则有可能发生复制消除。
```c++
#include <iostream>
using namespace std;
class Complex
{
private:
double real_;
double imaginary_;
public:
Complex() : real_(0), imaginary_(0)
{
cout << "已调用无参构造函数" << endl;
}
Complex(double real) : real_(real), imaginary_(0)
{
cout << "已调用普通构造函数" << endl;
}
Complex(double real, double imaginary) : real_(real), imaginary_(imaginary)
{
cout << "已调用普通构造函数" << endl;
}
Complex(const Complex &old)
{
this->real_ = old.real_;
this->imaginary_ = old.imaginary_;
cout << "已调用拷贝构造函数" << endl;
}
double get_real()
{
return this->real_;
}
double get_imaginary()
{
return this->imaginary_;
}
Complex &set_real(double new_val)
{
this->real_ = new_val;
return *this;
}
Complex &set_imaginary(double new_val)
{
this->imaginary_ = new_val;
return *this;
}
};
void pass_a_complex(Complex c) //拷贝初始化,调用拷贝构造函数
{
}
Complex return_a_complex()
{
return Complex(10, 10); //直接初始化,普通构造函数
}
int main()
{
Complex a; //默认初始化,调用无参构造函数
Complex b(10, 10); //直接初始化,调用普通构造函数
Complex c = 10; //拷贝初始化,调用普通构造函数
Complex d = c; //拷贝初始化,调用拷贝构造函数
Complex e(a); //拷贝初始化,调用拷贝构造函数
Complex f = return_a_complex(); //拷贝初始化,可能调用两次拷贝构造函数,也可能不调用任何构造函数(复制消除)
pass_a_complex(b); //向函数传递对象,调用拷贝构造函数
return_a_complex(); //从函数返回对象,可能会调用拷贝构造函数
return 0;
}
```
>[test-gpp]
> 已调用无参构造函数
> 已调用普通构造函数
> 已调用普通构造函数
> 已调用拷贝构造函数
> 已调用拷贝构造函数
> 已调用普通构造函数
> 已调用拷贝构造函数
> 已调用普通构造函数
>[test-vc]
> 已调用无参构造函数
> 已调用普通构造函数
> 已调用普通构造函数
> 已调用拷贝构造函数
> 已调用拷贝构造函数
> 已调用普通构造函数
> 已调用拷贝构造函数
> 已调用拷贝构造函数
> 已调用拷贝构造函数
> 已调用普通构造函数
> 已调用拷贝构造函数
### 5.默认拷贝构造函数
如用户未定义拷贝构造函数,则编译器会产生一个默认的拷贝构造函数。在默认的拷贝构造函数中,每个成员直接逐个复制。类类型成员调用 **拷贝构造函数** 。
>当类满足下列条件时,编译器 **不会** 自动创建默认的拷贝构造函数:
>
>+ 有用户定义的拷贝构造函数
>当类满足下列条件时,编译器会将拷贝构造函数定义为弃置的(`delete`)(C++11之前,这些情况下编译器不会自动创建默认的拷贝构造函数):
>+ 非静态类类型数据成员或基类没有拷贝构造函数,或拷贝构造函数不能访问(例如将拷贝构造函数设置为私有)。
>+ 非静态类类型数据成员或基类没有析构函数,或析构函数不能访问(例如将析构函数设置为私有)。
>+ [11+]有用户定义的移动构造函数和移动赋值操作。
## 三.拷贝赋值操作
### 1.作用
+ 完成对象之间的赋值
### 2.特点
1. **有名**
2. **有参**
3. **有返回值**
### 3.定义与声明
+ **重载赋值运算符** 参数为对象的引用,返回值为自身的引用。
+ **只能作为成员函数**
+ **不允许默认参数**
### 4.调用时机
+ 对象赋值(不是对象的初始化)
### 5.默认拷贝赋值操作
>当类满足下列条件时,编译器 **不会** 自动创建默认的拷贝赋值操作:
>
>+ 有用户定义的拷贝赋值操作
>当类满足下列条件时,编译器会将拷贝构造函数定义为弃置的(`delete`)(C++11之前,这些情况下编译器不会自动创建默认的拷贝构造函数):
>+ 非静态类类型数据成员或基类没有拷贝构造函数,或拷贝构造函数不能访问(例如将拷贝赋值操作设置为私有)。
>+ 非静态类类型数据成员或基类没有析构函数,或析构函数不能访问(例如将析构函数设置为私有)。
>+ [11+]有用户定义的移动构造函数和移动赋值操作。
>+ 某个非静态数据成员是引用类型。
>+ 某个非静态数据成员是常量或常量数组。
## 四.合成的拷贝函数与动态内存分配
- 如果数据成员的值是一个动态分配的地址,那么在对象复制的时候,程序仅仅把地址复制过来,而不会重新创建新的空间。这样,两个对象的某个数据成员会指向同一片内存区域。 **在对象析构的时候,会重复删除同一片动态内存空间** 。
+ 解决问题的办法就是自定义拷贝函数。两种设计方法:
+ **深拷贝法** 复制对象的时候重新创建空间。
+ **优点** 简单,每个对象拥有独立的空间。
+ **缺点** 内存开销较大,速度慢。(时间复杂度和空间复杂度高,为线性级)
+ 在C++中,对象的复制一般使用 **深拷贝法** 。
+ **浅拷贝法** 设置一个数据成员 `power` ,对象复制的时候,将运算符右边的对象的 `power` 设置为 `false`。析构的时候,若 `power` 为 `false` 则不删除动态空间。
+ **优点** 速度快,内存开销小。(时间复杂度和空间复杂度低,为常数级)
+ **缺点** 不同对象之间占用同一片内存区域,修改那一块区域的值,所有对象获得的数据都会改变。
+ 其他的语言如 `python` 和 `JavaScript` 复制一个容器,默认使用浅拷贝法。
- 阅读说明
- 1.1 概述
- C++基础
- 1.2 变量与常量
- 1.2.1 变量
- 1.2.2 字面值常量
- 字符型常量
- 数值型常量
- 1.2.3 cv限定符
- 1.3 作用域
- 1.3.1 标识符
- 1.3.2 *命名空间
- 1.3.3 作用域
- 1.3.4 可见性
- 1.4 数据类型
- 1.4.1 概述
- 1.4.2 处理类型
- 类型别名
- * auto说明符
- * decltype说明符
- 1.4.3 数组
- 1.4.4 指针
- 1.4.5 引用
- 1.5 表达式
- 1.5.1 概述
- 1.5.2 值的类别
- 1.5.3 *初始化
- 1.5.4 运算符
- 算术运算符
- 逻辑和关系运算符
- 赋值运算符
- 递增递减运算符
- 成员访问运算符
- 位运算符
- 其他运算符
- 1.5.5 *常量表达式
- 1.5.6 类型转换
- 第2章 面向过程编程
- 2.1 流程语句
- 2.1.1 条件语句
- 2.1.2 循环语句
- 2.1.3 跳转语句
- 2.1.4 *异常处理
- 2.2 函数
- 2.2.1 概述
- 2.2.2 函数参数
- 2.2.3 内置函数
- 2.2.4 函数重载
- 2.2.5 * 匿名函数
- 2.3 存储和生命期
- 2.3.1 生命周期与存储区域
- 2.3.2 动态内存
- 2.4 *预处理命令
- 第3章 面向对象编程
- 3.1 概述
- 3.2 类和对象
- 3.3 成员
- 3.3.1 访问限制
- 3.3.2 常成员
- 3.3.3 静态成员
- 3.3.4 成员指针
- 3.3.5 this指针
- 3.4 特殊的成员函数
- 3.4.1 概述
- 3.4.2 构造函数
- 3.4.3 析构函数
- 3.4.4 拷贝语义
- 3.4.5 * 移动语义
- 3.5 友元
- 3.6 运算符重载与类型转换
- 3.6.1 概述
- 3.6.2 重载方法
- 3.6.3 类型转换
- 3.7 继承与多态性
- 3.7.1 概述
- 3.7.2 派生类
- 3.7.3 子类型
- 3.7.4 虚基类
- 3.7.5 虚函数
- 3.7.6 抽象类
- 3.8 模板与泛型
- 3.8.1 概述
- 3.8.2 模板类型
- 3.8.3 *模板参数
- 3.8.4 *模板编译
- 3.8.5 *模板推断
- 3.8.6 *实例化与特例化
- 第4章 C++标准库
- 4.1 概述
- 4.2 输入输出流
- 4.2.1 概述
- 4.2.2 *流的状态
- 4.2.3 *常用流
- 4.2.4 *格式化I/O
- 4.2.5 *低级I/O
- 4.2.6 *随机访问
- 4.3 *C输入输出
- 4.3.1 *字符输入输出
- 4.3.2 *格式化输入输出
- 4.4 * 容器
- 4.4.1 * 概述
- 4.4.2 * 基本操作
- 4.4.3 * 顺序容器
- 4.4.4 * 迭代器
- 4.4.5 * 容器适配器
- 4.5 * 泛型算法
- 4.6 * 内存管理
- 4.6.1 * 自动指针
- 4.7 * 其他设施