2019獨角獸企業重金招聘Python工程師標準>>>
前言
C++的函數參數和返回分為按值傳遞和按引用傳遞,應用到類上面,會涉及到類的 賦值操作 復制函數 構造函數 析構函數
雖然java開發了兩年,但對我而言c++我還只是一個初學者.c++還有很多陌生的特性需要自己親自探索.這里用實際例子作為探索,不對之處望斧正:
由于基本類型和類在函數里的生命周期是相似的,這里以一個自定義類為例,一是為了方便觀察對象的地址,二是可以了解每一步會涉及到類的哪些生命周期:
類的定義
#include <iostream>using std::cout;
using std::endl;
using std::string;class A {
public:A() {cout << "構造函數:" << this << endl;}A(const A &input) {cout << "復制函數:" << this << endl;}// 復制直接返回 *this 本身A &operator=(const A &input) {cout << "賦值函數:" << this << endl;// this 其實是個新的內存空間,啥值都沒設置return *this;}~A() {cout << "析構函數:" << this << endl;}
};
值傳遞 和 返回值
我們定義一個值傳遞的函數,返回的也是值
A test1(A a) {cout << "test1函數體:&a=" << &a << endl;return a;
}
執行main函數如下
int main() {cout << "--- 對象創建 ---" << endl;A a;cout << "--- 函數調用 ---" << endl;A b = test1(a);cout << "--- 結束 ---" << endl;return 0;
}
執行結果
--- 對象創建 ---
構造函數:0x7fff5ed19768
--- 函數調用 ---
復制函數:0x7fff5ed19750
test1函數體:&a=0x7fff5ed19750
復制函數:0x7fff5ed19758
析構函數:0x7fff5ed19750
--- 結束 ---
析構函數:0x7fff5ed19758
析構函數:0x7fff5ed19768
由此可見,在函數的棧幀里,對入參a進行了一次復制(0x7fff5ed19750),而在返回棧幀里的input時,又會對函數結果復制生成一個臨時變量(0x7fff5ed19758),并回收棧幀里的input. 函數結束會把臨時變量返回給b,至此結束函數調用.也就是說,這里函數的入參和返回各涉及一次復制操作
函數入參和返回使用引用
我們對test1做出修改,將入參和返回變更為引用,如下
A &test1(A &a) {cout << "test1函數體:&a=" << &a << endl;return a;
}
將main中的b也變更為引用如下:
int main() {cout << "--- 對象創建 ---" << endl;A a;cout << "--- 函數調用 ---" << endl;A &b = test1(a);cout << "--- 結束 ---" << endl;return 0;
}
執行結果:
--- 對象創建 ---
構造函數:0x7fff56dd5768
--- 函數調用 ---
test1函數體:&a=0x7fff56dd5768
--- 結束 ---
析構函數:0x7fff56dd5768
因為是引用函數棧幀內并沒有進行任何復制操作
函數返回的臨時變量探索
如果函數不返回引用,或b不是引用,仍然會產生臨時變量
A test1(A &a) {cout << "test1函數體:&a=" << &a << endl;return a;
}
A& test2(A &a) {cout << "test2函數體:&a=" << &a << endl;return a;
}
int main() {cout << "--- 對象創建 ---" << endl;A a;cout << "--- 函數調用1 ---" << endl;A b1 = test1(a);cout << "--- 函數調用2 ---" << endl;A b2 = test2(a);cout << "--- 結束 ---" << endl;return 0;
}
返回結果如下
--- 對象創建 ---
構造函數:0x7fff5daae758
--- 函數調用1 ---
test1函數體:&a=0x7fff5daae758
復制函數:0x7fff5daae748
--- 函數調用2 ---
test2函數體:&a=0x7fff5daae758
復制函數:0x7fff5daae740
--- 結束 ---
析構函數:0x7fff5daae740
析構函數:0x7fff5daae748
析構函數:0x7fff5daae758
test1和test2的區別只在于返回結果是否為引用,但在棧幀外都產生了臨時變量
賦值操作探索
這里再對賦值做探索,這次把以上3中情況合在一起為例,如下
A &test1(A &a) {cout << "test1函數體:&a=" << &a << endl;return a;
}
A &test2(A a) {cout << "test2函數體:&a=" << &a << endl;return a;
}
A test3(A a) {cout << "test2函數體:&a=" << &a << endl;return a;
}
int main() {cout << "--- 對象創建 ---" << endl;A a;cout << "--- 函數調用1 ---" << endl;a = test1(a);cout << "--- 函數調用2 ---" << endl;a = test2(a);cout << "--- 函數調用3 ---" << endl;a = test3(a);cout << "--- 結束 ---" << endl;return 0;
}
輸出結果
--- 對象創建 ---
構造函數:0x7fff513b6748
--- 函數調用1 ---
test1函數體:&a=0x7fff513b6748
賦值函數:0x7fff513b6748
--- 函數調用2 ---
復制函數:0x7fff513b6738
test2函數體:&a=0x7fff513b6738
賦值函數:0x7fff513b6748
析構函數:0x7fff513b6738
--- 函數調用3 ---
復制函數:0x7fff513b6728
test2函數體:&a=0x7fff513b6728
復制函數:0x7fff513b6730
賦值函數:0x7fff513b6748
析構函數:0x7fff513b6730
析構函數:0x7fff513b6728
--- 結束 ---
析構函數:0x7fff513b6748
- test1 相當于
a=a;
除了將值賦值給自己外棧幀沒有額外操作,這個很好理解 - test2 對入參進行復制,并把復制的入參值賦值給a并在函數棧幀結束后回收入參
- test3 比較復雜,會復制入參和臨時變量,并把臨時變量賦值給a,賦值結束后對入參和臨時變量進行回收