1. 程式人生 > >淺談類的幾個基礎構造函數

淺談類的幾個基礎構造函數

his 發生 cout tro 構造 spa 默認構造函數 字符 ima

通過例子來介紹下C++類的幾個基礎構造函數。

我們以一個C類型的字符串為例:

class myString
{
public:
    myString(const char* rhs = 0);                 // 默認(含參)構造函數
    myString(const myString& rhs);                 // 拷貝構造函數
    myString(myString&& rhs) noexcept;             // 移動構造函數

    myString& operator=(const
myString& rhs); // 拷貝賦值函數 myString& operator=(myString&&) noexcept; // 移動賦值函數 ~myString(); // 析構函數 private: char* m_data; };

  

(一)、我們定義一個myString類,僅包含一個char* 的指針。先來看看它的默認構造函數

inline myString::myString(const char* rhs)
{
    
if (rhs) { m_data = (char*)new char[strlen(rhs) + 1]; strcpy_s(m_data,strlen(rhs)+1, rhs); } else { m_data = new char[1]; *m_data = \0; } }

這裏僅是申請了一塊內存,對傳入字符串進行了拷貝。

(二)、關於拷貝構造函數。拷貝構造函數是僅是對於傳入對象的一次深拷貝。記得使用引用傳入,由於我們不需要對傳入對象進行修改操作,那就對它聲明為const吧。

inline myString::myString(const myString& rhs)
{
    m_data = (char*)new char[strlen(rhs.m_data) + 1];
    strcpy_s(m_data, strlen(rhs.m_data)+1, rhs.m_data);
}

 

(三)、對於拷貝賦值函數我們尤其要註意自我賦值問題。如果我們不進行自我賦值檢測,即傳入對象和被賦值對象是同一個的話,當delete完之後,傳入的對象也已經不存在了,這並不是我們想要的結果。

inline myString& myString::operator=(const myString& rhs)
{
    if (this != &rhs)
    {
        if (m_data)
            delete m_data;
        m_data = (char*)new char[strlen(rhs.m_data) + 1];
        strcpy_s(m_data, strlen(rhs.m_data)+1, rhs.m_data);
    }
return *this; }

  (四)、關於移動構造函數。移動構造函數給我帶來一種 “ ” 的概念。如何理解呢?我們來列舉2個移動構造函數的主要應用場景:1. 假設我們需要將一批myString對象存入vector,當vector由於原容量不夠大而發生擴充時,之前的C++版本中vector內部會重新申請一塊內存,然後把之前存儲的對象一個一個拷貝到新內存上,並且釋放原內存。

技術分享圖片

當C++11以後我們可以借助移動構造函數這個“偷”的概念。怎麽偷? 先看下代碼:

inline myString::myString(myString&& rhs) noexcept
    : m_data(rhs.m_data)
{
    rhs.m_data = NULL;
}

這不就是指針的拷貝,換言之淺拷貝嗎? 可以這麽說!既然原先的對象可以被拿來用,我們又何必大費周章先做一份拷貝,再刪除原副本呢?這換來的是效率上的巨大提升。使用移動構造函數我們需要註意2點:1). 不能讓移動構造函數拋出異常,我們將它設為noexcept; 2). “ 偷 ”完東西將原指針設為NULL, 否則要是原對象被delete,“ 偷 ”的東西也就沒了,這讓我們難以接受。2. 如果我們要將一個容器拷貝到另一個容器,將容器內的對象一個一個拷貝?天哪!我們還是來 “ 偷 ” 吧。C++11以後容器都內置有移動構造函數,當我們對容器進行拷貝時,它已經在背後悄悄地 “ 偷 ”了。(舉個例子, 將一個300萬個對象的vector進行拷貝, 是一個一個拷貝好呢, 還是只需要“ 偷 ” 3個指針好呢(start, finish, end_of_storage)? 果然還是 “ 偷 ” 起來爽呀)

(五)、移動賦值函數。移動賦值的原理同上,也是采用 “ 偷 ” 的方法,尤其註意自我賦值即可。

inline myString& myString::operator= (myString&& rhs) noexcept
{
    if (this != &rhs)
    {
        if (m_data)
            delete m_data;
        m_data = rhs.m_data;
        rhs.m_data = NULL;

    }
   return *this;
}

(六)、析構函數。析構函數的任務就是把申請的對象進行釋放。

inline myString::~myString()
{
    delete m_data;
}

 這裏給出測試代碼:

(我們對一些代碼加了些提示性的語句。 測試環境: VS2017)

#include <iostream>
#include <cstring>
#include <vector>
using namespace std;

class myString
{
public:
    myString(const char* rhs = 0);
    myString(const myString& rhs);
    myString(myString&& rhs) noexcept;

    myString& operator=(const myString& rhs);
    myString& operator=(myString&&) noexcept;

    ~myString();

    char* getStr() { return m_data; }
private:
    char* m_data;
};

inline myString::myString(const char* rhs)
{
    if (rhs)
    {
        m_data = (char*)new char[strlen(rhs) + 1];
        strcpy_s(m_data,strlen(rhs)+1, rhs);
    }
    else
    {
        m_data = new char[1];
        *m_data = \0;
    }
}

inline myString::myString(const myString& rhs)
{
    m_data = (char*)new char[strlen(rhs.m_data) + 1];
    strcpy_s(m_data, strlen(rhs.m_data)+1, rhs.m_data);

}

inline myString::myString(myString&& rhs) noexcept
    : m_data(rhs.m_data)
{
    rhs.m_data = NULL;
    cout << " 調用了我一次。myString(myString&& rhs) " << endl;
}

inline myString& myString::operator=(const myString& rhs)
{
    if (this != &rhs)
    {
        if (m_data)
            delete m_data;
        m_data = (char*)new char[strlen(rhs.m_data) + 1];
        strcpy_s(m_data, strlen(rhs.m_data)+1, rhs.m_data);
    }
return *this;
}

inline myString& myString::operator=(myString&& rhs) noexcept
{
    if (this != &rhs)
    {
        if (m_data)
            delete m_data;
        m_data = rhs.m_data;
        rhs.m_data = NULL;

    }

    cout << " 調用了我一次。operator(myString&& rhs) " << endl;

    return *this;
}

inline myString::~myString()
{
    delete m_data;
}


int main()
{
    myString str1;
    myString str2("wang");
    myString str3(str2);
    myString str4 = str2;

    vector<myString> vec;

    int n = 20;
    while (n--)      // 通過size 和 capacity 的值以及輔助性語句,查看容器擴充時是否調用移動拷貝。
    {
        cout << "vector size = ";
        cout << vec.size() << endl;
        cout << "vector capacity = " ;
        cout << vec.capacity() << endl;

        cout << endl;
        vec.push_back(str1);

        cout << "vector size = ";
        cout << vec.size() << endl;
        cout << "vector capacity = ";
        cout << vec.capacity() << endl;

        cout << endl;
        vec.push_back(str2);

        cout << "vector size = ";
        cout << vec.size() << endl;
        cout << "vector capacity = ";
        cout << vec.capacity() << endl;

        cout << endl;
        vec.push_back(str3);

        cout << "vector size = ";
        cout << vec.size() << endl;
        cout << "vector capacity = ";
        cout << vec.capacity() << endl;

        cout << endl;
        vec.push_back(str4);

        cout << "vector size = ";
        cout << vec.size() << endl;
        cout << "vector capacity = ";
        cout << vec.capacity() << endl;

    }

    vector<myString> vec2{ vec };           // 查看容器賦值時是否調用內部移動構造(無提示性語句。 可通過vs2017調試跟蹤函數調用過程)
    return 0;
}

淺談類的幾個基礎構造函數