8. C++中的構造函數和析構函數

一、對象的初始化

        生活中存在的對象都是被初始化後才上市的,初始狀態是對象普遍存在的一個狀態的。C++中如何給對象初始化呢?

        解決方案
                        1. 爲每個類都提供一個public的initialize函數
                        2. 對象創建後立即調用initialize函數進行初始化

#include <stdio.h>

class Test
{
private:
    int i;
public:
    void initialize()
    {
        i = 0;
    }
    
    int getI()
    {
        return i;
    }
};

int main()
{
    Test t1;
    Test t2;
    Test t3;
    
    t1.initialize();
    t2.initialize();
    t3.initialize();
    
    printf("t1.i = %d\n", t1.getI());
    printf("t2.i = %d\n", t2.getI());
    printf("t3.i = %d\n", t3.getI());
    
    printf("Press any key to continue...");
    getchar();
    return 0;
}

編譯運行結果如下:

initialize只是一個普通的函數,必須顯示的調用。一旦由於失誤的原因,對象沒有初始化,那麼結果將是不確定的。沒有初始化的對象,其內部成員變量的值是不定的。

#include <stdio.h>

class Test
{
private:
    int i;
    int j;
    int k;
    
public:
    void initialize()
    {
        i = 0;
        j = 0;
        k = 0;
    }
    
    void print()
    {
        printf("i = %d, j = %d, k = %d\n", i, j, k);
    }
};

int main()
{
    Test t1;
    Test t2;
    Test t3;
    
    t1.print();
    t2.print();
    t3.print();
    
    printf("Press any key to continue...");
    getchar();
    return 0;
}

編譯運行結果如下:

 

二、構造函數

        C++中的類可以定義與類名相同的特殊成員函數。這種與類名相同的成員函數叫做構造函數。構造函數在定義時可以有參數,但是沒有任何返回類型的聲明。

        構造函數的調用。一般情況下C++編譯器會自動調用構造函數。在一些情況下則需要手工調用構造函數。

#include <stdio.h>

class Test
{
private:
    int i;
    int j;
    int k;
    
public:
    Test(int v)
    {
        i = v;
        j = v;
        k = v;
    }
    
    void print()
    {
        printf("i = %d, j = %d, k = %d\n", i, j, k);
    }
};

int main()
{
    Test t1(4);
    Test t2 = 5;
    Test t3 = Test(6);
    
    t1.print();
    t2.print();
    t3.print();
    
    Test tA[3] = {Test(1), Test(2), Test(3)};
    
    for(int i=0; i<3; i++)
    {
        tA[i].print();
    }
    
    printf("Press any key to continue...");
    getchar();
    return 0;
}

編譯運行結果如下:

 

成員函數的重載,類的成員函數和普通函數一樣可以進行重載,並遵守相同的重載規則。

#include <stdio.h>

class Test
{
private:
    int i;
    int j;
    int k;
    
public:
    Test()
    {
        i = 0;
        j = 0;
        k = 0;
    }
    
    Test(int v)
    {
        i = v;
        j = v;
        k = v;
    }
    
    void print()
    {
        printf("i = %d, j = %d, k = %d\n", i, j, k);
    }
    
    void print(int v)
    {
        printf("v = %d\n", v);
    }
};

int main()
{
    Test t1(4);
    Test t2 = 5;
    Test t3 = Test(6);
    Test t4;
    
    t4.print();
    t1.print();
    t2.print();
    t3.print();
    
    Test tA[3];
    
    for(int i=0; i<3; i++)
    {
        tA[i].print();
    }
    
    printf("Press any key to continue...");
    getchar();
    return 0;
}

編譯運行結果如下:

 

兩個特殊的構造函數

         1. 無參構造函數:當類中沒有定義任意一個構造函數時,編譯器默認提供一個無參構造函數,並且其函數體爲空。
                 2. 拷貝構造函數:當類中沒有定義拷貝構造函數時,編譯器默認提供一個拷貝構造函數,簡單的進行成員變量的值複製。

#include <stdio.h>

/*
    注意:
    1. 當類中沒有定義任何一個構造函數,C++編譯器會爲提供無參構造函數和拷貝構造函數
    2. 當類中定義了任意的非拷貝構造函數時,C++編譯器不會爲提供無參構造函數 
*/

class Test
{ 
public:
    Test()
    {
        printf("Test()\n");
    }
    
    Test(const Test& obj)
    {
        printf("Test(const Test& obj)\n");
    }
};

int main()
{
    Test t1;
    Test t2 = t1;
    
    printf("Press any key to continue...");
    getchar();
    return 0;
}

編譯運行結果如下:

#include <stdio.h>


class Test
{ 
private:
    int i;
    int j;
    int k;
    
public:
    void print()
    {
        printf("i = %d, j = %d, k = %d\n", i, j, k);
    }
};

int main()
{
    Test t1;
    Test t2 = t1;
    
    t1.print();
    t2.print(); 
    
    printf("Press any key to continue...");
    getchar();
    return 0;
}

編譯運行結果如下:

 

三、數組類的創建

//main.cpp
#include <stdio.h>
#include "Array.h"

int main()
{
    Array a1(10);
    
    for(int i=0; i<a1.length(); i++)
    {
        a1.setData(i, i);
    }
    
    for(int i=0; i<a1.length(); i++)
    {
        printf("Element %d: %d\n", i, a1.getData(i));
    }
    
    Array a2 = a1;
    
    for(int i=0; i<a2.length(); i++)
    {
        printf("Element %d: %d\n", i, a2.getData(i));
    }
    
    a1.destory();
    a2.destory();
    
    printf("Press any key to continue...");
    getchar();
    return 0;
}
//Array.cpp
#include "Array.h"

Array::Array(int length)
{
    if( length < 0 )
    {
        length = 0;
    }
    
    mLength = length;
    mSpace = new int[mLength];
}

Array::Array(const Array& obj)
{
    mLength = obj.mLength;
    
    mSpace = new int[mLength];
    
    for(int i=0; i<mLength; i++)
    {
        mSpace[i] = obj.mSpace[i];
    }
}

int Array::length()
{
    return mLength;
}

void Array::setData(int index, int value)
{
    mSpace[index] = value;
}

int Array::getData(int index)
{
    return mSpace[index];
}

void Array::destory()
{
    mLength = -1;
    delete[] mSpace;
}

//Array.h
#ifndef _ARRAY_H_
#define _ARRAY_H_

class Array
{
private:
    int mLength;
    int* mSpace;

public:
    Array(int length);
    Array(const Array& obj);
    int length();
    void setData(int index, int value);
    int getData(int index);
    void destory();
};

#endif

編譯運行結果如下:

 

四、C++中的對象組合

C++中的類可以使用其它類定義成員變量,如何給對象成員進行初始化呢?

對象組合示例:

C++中提供了初始化列表對成員變量進行初始化,語法規則如下:

Constructor::Contructor() : m1(v1), m2(v1,v2), m3(v3)
{
    // some other assignment operation
}

注意:

        1. 成員變量的初始化順序與聲明的順序相關,與在初始化列表中的順序無關;
                2. 初始化列表先於構造函數的函數體執行;

#include <stdio.h>

class M
{
private:
    int mI;
public:
    M(int i)
    {
        printf("M(int i), i = %d\n", i);
        mI = i;
    }
    
    int getI()
    {
        return mI;
    }
};

class Test
{
private:
    const int c;
    M m1;
    M m2;
public:
    Test() : c(1), m2(3), m1(2)
    {
        printf("Test()\n");
    }
    
    void print()
    {
        printf("c = %d, m1.mI = %d, m2.mI = %d\n", c, m1.getI(), m2.getI());
    }
};

void run()
{
    Test t1;
    
    t1.print();
}

int main()
{
    run();
    
    printf("Press any key to continue...");
    getchar();
    
    return 0;
}

編譯運行結果如下:

 

小插曲:

        類中的const成員是肯定會被分配空間的,類中的const成員變量只是一個只讀變量。編譯器無法直接得到const成員變量的初始值,因此無法進入符號表成爲真正意義上的常量。

初始化與賦值不同,初始化是用已存在的對象或值對正在創建的對象進行初值設置。賦值是用已存在的對象或值對已經存在的對象進行值設置。

區別:

       初始化:被初始化的對象正在創建;
               賦值:被賦值的對象已經存在;

 

 

五、C++中的析構函數

生活中存在的對象都是被初始化後才上市的,生活中的對象被銷燬前會做一些清理工作。如何清理被銷燬的對象?

解決方案:

        1. 爲每個類都提供一個public的destroy函數;
               2. 對象不再被需要時立即調用destroy函數進行清理;

destroy只是一個普通的函數,必須顯示的調用。如果對象銷燬前沒有做清理,那麼很可能造成資源泄漏。在構造函數中申請的資源,需要在對象銷燬前釋放。

C++編譯器是否能夠自動調用某個特殊的函數進行對象的清理?

C++中的類可以定義一個特殊的成員函數清理對象。這個特殊的成員函數叫做析構函數
              1. 定義:~ClassName()
              2. 析構函數沒有參數也沒有任何返回類型的聲明;
              3. 析構函數在對象銷燬時自動被調用;

#include <stdio.h>

class Test
{
private:
    int mI;
public:
    Test(int i) : mI(i)
    {
        printf("Test(), mI = %d\n", mI);
    }
    
    ~Test()
    {
        printf("~Test(), mI = %d\n", mI);
    }
};

void run()
{
    Test t1(1);
    Test t2(2);
}

int main()
{
    run();
    
    printf("Press any key to continue...");
    getchar();
    
    return 0;
}

編譯運行結果如下:

 

六、數組類的進化

前面我們創建了一個數組類,下面通過剛學的析構函數進行改進。

//Array.cpp
#include "Array.h"

Array::Array(int length)
{
    if( length < 0 )
    {
        length = 0;
    }
    
    mLength = length;
    mSpace = new int[mLength];
}

Array::Array(const Array& obj)
{
    mLength = obj.mLength;
    
    mSpace = new int[mLength];
    
    for(int i=0; i<mLength; i++)
    {
        mSpace[i] = obj.mSpace[i];
    }
}

int Array::length()
{
    return mLength;
}

void Array::setData(int index, int value)
{
    mSpace[index] = value;
}

int Array::getData(int index)
{
    return mSpace[index];
}

Array::~Array()
{
    mLength = -1;
    delete[] mSpace;
}

//Array.h
#ifndef _ARRAY_H_
#define _ARRAY_H_

class Array
{
private:
    int mLength;
    int* mSpace;

public:
    Array(int length);
    Array(const Array& obj);
    int length();
    void setData(int index, int value);
    int getData(int index);
    ~Array();
};

#endif
//main.cpp
#include <stdio.h>
#include "Array.h"

int main()
{
    Array a1(10);
    
    for(int i=0; i<a1.length(); i++)
    {
        a1.setData(i, i);
    }
    
    for(int i=0; i<a1.length(); i++)
    {
        printf("Element %d: %d\n", i, a1.getData(i));
    }
    
    Array a2 = a1;
    
    for(int i=0; i<a2.length(); i++)
    {
        printf("Element %d: %d\n", i, a2.getData(i));
    }
    
    printf("Press any key to continue...");
    getchar();
    return 0;
}

編譯運行結果如下:

 

構造函數與析構函數的調用秩序

      1.當類中有成員變量是其它類的對象時;
              2. 首先調用成員變量的構造函數;
              3. 調用順序與聲明順序相同;
              4. 之後調用自身類的構造函數;
              5. 析構函數的調用秩序與對應的構造函數調用秩序相反;
 

#include <stdio.h>

class Test
{
private:
    int mI;
public:
    Test()
    {
        printf("Test()\n");
        mI = -1;
    }
    
    Test(int i)
    {
        printf("Test(int i), i = %d\n", i);
        mI = i;
    }
    
    Test(const Test& obj)
    {
        printf("Test(const Test& obj), i = %d\n", obj.mI);
        mI = obj.mI;
    }
    
    ~Test()
    {
        printf("~Test(), i = %d\n", mI);
    }
};

void func(Test t)
{
    Test r(1);
}

void run()
{
    Test t(0);
    
    func(t);
}

int main()
{
    run();
    
    printf("Press any key to continue...");
    getchar();
    return 0;
}

編譯運行結果如下:

 

小結:

        1. 析構函數是C++中對象銷燬時做清理工作的特殊函數;
               2. 析構函數在對象銷燬時自動被調用;
               3. 析構函數是對象所使用的資源及時釋放的保障;
               4. 析構函數的調用秩序與構造函數相反;
 

#include <stdio.h>

class Test
{
private:
    int mI;
    int mJ;
    const char* mS;
public:
    Test()
    {
        printf("Test()\n");
        
        mI = 0;
        mJ = 0;
    }
    
    Test(const char* s)
    {
        printf("Test(const char* s)\n");
        
        Test();
        
        mS = s;
    }
    
    ~Test()
    {
        printf("~Test()\n");
    }
    
    void print()
    {
        printf("mI = %d, mJ = %d, mS = %s\n", mI, mJ, mS);
    }
};

void run()
{
    Test t = Test("Delphi Tang"); // Test t("Delphi Tang");
    
    t.print();
}

int main()
{
    run();
    
    printf("Press any key to continue...");
    getchar();
    return 0;
}

編譯運行結果如下:

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章