目錄
- 1. 五種函式介紹
- 2. 左值&右值怎么區分?怎么看?
- 3. 匿名物件的3種使用情況
- 4. 代碼詳細驗證每個函式呼叫情況
- 4.1 測驗 f_1 函式(函式形參測驗 -- 值傳遞)
- 4.2 測驗 f_2 函式(函式形參測驗 -- 參考傳遞)
- 4.3 測驗 f_3 函式(函式回傳值測驗 -- 具名物件)
- 4.3.1 測驗代碼-1(初始化新物件)
- 4.3.2 測驗代碼-2(賦值給已存在物件)
- 4.4 測驗 f_4 函式(函式回傳值測驗 -- 匿名物件)
- 5. 完整測驗代碼
1. 五種函式介紹
建構式:負責物件的初始化作業,建構式可以多載,但不可以在建構式前加virtual
解構式:負責在撤銷物件前,完成清理作業(釋放記憶體),解構式不可以多載,一個類中有且只有一個解構式
拷貝建構式:一種特殊的建構式,用同類的物件去構造和初始化另一個物件,函式名和類名一致,只有一個引數,這個引數是一個被const修飾的本型別參考變數
賦值建構式:當一個類的物件向該類的另一個物件賦值時,就會用到該類的賦值函式,就是多載了=運算子,去完成對應的物件賦值操作(這里涉及深淺拷貝問題)
移動建構式:使用一個右值來初始化或賦值時,會呼叫移動建構式或移動賦值運算子來移動資源,從而避免拷貝,提高效率,
2. 左值&右值怎么區分?怎么看?
判斷方法: 可以取地址的變數就是左值,不可以的就是右值
| 型別 | 含義 |
|---|---|
| 泛左值 (glvalue) | 一個運算式,其值可確定某個物件或函式的標識 |
| 純右值 (prvalue) | 符合下列之一的運算式: ① 計算某個運算子的運算元的值(這種純右值沒有結果物件) ② 初始化某個物件(稱這種純右值有一個結果物件) |
| 亡值 (xvalue) | 代表它的資源能夠被重新使用的物件或位域的泛左值 (通過移動建構式) |
| 左值 (lvalue) | 非亡值的泛左值 |
| 右值 (rvalue) | 純右值或亡值 |
注意:亡值就是將亡值,同一個概念,
3. 匿名物件的3種使用情況
情況1:沒有被物件接收,執行完所在行之后立刻呼叫解構式,釋放其所占記憶體,
情況2:用來初始化物件,用匿名物件來初始化新物件時,可以這么理解:匿名物件被創建,但被用來初始化新物件,相當于匿名物件變成有名物件,因此只存在拷貝構造,
情況3:用來賦值給物件,用匿名物件來賦值給已存在物件時,此時會發生賦值構造,是會呼叫賦值函式的,同樣執行完該行之后匿名物件所占記憶體被釋放,
針對三種情況舉例測驗:
點擊展開[匿名物件]測驗代碼
class Person{
public:
// 解構式
~Person() { std::cout << "destroy...\n";}
// 建構式
Person() { std::cout << "default constructor...\n"; }
// 賦值函式
Person& operator=(const Person& p) {
std::cout << "assign function...\n";
return *this;
}
};
Person f() { return Person(); }
int main()
{
// 情況一:匿名物件沒有被物件接收
Person();
// 情況二:用建匿名物件來初始化物件
Person p_2 = Person(); // 等價于 Person p_2 = f();
// 情況三:用匿名物件來賦值給已存在物件
Person p_3;
p_3 = f();
return 0;
}
4. 代碼詳細驗證每個函式呼叫情況
代碼詳細簡單,每一步都做過注釋,很好理解,
點擊展開[函式呼叫]測驗代碼
class Person
{
public:
// 解構式
~Person() { std::cout << "destroy...\n";}
// 默認建構式
Person() { std::cout << "default constructor...\n"; }
// 拷貝建構式
Person(const Person& p) { std::cout << "copy constructor...\n"; }
// 移動建構式
//Person(Person&& p) { std::cout << "move constructor...\n"; }
// 賦值函式
Person& operator=(const Person& p) {
std::cout << "assign function...\n";
return *this;
}
};
// 1.呼叫拷貝建構式
void f_1(Person p) {}
// 2.不會呼叫拷貝建構式
void f_2(Person& p) {}
// 3.呼叫默認建構式
Person f_3() {
Person p;
return p;//注意p它是一個將亡值,如果此時存在移動構造是會呼叫移動構造的
}
// 4.呼叫默認建構式
Person f_4() { return Person(); }
4.1 測驗 f_1 函式(函式形參測驗 -- 值傳遞)
void f_1(Person p) {}
測驗代碼:
Person p_1;
f_1(p_1);
運行結果:

結果分析:
第一個默認構造:宣告 [物件p_1] 時呼叫;
第二個拷貝構造:[物件p_1] 傳值給 [形參物件p] 時呼叫;
第三個解構式:函式結束,形參物件p 占用記憶體被釋放,
4.2 測驗 f_2 函式(函式形參測驗 -- 參考傳遞)
void f_2(Person& p) {}
測驗代碼:
Person p_2;
f_2(p_2);
運行結果:

結果分析:
第一個默認構造:宣告 [物件p_2] 時呼叫;
注意:因為函式形參是該型別的物件參考,所以不存在拷貝構造,函式執行完后也就不存在記憶體被釋放,
4.3 測驗 f_3 函式(函式回傳值測驗 -- 具名物件)
Person f_3() {
Person p;
return p;//注意p它是一個將亡值,如果此時存在移動構造是會呼叫移動構造的
}
4.3.1 測驗代碼-1(初始化新物件)
Person p_3 = f_3();
運行結果:

結果分析:
第一個默認構造:函式體中宣告 [物件p] 時呼叫;
第二個拷貝構造:函式體中創建的 [物件p] 被回傳,然后賦值給 [物件p_3] 時呼叫(注意:由于函式體中的物件不是 [匿名物件],無法直接轉化為有名 [物件p_3],需要呼叫拷貝構造去將[物件p]的資料拷貝給[物件p_3]);
第三個解構式:是執行完拷貝構造之后,將在函式體中創建的 [物件p] 析構時呼叫,
重點: 如果此時類中存在 移動建構式(把注釋去掉),那么是不會呼叫拷貝建構式的,而是呼叫 移動建構式(避免大量資料的拷貝),結果如下:

4.3.2 測驗代碼-2(賦值給已存在物件)
Person p_3;
p_3 = f_3();
運行結果:

結果分析:
第一次建構式:宣告 [物件p_3] 時呼叫;
第二次建構式:函式體中宣告 [物件p] 時呼叫;
第三次拷貝構造:將函式體中宣告的 [物件p] 的資料拷貝給一個 [臨時物件] 時呼叫(如果把類中 移動建構式的注釋去掉 ,那么是不會呼叫拷貝建構式的,而是呼叫移動建構式);
第四次解構式:函式體中宣告的 [物件p] 被析構時呼叫;
第五次賦值函式:是將 [臨時變數] 的資料復制給 [物件p_3] 時呼叫;
第六次解構式:[臨時變數] 完成拷貝構造之后,呼叫解構式,釋放所占記憶體,
為什么不直接呼叫拷貝構造就完事了呢?
?在 【4.3.1 測驗代碼-1】中可知,用函式回傳的物件來初始化新物件時只需要呼叫一次拷貝構造或移動構造就行,是不存在復制函式被呼叫的情況,那為什么4.3.2 測驗代碼-2就需要呼叫?
原因剖析:
?原因一:執行Person p_3導致創建了 [物件p_3] ,而函式回傳的也是一個被創建的物件(不是匿名函式),在兩個已存在物件之間使用=是賦值操作,是不會呼叫拷貝構造或移動構造的,
?原因二:由于原因一,導致函式回傳的 [物件p] 沒用被使用,函式回傳的 [物件p] 會被解構式釋放其所占記憶體,
?因為原因二會導致函式回傳的 [物件p] 會被析構,注意此時都還沒有賦值給 [物件p_3] 咧,所以 [物件p] 在析構之前需要把 [物件p] 的資料拷貝給一個 [臨時物件] (呼叫拷貝構造),完成拷貝之后 [物件p] 就被析構,最后把 [臨時物件] 的資料賦值給已存的 [物件p_3] 即可 (呼叫賦值函式),完成賦值之后 [臨時物件] 就被析構,
?至此上面的所有步驟分析完成,
4.4 測驗 f_4 函式(函式回傳值測驗 -- 匿名物件)
Person f_4() { return Person(); }
測驗代碼:
Person p_4 = f_4();
運行結果:

結果分析:
宣告 [物件p_4] 的同時直接使用 [匿名物件] 去初始化,此時 [匿名物件] 會直接轉化成 [有名物件p_4] (匿名物件使用情況2),所以就這個情況而已不會呼叫拷貝建構式和移動建構式,
如果是如下分開情況(先宣告,再用匿名物件賦值):
Person p_4;
p_4 = f_4();
運行結果:

結果分析:
第一個默認構造:宣告 [物件p_4] 時呼叫;
第二個默認構造:函式體中的 [匿名物件] 被創建時呼叫的;
第三個賦值函式:[匿名物件] 賦值給已存在的 [物件p_4] 時呼叫(匿名物件使用情況3,用匿名物件來賦值給已存在物件時,此時會發生賦值構造,是會呼叫賦值函式的,同樣執行完該行之后匿名物件所占記憶體被釋放);
第四個解構式:完成賦值之后,[匿名物件] 所占記憶體被釋放時呼叫,
5. 完整測驗代碼
點擊展開完整測驗代碼
#include<iostream>
#include<string>
using std::cout;
using std::cin;
using std::endl;
using std::string;
class Person
{
public:
// 解構式
~Person() { std::cout << "destroy...\n"; }
// 默認建構式
Person() { std::cout << "default constructor...\n"; }
// 拷貝建構式
Person(const Person& p) { std::cout << "copy constructor...\n"; }
// 移動建構式
//Person(Person&& p) { std::cout << "move constructor...\n"; }
// 賦值函式
Person& operator=(const Person& p) {
std::cout << "assign function...\n";
return *this;
}
};
// 1.呼叫拷貝建構式
void f_1(Person p) {}
// 2.不會呼叫拷貝建構式
void f_2(Person& p) {}
// 3.呼叫默認建構式
Person f_3() {
Person p;
return p;//注意p它是一個將亡值(右值的一種)
}
// 4.呼叫默認建構式
Person f_4() { return Person(); }
int main()
{
//Person();
//Person p = Person();
cout << "------測驗f_1函式------\n";
Person p_1;
f_1(p_1);
cout << "-----------------------\n\n";
cout << "------測驗f_2函式------\n";
Person p_2;
f_2(p_2);
cout << "----------------------\n\n";
cout << "------測驗f_3函式------\n";
Person p_3;
p_3 = f_3();
//Person p_3 = f_3();
cout << "----------------------\n\n";
cout << "------測驗f_4函式------\n";
Person p_4 = f_4();
cout << "----------------------\n\n";
return 0;
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/547063.html
標籤:C++
上一篇:讀Java性能權威指南(第2版)筆記18_垃圾回收E
下一篇:obs錄屏核心流程分析
