這裡寫目錄标題
- 18.虛析構
-
- 問題提出:在繼承關系中構造和析構什麼時候被調用?
- 虛析構
- 關于NULL在C語言中和在C++中的差別:
- 19.接口類
-
- 純虛函數
- 抽象類
- 抽象方法
- 抽象類的派生類的使用
- 接口類
- 20.頭檔案
-
- 類的頭檔案
- 類的源檔案
- 頭檔案&編譯
- 頭檔案重複包含問題
- 21.operator重載操作符
-
- operator:
-
- 為什麼使用重載操作符:
- 如何重載
-
- 1.将操作符重載實作為類成員函數
- 2.将操作符重載實作為非類成員函數(即全局函數)
- 3.特殊重載
- 補充:
- 22.list類
- 23.拷貝構造
-
- 什麼是拷貝構造
- 拷貝構造的規則:
-
- 為什麼類中指針成員變量動态配置設定記憶體時,我們需要自己寫一個拷貝構造?
- 如何解決?(如何在自定義的拷貝構造中避免此問題的發生?)
- 哪些地方會存在拷貝構造的現象?
-
- 1. 函數值傳遞
- 2. list類
- 重載“=”操作符
- 24.設計模式
-
- Template模式
-
- 1.問題:
- 2.代碼實作:
- 3.代碼說明:
- 4.注意:
- 單例模式
-
- 1.問題:
- 2.示例代碼:
- 3.代碼說明:
- 25.模闆
-
- 1.函數模闆
-
- 問題引出:
- 函數模闆的使用
- 2.類模闆:
-
- 類模闆的使用:
18.虛析構
問題提出:在繼承關系中構造和析構什麼時候被調用?
假如目前有類
CSon
繼承
CFather
- 構造:當
的時候,就會調用new CSon
,程式跳進CSon()
,在CSon()
裡會先調用CSon()
,然後執行CFather()
中的程式,然後從CSon()
中結束。CSon()
- 析構:當
一個delete
類型的對象的時候,程式會調用CSon
,先執行~CSon()
中的程式,然後在~CSon()
退出前調用~CSon()
,再從~CFather()
結束。~CSon()
虛析構
- 什麼是虛析構?
- 在面向對象的程式設計中,給基類的析構函數加上
關鍵字,使其成為虛函數(注意:隻能是析構函數,構造函數不可以是虛函數)virtual
- 虛析構用來解決什麼問題?
- 申請子類對象指派為父類指針時:
CFather *p = new CSon();
- 我們知道,
的時候,可以調用new CSon()
、CFather()
,但是當CSon()
的時候,p是一個delete p
類型,是以系統會直接去調用CFather
而沒有調用~CFather()
,這種情況 就會導緻程式出錯以及記憶體洩漏,而虛析構就是用來解決此問題的。~CSon()
- 為什麼虛析構可以解決此問題?
- 如果
的析構函數是這樣的:CFather()
, 當virtual ~CFather()
的時候,預設會調用delete p
中的析構函數,但是這時候CFather()
指向的析構函數是虛函數,被子類重寫,實際上就是調用的是子類的析構函數,程式跳轉進入vfptr
,先執行~CSon()
中的内容,在退出之前調用基類(CFather)的析構函數~CSon()
,然後從~CFather()
中退出。~CSon()
是以我們可以得到虛析構的作用:通過父類指針完整的删除子類的對象,防止記憶體洩漏。
關于NULL在C語言中和在C++中的差別:
在stdio.h檔案中這樣定義:
#ifndef NULL
#ifdef _cplusplus
#define NULL 0
#else
#define NULL ((void*)0)
#endif
#endif
也就是說,在C++中NULL其實就是0
在C語言中,NULL是将0強轉成指針類型之後的結果
19.接口類
純虛函數
形如
virtual void show() = 0;
的函數
抽象類
- 包含抽象純虛函數的類,叫做抽象類,這些抽象類是一些具體的類的基類,可以用抽象類的派生類去定義對象,但是不能使用抽象類去定義對象
- 例如:鳥這個類就是一個抽象類,而我們所說的麻雀,鹦鹉……這些就是一些具體的類,你能刻畫出麻雀等具體的一種鳥是什麼樣子,但是你不能刻畫出鳥類是什麼樣子
抽象方法
- 抽象類中的純虛函數就叫做抽象方法,但是抽象方法在抽象類中的具體作用是确定不了的,并且無法将其執行個體化
- 例如:人類都會吃飯,但是具體怎樣吃,你無法确定,中國人用筷子,美國人用刀叉,印度人用手抓,視具體人種而定
- 正是因為這個原因:在C++中對于一個虛函數,“=0”和“{}”是不能共存的,言外之意就是純虛函數無法執行個體化
抽象類的派生類的使用
- 抽象類的派生類被強制要求要對抽象類中的抽象方法進行重寫,否則無法使用
- 因為派生類可以使用基類的方法,基類如果是抽象類,前面說到過抽象方法不能在基類中進行執行個體化,是以在派生類中必須去重寫它,才能夠使用。抽象方法在基類中的定義,實際上隻是提供了一種接口,告訴子類,我有這個功能,但是這個功能要如何實作,具體要做什麼,就需要子類去重寫實作了。
接口類
- 一個類中的是以函數都是純虛函數,那麼這個類就是一個接口類
- 設計抽象類的目的是為了給其他類提供一個可以繼承的适當的基類
- 面向對象的系統可能會使用一個抽象基類為所有的外部應用程式提供一個适當的、通用的、标準化的接口。然後,派生類通過繼承抽象基類,就把所有類的操作都繼承下來
- 外部應用程式提供的功能(即共有函數)在抽象類基類中是以春旭函數的形式存在的。這些純虛函數在相應的派生類中被實作。這個架構是的新的應用程式可以很容易地被添加到系統中,即使是在系統被定義之後依然可以如此
20.頭檔案
我們知道,一個項目通常都是由多個檔案構成,通常在我們寫程式的時候,都是将聲明部分和實作不用分分開,一般在頭檔案中進行聲明,在源檔案中實作,這樣更加符合規範
類的頭檔案
- 将類中的成員方法的實作部分去掉,就是類的聲明,聲明部分放在頭檔案中,例如:
#pragma once
class CPerson
{
public:
int a; //普通變量
static int b; //靜态變量
const int c; //常量
int* p; //指針變量
public:
CPerson(void); //構造函數
~CPerson(void); //析構函數
public:
void AA(); //普通函數
static void BB(); //靜态函數
void CC() const; //常函數
virtual void DD(); //虛函數
virtual void EE() = 0; //純虛函數(接口函數)
}
類的源檔案
方法的實作在源檔案中進行,需要注意的是:
(1)在源檔案中實作的時候要加類名作用域
(2)虛函數、靜态函數在源檔案中實作的時候需要将辨別符去掉
(3)常函數的修飾符const必須保留,因為它表明這個函數的this指針是個常量
(4)純虛函數不需要實作,也無法實作
例如:
#include "Person.h"
#include <iostream>
using namespace std;
int CPerson::b = 200; //靜态變量可以直接加類名作用域指派
CPerson::CPerson(void):c(300) //構造函數
{
a = 100;
p = new int;
}
CPerson::~CPerson(void) //析構函數
{
delete p;
p = 0;
}
void CPerson::AA() //普通函數
{
}
void CPerson::BB() //靜态函數去掉static辨別符
{
}
void CPerson::CC() const //常函數const辨別符保留
{
}
void CPerson::DD() //虛函數去掉virtual辨別符
{
}
頭檔案&編譯
- 編譯的時候是跳過頭檔案的,也就是說,建立一個未被包含的頭檔案,在裡面無論寫什麼,編譯的時候都不會報錯
- 但是我們一般建立頭檔案的目的就是為了被包含并且使用的,否則就沒有意義,如果源檔案中包含了該頭檔案,就相當于将頭檔案中的内容拷貝到了包含的位置,那麼在編譯源檔案的時候,如果頭檔案中有錯誤,編譯器這時候就會報錯了(編譯源檔案時報錯)
頭檔案重複包含問題
當各個檔案之間頭檔案引用關系複雜的時候,可能就會出現頭檔案重複包含的問題,這種情況下編譯就會報錯。
如何解決?
- 使用ifndef,他的作用時判斷這個宏是否被定義過,如果定義了就跳過#ifndef和#endif之間的内容
#ifndef _AA_H
#define _AA_H
//頭檔案内容
#endif
- #pragma once,在C++中還提供的一種解決方法時,可以在頭檔案中寫#pragma once,他的意思時該頭檔案隻編譯一次
21.operator重載操作符
operator:
operator是C++的一個關鍵字,它和運算符(如=)一起使用,表示一個運算符重載函數,在了解時可以将operator和運算符(如operator=)視為一個函數名。
使用operator重載運算符,是C++擴充運算符功能的方法。
為什麼使用重載操作符:
- 對于C++提供的所有操作符,通常隻支援對于基本資料類型和标準庫中提供的類的操作,而對于使用者自己定義的類,如果想要通過該操作符實作一些基本操作(比如比較大小,判斷是否相等),就需要使用者自己來定義這個操作符的具體實作了。
- 比如,我們要設計一個名為“person”的類,現在要判斷person類的的兩個對象p1和p2是否一樣大,我們設計的比較規則是每個對象中的
這一屬性去比較,那麼,在設計person類的時候,就可以通過對操作符int age
進行重載,來使用操作符==
對對象p1和p2進行比較了。==
- 我們上面所說的對操作符
進行重載。說是“重載”,是由于編譯器在實作操作符“==”的功能的時候,已經為我們提供了這個操作符對于一些基本資料類型的操作支援,隻不過由于現在該操作符所操作的内容變成了我們自定義的資料類型(如class),而預設情況下,該操作符是不能對我們自定義的class類進行操作的,是以就需要我們通過重載該操作符,該出該操作符操作我們自定義的class類型的方法,進而達到使用該操作符對我們自定義的class類進行運算的目的。==
如何重載
1.将操作符重載實作為類成員函數
例如:
class CPerson
{
public:
int age;
public:
CPerson():age(18)
{}
public:
bool operator==(CPerson& p2)
{
if(this->age == p2.age)
return true;
return false;
}
};
int main()
{
CPerson p1,p2;
int result = 1 + (p1==p2);
cout << result << endl;
system("pause");
}
結果輸出為2,可以看到:
- 傳入的參數作為“==”的右操作符,自身作為左操作符
- 傳回值還可以參與其他運算符的運算
2.将操作符重載實作為非類成員函數(即全局函數)
當我們要重載的操作符的左操作數不是自身類型,那麼類成員就無法實作了,就需要使用全局函數
例如:
class CPerson
{
public:
int age;
public:
CPerson():age(18){}
public:
int operator+(CPerson& p2)
{
return this->age + p2.age;
}
int operator+(int b)
{
return this->age + b;
}
};
int operator+(int a,CPerson& p2)
{
return a + p2.age;
}
int main()
{
CPerson p1,p2;
cout << p1 + p2 << endl;
cout << p1 + 2 << endl;
cout << 2 + p2 << endl;
system("pause");
}
輸出結果為:
可以看到:
- 他需要兩個參數,一個符号左邊的,一個符号右邊的
- 這裡我們用這種方式,實作了class + class,class + int,int + class
注意:
(1)重載運算符的優先級不會改變
(2)重載操作符要有傳回值,傳回值繼續和其他的操作符去結合,去運算
3.特殊重載
(1)重載 “<<” 和 “>>”
- 目的:
cout << 右邊是一個CPerson類型的對象的時候,輸出CPerson類中的age的值
cin >> 右邊是一個CPerson類型的對象的時候,輸出CPerson類中的age的值
-
注意:cout是輸出流對象,它的類型是ostream
cin是輸入流的對象,它的類型是istream
- 實作
class CPerson
{
public:
int age;
public:
CPerson(){age = 18;}
};
ostream& operator<<(ostream& os,CPerson& p)
{
os << p.age;
return os;
}
istream& operator>>(istream& is,CPerson& p)
{
is >> p.age;
return is;
}
int main()
{
CPerson p1;
cin >> p1;
cout << p1 << " " << p1 << endl;
system("pause");
}
運作結果:
(2)重載 ++ 操作符
- 目的:
Person p;
可以通過p++和++p來讓類中的age分别以age++和++age的方式自加
-
注意:這種單目運算符作為類内成員函數或者作為全局函數實作都可以。
a++和++a的差別:
a++需要一個臨時變量來存儲原來的值,用來傳回。
++a不需要臨時變量,直接a=a+1,然後傳回。
實作:
class CPerson
{
public:
int age;
public:
CPerson(){age = 10;}
public:
int operator++() //不帶參數的是++a
{
age = age + 1;
return age;
}
int operator++(int temp) //帶參數的是a++
{
//傳入的參數不一定要用,隻是用來區分a++還是++a,也可以自己定義一個臨時變量
int f1 = age;
age = age +1;
return f1;
}
};
int main()
{
CPerson p1;
cout << p1++ << endl;
cout << ++p1 << endl;
}
運作結果:
補充:
這裡是後續在使用過程中發現的一些問題:
- 有繼承關系的兩個類之間可以通過虛函數的方法使得derived class重載base class的operator使其具有多态特性。
- operator隻能對于有自定義的類型參與的系統沒有規定的操作符進行重載,經過實驗,有以下幾種自定義類型可以使用operator,他們是class,struct,enum這三種類型,值得注意的是,才傳入對象的時候使用引用傳參,而不是指針。
22.list類
說明
該類作為一個容器,是一個用來實作連結清單功能的類
頭檔案:
list
用法:
- 建立一個裝int類型的連結清單
- 尾添加節點:
- 頭添加節點:
- 頭删除:
- 尾删除:
- 連結清單頭:
- 連結清單尾:
-
定義一個疊代器:
每一種容器都會有配套的疊代器。這裡是連結清單類list的疊代器定義
-
周遊連結清單:
(1)使用疊代器周遊
list<int*>::iterator ite = lst.begin();
while(ite != lst.end())
{
cout << *ite << end;
++ite;
}
(2)使用for_each函數
for_each()函數:
- 功能:将指定的函數對象按前向順序應用到範圍内的每個元素,并傳回該函數對象。
- 頭檔案:
algorithm
- 原型:
template<class InputIterator, class Function>
Function for_each(
InputIterator _First,
InputIterator _Last,
Function _Func
);
- 參數:
_First 輸入疊代器,尋址要操作的範圍内第一個元素的位置。
_Last 輸入疊代器,尋址操作範圍内最後一個元素後1的位置。
_Func 使用者定義的函數對象,應用于範圍中的每個元素。
- 傳回值:
函數對象被應用到範圍内所有元素後的一個副本。(不用去關心)
是以我們可以這樣寫,去周遊一個list連結清單類
::for_each(lst.begin(),lst.end(),&show);
//自定義的show函數
void show(int nVal) //參數類型取決于list<>尖括号中是什麼類型
{
cout << nVal << " ";
}
-
查找:
find()函數:
- 功能:定位具有指定值的範圍中某個元素的第一個比對項的位置。
- 頭檔案:
algorithm
- 原型:
template<class InputIterator, class Type>
InputIterator find(
InputIterator _First,
InputIterator _Last,
const Type& _Val
);
- 參數:
_First 輸入疊代器,尋址要操作的範圍内第一個元素的位置。
_Last 輸入疊代器,尋址操作範圍内最後一個元素後1的位置。
_Val 要搜尋的值
- 傳回值:
一個輸入疊代器,用于尋址正在搜尋範圍内指定值的第一個比對項。如果範圍内不存在這樣的值,則疊代器傳回的位址将指向範圍内最後一個位置,即在最後一個元素的下一位置。
是以我們可以這樣去寫:
-
指定位置插入:
insert()函數:
示例:
-
指定位置删除:
erase()函數:
示例:
23.拷貝構造
什麼是拷貝構造
拷貝構造函數是一種特殊的構造函數,它在建立對象時,是使用同一類中之前建立的對象來初始化新建立的對象。拷貝構造函數通常用于:
- 通過使用另一個同類型的對象來初始化新建立的對象。
- 複制對象把它作為參數傳遞給函數。
- 複制對象,并從函數傳回這個對象。
例子:
class CPerson
{
public:
int age;
public:
CPerson()
{
age = 5;
}
};
int main()
{
CPerson p1;
cout << p1.age << endl;
p1.age = 10;
CPerson p2(p1);
cout << p1.age << endl;
cout << p2.age << endl;
system("pause");
}
運作結果:
上面的例子中,可以看到,我們定義了一個p1,它執行預設正常的構造函數,給age指派為10,定義p2的時候我們傳入的參數是一同類型對象,也是允許的,可以正常運作而沒有報錯。
拷貝構造的規則:
拷貝構造的規則是這樣的:如果在類中沒有定義拷貝構造函數,編譯器會自行定義一個。如果類帶有指針變量,并有動态記憶體配置設定,則它必須有一個拷貝構造函數。拷貝構造函數的最常見形式如下:
classname (const classname &obj) {
// 構造函數的主體
}
注意:在這裡,==obj 是一個對象引用,該對象是用于初始化另一個對象的。==可以傳入多個值,但是第一個值必須為目前這個類的const類型的引用。
為什麼類中指針成員變量動态配置設定記憶體時,我們需要自己寫一個拷貝構造?
看下面例子:
class CPerson
{
public:
int* value;
public:
CPerson()
{
value = new int(200);
}
~CPerson()
{
delete value;
value = NULL;
}
};
int main()
{
{
CPerson p1;
CPerson p2(p1);
}
}
這個例子在運作時,會發生如下錯誤:
實際上,編譯器自行定義的拷貝構造是這個樣子的:
CPerson(const CPerson& p)
{
this->value = p.value;
}
它實際上是将傳入的對象的各個成員變量的值通過直接指派的方式來初始化将要定義的變量來完成構造。
實際上我們将這種拷貝構造的方法叫做淺拷貝。
那麼我們可以分析以下上面的代碼:
- 我們定義了
,他執行普通構造函數,在記憶體中申請一塊空間(假設位址為0x3f)存放了一個200的值,然後CPerson p1
指向那塊空間,即p1.value
中存的資料是p1.value
0x3f
- 接下來我們通過拷貝構造的方式定義了
,然後預設的拷貝構造函數通過指派的方式初始化了CPerson p2(p1)
,即p2.value
,現在p2.value = p1.value
中存到也是p2.value
了,也就是說,0x3f
和p1.value
指向了同一塊記憶體空間p2.value
- 之後當兩個對象生命周期結束的時候,p1的析構函數被調用,将
指向的記憶體空間釋放了,當p2的析構函數被調用,就會再去釋放一次該記憶體空間,這種操作了不屬于自己的記憶體的行為是不被允許的。p1.value
如何解決?(如何在自定義的拷貝構造中避免此問題的發生?)
有淺拷貝,那麼對應的就有深拷貝
我們将上面拷貝構造這樣去寫:
CPerson(const CPerson& p)
{
this->value = new int(*(p.value));
}
再去運作就不會出現之前那種錯誤了。
可以看到我們在這個拷貝構造裡做了這樣的事情:給我們将要定義的變量申請一塊記憶體空間,這塊記憶體使用傳入的變量的
p.value
所指記憶體空間中的值去初始化。這時候在主函數裡面執行
CPerson p2(p1)
之後,p1.value 和 p2.value各自有一塊記憶體空間,各指各的,最後調用析構函數的時候也是各自釋放各自的那塊記憶體。
像這種拷貝我們稱之為深構造,我們不隻是簡單地指派了指針變量中裝的值,而是複制出一塊一樣的記憶體空間。
哪些地方會存在拷貝構造的現象?
1. 函數值傳遞
比如我們定義了一個CPerson類(代碼略)
我們再去寫一個函數:
void Func(CPerson per)
{
}
這個函數使用的是值傳遞,傳入一個Person類型的對象,在實參到形參指派的這個過程中其實就執行的是一個拷貝構造,那麼如果我們寫的CPerson類中沒有去寫一個深拷貝的話,就有可能會出現上述錯誤。
當然這種情況我們實際上是有兩種方法去解決的:
(1)在CPerson類中寫一個深拷貝,需要注意的是這種情況下,函數裡面的對象和函數外面的對象不是一個對象
(2)函數使用引用傳遞的方式去傳參,這個時候才函數裡和函數外實際上操作的是同一個對象,沒有新的對象産生,自然不會産生拷貝構造了。
2. list類
假如我們定義一個CPerson類型的連結清單,這樣去寫:
這裡也會出現一個淺拷貝的問題,是以盡量避免這樣去寫,而是用
// 。。。。。。。。待補充完善。。。。。。
重載“=”操作符
我們去看這樣一個代碼:
class CPerson
{
public:
int age;
};
int main()
{
CPerson p1;
p1.age = 10;
CPerson p2;
p2 = p1;
cout << p1.age << " " << p2.age << endl;
system("pause");
}
運作結果:
這裡我們并沒有去重載“=”操作符,但是兩個類之間使用“=”操作符确實可以的,說明系統預設對類之間的“=”操作符進行了重載,但是需要注意的是,這種重載還是一種淺拷貝
class CPerson
{
public:
int* m;
public:
CPerson()
{
m = new int(100);
}
~CPerson()
{
delete m;
m = NULL;
}
};
int main()
{
{
CPerson p1;
CPerson p2;
p2 = p1;
}
}
還是這種當類中有指針變量并且動态配置設定記憶體的時候,這樣去使用“=”操作符,就會報錯。
那麼解決辦法就是自己去重新重載“=”操作符就好了,如下:
class CPerson
{
public:
int* m;
public:
CPerson()
{
m = new int(100);
}
~CPerson()
{
delete m;
m = NULL;
}
public:
CPerson& operator=(const CPerson& p)
{
//删除原來執行構造函數時為this->m申請的空間
delete this->m;
this->m = NULL;
//為this->m申請新的空間并用*(p.m)的值初始化
this->m = new int(*(p.m));
return (*this);
}
};
int main()
{
{
CPerson p1;
CPerson p2;
p2 = p1;
}
}
如此便可避免出錯。
是以我們可以總結一下:一個空類當中,預設函數有:構造函數,析構函數,拷貝構造,重載 等号 操作符
24.設計模式
設計模式:處理
Template模式
1.問題:
假設有這樣的場景,某一個功能在不同的類中大體要執行的操作是一樣的,隻有一些細節的差異,最簡單的來說,有100行代碼,這100行代碼在兩個類當中都需要執行,但是其中99行都是相同的,隻有中間的一行代碼不同,如果我們在每個類中定義一個方法,将這100行代碼逐一寫進去,這樣做顯得有點傻,是以我們想要更加高效的方法,Template模式就是去解決這樣的問題的。
2.代碼實作:
#include <iostream>
using namespace std;
class CPerson
{
public:
CPerson(){};
virtual ~CPerson(){};
void Speak()
{
cout << "use ";
this->Use_Language();
cout << " speaking" << endl;
}
protected:
virtual void Use_Language(){}
};
class CChinese : public CPerson
{
public:
CChinese(){};
~CChinese(){};
protected:
void Use_Language()
{
cout << "Chinese";
}
};
class CAmerican : public CPerson
{
public:
CAmerican(){};
~CAmerican(){};
protected:
void Use_Language()
{
cout << "English";
}
};
int main()
{
CPerson* p1 = new CChinese();
CPerson* p2 = new CAmerican();
p1->Speak();
p2->Speak();
system("pause");
return 0;
}
3.代碼說明:
- 這裡采用一個例子:美國人和中國人都是人這個類的一個派生類,他們都能夠講話,講話這個功能(算法)大體架構是一樣的,但是存在細節上的不同,美國人講英語,中國人講漢語,是以我們将那一點不同的細節封裝起來提供一個接口(由子類去實作這一細節),讓說話這個功能(算法)去調用,這樣一來,人類講話這個功能就相當于執行了相同的操作,在人這個類中去實作即可。
4.注意:
需要注意的是,我們将原語操作(細節算法)定義為保護(Protected)成員,隻供模闆方法調用(子類可以),說人話就是防止了代碼中的
Use_Language()
被使用者在
main
函數中調用,你隻能去調用
Speak()
由
Speak()
去調用
Use_Language()
單例模式
1.問題:
單例模式用于解決在一個項目中某一種類型的類隻允許申請一個對象的需求。
2.示例代碼:
#include <iostream>
using namespace std;
class CSingleton
{
private:
static bool bflag;
private:
CSingleton()
{}
~CSingleton()
{}
CSingleton(const CSingleton& p)
{}
public:
static CSingleton* GetSingleton()
{
if(bflag == false)
{
bflag = true;
return new CSingleton();
}
else
{
return NULL;
}
}
void DestroySingleton(CSingleton* p)
{
bflag = false;
delete p;
p = 0;
}
};
bool CSingleton::bflag = false;
int main()
{
CSingleton* p = CSingleton::GetSingleton();
p->CSingleton::DestroySingleton(p);
p = NULL;
CSingleton* p2 = CSingleton::GetSingleton();
return 0;
}
3.代碼說明:
- 構造函數私有化:我們不想使用者在主函數或者其他地方随意調用構造函數去定義CSingleton對象,是以将其私有化
- GetSingleton()函數:我們隻允許申請一個CSingleton類型的對象,并不是徹底不讓申請該類型的對象,是以我們應該去提供一個接口供使用者使用,但是需要加特殊的限制
- bflag變量:正如第2條所說,改變量用來标記,是否已經有一個CSingleton類型的對象存在了,如果沒有(bflag為false)就可以通過接口去定義一個,如果有(bflag為true),接口就傳回NULL,不會再申請第二個CSingleton類型變量了
- 析構函數私有化:如果我們建立了一個CSingleton類型對象,如果不對析構函數加以限制,使用者就可以随意調用析構函數,将該對象銷毀,并且我們無法再定義第二個,防止此現象,需要将析構函數私有化
- DestroySingleton()函數:既然構造函數被私有化,外界就無法去銷毀已經定義了的CSingleton類型的對象,這是不合理的,是以我們應該提供一個銷毀該對象的接口,并且在銷毀之前要将bflag指派為false,表示程式中唯一存在的一個CSingleton類型的對象已經被銷毀,允許重新定義一個
- 拷貝構造私有化:别忘了,一個空類中預設的函數有構造函數,析構函數,拷貝構造,重載等号操作符,其中重載等号操作符不需要去考慮,因為不會有第二個相同類型變量,是以不會用到該操作。但是我們需要考慮拷貝構造,如果我們不去加以限制,那麼使用者就可以通過現有的一個對象去拷貝構造出新的CSingleton類型的對象了,這是不允許的。
25.模闆
1.函數模闆
問題引出:
有一系列函數,如下:
void Show(int c)
{
cout << c << endl;
}
void Show(char c)
{
cout << c << endl;
}
void Show(double c)
{
cout << c << endl;
}
void Show(char* c)
{
cout << c << endl;
}
它有如下特點:
- 函數名相同
- 傳入參數不同
- 傳回值相同
- 函數體執行的功能相同
我們可以通過函數重載的方式是實作以上功能,但是代碼重複的部分太多,不夠簡潔
是以提供了另一種方法,叫做函數模闆
函數模闆的使用
1. 定義函數模闆:
//定義一個
template <typename 類型名>
void Show(類型名 變量名)
{
//函數體
}
//定義多個
template <typename 類型名1,typename 類型名2>
void Show(類型名 變量名)
{
//函數體
}
這裡的
類型名
可以當作任意一種資料類型,視傳入的具體資料類型而定,比如上面問題中的示例代碼現在就可以使用如下代碼代替了:
template<typename AB>
void Show(AB c)
{
cout << c << endl;
}
Show函數就可以實作傳入任意類型,并且實作輸出操作了。
2.注意:
- template語句隻能放在函數定義的上面一行,中間不能有其他代碼
-
函數模闆在調用函數的時候确定參數的類型
3.使用函數模闆改寫冒泡排序:
在之前寫的冒泡排序算法中,我們隻是對int類型進行了排序,現在我們學習了函數模闆,我們要求要能夠對任意類型進行排序,實作如下:
#include <iostream>
using namespace std;
template<typename AA>
void BubbleSort(AA arr[],int n)
{
int i,j;
for(i = 0;i < n-1;i++)
{
for(j = 0;j < (n-1)-i;j++ )
{
if(arr[j] < arr[j+1])
{
AA temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
int main(int agec,char argv[])
{
char arr[] = {'a','c','r','g','h','s','t'};
BubbleSort(arr,sizeof(arr)/sizeof(char));
for(char nVal:arr)
{
cout << nVal << " ";
}
system("pause");
return 0;
}
運作結果:
以上程式可以在不修改冒泡排序算法的前提下,實作任意類型從大到小的一個排序(前提是這種類型支援比較運算)
現在提高一下要求:要求當輸入int時,從大到小排序,輸入double時,從小到大排序
代碼實作如下:
#include <iostream>
using namespace std;
bool Rule1(int a,int b)
{
return a < b;
}
bool Rule2(double a,double b)
{
return a > b;
}
template<typename AA>
void BubbleSort(AA arr[],int n,bool (*pFun)(AA a,AA b))
{
int i,j;
for(i = 0;i < n-1;i++)
{
for(j = 0;j < (n-1)-i;j++ )
{
if((*pFun)(arr[j],arr[j+1]))
{
AA temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
template<typename BB>
void Print(BB arr[],int n)
{
for(int i=0;i<n;i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
int main()
{
int arr1[7] = {1,5,8,7,2,8,98};
double arr2[4] = {2.3,4.5,89.5,2};
BubbleSort(arr1,7,Rule1);
Print(arr1,7);
BubbleSort(arr2,4,Rule2);
Print(arr2,4);
system("pause");
return 0;
}
通過以上的冒泡排序程式,我們可以做到不去修改冒泡排序函數的前提下,去實作各種資料類型按照自定義的規則進行冒泡排序操作。
2.類模闆:
類模闆的使用:
1.定義一個類模闆:
同樣的我們在類的上面去加
template <typename AA>
這樣的語句,在類内就可以使用AA 去定義變量,并且這個變量的類型可以在定義類的時候任意給定,值得注意的是我們在定義類的時候,應該這樣去寫
類名<要替換AA的資料類型> 對象
。
示例代碼如下:
template <typename AA>
class CPerson
{
public:
AA a;
public:
CPerson(AA a)
{
this->a = a; //使用傳入的值去初始化變量a
}
void Show()
{
cout << a << endl;
}
};
int main()
{
CPerson<int> per(100);
per.a = 89;
per.Show();
return 0;
}
2.類模闆的應用:自己實作list容器
#include <iostream>
using namespace std;
template <typename AA>
class CList
{
private:
struct Node{
AA value;
Node *pNext;
};
Node* pHead;
Node* pEnd;
int m_nLen;
public:
CList()
{
pHead = NULL;
pEnd = NULL;
m_nLen = 0;
}
~CList()
{
Node* pDel;
while(pHead)
{
pDel = pHead;
pHead = pHead->pNext;
delete pDel;
pDel = NULL;
}
}
public:
void push_back(int n)
{
Node* temp = new Node;
temp->value = n;
temp->pNext = NULL;
if(pHead == NULL)
{
pHead = temp;
pEnd = temp;
}
else
{
pEnd->pNext = temp;
pEnd = temp;
}
m_nLen++;
}
void push_front(int n)
{
Node* temp = new Node;
temp->value = n;
if(pHead == NULL)
{
pHead = temp;
pEnd = temp;
}
else
{
temp->pNext = pHead;
pHead = temp;
}
m_nLen++;
}
void delete_node(int n)
{
if(pHead->value == n)
{
Node* pDel = pHead;
pHead = pHead->pNext;
delete pDel;
pDel = NULL;
}
else if(pEnd->value == n)
{
Node* p = pHead;
while(p->pNext != pEnd)
{
p = p->pNext;
}
pEnd = p;
pEnd->pNext = NULL;
p = p->pNext;
delete p;
p = NULL;
}
else
{
Node* p = pHead;
while(p != pEnd)
{
if(p->pNext->value == n)
{
Node* pDel = p->pNext;
p->pNext = p->pNext->pNext;
delete pDel;
pDel = NULL;
}
p = p->pNext;
}
}
m_nLen--;
}
void show()
{
Node* p = pHead;
while(p != NULL)
{
cout << p->value << " ";
p = p->pNext;
}
cout << "size:" << m_nLen;
cout << endl;
}
};
int main()
{
CList<char> lst;
lst.push_back('a');
lst.push_back('b');
lst.push_back('c');
lst.push_back('3');
lst.push_back('2');
lst.show();
lst.push_front('u');
lst.show();
lst.delete_node('u');
lst.show();
lst.delete_node('2');
lst.show();
lst.delete_node('b');
lst.show();
system("pause");
return 0;
}