GeekBand STL與泛型編程 第一周

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指向的元素。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容