C++類和對象:對象的初始化和清理

本文是筆者在C++學習過程中,對類和對象的總結(jié)記錄,如有不對以及不足的地方,歡迎大家指正!

文中代碼學于黑馬。


1. 構(gòu)造函數(shù)與析構(gòu)函數(shù)

??在C++當中,提供了一種特殊的公有成員函數(shù)構(gòu)造函數(shù)析構(gòu)函數(shù),用于對對象進行初始化和銷毀。

  • 構(gòu)造函數(shù):主要用于在創(chuàng)建對象時為對象的成員屬性賦值,即給對象分配資源。
    語法:類名(){}
    構(gòu)造函數(shù)可以有參數(shù),也就是說,可以發(fā)生重載。
  • 析構(gòu)函數(shù):主要用于在對象銷毀時釋放相應的資源。
    語法:~類名(){}
    析構(gòu)函數(shù)不可以有參數(shù),所以不能發(fā)生重載。

??一個類,可以有多個構(gòu)造函數(shù),但只能有一個析構(gòu)函數(shù),不同構(gòu)造函數(shù)之間通過參數(shù)的個數(shù)和類型來進行區(qū)分。

??對于C++來說,對象的初始化與銷毀是必須的,所以即使我們不提供構(gòu)造函數(shù)與析構(gòu)函數(shù),編譯器也會提供默認的構(gòu)造函數(shù)與析構(gòu)函數(shù),并在對象初始化和銷毀時由編譯器自動調(diào)用,不需要手動調(diào)用,且只會調(diào)用一次。編譯器提供的構(gòu)造函數(shù)與析構(gòu)函數(shù)是空實現(xiàn)

#include <iostream>
#include <string>
using namespace std;

// 1. 構(gòu)造函數(shù)與析構(gòu)函數(shù)
class Person_1{
public:
    // 構(gòu)造函數(shù)
    Person_1(){
        cout<<"Person_1 構(gòu)造函數(shù)"<<endl;
    }

    // 析構(gòu)函數(shù)
    ~Person_1(){
        cout<<"Person_1 析構(gòu)函數(shù)"<<endl;
    }
};

void test_1(){
    // 棧區(qū)的數(shù)據(jù),會在函數(shù)調(diào)用結(jié)束后立即釋放
    Person_1 p1;
}

int main() {
    // 1. 構(gòu)造函數(shù)與析構(gòu)函數(shù)
    test_1();
    return 0;
}

運行結(jié)果:


構(gòu)造函數(shù)與析構(gòu)函數(shù)

2.構(gòu)造函數(shù)的分類與調(diào)用

2.1 分類

??構(gòu)造函數(shù)可以按參數(shù)來分類,也可以按類型來進行分類。

  • 參數(shù)
    1. 有參構(gòu)造
    2. 無參構(gòu)造(默認構(gòu)造
  • 類型
    1. 普通構(gòu)造
    2. 拷貝構(gòu)造
      在創(chuàng)建對象時,是通過同一個類的已創(chuàng)建的對象來對自己進行初始化,即對原對象進行復制,來初始化本對象。
      語法示例:Object(const Object &o){...}
#include <iostream>
using namespace std;

//2.構(gòu)造函數(shù)的分類與調(diào)用
class Person_2{
public:
    //無參構(gòu)造函數(shù)(默認)
    Person_2(){
       cout<<"Person_2 無參構(gòu)造函數(shù)(默認)"<<endl;
    }

    //有參構(gòu)造函數(shù)
    Person_2(int age){
        this->age=age;
        cout<<"Person_2 有參構(gòu)造函數(shù)"<<endl;
    }

    //拷貝構(gòu)造函數(shù)
    Person_2(const Person_2 &p){
        age=p.age;
        cout<<"Person_2 拷貝構(gòu)造函數(shù)"<<endl;
    }

    //析構(gòu)函數(shù)
    ~Person_2(){
        cout<<"Person_2 析構(gòu)函數(shù)"<<endl;
    }

private:
    int age;
};

void test_2_1(){
    Person_2 p1;
    Person_2 p2(10);
    Person_2 p3(p2);

}

int main() {
    //2.構(gòu)造函數(shù)的分類與調(diào)用
    test_2_1();
    return 0;
}

運行結(jié)果:


構(gòu)造函數(shù)的分類

2.2 調(diào)用

??構(gòu)造函數(shù)的調(diào)用方式,可以分為以下幾類:

  • 括號法
    ??調(diào)用默認構(gòu)造(無參構(gòu)造)時,不可以加括號,否則編譯器會認為這是函數(shù)聲明。
  • 顯示法
    ??匿名對象(只調(diào)用構(gòu)造方法而沒有給對象命名),會在當前行執(zhí)行結(jié)束后,立即被系統(tǒng)回收,即調(diào)用析構(gòu)函數(shù)。
    ??同時,不要用拷貝構(gòu)造函數(shù)來初始化匿名函數(shù),否則,在編譯器看來這是一個對象聲明:Object(o1); == Object o1;
  • 隱式轉(zhuǎn)換法
#include <iostream>
using namespace std;

//調(diào)用
void test_2_2(){
    // 括號法
    Person_2 p1(10);

    // 調(diào)用默認構(gòu)造(無參構(gòu)造)時,不可以加括號,否則編譯器會認為這是函數(shù)聲明。
    // Person_2 p2();  // Function 'p2' is never used

    // 顯示法
    Person_2 p2=Person_2(10);   // 有參
    Person_2 p3=Person_2(p2);       // 拷貝
    // Person_2(10) 單獨寫即為匿名對象,在當前行執(zhí)行結(jié)束后,立即被釋放(析構(gòu))
    
    // 不要用拷貝構(gòu)造函數(shù)來初始化匿名函數(shù),否則,在編譯器看來這是一個對象聲明
    // Person_2(p4);   // 等價于 Person_2 p4
    
    // 隱式轉(zhuǎn)換法
    Person_2 p4=10; // 等價于 Person_2 p2=Person_2(10);
    Person_2 p5=p4; // 等價于 Person_2 p3=Person_2(p4);
}


3. 拷貝函數(shù)的調(diào)用時機

??在C++中什么時候會調(diào)用拷貝構(gòu)造函數(shù)呢?有以下三種情況:

  1. 一個對象需要通過一個已經(jīng)創(chuàng)建的對象來初始化
  2. 一個對象以值傳遞的方式傳入函數(shù)體
  3. 一個對象以值傳遞的方式從函數(shù)返回
#include <iostream>
using namespace std;

class Person_2{
public:
    //無參構(gòu)造函數(shù)(默認)
    Person_2(){
       cout<<"Person_2 無參構(gòu)造函數(shù)(默認)"<<endl;
    }

    //有參構(gòu)造函數(shù)
    Person_2(int age){
        this->age=age;
        cout<<"Person_2 有參構(gòu)造函數(shù)"<<endl;
    }

    //拷貝構(gòu)造函數(shù)
    Person_2(const Person_2 &p){
        age=p.age;
        cout<<"Person_2 拷貝構(gòu)造函數(shù)"<<endl;
    }

    //析構(gòu)函數(shù)
    ~Person_2(){
        cout<<"Person_2 析構(gòu)函數(shù)"<<endl;
    }

private:
    int age;
};

// 3.拷貝函數(shù)的調(diào)用時機
// 3.1 一個對象需要通過一個已經(jīng)創(chuàng)建的對象來初始化
void test_3_1(){
    Person_2 p(10); // 無參構(gòu)造函數(shù)
    Person_2 p1(p);     // 調(diào)用拷貝構(gòu)造函數(shù)
    Person_2 p2=p;      // 調(diào)用拷貝構(gòu)造函數(shù)

    Person_2 p3;
    p3=p;   // 不調(diào)用拷貝構(gòu)造函數(shù),是賦值操作
}

// 3.2 一個對象以值傳遞的方式傳入函數(shù)體
void value(Person_2 p){}
void test_3_2(){
    Person_2 p; // 無參構(gòu)造函數(shù)
    value(p);
}

// 3.3 一個對象以值傳遞的方式從函數(shù)返回
Person_2 result(){
    Person_2 p;
    cout<<"result()函數(shù)內(nèi)變量p的地址為:\t\t"<<(int *)&p<<endl;
    return p;
}
void test_3_3(){
    Person_2 p=result();
    cout<<"result()函數(shù)返回的變量p的地址為:\t"<<(int *)&p<<endl;
}

int main() {
    // 3.拷貝函數(shù)的調(diào)用時機
    // 3.1 一個對象需要通過一個已經(jīng)創(chuàng)建的對象來初始化
    test_3_1();
    cout<<"===================="<<endl;
    // 3.2 一個對象以值傳遞的方式傳入函數(shù)體
    test_3_2();
    cout<<"===================="<<endl;
    // 3.3 一個對象以值傳遞的方式從函數(shù)返回
    test_3_3();
    cout<<"===================="<<endl;

    return 0;
}

運行結(jié)果:


image.png

4. 構(gòu)造函數(shù)的調(diào)用規(guī)則

??在默認的情況下,C++編譯器會至少給類添加以下三個函數(shù):

  1. 默認構(gòu)造函數(shù):無參,函數(shù)體為空
  2. 默認析構(gòu)函數(shù):無參,函數(shù)體為空
  3. 默認拷貝構(gòu)造函數(shù):對對象的屬性進行簡單的值拷貝

??而構(gòu)造函數(shù)的調(diào)用規(guī)則如下所示:

  1. 若用戶自定義了有參構(gòu)造函數(shù),那么C++就不會再提供默認無參構(gòu)造函數(shù),而僅提供默認拷貝構(gòu)造函數(shù)。
  2. 若用戶自定義了拷貝構(gòu)造函數(shù),那么C++就不會再提供其他的構(gòu)造函數(shù)。

5. 深拷貝與淺拷貝

??深拷貝與淺拷貝,一直是C++面試的一個熱點與難點,那么這兩者的區(qū)別是什么呢?

  • 淺拷貝:在對象復制時,僅進行簡單的賦值拷貝
  • 深拷貝:在對象復制時,會在堆區(qū)重新申請空間,再進行拷貝操作。

??在默認拷貝構(gòu)造函數(shù)中,進行的是淺拷貝,而淺拷貝會將新舊對象的指針型成員變量指向同一塊內(nèi)存。因此在一個對象被銷毀(調(diào)用析構(gòu)函數(shù))后,這一塊內(nèi)存就已經(jīng)被釋放了,所以當另一個對象調(diào)用析構(gòu)函數(shù)時,就會造成對同一內(nèi)存的重復釋放,從而引發(fā)錯誤。所以如果有在堆區(qū)開辟的屬性,就一定要自定義拷貝構(gòu)造函數(shù),通過深拷貝來進行拷貝

#include <iostream>
using namespace std;

// 5. 深拷貝與淺拷貝
class Person_3{
public:
    //無參構(gòu)造函數(shù)(默認)
    Person_3(){
        cout<<"Person_3 無參構(gòu)造函數(shù)(默認)"<<endl;
    }

    //有參構(gòu)造函數(shù)
    Person_3(int age,int height){
        this->age=age;
        this->height=new int(height);
        cout<<"Person_3 有參構(gòu)造函數(shù)"<<endl;
    }

    //拷貝構(gòu)造函數(shù)
    Person_3(const Person_3 &p){
        age=p.age;
        //利用深拷貝來在堆區(qū)開辟空間,避免利用淺拷貝帶來的堆區(qū)重復釋放問題
        height=new int(*p.height);
        cout<<"Person_3 拷貝構(gòu)造函數(shù)"<<endl;
    }

    //析構(gòu)函數(shù)
    ~Person_3(){
        //釋放在堆區(qū)開辟的空間
        if(height!=NULL){
            delete height;
        }
        cout<<"Person_3 析構(gòu)函數(shù)"<<endl;
    }

    void show(){
        cout<<"age:"<<age<<",height:"<<*height<<endl;
        cout<<"height變量的地址:"<<height<<endl;
    }

private:
    int age;
    int *height;
};
void test_5(){
    Person_3 p1(22,180);
    Person_3 p2(p1);
    cout<<"p1:"<<endl;
    p1.show();
    cout<<"p2:"<<endl;
    p2.show();
}


int main() {
    // 5. 深拷貝與淺拷貝
    test_5();

    return 0;
}

運行結(jié)果:


image.png

6. 初始化列表

??C++除了提供傳統(tǒng)的構(gòu)造函數(shù)之外,還提供了初始化列表語法,用來初始化屬性。

  • 語法:構(gòu)造函數(shù)():屬性1(值1),屬性2(值2)...{...}
#include <iostream>
using namespace std;

// 6. 初始化列表
class Person_4{
public:

    // 初始化列表方式初始化
    Person_4(int a,int b,int c):A(a),B(b),C(c){}

    void show(){
        cout<<"A:"<<A<<",B:"<<B<<",C:"<<C<<endl;
    }

private:
    int A;
    int B;
    int C;

};
void test_6(){
    Person_4 p(1,2,3);
    p.show();
}

int main() {
    // 6. 初始化列表
    test_6();
    return 0;
}

7. 類對象作為類成員

??在C++中,一個類的成員可以是另一個類的對象,我們稱這種成員為對象成員。

??eg:

class A{}
class B{
    A a;    // 對象成員
}

??帶有對象成員的類,在初始化時,先構(gòu)造對象成員,再構(gòu)造自己;在銷毀時,先析構(gòu)自己,再析構(gòu)對象成員,兩者順序相反。

#include <iostream>
using namespace std;

// 7. 類對象作為類成員
class Pat{
public:
    Pat(string name){
        this->name=name;
        cout<<"Pat 有參構(gòu)造函數(shù)"<<endl;
    }

    ~Pat(){
        cout<<"Pat 析構(gòu)函數(shù)"<<endl;
    }
private:
    string name;
};
class Person{
public:
    string name;
    Pat pat;

    // 初始化列表傳參
    Person(string person,string patname):name(person), pat(patname){
        cout<<"Person 有參構(gòu)造函數(shù)"<<endl;
    }

    ~Person(){
        cout<<"Person 析構(gòu)函數(shù)"<<endl;
    }
};
void test_7(){
    Person p("Alice","cat");
}

int main() {
    // 7. 類對象作為類成員
    test_7();
    return 0;
}

運行結(jié)果:


image.png

8. 靜態(tài)成員

??類的成員變量與成員方法,在前面加上關(guān)鍵字static后。就被稱為靜態(tài)成員,所以靜態(tài)成員可以分為靜態(tài)成員變量靜態(tài)成員函數(shù)。

??這兩者的訪問,都可以通過以下兩種方式達成:

  • 通過對象:對象名.靜態(tài)成員變量名/靜態(tài)成員函數(shù)名
  • 通過類名:類名::靜態(tài)成員變量名/靜態(tài)成員函數(shù)名

8.1 靜態(tài)成員變量

  • 所有對象共享同一份數(shù)據(jù)
  • 在編譯階段分配內(nèi)存
  • 類內(nèi)聲明,類外初始化
#include <iostream>
using namespace std;

// 8. 靜態(tài)成員
// 靜態(tài)成員變量
class Person_5{
public:
    static int sA;
private:
    static int sB;
};
//類內(nèi)聲明,類外初始化
int Person_5::sA=10;
int Person_5::sB=20;
void test_8_1(){
    Person_5 p1;
    //1. 通過對象訪問
    p1.sA=100;
    cout<<"p1.sA="<<p1.sA<<endl;

    Person_5 p2;
    p2.sA=200;  //所有對象共享同一份數(shù)據(jù)
    cout<<"p1.sA="<<p1.sA<<endl;
    cout<<"p2.sA="<<p2.sA<<endl;

    //2. 通過類名訪問
    cout<<"Person_5::sA="<<Person_5::sA<<endl;

}

int main() {
    // 8. 靜態(tài)成員
    // 靜態(tài)成員變量
    test_8_1();
    return 0;
}

運行結(jié)果:


image.png

8.2 靜態(tài)成員函數(shù)

  • 所有對象共享同一個函數(shù)
  • 靜態(tài)成員方法僅能訪問靜態(tài)成員變量。如若不然,某個對象在調(diào)用靜態(tài)成員方法時,不能確認方法中的普通變量屬于哪個對象。
#include <iostream>
using namespace std;

class Person_6{
public:
    static int sA;

    static void func(){
        cout<<"static void func()"<<endl;
        sA=200;
    }

};
//類內(nèi)聲明,類外初始化
int Person_6::sA=10;
void test_8_2(){
    Person_6 p;
    //1. 通過對象訪問
    p.func();
    //2. 通過類名訪問
    Person_6::func();
}

int main() {
    // 靜態(tài)成員函數(shù)
    test_8_2();
    return 0;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容