1.模板觀念與函數模板
課程主要內容
- C++模板簡介
- 泛型編程
- 容器
- 進階
C++模板簡介
??generic types:泛型。type翻譯為型別。型別更加的具體。
??簡單的例子:
int Max(int a, int b){
return (a > b) ? a : b;
}
long Max(long a, long b){
return (a > b) ? a : b;
}
...
===>
template<typename T>
T Max(T a, T b){
return (a > b) ? a : b;
}
??在這個函數中,T是一個abstract type,generic type,不是一個具體的類型。
兩種類模板
- 類模板(Class template)
- 函數模板(Function template)
??模板聲明的時候,并未給出函數或類的完整定義。只是提供了一個語法框架。
??模板的實例化是從模板構建出一個真正的函數或類的過程,指定T真正型別的時候。
實例化(instantiation)
- 顯式實例化,在代碼中明確指定
- 隱式實例化,由編譯器推導
C++函數模板
??是指參數化的一族函數(不止一個)。
class 和 typename,在用作定義型別參數時,在語法上沒有區別,但是在語義上有區別,建議使用typename。但是不能使用struct。
??在使用Max模板函數時,不能使用不同型別的參數來調用。
??用具體型別代替模板參數T的過程就叫做實例化,從而產生了一個模板實例。
模板被編譯了兩次
- 沒有實例化之前,編譯器會檢查語法是否有錯誤。
- 實例化期間,編譯器會檢查調用是否合法。
參數推導
- 模板參數是由傳遞給模板函數的實參決定的。
- 不允許自動型別轉換,每個T必須嚴格匹配。
Max(1, 2.0);
===>
Max(static_cast<double>(1), 2.0);//將1轉換為double
Max<double>(1, 2.0);//編譯器認為1為double
函數模板重載
??函數模板也可以像普通函數一樣被重載。普通函數可以和模板函數同同時存在(名稱一樣),當調用即符合普通函數的調用,又符合模板函數時,優先調用普通函數。
??所有的重載版本的聲明必須位于他們被調用位置之前。
2.類模板與操作符重載
類模板
??類通過參數泛化,從而構建出一族不同型別的類實例。
??類模板實參可以是某一型別或常量(僅限int或enum),而且可以帶默認值。
一個例子
const std::size_t DefaultStackSize = 1024;
template<typename T, std::size_t n = DefaultStackSize>
class Stack{
public:
void Push(const T cosnt& element);
int Pop(T& element);
int Top(T& element) cosnt;
private:
std::vector<T> m_Members;
std::size_t m_nMaxSize = n; //n是編譯時的常量,n可以有默認值
};
類模板的聲明
??在類模板內部,T可以像其他型別一樣,定義變量和成員函數。
??除了Copy constructor之外,如果在類模板中需要使用到類本身,如operator=,應該使用完整的定義(Stack<T, n>),而不能省略型別T。
Stack<T>& operator= (Stack<T, n> const &)
類模板的實現
template<typename T, std::size_t nMaxSize>
void Stack<T, nMaxSize>::Push(const T cosnt& element){ ... }
類模板的特化
??允許對一個類模板的某些模板參數型別做特化。
??特化的作用或好處
- 對于某種特殊的型別,可以做一些特別的優化或提供不同的處理方式。
- 避免在實例化類模板時引起一些可能產生的詭異行為。
??特化一個類需要特化其所有參數化的成員函數。
template<>
class Stack<std::wstring>{ ... };
??特化后可以添加新的成員函數,也可以改為使用list來存儲Stack的內部實現。
偏特化
類模板被定義為:
template <typename T1, typename T2>
class MyClass{ ... };
- 偏特化為同樣類型:
template <typename T> class MyClass<T, T> { ... };
- 偏特化部分模板參數為指定型別:
template <typename T> class MyClass<T, int> { ... };
- 偏特化為指針:
template <typename T1, typename T2>
class MyClass<T1*, T2*>{ ... };
使用 | 原型 |
---|---|
MyClass<int, float> obj; | MyClass<T1, T2> |
MyClass<float, float> obj; | MyClass<T, T> |
MyClass<float, int> obj; | MyClass<T, int> |
MyClass<int, float> obj; | MyClass<T1, T2> |
??如果不止一個偏特化同等程度地能夠匹配某個調用,那么該調用具有二義性,編譯會報錯。
使用 | 原型 |
---|---|
MyClass<int, int> obj; | Error matches MyClass<T, T> and MyClass<T, int> |
MyClass<int, int> obj; | Error matches MyClass<T, T> and MyClass<T1, T2> |
默認模板實參
??類似于函數的默認參數,對于類模板而言也可以定義其模板參數的默認值,這些值就叫做默認模板參數。
C++操作符重載
- 不可以用operator定義一種新的操作符
- 對于內置型別,不能再用operator重載
- 可重載為非靜態成員函數或靜態全局函數,如果該全局函數需要訪問類的private和protected成員,則需要聲明為friend。
- 除了operator=,所有其他操作符重載均可以被子類繼承。
3.泛型編程(Generic Programming)
概觀
??泛型編程是一種思想,是一種編程方法。在不同的語言表現方式不一樣,在C++中使用模板的方式表現出來。
關聯特性 Traits
什么是traits,以及為什么使用traits?
template<typename T>
T Sigma(const T* begin, const T* end){
T total = T();
while(end != begin){
total += *begin++;
}
return total;
}
char str[] = "abc";
int length = strlen(str);
char* p = str;
char* e = str + length;
printf("Sigma(str) = %d\n", Sigma(p, q)); //得到的結果溢出。
運行結果(溢出):
Sigma(str) = 38
??為每個Sigma函數的參數型別創建一種關聯(association),關聯的型別就是用來存儲Sigma結果的型別。
??這種關聯可以看做是型別T的一種特性(characteristic of the type T),此種型別可以稱作T的trait。
??Trais可以實現為模板類,association則是針對每個具體型別T的特化。
template<typename T> class SigmaTraits{};
template<> class SigmaTraits<char>{
public: typedef int ReturnType;
};
template<> class SigmaTraits<short>{
public: typedef int ReturnType;
};
...
修改后的Sigma函數:
template<typename T>
typename SigmaTraits<T>::ReturnType Sigma(const T* begin, const T* end){
typedef SigmaTraits<T>::ReturnType ReturnType;
ReturnType total = ReturnType();
while(end != begin){
total += *begin++;
}
return total;
}
修改后的執行結果:
Sigma(str) = 294
??雖然此時傳入參數T的型別是char,但是返回類型是int。原因就是使用了Traits。
迭代器
??迭代器是指泛化的指針,迭代器本身是一個對象,指向另外一個(可以被迭代的)對象。
??在STL中迭代器是容器和算法之間的接口。
基本思想
- 分離算法和容器,不需要相互依賴。
- 粘合算法和容器,使得一種算法的實現可以運用到多種不同的容器上。
- 每種容器都有其對應的迭代器。
4.容器(上)
Vector
??Vector是一種可以存放任意型別的動態數組,連續的內存空間。
#include<vector>//使用的時候,不要加.h
訪問vector的元素:
- vector::at() //有數組越界檢查,效率低。
- vector::operator[] //不檢查,效率高。
刪除vector的元素:
- clear:清除整個vector
- pop_back:彈出vector尾部元素
- erase:刪除vector某一位置元素
v.erase(
std::remove_if(
v.begin(),
v.end(),
ContainsString(L"C++")
),
v.end());
??std::remove_if函數返回了一個迭代器,需要刪除的元素的位置,remove_if函數需要一個條件函數,條件函數是一個派生自std::unary_function的一個仿函數,返回true或false來決定該元素是否是否會被刪除。
Deque
??Deque是一種可以存放任意型別的雙向隊列。
??Deque提供的函數與vector類似,新增了兩個函數:
- push_front:在頭部插入一個元素
- pop_front:在頭部彈出一個元素
List
??List是一種可以存放任意型別的雙向鏈表(doubly linked list)。內存中地址不連續。
List的優勢:
- List的優勢在于其彈性,可以隨意插入和刪除元素,僅僅改變節點前項和后項的鏈接。
- 對于插入、刪除和替換等,效率極高。
- 通常只改變鏈接,沒有元素復制。
List的劣勢:
- 只能以連續的方式存取List中的元素。
- 對于查找、隨機存取等元素定位,效率低。
splice
list::splice實現list拼接的功能。將源list的內容部分或全部元素刪除,拼插入到目的list。
函數有以下三種聲明:
void splice ( iterator position, list<T,Allocator>& x );
void splice ( iterator position, list<T,Allocator>& x, iterator i );
void splice ( iterator position, list<T,Allocator>& x, iterator first, iterator last );
函數說明:在list間移動元素:
- 將x的元素移動到目的list的指定位置,高效的將他們插入到目的list并從x中刪除。
- 目的list的大小會增加,增加的大小為插入元素的大小。x的大小相應的會減少同樣的大小。
- 前兩個函數不會涉及到元素的創建或銷毀。第三個函數會.
- 指向被刪除元素的迭代器會失效。
參數:
- position:目的list的位置,用來標明 插入位置。
- x :源list。
- first,last:x里需要被移動的元素的迭代器。區間為[first, last),包含first指向的元素,不包含last指向的元素。