之所以要把它們放在一起,是因為在使用C/C++類語言的時候,很容易混淆這幾個概念(對Java來說完全沒有這樣的問題,表示Javaor完全沒有壓力)。
先建立一個測試類(包含.h和.cpp)
//~ Person.h #ifndef PERSON_H_ #define PERSON_H_ #include <iostream> class Person { private: static int counter; public: Person() { counter++; std::cout << "構造函數" << std::endl; std::cout << "counter:" << counter << std::endl; } Person(const Person& pr) { counter++; std::cout << "拷貝構造函數" << std::endl; std::cout << "counter:" << counter << std::endl; } Person& operator=(const Person& pr) { std::cout << "賦值運算函數" << std::endl; return *this; } virtual ~Person() { counter--; std::cout << "析構函數" << std::endl; std::cout << "counter:" << counter << std::endl; } }; #endif /* PERSON_H_ */ //~ Person.cpp #include "Person.h" int Person::counter = 0;
通常重載賦值運算符容易遺忘,但是它真的很重要。所以推薦,如果你記得重載拷貝構造就一定要對賦值運算符做對應處理。
下面看看它們在什么情況下發生作用,然后再做簡要說明。
1.作為自動變量聲明
#include "Person.h" int main() { Person p1; }
運行結果:
構造函數
counter:1
析構函數
counter:0
說明:作為自動變量聲明的時候,p1變量保存在堆棧中,main函數結束的時候會自動調用析構函數。
2.作為自由變量聲明
#include "Person.h" int main() { Person * p1 = new Person(); }
運行結果:
構造函數
counter:1
說明:自由變量聲明,p1變量保存在heap(堆)中,內存不會被自動釋放。因此必須在使用完成以后手工調用“delete p1”,切記。
3.使用另一個對象構造
#include "Person.h" int main() { Person p1; Person p2 = p1; }
運行結果:
構造函數
counter:1
拷貝構造函數
counter:2
析構函數
counter:1
析構函數
counter:0
說明:p1對象使用構造函數,p2對象使用拷貝構造。由於它們都是自動變量,因此在函數結束時會自動調用析構函數。
4.賦值運算
#include "Person.h" int main() { Person p1; Person p2; p2 = p1; }
運行結果:
構造函數
counter:1
構造函數
counter:2
賦值運算函數
析構函數
counter:1
析構函數
counter:0
說明:我特意把“賦值運算函數”加色。目的是讓讀者清楚賦值運算和拷貝構造本質上是不同的,因為他們調用不同的函數。雖然在大多數情況下,結果是相同的。
5.指針賦值
#include "Person.h" int main() { Person* p1 = new Person; Person* p2 = p1;
p2 = p1; delete p1; }
運行結果:
構造函數
counter:1
析構函數
counter:0
說明:這里發生的事情其實就是在Java中發生的事情。除了第一條語句使用了構造函數,第2和第3條語句都僅僅是做指針的賦值。
6.函數調用一
#include "Person.h" Person func(Person p); // 調用對象原型 int main() { Person p; // 構造 func(p); } // 析構#2 和 析構p Person func(Person p) { // 拷貝構造#1 return p; // 拷貝構造#2 } // 析構#1
運行結果:
構造函數
counter:1
拷貝構造函數
counter:2
拷貝構造函數
counter:3
析構函數
counter:2
析構函數
counter:1
析構函數
counter:0
說明:函數需要調用對象原型,並且也返回一個對象原型。之所以叫返回 一個 對象原型是因為它真的是返回了另一個對象。
7.函數調用二
#include "Person.h" Person& func(Person & p); // 調用對象引用 int main() { Person p; func(p); } Person& func(Person & p) { return p; }
運行結果:
構造函數
counter:1
析構函數
counter:0
說明:調用引用不會構造新的對象。