C++之this指標、拷貝構造、賦值構造、單列模式(餓漢模式、懶漢模式)

網際網路並非法外之地發表於2020-09-24

類的成員變數儲存在每個類物件中(每個物件一份),成員函式儲存在程式碼段中(所有的物件共享一份),那麼成員函式是如何區分呼叫它的是哪個物件?
答:與C語言的解決方案一樣,把物件的地址傳遞給成員函式,這樣成員函式就知道是哪個物件在呼叫它,與C語言不同的是該操作是隱藏,編譯器幫我們自動完成的,也就是類的成員函式都隱藏著一個引數,這個引數就是this指標。

this指標:
指向訪問成員函式物件的指標,預設是隱藏的,但也可以顯示使用。

物件呼叫成員函式時會自動計算它的記憶體地址傳遞給成員函式中的this,而在成員函式呼叫其它成員函式,會把物件的地址接力傳遞下去。

建構函式中也有this指標,指向這個正在構造的物件,原因是建構函式執行前物件所需的記憶體就已經分配完畢,執行建構函式的目的是為了申請其它資源、做一些準備工作,對指標成員分配記憶體。
this指標的用法:
1、函式引數與成員變數重名,引數會遮蔽成員變數,可以通過this指標訪問成員變數。
2、如果成員函式需要返回當前物件的地址或引用,可以使用this指標完成。
3、可以把this指標傳遞其它類物件,這樣可以實現物件間的互動。
二、常函式
如果物件被const修飾,那麼它就不能呼叫普通的成員函式,因此普通成員函式的this指標沒有被const修飾,而const物件在呼叫成員函式傳遞的物件指標帶const屬性,因此不相容。

在類的成員函式末尾用const修飾一下,就表示用const修飾該成員函式的this指標,這種函式叫常函式,cosnt物件只能呼叫常函式,常函式也只能呼叫函式。

如果一個成員函式可能const物件和非常物件呼叫,則可以過載,實現兩個版本,const指標和非const指標可以影響函式的過載。

如果在常函式中要修改成員變數值,可以使用mutable修飾成員變數即可。

三、拷貝構造
拷貝構造也是一種建構函式,只是引數一箇舊的物件,當使用舊物件給新建立的物件初始化時將呼叫拷貝構造。
類名(const 類& that)
{

}
預設情況下編譯器會自動生成一個拷貝建構函式,該函式負責把舊物件中的資料拷貝給新的物件。

深拷貝與淺拷貝:
如果類的成員中有指標,在拷貝時只拷貝指標變數的值,這種叫淺拷貝,深拷貝是拷貝指所指向的資料。
注意:預設的拷貝構造只能完成淺拷貝。

什麼情況要重寫拷貝構造:
當類中的成員有指標時,想完成深拷貝的效果,就需要重寫拷貝構造。

什麼情況下會呼叫拷貝構造:
1、使用舊物件給新物件初始化時
Test t1 = t;
2、使用物件作為函式的引數或返回值時。

四、賦值執行符
使用舊物件給其它物件賦值時,呼叫賦值執行符,它的任務就是把物件a中的資料拷貝給物件b,預設情況下也是淺拷貝。
void operator=(const 類& that)
{
}
當你需要重寫拷貝構造時,也就需要重寫賦值運算子。
常用考筆試題:實現string類的構造、拷貝構造、賦值運算子、解構函式。
class String
{
char* str;
public:
String(const char* str="");
~String(void);
String(const String& that);
void operator=(const String& that);
};

五、關於拷貝構造和賦值函式的建議
1、拷貝構造和賦值函式不光會賦值本類的資料,也會呼叫父類和成員類的拷貝構造和賦值函式,而不是單純的記憶體拷貝,因此儘量少使用指標成員。

2、函式的引數中儘量指標和引用,減少呼叫拷貝構造的次數,這樣也可以提高傳遞的效率。

3、如果由於特殊原因無法實現拷貝構造和賦值函式,可以把實現個空的然後私有化,防止誤用。

4、一旦實現了拷貝構造也要實現賦值函式,反之亦然。

六、靜態成員
類的成員變數可以被static修飾,儲存位置由原來的棧或堆變成data或bss,整個程式中只存在一份,被所有的物件共享(靜態成員屬於類,而不是某個物件)。
靜態成員變數在類中宣告,但必須在類外定義、初始化,與其它在類外的成員函式一樣,需要加 類名:: 表示它屬於哪個類,但不需要再加static。

靜態成員變數雖然在類定義,但它依然受訪問控制符的限制,私有成員和保護成員只能在類內訪問。

靜態成員變數不需要物件就可以訪問,類名::靜態成員變數名,public靜態成員變數可以當全域性變數使用。

靜態成員函式也可以被static修飾,這種成員引數中就沒有隱藏this指標,因此靜態成員函式不需要物件就可以訪問,類名::靜態成員函式名,也就導致靜態成員函式中不訪問成員變數,也不能呼叫其它成員函式。

普通成員函式中可以直接訪問類的靜態成員變數和靜態成員函式。

靜態成員的作用:
靜態成員變數可以當作類範圍內的"全域性變數使用"。
而靜態成員函式可以作為其它函式的回撥函式,或者當作類的介面,實現對類的管理。
常見的面試:C語言中的static與C++的static有什麼區別?
在C語言static具有限制作用域、改變儲存位置、延長生命週期的功能。
在C++中static又增加了新功能,可以修飾類的成員變數和成員函式。
靜態成員變數:
儲存位置由原來的棧或堆變成data或bss,整個程式中只存在一份,被所有的物件共享。
在類裡面宣告,在類外定義,訪問時不需要物件,依然受訪問控制限定符控制,只有public可以在類外訪問被當作全域性變數使用。
靜態成員函式:
沒有隱藏的this指標,不需要物件就可以呼叫,不能在函式訪問成員變數和成員函式,但可以被其它成員函式呼叫。
可以讓類的成員函式當作回撥函式,可以當作類的管理介面。

七、單例模式
只能建立出一個物件的類叫單例模式。
單例模式的應用場景:
1、Windows系統的工作管理員。
2、Linux\UNIX系統的日誌管理系統。
3、網站的訪問計數器。
4、伺服器的連線池、執行緒池、資料池等。
建立單一物件的方法:
1、定義全域性的,但不受控制,且有再次建立的風險。
2、專門實現一個類,把類的建構函式呼叫為私有,然後藉助靜態成員函式提供一個獲取唯一物件的介面。
C++實現單例的思路:
1、禁止類外建立類物件:把構造和拷貝構造設定為私有的。
2、類自己維護一個物件:使用靜態指標或靜態物件。
3、提供一個獲取例項的方法:使用靜態成員函式獲取靜態物件。
懶漢模式:
用靜態成員指標來指向唯一的單例物件,只有真正呼叫單例介面時才建立物件。
優點:什麼時候用什麼時候建立,節約記憶體、資源。
缺點:只有在呼叫獲取介面時才建立物件,當多個執行緒同時呼叫該介面時,可能會建立出多個物件,存線上程不安全問題。

#include <iostream>
using namespace std;

class Single
{
	static Single* single;
	Single(void) {}
	Single(const Single& that) {}
public:
	static Single* get_single(void)
	{
		if(NULL == single)
		{
			single = new Single;
		}
		return single;
	}
	void show(void)
	{
		cout << single << endl;
	}
};
Single* Single::single = NULL;

void func(Single& t)
{
	cout << &t << endl;
}

int main(int argc,const char* argv[])
{
	Single* s = Single::get_single();
	s->show();
	func(*s);
}

餓漢模式:
將單例物件設定為類的靜態成員變數,在main函式開始執行前,例項物件就已經建立完成。
優點:獲取單例物件時,執行緒安全。
缺點:無論是否使用,單例物件都已經建立完成,浪費記憶體、資源。

#include <iostream>
using namespace std;

class Single
{
	static Single single;
	Single(void)
	{
		cout << this << endl;
		cout << "我是單例建構函式" << endl;
	}
	Single(const Single& that) {}
public:
	static Single& get_single(void)
	{
		return single;
	}
};
Single Single::single;

int main(int argc,const char* argv[])
{
	cout << "hehe" << endl;
	Single& s = Single::get_single();
	cout << &s << endl;
	Single& s1 = Single::get_single();
	cout << &s1 << endl;
}

相關文章