## 一.對象的賦值
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 * 其他設施