2020, Aug.4 類別建構函式與解構函式

2020-08-11 23:37:24

類別建構函式與解構函式

  1. 類別建構函式
  • 引例

    / *
      * 定義一個型別:Cirlce,圓
      * 成員:圓心座標(x, y),半徑radius
      * /
    class Cirlce {
    public:
        int x, y;
        int radius;
    };
    
    int main()
    {
        // 定義一個物件
        Circle a;
        // 問題:此時這個物件a算是一個有效的物件麼?
        // 看看它的x,y和radius,是亂的
        
        // 那麼,再給它賦值不就好了麼?
        a.x = 0;
        a.y = 0;
        a. radius = 1;
        
        // 但是從「物件」的觀點來看,物件被建立之時,就應該是有效的,不應該存在「半成品」物件。
        // 比如說,一輛車Car在造出來的時候,必須帶着輪子。如果沒有輪子,此時他還不能稱爲Car。
        
        return 0;
    }
    
  • 存在問題:定義了一個物件,新建的的物件在記憶體中的值是無效的(雜亂的負值)。如何讓一個物件在被建立的時候,就賦予初始值。

  • 解決方法:建構函式

  • 建構函式是類的一種特殊的成員函數,建構函式不是普通的函數

    1. 函數名與類名必須相同
    2. 沒有返回值
    class Circle {
    public:
        Circle()
        {
            x = y = 0;
            radius = 1;
        }
    };
    
  • 建構函式可以帶參數,也可以過載

    class Cirlce {
    ...
    public:
        Cirlce ()
        {
            printf("111\n");
            x = y =0;
            radius = 1;
        }
        Circle(int x, int y, int r)
        {
            printf("222\n");
            this->x = x;
            this->y = y;
            this->radius = r;
        }
    publicint x, y;
        int radius;
    };
    
  • 建構函式如何呼叫?

    1. 建構函式和普通成員函數不一樣,一般不顯示呼叫。

    2. 在建立一個物件時,建構函式被自動呼叫(由編譯器完成)

      Circle a;
      Circle b(1,1,4)l;
      
    3. 它們在內部實質上是分別呼叫了不同的建構函式,但是表面上沒有這個函數呼叫過程。

    int main()
    {
        Circle a;
        Circle b(1, 1, 4);
    	
        return 0;
    }
    
  • 建構函式的作用:物件一「出生」就是有效的。不存在「半成品」物件。

    它可以理解爲「初始化」動作。

    基本型別的初始化;

    int a(10);  			 // 將a初始化爲10, 也可以寫成int a = 10;
    Student s = {1, "name"}; // struct的初始化
    

    現在,類class的初始化使用建構函式的方式。

  • 小結:

    • 介紹建構函式的語法:名字與類名相同,沒有返回值
    • 建構函式的作用:用於初始化物件
    • 建構函式的呼叫:建立物件的同時,被編譯器自動呼叫。
    • 建構函式也可以過載。
  1. 解構函式
  • 解構和構造是一對相反的過程。
  • 建構函式:物件被建立時被呼叫
  • 解構函式:物件被銷燬時被呼叫

析:分崩離析

英文:建構函式 constructor

​ 解構函式 destructor

  • 解構函式也不是普通的函數

    1. 名稱固定:類名前加上波浪線~
    2. 沒有返回值
    3. 不能帶參數
    class Object {
    public:
        ~Object()
        {
            
        }
    };
    

    注意:解構函式只能有一個,不能過載

  • 解構函式如何呼叫?

    解構函式從不顯示地呼叫,而是被編譯器自動地呼叫。

    什麼時候被呼叫?物件被銷燬之時

    對於區域性變數(物件),在超出變數作用域後,該物件失效

  • 解構函式的作用?

    解構函式:物件在銷燬之前,做一個清理和善後的工作。

    比如,申請來的記憶體要釋放,開啓的檔案FIle*要關閉。

    class StringBuffer {
    public:
        StringBuffer()
        {
            m_buffer = malloc(1024*512); // 申請記憶體
        }
        ~StringBuffer()
        {
            free(m_buffer); 			// 釋放記憶體
        }
    };
    
    class DataStore {
    public:
        DataStore()
        {
            m_head.next = NULL;
        }
        ~DataStore()
        {
            // 刪除鏈表中所有節點,把所有,malloc的記憶體都free掉
        }
    private:
        Student m_head;
    };
    
  • 分開寫在.h和.cpp裡時,規則和普通函數一樣

    class DataStore {
    public:
        // 函數宣告
        DataStore();
        ~DataStore();
    };
    // 函數定義,要加類名字首
    DataStore::DataStore() {}
    DataStore::~DataStore() {}
    
  • 完整DataStore範例(Add和Find函數)

    ///////DataStore.h////////
    struct Student {
        int id;
        char name[16];
        Student* next;
    }
    
    class DataStore {
    public:
        DataStore();
        ~DataStore();
    publicvoid Add(const Student* data);
        Student* Find(int id);
        void Print();
    private:
        Student m_head;
    };
    
    
    ///////DataStore.cpp///////
    #include <stdio.h>
    #include "DataStore.h"
    
    DataStore::DataStore() // 建立資源
    {
        m_head.next = NULL;
    }
    DataStore::~DataStore() // 釋放資源
    {
        Student* p = m_head.next;
        while(p)
        {
            Student* next = p->next;
            free(p);
            p = next;
        }
    }
    
    void DataStore::Add(const Student* data)
    {
        // 建立物件、複製數據
        Student* copy = (Student*)malloc(sizeof(Student));
        *copy = *data;
        
        // 插入一個物件到鏈表中
        Student* cur = m_head.next; // 省略了this指針,直接呼叫成員變數m_head
        Student* pre = &m_head;     // 省略了this指針,直接呼叫成員變數m_head
        while(cur)
        {
            if(copy->id < cur->id)  // 找到這個位置
                break;
            pre = cur;
            cur = cur->next;		// 找到最後一個物件 
        }
        
        // 插入到pre節點的後面
        copy->next = pre->next;
        pre->next = copy;
    }
    
    Student& DataStore::Find(int id)
    {
        Student* p = m_head.next;
        while(p)
        {
            if(p->id == id)
                return p;
            p = p->next; // 下一個物件
        }
        return NULL;
    }
    
    void DataStore::Print()
    {
        Student* p = this->m_head.next; // this指針可加可不加
        while(p)
        {
            printf("ID: %d, name: %s\n", p->id, p->name);
            p = p->next; // 下一個物件
        }
    }
    
    ////////main.cpp////////
    #include <stdio.h>
    #include"DataStore.h"
    
    int main()
    {
        DataStore ds;
        Student nodeA = {13,"JWB",NULL}; 
        Student *p = &nodeA; 
        ds.Add(p);
        ds.Find(13);
        ds.print();
        return 0;
    }
    
  1. 構造與解構
  • 預設建構函式
    把那種不需要傳參的建構函式,稱爲預設建構函式
    // 沒有預設值
    	Object();
    	// 所有參數都有預設值
    	Object(int a = 10, int b = 11);
    
  • 有了預設建構函式之後,物件在構造時就可以不傳遞參數,Object obj;
    如果一個類沒有預設建構函式,則無法構造陣列
  • 如果一個類沒有寫任何建構函式,則編譯器隱含地生成一個建構函式,相當於新增了 Object::Object() {},寫了就不會新增
    如果沒有寫解構函式,則編譯器隱含地生成一個解構函式,相當於新增了 Object::~Object() {}
  • 成員的初始化與解構
    (考慮成員變數本身也是class型別的情況)
    1. 當物件被構造時,成員變數也被構造(成員變數的建構函式被呼叫)
    2. 當物件被解構時,成員變數也被解構(成員變數的解構函式被呼叫)
  • 結論:
    1. 構造的時候:
      成員被依次構造:從前到後
      先執行成員的構造,再執行自己的建構函式
    2. 成員被一次解構:從後到前
      成員被依次解構
      先執行自己的解構函式,再執行成員的解構
  • 初始化列表
    可以在建構函式後面直接初始化
    1. 以冒號引導
    2. 使用小括號來初始化
// Object有兩個成員變數x,y
Object:x(1),y(2)
{
}
/// ///
Object: m_child(1, 2)
{
}
  • 小結:
    1. 預設建構函式:不要傳參的建構函式
    2. 建構函式與解構函式的順序問題
    3. 初始化列表:在建構函式的位置指定成員的初始化列表