1. 程式人生 > >C++: 複製建構函式

C++: 複製建構函式

C++複製建構函式

標籤(空格分隔): C++

我們經常會需要用一個也已經存在的物件,去初始化新的物件,這時就需要一種特殊的建構函式——複製建構函式;
預設的複製建構函式可以實現對應資料成員一一複製;

複製建構函式定義

複製建構函式是一種特殊的建構函式,其形參為本類的物件引用。作用是用一個已存在的物件去初始化同類型的新物件。

class 類名 {
public :
    類名(形參);//建構函式
    類名(const 類名 &物件名);//複製建構函式
    // ...
};

類名::類( const 類名 &物件名)//複製建構函式的實現
{ 函式體 }

隱含的複製建構函式

  • 如果程式設計師沒有為類宣告拷貝初始化建構函式,則編譯器自己生成一個隱含的複製建構函式。
  • 這個建構函式執行的功能是:用作為初始值的物件的每個資料成員的值,初始化將要建立的物件的對應資料成員。

“=delete”

如果不希望物件被複制構造
▫ C++98做法:將複製建構函式宣告為private,並且不提供函式的實現。
▫ C++11做法:用“=delete”指示編譯器不生成預設複製建構函式。
例:

class Point { //Point 類的定義
public:
    Point(int xx=0, int yy=0) { x = xx; y = yy; } //建構函式,內聯
Point(const Point& p) =delete; //指示編譯器不生成預設複製建構函式 private: int x, y; //私有資料 };

複製建構函式被呼叫的三種情況

  • 定義一個物件時,以本類另一個物件作為初始值,發生複製構造;
  • 如果函式的形參是類的物件,呼叫函式時,將使用實參物件初始化形參物件,發生複製構造;
  • 如果函式的返回值是類的物件,函式執行完成返回主調函式時,將使用return語句中的物件初始化一個臨時無名物件,傳遞給主調函式,此時發生複製構造。

定義一個物件時,以本類另一個物件作為初始值,發生複製構造

class Point{
    public
: Point(int a, int b){x=a; y=b;} // 建構函式 Point(const Point & p); // 複製建構函式 void setX(int a){ x = a; } void setY(int b){ y = b; } int getX(){ return x; } int getY(){ return y; } private: int x,y; }; // 複製建構函式的實現 Point::Point(const Point &p){ x = p.x; y = p.y; } // 主函式 int main(int argc, char** argv) { Point point1(2,3); cout << point1.getX() << endl; Point point2(point1); cout << point2.getX() << endl; return 0; }

物件以值傳遞的方式傳入函式引數

class Point{
    public:
        Point(int a, int b){x=a; y=b;} // 建構函式 
        Point(const Point & p); //複製建構函式
        void setX(int a){ x = a; } 
        void setY(int b){ y = b; }
        int getX(){ return x; }
        int getY(){ return y; }
    private:
        int x,y;
};

// 複製建構函式的實現 
Point::Point(const Point &p){
    x = p.x;
    y = p.y;
}

// 形參為Point類物件void
void function1(Point p){
    cout << p.getY() << endl;
}

// 主函式 
int main(int argc, char** argv) {
    Point point1(2,3);
    function1(point1);
    return 0;
}

物件以值傳遞的方式從函式返回

class Point{
    public:
        Point(int a, int b){x=a; y=b;} // 建構函式 
        Point(const Point & p); //複製建構函式
        void setX(int a){ x = a; } 
        void setY(int b){ y = b; }
        int getX(){ return x; }
        int getY(){ return y; }
    private:
        int x,y;
};

// 複製建構函式的實現 
Point::Point(const Point &p){
    x = p.x;
    y = p.y;
}

//返回值為Point類物件
Point function2(){
    Point a(4, 5);
    return a;
} 

// 主函式 
int main(int argc, char** argv) {
    Point c = function2();
    cout << c.getY() << endl;
    return 0;
}

淺拷貝和深拷貝

預設拷貝建構函式

很多時候在我們都不知道拷貝建構函式的情況下,傳遞物件給函式引數或者函式返回物件都能很好的進行,這是因為編譯器會給我們自動產生一個拷貝建構函式,這就是“預設拷貝建構函式”,這個建構函式很簡單,僅僅使用“老物件”的資料成員的值對“新物件”的資料成員一一進行賦值,它一般具有以下形式:

Point(const Point & p){
    x = p.x;
    y = p.y;
}

當然,以上程式碼不用我們編寫,編譯器會為我們自動生成。

但是問題來了,請看下面問題:

class Point{
    public:
        Point(int a, int b); // 建構函式 
        ~Point(); // 解構函式 
        Point(const Point & p); //複製建構函式
        static int showCount(); // 顯示count數 
    private:
        int x,y;
        static int count;
};

// 建構函式 
Point::Point(int a, int b){
    x = a;
    y = b;
    count++;
}

// 解構函式
Point::~Point(){
    count--;
} 

// 複製建構函式的實現 
Point::Point(const Point &p){
    x = p.x;
    y = p.y;
}

// 顯示count數 
int Point::showCount(){
    return count; 
} 

// 初始化計數器 
int Point::count = 0;

// 主函式 
int main(int argc, char** argv) {
    Point point1(2,3);
    cout << "count=" << Point::showCount() << endl;
    Point point2(point1);
    cout << "count=" << Point::showCount() << endl;

    return 0;
}

這段程式碼對前面的類,加入了一個靜態成員,目的是進行計數。在主函式中,首先建立物件point1,輸出此時的物件個數,然後使用rect1複製出物件point2,再輸出此時的物件個數,按照理解,此時應該有兩個物件存在,但實際程式執行時,輸出的都是1,反應出只有1個物件。此外,在銷燬物件時,由於會呼叫銷燬兩個物件,類的解構函式會呼叫兩次,此時的計數器將變為負數。

問題來了:拷貝建構函式沒有處理靜態資料成員

重新編寫程式碼如下:

class Point{
    public:
        Point(int a, int b); // 建構函式 
        ~Point(); // 解構函式 
        Point(const Point & p); //複製建構函式
        static int showCount(); // 顯示count數 
    private:
        int x,y;
        static int count;
};

// 建構函式 
Point::Point(int a, int b){
    x = a;
    y = b;
    count++;
}

// 解構函式
Point::~Point(){
    count--;
} 

// 複製建構函式的實現 
Point::Point(const Point &p){
    x = p.x;
    y = p.y;
    count++;
}

// 顯示count數 
int Point::showCount(){
    return count; 
} 

// 初始化計數器 
int Point::count = 0;

// 主函式 
int main(int argc, char** argv) {
    Point point1(2,3);
    cout << "count=" << Point::showCount() << endl;
    Point point2(point1);
    cout << "count=" << Point::showCount() << endl;

    return 0;
}

問題解決

淺拷貝

所謂淺拷貝,指的是在物件複製時,只對物件中的資料成員進行簡單的賦值,預設拷貝建構函式執行的也是淺拷貝。大多情況下“淺拷貝”已經能很好地工作了,但是一旦物件存在了動態成員,那麼淺拷貝就會出問題了,讓我們考慮如下一段程式碼:

class Point{
    public:
        Point(int a, int b); // 建構函式 
        ~Point(); // 解構函式 
    private:
        int x,y;
        int* p;
};

// 建構函式 
Point::Point(int a, int b){
    x = a;
    y = b;
    p = new int(100);
}

// 解構函式
Point::~Point(){
    if(p != NULL){
        delete p;
    }
} 

// 主函式 
int main(int argc, char** argv) {
    Point point1(2,3);
    Point point2(point1);

    return 0;
}

在這段程式碼執行結束之前,會出現一個執行錯誤。原因就在於在進行物件複製時,對於動態分配的內容沒有進行正確的操作。我們來分析一下:

在執行定義point1物件後,由於在建構函式中有一個動態分配的語句,因此執行後的記憶體情況大致如下:

這裡寫圖片描述

在使用point1複製point2時,由於執行的是淺拷貝,只是將成員的值進行賦值,這時 point1.p = point2.p,也即這兩個指標指向了堆裡的同一個空間,如下圖所示:

這裡寫圖片描述

當然,這不是我們所期望的結果,在銷燬物件時,兩個物件的解構函式將對同一個記憶體空間釋放兩次,這就是錯誤出現的原因。我們需要的不是兩個p有相同的值,而是兩個p指向的空間有相同的值,解決辦法就是使用“深拷貝”。

深拷貝

在“深拷貝”的情況下,對於物件中動態成員,就不能僅僅簡單地賦值了,而應該重新動態分配空間,如上面的例子就應該按照如下的方式進行處理:

class Point{
    public:
        Point(int a, int b); // 建構函式 
        ~Point(); // 解構函式 
        Point(const Point & p); //複製建構函式
    private:
        int x,y;
        int* p;
};

// 建構函式 
Point::Point(int a, int b){
    x = a;
    y = b;
    p = new int(100);
}

// 解構函式
Point::~Point(){
    if(p != NULL){
        delete p;
    }
} 

// 複製建構函式的實現 
Point::Point(const Point &q){
    x = q.x;
    y = q.y;
    p = new int;    // 為新物件重新動態分配空間  
    *p = *(q.p); 
}


// 主函式 
int main(int argc, char** argv) {
    Point point1(2,3);
    Point point2(point1);

    return 0;
}

此時,在完成物件的複製後,記憶體的一個大致情況如下:

這裡寫圖片描述

此時point1的p和point22的p各自指向一段記憶體空間,但它們指向的空間具有相同的內容,這就是所謂的“深拷貝”。

相關推薦

C++ 複製建構函式和運算子過載示例

string1.h // // Created by lance on 10/16/18. // #ifndef CPP_PRIMER_STRING1_H #define CPP_PRIMER_STRING1_H #include <iostream> u

C++複製建構函式引數問題

今天遇到一個題, #include <iostream> using namespace std; class Sample { public: int v; // 在此處補充你的程式碼 }; void PrintAndDouble(Sample o) { c

C++ 複製建構函式和賦值運算子過載函式

宣告一個空的類testsize,sizeof(testsize)為1,為其宣告建構函式和解構函式,依舊為1 建構函式不能使用關鍵字virtual,解構函式可以 一旦類中存在虛擬函式,就會為該類生成虛擬函式表,並在每一個例項中新增一個指向虛擬函式表的指標,從而大小為一個指標大

C++複製建構函式&移動建構函式複製賦值運算子&移動賦值運算子

一、呼叫時機 1、複製建構函式呼叫的時機 ·物件在建立時使用其他的物件初始化 Person p(q); //此時複製建構函式被用來建立例項p Person p = q; //此時複製建構函式被用來在定義例項p時初始化p return_p()  //當函式返回該型別的物件

C++----複製建構函式(拷貝建構函式)--- 轉自CSDN

向作者致敬!也許很多C++的初學者都知道什麼是建構函式,但是對複製建構函式(copy constructor)卻還很陌生。對於我來說,在寫程式碼的時候能用得上覆制建構函式的機會並不多,不過這並不說明覆制建構函式沒什麼用,其實複製建構函式能解決一些我們常常會忽略的問題。       為了說明覆制建構函式作用,

C++: 複製建構函式

C++複製建構函式 標籤(空格分隔): C++ 我們經常會需要用一個也已經存在的物件,去初始化新的物件,這時就需要一種特殊的建構函式——複製建構函式; 預設的複製建構函式可以實現對應資料成員一一複製; 複製建構函式定義 複製建構函式是一種特殊的

C++複製建構函式,過載賦值運算子

C++的複製建構函式, 賦值建構函式, 有時候會有點暈,下面總結一下: 首先來談一下複製建構函式: 程式碼: #include<iostream> using namespace std; #include<cstring> #include<

C++複製建構函式與過載賦值操作符

內容整理自: 函式原型 在C++中建立一個類,這個類中肯定會包括建構函式、解構函式、複製建構函式和過載賦值操作。 複製建構函式是一種特殊的建構函式,其作用也是為類的成員初始化以及為物件的構造分配儲存空間。函式的名稱必須和類名稱一致,無返回型別,它的唯一的一個引數

c++ 複製建構函式示例程式

/******************************************************* *author:彭曉林 *copyright: 版權所有,翻版不究 *function: 複製建構函式測試程式 *************************

沒想通的關於複製建構函式C++程式碼: A obj=func()

class A { public: A(int x=0):i(x) { cout << "Normal Contructor: " << i << endl; } ~A() {

關於複製建構函式及關於類的簡單函式使用(C++)

剛學C++,關於複製建構函式的思考: 關於複製建構函式 : 相當於在記憶體新建一個類,再把資料複製給目標。可以是不完全複製(也是其存在意義),比如資料加減等。 三種使用情景: 1.用已知物件初始化信物件; 2.形參為物件; 3.返回值為物件; 簡單函式: 1.宣

c++】=過載,報錯:沒有合適的複製建構函式

不想看太多的朋友看這句就行了:在你的複製建構函式作為引數傳進去的那個物件前加個const. 是這樣的,在寫機器學習作業的時候,遇到了很多矩陣演算法,為了處理上的方便,我寫了一個矩陣類Matrix,如下

C++中類的建構函式複製建構函式

1 相關定義 1.1 建構函式 建構函式是類的特殊的成員函式,只要建立類型別的新物件,都要執行建構函式。建構函式的工作是保證每個物件的資料成員具有合適的初始值。建構函式的名字與類的名字相同,並且不能指定返回型別。像其他任何函式一樣,它們可以沒有形參,也可以定義多個形參。 1

C++在單繼承、多繼承、虛繼承時,建構函式複製建構函式、賦值操作符、解構函式的執行順序和執行內容

一、本文目的與說明     1. 本文目的:理清在各種繼承時,建構函式、複製建構函式、賦值操作符、解構函式的執行順序和執行內容。     2. 說明:雖然複製建構函式屬於建構函式的一種,有共同的地方,但是也具有一定的特殊性,所以在總結它的性質時將它單獨列出來了。  

C++結構體:預設建構函式複製建構函式,過載=運算子

C++結構體提供了比C結構體更多的功能,如預設建構函式,複製建構函式,運算子過載,這些功能使得結構體物件能夠方便的傳值。 比如,我定義一個簡單的結構體,然後將其作為vector元素型別,要使用的話,就需要實現上述三個函式,否則就只能用指標了。 #include

c++實現String類(建構函式,解構函式複製建構函式,各類運算子過載函式的編寫)

編寫類 String 的建構函式,解構函式,複製建構函式  需要過載下面的運算子: 1、<、>、==和!=比較運算子 2、+=連線運算子和賦值運算子 3、<<輸出運算子和>>輸入運算子 String.h #ifndef _STRING

google C++ 程式設計規範中的禁用複製建構函式和賦值運算子

在google C++程式設計規範中有下面一段描述: 僅在程式碼中需要拷貝一個類物件的時候使用拷貝建構函式;不需要拷貝時應使用 DISALLOW_COPY_AND_ASSIGN。 定義:通過拷貝新建物件時可使用拷貝建構函式(特別是物件的傳值時)。 優點:拷貝建

C++語法,複製建構函式與=運算子過載

1、物件在建立時使用其他的物件初始化 Person p(q); //此時複製建構函式被用來建立例項p Person p = q; //此時複製建構函式被用來在定義例項p時初始化p 2、物件作為函式的引數進行值傳遞時 f(p); //此時p作為函式的引數進行值傳遞,p入棧時會呼叫複製建構函式建立一

C++基礎知識(八)---函式返回值(返回值,返回指標,返回物件,返回引用)---引用---複製建構函式(拷貝建構函式

一、函式返回值   1.返回值: int test () { int a=1; return a; }   返回值時最簡單的方式,它的操作主要在棧上,變數a在函式結束後會刪除,為了返回a的值,系統會在內部建立一個臨時變數儲存a的值,以返回給呼叫該函式的表示式,呼叫結束後變數便

C++】三種呼叫類的複製建構函式的情況

用類的一個物件初始化同類的另一個物件時。 某函式的返回值是類的物件,呼叫該函式時。 某函式的形參是類的物件,呼叫該函式時。 ※注意區分“初始化”和“賦值”: ClassName c2 = c1;    (初始化語句) ClassName c1 , c2;