【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;
}
到這裡,拷貝建構函式就告一段落,但是學習卻沒有停止,若有不足,希望大家能夠多多指教。