拷貝構造函數是一種特殊的構造函數,它在創建對象時,是使用同一類中之前創建的對象來初始化新創建的對象。
如果在類中沒有定義拷貝構造函數,編譯器會自行定義一個。如果類帶有指針變量,並有動態內存分配,則它必須有一個拷貝構造函數。拷貝構造函數的最常見形式如下:
classname (const classname &obj) { // 構造函數的主體 }
在這里,obj 是一個對象引用,該對象是用於初始化另一個對象的。
1.拷貝構造函數被調用的三種情況
拷貝構造函數通常用於:
-
通過使用另一個同類型的對象來初始化新創建的對象。
-
復制對象把它作為參數傳遞給函數。
-
復制對象,並從函數返回這個對象。
1)當用一個對象去初始化同類的另一個對象時,會引發復制構造函數被調用。
Point p1 = Point(); //下面的兩條語句都會引發復制構造函數的調用。 Point p2 = Point(p1); Point p3 = p1;
注意,第二條語句是初始化語句,不是賦值語句。賦值語句的等號左邊是一個早已有定義的變量,賦值語句不會引發復制構造函數的調用。例如:
Point p1 = Point(); Point p3; p3 = p1;// 賦值語句不會引發復制構造函數的調用
這條語句不會引發拷貝構造函數的調用,因為p3早已生成,已經初始化過了。
2)如果函數Fun的參數是類Point的對象,那么當Fun被調用時,類Point的復制構造函數將被調用。換句話說,作為形參的對象,是用復制構造函數初始化的,而且調用復制構造函數時的參數,就是調用函數時所給的實參。
#include "Point.h" #include <iostream> using namespace std; void Fun(Point p); int main() { Point p1 = Point(); Fun(p1); return 0; } void Fun(Point p) { cout << "Fun run!\n"; }
運行結果
Copy constructor called!
Fun run!
這是因為Fun函數的形參p在初始化時調用了拷貝構造函數。
從這里也可以看出,函數的形參的值不一定等於實參,這取決於該對象所屬的拷貝構造函數是如何實現的。
以對象作為函數的形參,在函數被調用時,生成的形參要用拷貝構造函數初始化,會帶來時間上的開銷。如果用對象的引用而不是對象作為形參,就沒有這個問題了。但是以引用作為形參有一定的風險,因為這種情況下如果形參的值發生改變,實參的值也會跟着改變。
如果要確保實參的值不會改變,又希望避免復制構造函數帶來的開銷,解決辦法就是將形參聲明為對象的 const 引用。
void Fun(const Point& p);
3) 如果函數的返冋值是類Point的對象,則函數返冋時,類Point的復制構造函數被調用。換言之,作為函數返回值的對象是用復制構造函數初始化的,而調用復制構造函數時的實參,就是 return 語句所返回的對象。
#include "Point.h" #include <iostream> using namespace std; Point Fun(); int main() { Fun(); return 0; } Point Fun() { cout << "Fun run!\n"; Point p1 = Point(); return p1; }
運行結果:
Fun run!
Copy constructor called!