【C++】 19_對象的構造 (下)

兩個特殊的構造函數

  • 無參構造函數面試

    • 沒有參數的構造函數
  • 拷貝構造函數編程

    • 參數爲 const class_name& 的構造函數

  • 無參構造函數segmentfault

    • 當類中沒有定義構造函數時,編譯器提供一個無參構造函數,而且函數體爲空
  • 拷貝構造函數數組

    • 當類中沒有定義拷貝構造函數時,編譯器默認提供一個拷貝構造函數,簡單的進行成員變量的值複製

編程實驗: 特殊的構造函數

Test_1.cpp網絡

#include <stdio.h>

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

int main()
{
    Test t1;
    Test t2;
    
    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;
}
第一次輸出:
t1.i = 134514464, t1.j = -1081276856
t2.i = 2528036, t2.j = 2527220
第二次輸出:
t1.i = 134514464, t1.j = -1081626328
t2.i = 3298084, t2.j = 3297268
第三次輸出:
t1.i = 134514464, t1.j = -1080045768
t2.i = 9716516, t2.j = 9715700

分析:
類中沒有定義構造函數,編譯器提供一個無參構造函數,函數體爲空,沒法對成員變量初始值。打印棧空間中的隨機值。

編譯器在類中未發現構造函數,會在類最後添加無參構造函數:
Test()
{
}

test_2.cpp函數

#include <stdio.h>

class Test
{
private:
    int i;
    int j;
public:
    int getI()
    {
        return i;
    }    
    int getJ()
    {
        return 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;
}
第一次輸出:
t1.i = 134514480, t1.j = -1081214552
t2.i = 134514480, t2.j = -1081214552
第二次輸出:
t1.i = 134514480, t1.j = -1082006488
t2.i = 134514480, t2.j = -1082006488
第三次輸出:
t1.i = 134514480, t1.j = -1077369784
t2.i = 134514480, t2.j = -1077369784


分析:類中沒有定義拷貝構造函數,編譯器提供一個拷貝構造函數,簡單的進行成員變量的值複製。
t1.i == t2.i
t1.i == t2.i

編譯器在類中未發現拷貝構造函數,會在類最後添加簡單的拷貝構造函數:
Test(const Test& t)
{
    i = t.i;
    j = t.j;
}
class Test
{
private:
    int i;
    int j;
public:
    int getI()
    {
        return i;
    }    
    int getJ()
    {
        return j;
    }
};

<==>spa

class Test
{
private:
    int i;
    int j;
public:
    int getI()
    {
        return i;
    }    
    int getJ()
    {
        return j;
    }
    Test()               // 無參構造函數,函數體爲空
    {
    }
    Test(const Test& t)  // 拷貝構造函數,進行簡單的成員變量值複製
    {
        i = t.i;
        j = t.j;
    }
};

經典的面試問題

class T
{
};

==>code

class T
{
public:
    T();
    T(const T&);
    T& operator = (const T&);
    ~T();
};

問: T 中包含什麼?
答: 無參構造函數、拷貝構造函數、析構函數賦值操做符重載函數對象

拷貝構造函數

拷貝構造函數的意義

  • 兼容 C 語言的初始化方式
  • 初始化行爲符合預期的邏輯

  • 淺拷貝blog

    • 拷貝後對象的物理狀態相同(兩對象佔用的內存空間中,每一個字節的值相等)
  • 深拷貝

    • 拷貝後對象的邏輯狀態相同

編譯器提供的拷貝構造函數只進行淺拷貝!

編程實驗: 對象的初始化

淺拷貝的現象:test_1.cpp

#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;
    }
    
    void free()
    {
        delete p;
    }
};

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

    return 0;
}
輸出:
t1.i = 1, t1.j = 2, t1.p = 0x950c008, *t1.p = 3
t2.i = 1, t2.j = 2, t2.p = 0x950c008, *t2.p = 3

分析:
類中沒有定義拷貝構造函數,編譯器提供一個拷貝構造函數,簡單的進行成員變量的複製。
t1.p = 0x950c008,t2.p = 0x950c008 指向同一內存空間,而沒有爲 t2.p 從新分配空間,不是指望結果。

淺拷貝的問題: test_2.cpp

#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;
    }
    
    void free()
    {
        delete p;
    }
};

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

    t1.free();    // 注意這裏!
    t2.free();    // 注意這裏!

    return 0;
}
輸出:
內存錯誤

深拷貝實例: test_3.cpp

#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; // ==> Test t2(t1);
    
    printf("t1.i = %d, t1.j = %d, t1.p = %p, *t1.p = %d\n", t1.getI(), t1.getJ(), t1.getP(), *t1.getP());
    printf("t2.i = %d, t2.j = %d, t2.p = %p, *t2.p = %d\n", t2.getI(), t2.getJ(), t2.getP(), *t2.getP());

    t1.free();
    t2.free();

    return 0;
}
輸出:
t1.i = 1, t1.j = 2, t1.p = 0x927b008, *t1.p = 3
t2.i = 1, t2.j = 2, t2.p = 0x927b018, *t2.p = 3

分析:
t1.p = 0x927b008, t2.p = 0x927b018 指向不一樣的內存空間。指向的內存空間中的值相同。

何時須要進行深拷貝?

  • 對象中有成員指代了系統中的資源

    • 成員指向了動態內存空間
    • 成員打開了外存中的文件
    • 成員使用了系統中的網絡端口
    • 。。。。。

問題分析

clipboard.png

淺拷貝只進行簡單的成員變量值複製,因此發生 t2.m_pointer=t1.m_pointer; 兩對象成員變量指向同一片內存空間而沒有發生新的內存申請,在 free 時,對同一片內存空間釋放兩次,致使運行時內存出錯。

通常原則

  • 自定義拷貝構造函數,必須須要實現深拷貝!!

編程實驗:數組類的改進

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 = (index >= 0) && (index < length());
    
    if( ret )
    {
        value = m_pointer[index];
    }
    
    return ret;
}

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

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

main.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);
        }
    }
    
    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;
}
輸出:
a.[0] = 1
a.[1] = 2
a.[2] = 3
a.[3] = 4
a.[4] = 5
b.[0] = 1
b.[1] = 2
b.[2] = 3
b.[3] = 4
b.[4] = 5

小結

  • C++ 編譯器會默認提供構造函數
  • 無參構造函數用於定義對象的默認初始狀態
  • 拷貝構造函數在建立對象時拷貝對象的狀態
  • 對象的拷貝有淺拷貝和深拷貝兩種方式

    • 淺拷貝使得對象的物理狀態相同
    • 深拷貝使得對象的邏輯狀態相同

以上內容參考狄泰軟件學院系列課程,請你們保護原創!

相關文章
相關標籤/搜索