溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

對(duì)象的構(gòu)造(十四)

發(fā)布時(shí)間:2020-07-14 03:45:12 來(lái)源:網(wǎng)絡(luò) 閱讀:487 作者:上帝之子521 欄目:編程語(yǔ)言

        我們?cè)?C 語(yǔ)言中,每個(gè)變量都有其初始值。那么問(wèn)題來(lái)了,對(duì)象中成員變量的初始值是多少呢?從設(shè)計(jì)的角度來(lái)看,對(duì)象只是變量,因此:在棧上創(chuàng)建對(duì)象時(shí),成員變量初始為隨機(jī)值;在堆上創(chuàng)建對(duì)象時(shí),成員變量初始為隨機(jī)值;在靜態(tài)存儲(chǔ)區(qū)創(chuàng)建對(duì)象時(shí),成員變量初識(shí)為 0 值。

        下來(lái)我們以代碼為例進(jìn)行驗(yàn)證,代碼如下

#include <stdio.h>

class Test
{
private:
    int i;
    int j;
public:
    int getI() { return i; }
    int getJ() { return j; }
};

Test gt;

int main()
{
    printf("gt.i = %d\n", gt.getI());
    printf("gt.j = %d\n", gt.getJ());
    
    Test at;
    
    printf("at.i = %d\n", at.getI());
    printf("at.j = %d\n", at.getJ());
    
    Test* pt = new Test;
    
    printf("pt->i = %d\n", pt->getI());
    printf("pt->j = %d\n", pt->getJ());
    
    return 0;
}

        gt 對(duì)象是在靜態(tài)存儲(chǔ)區(qū)創(chuàng)建的,所以 gt.i 和 gt.j 應(yīng)該都為 0,;at 對(duì)象是在棧上創(chuàng)建的,所以 at.i 和 at.j 應(yīng)該都為隨機(jī)值;pt 對(duì)象是在堆上創(chuàng)建的,所以 pt->i 和 pt->j 應(yīng)該也為隨機(jī)值。我們來(lái)編譯下,看看是否如我們所分析的那樣呢?

對(duì)象的構(gòu)造(十四)

        我們看到前面兩個(gè)如我們所分析的那樣,最后一個(gè)不一樣。我們?cè)賮?lái)看看BCC編譯器呢

對(duì)象的構(gòu)造(十四)

        我們看到BCC編譯器是如我們所分析的那樣。所以我們不能依賴于某種編譯器的特性。

        在生活中的對(duì)象都是在初始化后上市的,初識(shí)狀態(tài)(出廠設(shè)置)是對(duì)象普遍存在的一個(gè)狀態(tài)。那么程序中如何對(duì)一個(gè)對(duì)象進(jìn)行初始化呢?一般而言,對(duì)象都需要一個(gè)確定的初識(shí)狀態(tài)。解決方案便是在類中提供一個(gè) public 的 initialize 函數(shù),對(duì)象創(chuàng)建后立即調(diào)用 initialize 函數(shù)進(jìn)行初始化。下來(lái)我們以代碼為例進(jìn)行分析,在上面代碼基礎(chǔ)上加上 initialize 函數(shù)

#include <stdio.h>

class Test
{
private:
    int i;
    int j;
public:
    int getI() { return i; }
    int getJ() { return j; }
    void initialize()
    {
        i = 1;
        j = 2;
    }
};

Test gt;

int main()
{
    gt.initialize();
    
    printf("gt.i = %d\n", gt.getI());
    printf("gt.j = %d\n", gt.getJ());
    
    Test at;
    at.initialize();
    
    printf("at.i = %d\n", at.getI());
    printf("at.j = %d\n", at.getJ());
    
    Test* pt = new Test;
    pt->initialize();
    
    printf("pt->i = %d\n", pt->getI());
    printf("pt->j = %d\n", pt->getJ());
    
    return 0;
}

        我們編譯,看看結(jié)果是否初始化好呢

對(duì)象的構(gòu)造(十四)

        我們看到已經(jīng)全部初始化為按照我們所想要的狀態(tài)了。但是這個(gè)就存在一個(gè)問(wèn)題了,initialize 只是一個(gè)普通的函數(shù),必須顯示調(diào)用才行。如果為調(diào)用 initialize 函數(shù)的話,結(jié)果是不確定的。如果我們忘記在 at 對(duì)象中調(diào)用 initialize 函數(shù),編譯結(jié)果如下

對(duì)象的構(gòu)造(十四)

        那么這時(shí)問(wèn)題來(lái)了,我們?cè)撊绾谓鉀Q這個(gè)問(wèn)題呢?在 C++ 中介意定義與類名相同的特殊成員函數(shù),這種特殊的成員函數(shù)叫做構(gòu)造函數(shù)。注意:構(gòu)造函數(shù)沒(méi)有返回類型的聲明;構(gòu)造函數(shù)在對(duì)象定義時(shí)自動(dòng)被調(diào)用。那么這時(shí)我們就可以將上面的程序改為這樣

#include <stdio.h>

class Test
{
private:
    int i;
    int j;
public:
    int getI() { return i; }
    int getJ() { return j; }
    Test()
    {
        printf("Test() Begin\n");
        
        i = 1;
        j = 2;
        
        printf("Test() End\n");
    }
};

Test gt;

int main()
{
    printf("gt.i = %d\n", gt.getI());
    printf("gt.j = %d\n", gt.getJ());
    
    Test at;
    
    printf("at.i = %d\n", at.getI());
    printf("at.j = %d\n", at.getJ());
    
    Test* pt = new Test;
    
    printf("pt->i = %d\n", pt->getI());
    printf("pt->j = %d\n", pt->getJ());
    
    return 0;
}

        我們編譯后結(jié)果如下

對(duì)象的構(gòu)造(十四)

        我們這樣是不是就方便很多呢?那肯定了。我們可以明顯看到定義了三個(gè)對(duì)象后,調(diào)用了三次構(gòu)造函數(shù)。那么我們既然知道了有構(gòu)造函數(shù)這一類的函數(shù),它是否能像一般函數(shù)那樣進(jìn)行帶參數(shù)呢?構(gòu)造函數(shù)可以根據(jù)需要定義參數(shù);一個(gè)類中可以存在多個(gè)重載的構(gòu)造函數(shù);構(gòu)造函數(shù)的重載遵循 C++ 重載的規(guī)則。我們之前說(shuō)過(guò)定義和聲明不同,在對(duì)象這塊也同樣適用。對(duì)象定義和對(duì)象聲明時(shí)不同的:對(duì)象定義 -- 申請(qǐng)對(duì)象的空間并調(diào)用構(gòu)造函數(shù);對(duì)象聲明 -- 告訴編譯器存在這樣一個(gè)對(duì)象。下來(lái)我們以代碼為例進(jìn)行分析

#include <stdio.h>

class Test
{
public:
    Test()
    {
        printf("Test()\n");
    }
    Test(int v)
    {
        printf("Test(int v), v = %d\n", v);
    }
};

int main()
{
    Test t1;         // 調(diào)用 Test()
    Test t2(1);      // 調(diào)用 Test(int v)
    Test t3 = 2;     // 調(diào)用 Test(int v)
    
    int i(10);
    
    printf("i = %d\n", i);
    
    return 0;
}

        我們看到第 18 行的 t1 對(duì)象的構(gòu)造函數(shù)肯定調(diào)用了 Test(),第 19 和 20 行則是調(diào)用了 Test(int v);在 C 語(yǔ)言中還有 int i(10) 這種寫法,我們看看編譯是否會(huì)通過(guò)?

對(duì)象的構(gòu)造(十四)

        我們看到編譯通過(guò),并且如我們所分析的那樣。那么構(gòu)造函數(shù)的調(diào)用是否有什么規(guī)則呢?在一般情況下,構(gòu)造函數(shù)在對(duì)象定義時(shí)被自動(dòng)調(diào)用,一些特殊情況下,需要手工調(diào)用構(gòu)造函數(shù)。我們?nèi)绾卫脴?gòu)造函數(shù)來(lái)創(chuàng)建一個(gè)數(shù)組呢?

#include <stdio.h>

class Test
{
private:
    int m_value;
public:
    Test()
    {
        printf("Test()\n");
        
        m_value = 0;
    }
    Test(int v)
    {
        printf("Test(int v), v = %d\n", v);
        
        m_value = v;
    }
    
    int getValue()
    {
        return m_value;
    }
};

int main()
{
    Test ta[3] = {Test(), Test(1), Test(2)};
    
    for(int i=0; i<3; i++)
    {
        printf("ta[%d].getValue() = %d\n", i, ta[i].getValue());
    }
    
    Test t = Test(10);
    
    printf("t.getValue() = %d\n", t.getValue());
    
    return 0;
}

        我們首先來(lái)分析下,數(shù)組第一個(gè)成員調(diào)用的構(gòu)造函數(shù)應(yīng)該是 Test(),后面兩個(gè)成員調(diào)用的是 Test(int v) 函數(shù),并打印出相應(yīng)的值。最后定義的對(duì)象 t,它會(huì)打印出構(gòu)造函數(shù)和得到的值都為 10,我們來(lái)看看編譯結(jié)果

對(duì)象的構(gòu)造(十四)

        下來(lái)我們來(lái)開(kāi)發(fā)一個(gè)數(shù)組類解決原生數(shù)組的安全性問(wèn)題:提供函數(shù)獲取數(shù)組長(zhǎng)度;提供函數(shù)獲取數(shù)組元素;提供函數(shù)設(shè)置數(shù)組元素。我們來(lái)看看它是怎么實(shí)現(xiàn)的


IntArray.h 源碼

#ifndef _INTARRAY_H_
#define _INTARRAY_H_

class IntArray
{
private:
    int m_length;
    int* m_pointer;
public:
    IntArray(int len);
    int length();
    bool get(int index, int& value);
    bool set(int index, int value);
    void free();
};

#endif


IntArray.cpp 源碼

#include "IntArray.h"

IntArray::IntArray(int len)
{
    m_pointer = new int[len];
    
    for(int i=0; i<len; i++)
    {
        m_pointer[i] = 0;
    }
    
    m_length = len;
}

int IntArray::length()
{
    return m_length;
}

bool IntArray::get(int index, int& value)
{
    bool ret = (0 <= index) && (index <= length());
    
    if( ret )
    {
        value = m_pointer[index];
    }
    
    return ret;
}

bool IntArray::set(int index, int value)
{
    bool ret = (0 <= index) && (index <= length());
    
    if( ret )
    {
        m_pointer[index] = value;
    }
    
    return ret;
}

void IntArray::free()
{
    delete[] m_pointer;
}


test.cpp 源碼

#include <stdio.h>
#include "IntArray.h"

int main()
{
    IntArray a(5);
    
    for(int i=0; i<a.length(); i++)
    {
        a.set(i, i+1);
    }
    
    for(int i=0; i<a.length(); i++)
    {
        int value = 0;
        
        if( a.get(i, value) )
        {
            printf("a[%d] = %d\n", i, value);
        }
    }
    
    a.free();
    
    return 0;
}

        我們編譯后得到如下結(jié)果

對(duì)象的構(gòu)造(十四)

        下來(lái)我們來(lái)看看特殊的構(gòu)造函數(shù):無(wú)參構(gòu)造函數(shù)和拷貝構(gòu)造函數(shù)。無(wú)參構(gòu)造函數(shù)顧名思義就是沒(méi)有參數(shù)的構(gòu)造函數(shù),而拷貝構(gòu)造函數(shù)則是參數(shù)為 const class_name& 的構(gòu)造函數(shù)。那么這兩類構(gòu)造函數(shù)有什么區(qū)別呢?無(wú)參構(gòu)造函函數(shù)是當(dāng)類中沒(méi)有定義構(gòu)造函數(shù)時(shí),編譯器默認(rèn)提供一個(gè)無(wú)參構(gòu)造函數(shù),并且其函數(shù)體為空;拷貝構(gòu)造函數(shù)是當(dāng)類中沒(méi)有定義拷貝構(gòu)造函數(shù)時(shí),編譯器默認(rèn)提供一個(gè)拷貝構(gòu)造函數(shù),簡(jiǎn)單的進(jìn)行成員變量的值復(fù)制。下來(lái)我們以代碼為例進(jìn)行分析

#include <stdio.h>

class Test
{
private:
    int i;
    int j;
public:
    int getI()
    {
        return i;
    }
    
    int getJ()
    {
        return j;
    }
/*    
    Test()
    {
        printf("Test()\n");
    }
    
    Test(const Test& t)
    {
        printf("Test(const Test& t)\n");
        i = t.i;
        j = t.j;
    }
*/
};

int main()
{
    Test t1;
    Test t2 = t1;
    
    printf("t1.i = %d, t1.j = %d\n", t1.getI(), t1.getJ());
    printf("t2.i = %d, t2.j = %d\n", t2.getI(), t2.getJ());
    
    return 0;
}

        我們先將自己提供的無(wú)參構(gòu)造函數(shù)和拷貝構(gòu)造函數(shù)注釋掉,編譯下,看編譯器是否提供默認(rèn)的構(gòu)造函數(shù),是否可以通過(guò)

對(duì)象的構(gòu)造(十四)

        我們看到編譯是通過(guò)的,也就是說(shuō),編譯器通過(guò)了默認(rèn)的構(gòu)造函數(shù)。我們?cè)賮?lái)自己提供呢,看看是否會(huì)發(fā)生沖突

對(duì)象的構(gòu)造(十四)

        我們看到打印出了自己定義的語(yǔ)句,證明它是調(diào)用了我們自己寫的構(gòu)造函數(shù)。那么這個(gè)拷貝構(gòu)造函數(shù)的意義在哪呢?一是兼容 C 語(yǔ)言的初始化方式,二是初始化行為能夠符合預(yù)期的邏輯。那么這塊就牽扯到是淺拷貝還是深拷貝。淺拷貝是拷貝后對(duì)象的物理狀態(tài)相同,深拷貝是拷貝后對(duì)象的邏輯狀態(tài)相同。注意:編譯器提供的拷貝構(gòu)造函數(shù)只進(jìn)行淺拷貝!

        下來(lái)我們以實(shí)例代碼看看對(duì)象的初始化是怎樣進(jìn)行的

#include <stdio.h>

class Test
{
private:
    int i;
    int j;
    int* p;
public:
    int getI()
    {
        return i;
    }
    
    int getJ()
    {
        return j;
    }
    
    int* getP()
    {
        return p;
    }
    
    Test(int v)
    {
        i = 1;
        j = 2;
        p = new int;
        
        *p = v;
    }
    
    Test(const Test& t)
    {
        i = t.i;
        j = t.j;
        p = new int;
        
        *p = *t.p;
    }
    
    void free()
    {
        delete p;
    }
};

int main()
{
    Test t1(3);
    Test t2(t1);
    
    printf("t1.i = %d, t1.j = %d, *t1.p = %d\n", t1.getI(), t1.getJ(), *t1.getP());
    printf("t2.i = %d, t2.j = %d, *t2.p = %d\n", t2.getI(), t2.getJ(), *t2.getP());
    
    t1.free();
    t2.free();
    
    return 0;
}

        我們看看 t1 應(yīng)該進(jìn)行的是淺拷貝,t2 應(yīng)該進(jìn)行的是深拷貝。我們看看編譯結(jié)果

對(duì)象的構(gòu)造(十四)

        我們?nèi)绻挥袦\拷貝,沒(méi)有深拷貝的話,看看結(jié)果會(huì)是怎樣的,將第 34 - 41 行的代碼注釋掉,將第 54 和 55 行的打印 *p 的值改為打印 p 的地址。對(duì)象的構(gòu)造(十四)

        我們看到它運(yùn)行的時(shí)候報(bào)段錯(cuò)誤了,t1.p 和 t2.p 指向了同一個(gè)地址。我們看看它是怎樣進(jìn)行的

對(duì)象的構(gòu)造(十四)

        我們看到將同一個(gè)地址釋放兩次肯定是會(huì)出問(wèn)題的,這時(shí)我們就需要進(jìn)行深拷貝了。那么我們就要考慮到底什么時(shí)候需要進(jìn)行深拷貝?當(dāng)對(duì)象中有成員指代了系統(tǒng)中的資源時(shí),如:成員指向了動(dòng)態(tài)內(nèi)存空間,成員打開(kāi)了外存中的文件,成員使用了系統(tǒng)中的網(wǎng)絡(luò)端口...

        我們?cè)趯?shí)現(xiàn)拷貝構(gòu)造函數(shù)這塊有個(gè)一般性的原則,自定義拷貝構(gòu)造函數(shù)時(shí),必須要實(shí)現(xiàn)深拷貝。那么我們?cè)賮?lái)優(yōu)化下之前的數(shù)組類


IntArray.h 源碼

#ifndef _INTARRAY_H_
#define _INTARRAY_H_

class IntArray
{
private:
    int m_length;
    int* m_pointer;
public:
    IntArray(int len);
    IntArray(const IntArray& obj);
    int length();
    bool get(int index, int& value);
    bool set(int index, int value);
    void free();
};

#endif

IntArray.cpp 源碼

#include "IntArray.h"

IntArray::IntArray(int len)
{
    m_pointer = new int[len];
    
    for(int i=0; i<len; i++)
    {
        m_pointer[i] = 0;
    }
    
    m_length = len;
}

IntArray::IntArray(const IntArray& obj)
{
    m_length = obj.m_length;
    
    m_pointer = new int[obj.m_length];
    
    for(int i=0; i<obj.m_length; i++)
    {
        m_pointer[i] = obj.m_pointer[i];
    }
}

int IntArray::length()
{
    return m_length;
}

bool IntArray::get(int index, int& value)
{
    bool ret = (0 <= index) && (index <= length());
    
    if( ret )
    {
        value = m_pointer[index];
    }
    
    return ret;
}

bool IntArray::set(int index, int value)
{
    bool ret = (0 <= index) && (index <= length());
    
    if( ret )
    {
        m_pointer[index] = value;
    }
    
    return ret;
}

void IntArray::free()
{
    delete[] m_pointer;
}


test.cpp 源碼

#include <stdio.h>
#include "IntArray.h"

int main()
{
    IntArray a(5);
    
    for(int i=0; i<5; i++)
    {
        a.set(i, i+1);
    }
    
    for(int i=0; i<a.length(); i++)
    {
        int value = 0;
        
        if( a.get(i, value) )
        {
            printf("a[%d] = %d\n", i, value);
        }
    }
    
    printf("\n");
    
    IntArray b = a;
    
    for(int i=0; i<b.length(); i++)
    {
        int value = 0;
        
        if( b.get(i, value) )
        {
            printf("b[%d] = %d\n", i, value);
        }
    }
    
    a.free();
    b.free();
    
    return 0;
}

        我們看看編譯結(jié)果是否如我們代碼所寫的那樣,創(chuàng)建數(shù)組并初始化。用數(shù)組 a 初始化數(shù)組 b。

對(duì)象的構(gòu)造(十四)

        通過(guò)對(duì)對(duì)象的構(gòu)造的學(xué)習(xí),總結(jié)如下:1、每個(gè)對(duì)象在使用之前都應(yīng)該初始化;2、類的構(gòu)造函數(shù)用于對(duì)象的初始化,構(gòu)造函數(shù)與類同名并且沒(méi)有返回值;3、構(gòu)造函數(shù)在對(duì)象定義時(shí)被自動(dòng)調(diào)用,構(gòu)造函數(shù)可以根據(jù)需要定義參數(shù);4、構(gòu)造函數(shù)之間可以存在重載關(guān)系,并且構(gòu)造函數(shù)遵循 C++ 中重載函數(shù)的規(guī)則;5、對(duì)象定義時(shí)會(huì)觸發(fā)構(gòu)造函數(shù)的調(diào)用,在一些情況下可以手動(dòng)調(diào)用構(gòu)造函數(shù);6、C++ 編譯器會(huì)默認(rèn)提供構(gòu)造函數(shù);7、無(wú)參構(gòu)造函數(shù)用于定義對(duì)象的默認(rèn)初識(shí)狀態(tài),拷貝構(gòu)造函數(shù)在創(chuàng)建對(duì)象時(shí)拷貝對(duì)象的狀態(tài);8、對(duì)象的拷貝有淺拷貝和深拷貝兩種方式:淺拷貝使得對(duì)象的物理狀態(tài)相同,而深拷貝則使得對(duì)象的邏輯狀態(tài)相同。


        歡迎大家一起來(lái)學(xué)習(xí) C++ 語(yǔ)言,可以加我QQ:243343083

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI