1. 程式人生 > >【C++】String拷貝(包含深拷貝淺拷貝)以及拷貝建構函式中幾種呼叫的情況

【C++】String拷貝(包含深拷貝淺拷貝)以及拷貝建構函式中幾種呼叫的情況

之前我們已經講過了類和物件,但是其中我們沒有仔細的分析建構函式以及拷貝建構函式。
現在我們仔細的來分析一下這兩類函式。

**建構函式**

    在寫建構函式時,必要情況下我們要給一些值進行初始化,不然在執行時可能會出現無法預知的錯誤

  初始化也分為兩種:
     (1)第一種是預設的,也就是在定義建構函式時,我們就給其物件賦予初值,當在使用建構函式時,如果沒有給其賦值,那麼系統就會使用我們預設給的值
    例如:
    #include<iostream>
    using namespace std;
    class Time
    {
    public
: //需要注意,建構函式的名字與類名一定要一致 Time(int hour = 0,int minute = 0,int second = 0) { _hour = hour; _minute = minute; _second = second; } private : int _hour; int _minute; int _second; }; int main() { Time time1();//使用預設值 Time time2(2017
,10,22);//重新賦值 cout<<time1<<endl; cout<<time2<<endl; } (2)第二種就是用初始化列表進行初始化,以上面的例子為繼: 格式為: Time(int hour = 0 , int minute = 0 ,int second = 0 ) :_hour(hour),_minute(minute),_second(second) {} 這種情況就顯得很簡單了,初始化時都會進行。

接下來就是拷貝構造函數了,拷貝建構函式也是建構函式的一種,但是它提供引數,但沒有返回值。

Time(const Time& t)
{
_hour = t._hour;
_minute = t._minute;
_second = t._second;

}

拷貝時存在深拷貝和淺拷貝

    //拷貝構造淺拷貝
    String(String& s) :_str(NULL)
    {
        _str = s._str;
    }
    //深拷貝
    String(String& s)
    {
        String tmp(s._str);
        swap(_str, tmp._str);
    }

為什麼會存在深拷貝和淺拷貝??深拷貝與淺拷貝有哪些區別?
假如我們在學校裡都不愛寫作業,那麼我們就會去抄作業,然後我們選了一個學習中等的同學的作業去抄,抄完了之後,那個同學發現他寫錯了,那他就會改正,那借他作業抄的同學也要跟著改正,這就相當於是淺拷貝。 如上,_str 和 s._str 指向的是同一塊空間,在我們修改或更新 s._str 的時候,原本寫好的 _str 就會跟著改變,這就是淺拷貝。而深拷貝就相當於是 A 把作業借給了 B,B 作業寫完了之後借給了C,A 修改作業但是 C 不知道,C 就不會修改他的作業,除非有人告訴他。上面的例子就是:s._str 拷貝給了 tmp ,然後 tmp 把自己的值給了 _str ,這時 tmp 出了這個作用域就已經不存在了, _str 和 s._str 就互相不干擾了。實際上通俗的講,就是 s._str 和 tmp 各指向了一塊空間,地址不同,但是內容卻相同,然後 _str 也指向了 tmp 的那塊空間,當tmp不存在的時候,那塊地址還有 _str 指向,所以不會銷燬 。但是 s._str 和 _str 又沒有指向同一塊空間,所以 s. _str 改變不會改變_str的值。

下面幾張圖就是我們在深淺拷貝時觀察到的,就會很容易的區分出深淺拷貝

淺拷貝前後的地址:

這裡寫圖片描述
這裡寫圖片描述

深拷貝前後的地址:

這裡寫圖片描述

這裡寫圖片描述

拷貝構造裡還有幾種情況值得我們注意:

(1)用類的一個物件去初始化另一個物件時需要呼叫拷貝建構函式
(2)當函式的返回值是類的物件或引用會呼叫拷貝建構函式
(3)當函式的形參是類的物件時,使用值傳遞的方法傳遞時會呼叫拷貝建構函式,如果是引用則不會呼叫
程式碼做證明:

#include<iostream>
using namespace std;
class AA
{
public :
    //建構函式
    AA(int a)
    {
        _a = a;
    }

    int get()
    {
        return _a;
    }
    //情況一 初始化一個物件
    AA(AA& a)
    {
        _a = a._a;
        cout << "該情況呼叫了拷貝建構函式!" << endl;
    }
    //情況二,返回值是物件型別
    //為什麼呼叫拷貝建構函式,因為函式體內生成的物件aa是臨時的,離開這個函式就消失了。
    //則會呼叫拷貝建構函式複製一份再傳回。
    AA get_A()
    {
        AA a(1);
        return a;
    }
    //返回值為引用型別,返回的是別名,則不需要呼叫
    AA& get_A1()
    {
        AA a(2);
        return a;
    }
    //情況三,物件型別做引數時,值傳遞
    int get_A2(AA a)
    {
        _a = a._a;
        return _a;
    }
    //引用做引數時,傳給的是別名,不需要拷貝
    int get_A3(AA& a)
    {
        _a = a._a;
        return _a;
    }

private:
    int _a;
};

void test()
{
    //第一種情況
    //AA x(3);   //呼叫建構函式
    //AA y(x);   //呼叫拷貝建構函式
    /*cout << x.get() << endl;
    cout << y.get() << endl;*/

    ////第二種情況
    //AA x1(5);
    //x1.get_A();//呼叫拷貝建構函式
    //x1.get_A1();//不呼叫拷貝建構函式

    //第三種情況
    AA x2(4);
    int c = x2.get_A2(x2);//呼叫拷貝建構函式
    int d = x2.get_A3(x2);//不呼叫拷貝建構函式
}


#include"String.h"
#include<stdlib.h>

int main()
{
    test();
    system("pause");
    return 0;
}

到這裡,拷貝建構函式就告一段落,但是學習卻沒有停止,若有不足,希望大家能夠多多指教。