【C++】 36_經典問題解析 三

關於賦值的疑問

何時須要重載賦值操做符?
編譯器是否提供默認的賦值操做符?ios

  • 編譯器爲每一個類默認重載了賦值操做符
  • 默認的賦值操做符僅完成淺拷貝
  • 當須要進行深拷貝時必須重載賦值操做符
  • 賦值操做符與拷貝構造函數有相同的存在乎義

編程實驗: 默認賦值操做符重載

test_1.cpp面試

#include <iostream>
#include <string>

using namespace std;

class Test
{
    int* m_pointer;
public:
    Test()
    {
        m_pointer = NULL;
    }
    Test(int i)
    {
        m_pointer = new int(i);
    }
    void print()
    {
        cout << "m_pointer = " << hex << m_pointer << endl;
    }
    ~Test()
    {
        delete m_pointer;
    }
};

int main()
{
    Test t1 = 1;
    Test t2;
    
    t2 = t1;
    
    t1.print();
    t2.print();
    
    return 0;
}
輸出:【運行時發生內存錯誤】
m_pointer = 0x942f008
m_pointer = 0x942f008
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x0942f008 ***
......
已放棄

現象:
t1.m_pointer 與 t2.m_pointer 指向同一片內存空間

修正: test_2.cpp編程

#include <iostream>
#include <string>

using namespace std;

class Test
{
    int* m_pointer;
public:
    Test()
    {
        m_pointer = NULL;
    }
    Test(int i)
    {
        m_pointer = new int(i);
    }
    Test(const Test& obj)
    {
        m_pointer = new int(*obj.m_pointer);
    }
    Test& operator = (const Test& obj)
    {
        if( this != &obj )
        {
            delete m_pointer;
            m_pointer = new int(*obj.m_pointer);
        }
        
        return *this;
    }
    void print()
    {
        cout << "m_pointer = " << hex << m_pointer << endl;
    }
    ~Test()
    {
        delete m_pointer;
    }
};

int main()
{
    Test t1 = 1;
    Test t2;
    
    t2 = t1;
    
    t1.print();
    t2.print();
    
    return 0;
}
輸出:
m_pointer = 0x83ac008
m_pointer = 0x83ac018

現象:
t1.m_pointer 與 t2.m_pointer 指向不一樣的內存空間

問題分析

clipboard.png

  1. test_2.cpp 類中未定義賦值操做符重載函數。在 t2 = t1; 對象賦值時,編譯器默認提供的賦值操做符重載函數被調用,發生淺拷貝;
  2. t1.m_pointer 與 t2.m_pointer 指向 t1.m_pointer 所指向的內存空間,未給 t2.m_pointer 從新分配;
  3. 在對象銷燬,析構函數被調用時,同一片內存空間被釋放兩次,致使運行時發生內存錯誤。

通常性原則

重載賦值操做符,必然須要實現深拷貝!數組

重載賦值操做符注意事項

Test& operator = (const Test& obj)
{
    if( this != &obj )
    {
       // ...
    }
    
    return *this;
}
  • 返回類型爲引用(連續賦值)
  • 參數爲 const 引用類型
  • 進行自賦值判斷
  • 返回當前對象

編程實驗: 數組類的優化

IntArray.happ

#ifndef _INTARRAY_H_
#define _INTARRAY_H_

class IntArray
{
private:
    int m_length;
    int* m_pointer;
    
    IntArray(int len);
    IntArray(const IntArray& obj);
    bool construct();

public:
    static IntArray* NewInstance(int length);
    int length();
    bool get(int index, int& value);
    bool set(int index, int value);
    int& operator [] (int index);
    IntArray& operator = (const IntArray& obj);
    IntArray& self();
    ~IntArray();
};

#endif

IntArray.cpp函數

#include "IntArray.h"

IntArray::IntArray(int len)
{
    m_length = len;
}

bool IntArray::construct()
{
    bool ret = true;
    
    m_pointer = new int[m_length];
    
    if( m_pointer )
    {
        for(int i=0; i<m_length; i++)
        {
            m_pointer[i] = 0;
        }
    }
    else
    {
        ret = false;
    }
    
    return ret;
}

IntArray* IntArray::NewInstance(int length)
{
    IntArray* ret = new IntArray(length);
    
    if( !(ret && (ret->construct())) )
    {
        delete ret;
        ret = 0;
    }
    
    return ret;
}

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;
}

int& IntArray::operator [] (int index)
{
    return m_pointer[index];
}

IntArray& IntArray::operator = (const IntArray& obj)
{
    if( this != &obj )
    {
        int* pointer = new int[obj.m_length];
        
        if( pointer )
        {
            for(int i=0; i<obj.m_length; i++)
            {
                pointer[i] = obj.m_pointer[i];
            }
            
            m_length = obj.m_length;
            delete m_pointer;
            m_pointer = pointer;
        }    
    }
    return *this;
}

IntArray& IntArray::self()
{
    return *this;
}

IntArray::~IntArray()
{
    delete[] m_pointer;
}

main.cpp優化

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

using namespace std;

int main()
{
    IntArray* a = IntArray::NewInstance(5);
    IntArray* b = IntArray::NewInstance(10);

    if( a && b )
    {
        IntArray& array = a->self();
        IntArray& brray = b->self();
        
        cout << "array.length() = " << array.length() << endl;
        cout << "brray.length() = " << brray.length() << endl;
        
        array = brray;
        
        cout << "array.length() = " << array.length() << endl;
        cout << "brray.length() = " << brray.length() << endl;
    }
    
    delete a;
    delete b;

    return 0;
}
輸出:
array.length() = 5
brray.length() = 10
array.length() = 10
brray.length() = 10

經典的面試問題

class T
{
};

==>this

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

關於 string 的疑問

  • 下面的代碼輸出什麼?爲何?
void code()
{
    string s = "12345";
    const char* p = s.c_str();
    
    cout << p << endl;
    
    s.append("abcde");
    
    cout << p << endl;
}

編程實驗: 字符串問題 1

#include <iostream>
#include <string>

using namespace std;

int main()
{
    string s = "12345";
    const char* p = s.c_str();
    
    cout << p << endl;
    
    s.append("abcde");
    
    cout << p << endl;          // p 成爲了野指針!
    
    return 0;
}
輸出:
12345
12345

問題分析

clipboard.png

【string 對象內部維護了一個指向數據(位於堆空間中)的 char* 指針,這個指針可能在運行的過程當中發生改變】spa

s = "12345"; ==> s 對象在堆空間申請內存並將字符串"12345"拷貝進入,同時內部指針指向這一空間 (0xFF112233)
p = s.c_str(); ==> p 指針指向保存數據的內存空間 (0xFF112233)
s.append("abcde"); ==> s 對象從新在堆空間申請內存,並把兩部分字符串拷貝進入,原有的堆空間被釋放(0xFF112233),內部指針指向新的內存空間 (0xFF445566)指針

  • 下面的程序輸出什麼?爲何?
void code()
{
    const char* p = "12345";
    string s = "";
    
    s.reserve(10);
    
    for(int i=0; i<5; i++)
    {
        s[i] = p[i];
    }
    
    cout << "s.length : " << s.length() << endl;
    cout << "s.empty : " << s.empty() << endl;
    
    cout << "s : " << s << endl;
}

編程實驗: 字符串問題 2

#include <iostream>
#include <string>

using namespace std;

int main()
{
    const char* p = "12345";
    string s = "";
    
    s.reserve(10);               // 將 s 的容量調整爲 10
    
    for(int i=0; i<5; i++)       // 不要使用 C 語言中的方式操做 C++ 中的字符串
    {
        s[i] = p[i];
    }
    
    cout << "s.length : " << s.length() << endl;
    cout << "s.empty : " << s.empty() << endl;
    
    cout << "s : " << s << endl;
    
    return 0;
}
輸出:
s.length : 0
s.empty : 1
s :

問題分析

clipboard.png

for 循環前:
m_cstr 指向10個字節的堆空間(0xFF010203)
m_length 字符長度爲 0 (空串)

for 循環後:
m_cstr 指向 10 個字節的堆空間前 5 個字節發生改變(0xFF010203)
m_length 字符長度爲 0 (空串)

警告:
在 C++ 中儘可能只採用 C++ 編程思想,而不採用混合方式開發,不然可能產生意想不到的問題

小結

  • 在須要進行深拷貝的時候必須重載賦值操做符
  • 賦值操做符和拷貝構造函數有同等重要的意義
  • string 類經過一個數據空間保存字符數據
  • string 類經過一個成員變量保存當前字符串的長度
  • C++ 開發時儘可能避開 C 語言中慣用的編程思想

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

相關文章
相關標籤/搜索