日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

c++詳細講解構造函數的拷貝流程_C 語言

作者:panamera12 ? 更新時間: 2022-07-01 編程語言
#include <iostream>
#include <string>
using namespace std;
void func(string str){
    cout<<str<<endl;
}
int main(){
    string s1 = "http:www.biancheng.net";
    string s2(s1);
    string s3 = s1;
    string s4 = s1 + " " + s2;
    func(s1);
    cout<<s1<<endl<<s2<<endl<<s3<<endl<<s4<<endl;
    return 0;
}

運行結果:

http:www.biancheng.net

http:www.biancheng.net

http:www.biancheng.net

http:www.biancheng.net

http:www.biancheng.net http:www.biancheng.net

s1、s2、s3、s4 以及 func() 的形參 str,都是使用拷貝的方式來初始化的。

對于 s1、s2、s3、s4,都是將其它對象的數據拷貝給當前對象,以完成當前對象的初始化。

對于 func() 的形參 str,其實在定義時就為它分配了內存,但是此時并沒有初始化,只有等到調用 func() 時,才會將其它對象的數據拷貝給 str 以完成初始化。

當以拷貝的方式初始化一個對象時,會調用一個特殊的構造函數,就是拷貝構造函數(Copy Constructor)。

#include <iostream>
#include <string>
using namespace std;
class Student{
public:
    Student(string name = "", int age = 0, float score = 0.0f);  //普通構造函數
    Student(const Student &stu);  //拷貝構造函數(聲明)
public:
    void display();
private:
    string m_name;
    int m_age;
    float m_score;
};
Student::Student(string name, int age, float score): m_name(name), m_age(age), m_score(score){ }
//拷貝構造函數(定義)
Student::Student(const Student &stu){
    this->m_name = stu.m_name;
    this->m_age = stu.m_age;
    this->m_score = stu.m_score;
    cout<<"Copy constructor was called."<<endl;
}
void Student::display(){
    cout<<m_name<<"的年齡是"<<m_age<<",成績是"<<m_score<<endl;
}
int main(){
   const Student stu1("小明", 16, 90.5);
    Student stu2 = stu1;  //調用拷貝構造函數
    Student stu3(stu1);  //調用拷貝構造函數
    stu1.display();
    stu2.display();
    stu3.display();
    return 0;
}

運行結果:

Copy constructor was called.

Copy constructor was called.

小明的年齡是16,成績是90.5

小明的年齡是16,成績是90.5

小明的年齡是16,成績是90.5

第 8 行是拷貝構造函數的聲明,第 20 行是拷貝構造函數的定義??截悩嬙旌瘮抵挥幸粋€參數,它的類型是當前類的引用,而且一般都是 const 引用。

1) 為什么必須是當前類的引用呢?

如果拷貝構造函數的參數不是當前類的引用,而是當前類的對象,那么在調用拷貝構造函數時,會將另外一個對象直接傳遞給形參,這本身就是一次拷貝,會再次調用拷貝構造函數,然后又將一個對象直接傳遞給了形參,將繼續調用拷貝構造函數……這個過程會一直持續下去,沒有盡頭,陷入死循環。

只有當參數是當前類的引用時,才不會導致再次調用拷貝構造函數,這不僅是邏輯上的要求,也是 C++ 語法的要求。

2) 為什么是 const 引用呢?

拷貝構造函數的目的是用其它對象的數據來初始化當前對象,并沒有期望更改其它對象的數據,添加 const 限制后,這個含義更加明確了。

另外一個原因是,添加 const 限制后,可以將 const 對象和非 const 對象傳遞給形參了,因為非 const 類型可以轉換為 const 類型。如果沒有 const 限制,就不能將 const 對象傳遞給形參,因為 const 類型不能轉換為非 const 類型,這就意味著,不能使用 const 對象來初始化當前對象了。

當然,你也可以再添加一個參數為非const 引用的拷貝構造函數,這樣就不會出錯了。換句話說,一個類可以同時存在兩個拷貝構造函數,一個函數的參數為 const 引用,另一個函數的參數為非 const 引用。

class Base{
public:
    Base(): m_a(0), m_b(0){ }
    Base(int a, int b): m_a(a), m_b(b){ }
private:
    int m_a;
    int m_b;
};
int main(){
    int a = 10;
    int b = a;  //拷貝
    Base obj1(10, 20);
    Base obj2 = obj1;  //拷貝
    return 0;
}

b 和 obj2 都是以拷貝的方式初始化的,具體來說,就是將 a 和 obj1 所在內存中的數據按照二進制位(Bit)復制到 b 和 obj2 所在的內存,這種默認的拷貝行為就是淺拷貝,這和調用 memcpy() 函數的效果非常類似。

對于簡單的類,默認的拷貝構造函數一般就夠用了,我們也沒有必要再顯式地定義一個功能類似的拷貝構造函數。但是當類持有其它資源時,例如動態分配的內存、指向其他數據的指針等,默認的拷貝構造函數就不能拷貝這些資源了,我們必須顯式地定義拷貝構造函數,以完整地拷貝對象的所有數據。

下面我們通過一個具體的例子來說明顯式定義拷貝構造函數的必要性。

#include <iostream>
#include <cstdlib>
using namespace std;
//變長數組類
class Array{
public:
    Array(int len);
    Array(const Array &arr);  //拷貝構造函數
    ~Array();
public:
    int operator[](int i) const { return m_p[i]; }  //獲取元素(讀?。?
    int &operator[](int i){ return m_p[i]; }  //獲取元素(寫入)
    int length() const { return m_len; }
private:
    int m_len;
    int *m_p;
};
Array::Array(int len): m_len(len){
    m_p = (int*)calloc( len, sizeof(int) );
}
Array::Array(const Array &arr){  //拷貝構造函數
    this->m_len = arr.m_len;
    this->m_p = (int*)calloc( this->m_len, sizeof(int) );
    memcpy( this->m_p, arr.m_p, m_len * sizeof(int) );
}
Array::~Array(){ free(m_p); }
//打印數組元素
void printArray(const Array &arr){
    int len = arr.length();
    for(int i=0; i<len; i++){
        if(i == len-1){
            cout<<arr[i]<<endl;
        }else{
            cout<<arr[i]<<", ";
        }
    }
}
int main(){
    Array arr1(10);
    for(int i=0; i<10; i++){
        arr1[i] = i;
    }
    Array arr2 = arr1;
    arr2[5] = 100;
    arr2[3] = 29;
    printArray(arr1);
    printArray(arr2);
    return 0;
}

運行結果:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9

0, 1, 2, 29, 4, 100, 6, 7, 8, 9

本例中我們顯式地定義了拷貝構造函數,它除了會將原有對象的所有成員變量拷貝給新對象,還會為新對象再分配一塊內存,并將原有對象所持有的內存也拷貝過來。這樣做的結果是,原有對象和新對象所持有的動態內存是相互獨立的,更改一個對象的數據不會影響另外一個對象,本例中我們更改了 arr2 的數據,就沒有影響 arr1。

這種將對象所持有的其它資源一并拷貝的行為叫做深拷貝,我們必須顯式地定義拷貝構造函數才能達到深拷貝的目的。深拷貝的例子比比皆是,除了上面的變長數組類,使用的動態數組類也需要深拷貝;此外,標準模板庫(STL)中的 string、vector、stack、set、map 等也都必須使用深拷貝。

讀者如果希望親眼目睹不使用深拷貝的后果,可以將上例中的拷貝構造函數刪除,那么運行結果將變為:0, 1, 2, 29, 4, 100, 6, 7, 8, 9

0, 1, 2, 29, 4, 100, 6, 7, 8, 9

可以發現,更改 arr2 的數據也影響到了 arr1。這是因為,在創建 arr2 對象時,默認拷貝構造函數將 arr1.m_p 直接賦值給了 arr2.m_p,導致 arr2.m_p 和 arr1.m_p 指向了同一塊內存,所以會相互影響。

另外需要注意的是,printArray() 函數的形參為引用類型,這樣做能夠避免在傳參時調用拷貝構造函數;又因為 printArray() 函數不會修改任何數組元素,所以我們添加了 const 限制,以使得語義更加明確。

  • 到底是淺拷貝還是深拷貝

如果一個類擁有指針類型的成員變量,那么絕大部分情況下就需要深拷貝,因為只有這樣,才能將指針指向的內容再復制出一份來,讓原有對象和新生對象相互獨立,彼此之間不受影響。如果類的成員變量沒有指針,一般淺拷貝足以。

另外一種需要深拷貝的情況就是在創建對象時進行一些預處理工作,比如統計創建過的對象的數目、記錄對象創建的時間等,請看下面的例子:

#include <iostream>
#include <ctime>
#include <windows.h>  //在Linux和Mac下要換成 unistd.h 頭文件
using namespace std;
class Base{
public:
    Base(int a = 0, int b = 0);
    Base(const Base &obj);  //拷貝構造函數
public:
    int getCount() const { return m_count; }
    time_t getTime() const { return m_time; }
private:
    int m_a;
    int m_b;
    time_t m_time;  //對象創建時間
    static int m_count;  //創建過的對象的數目
};
int Base::m_count = 0;
Base::Base(int a, int b): m_a(a), m_b(b){
    m_count++;
    m_time = time((time_t*)NULL);
}
Base::Base(const Base &obj){  //拷貝構造函數
    this->m_a = obj.m_a;
    this->m_b = obj.m_b;
    this->m_count++;
    this->m_time = time((time_t*)NULL);
}
int main(){
    Base obj1(10, 20);
    cout<<"obj1: count = "<<obj1.getCount()<<", time = "<<obj1.getTime()<<endl;
    Sleep(3000);  //在Linux和Mac下要寫作 sleep(3);
    Base obj2 = obj1;
    cout<<"obj2: count = "<<obj2.getCount()<<", time = "<<obj2.getTime()<<endl;
    return 0;
}

運行結果:

obj1: count = 1, time = 1488344372

obj2: count = 2, time = 1488344375

運行程序,先輸出第一行結果,等待 3 秒后再輸出第二行結果。Base 類中的 m_time 和 m_count 分別記錄了對象的創建時間和創建數目,它們在不同的對象中有不同的值,所以需要在初始化對象的時候提前處理一下,這樣淺拷貝就不能勝任了,就必須使用深拷貝了。

原文鏈接:https://blog.csdn.net/wteruiycbqqvwt/article/details/124641471

欄目分類
最近更新