【GeekBand】C++面向對象高級編程-第六周筆記

內容大綱:
1.C++模板簡介
1.1C++模板概觀
1.2C++函數模板
1.3C++類模板
1.4C++操作符重載
2.泛型編程
2.1概述
2.2關聯特性(Traits)
2.3迭代器(iterators)

1.C++模板簡介

1.1C++模板概觀

模板是C++一種特性,允許函數或者類(對象)通過泛型的形式表現或運行。

int Max(int a, int b)
{
    return (a>b) ? a : b;
}      
long Max(long a, long b)
{
    return (a>b) ? a : b;
}
char Max( char a, char b)
{
    return (a>b)? a : b;
}

倘若沒有模板,那么雖然功能相同,但返回類型和參數類型不同的函數就得重寫,這無疑增加了無意義的工作。
簡單地來說,模板就像一個模具,根據不同的需求可以做出不同材質的模型。
如果使用了模板,可以省去一堆冗余的代碼,上述的三段代碼可以縮減成一下的表達方式:

template <typename T> 
T Max( T a, T b)
{
    return (a>b)?a:b;
}

使用方式也非常簡單:

int i = 1, j = 2;
Max(i, j);          //編譯器會根據實參類型來推斷T是什么類型,這個過程叫隱式實例化

C++主要有兩種主要的模板:
類模板函數模板
實例化也有兩種類型:
顯式:在代碼中明確指出針對哪種類型進行實例化
隱式:在首次使用時根據具體情況使用一種合適的類型進行實例化

1.2C++函數模板

函數模板是參數化的一族函數。
簡而言之,就是如果不考慮返回類型和輸入參數類型,那么這些函數的功能是一樣的。
函數模板定義舉例:

template <typename T>    //模板參數由關鍵字typename引入 
T Max( T a, T b)         //參數型別未定,以模板參數T表示
{
    return (a>b)?a:b;
}

需要注意以下兩點:
1.可以用class替代typename來定義類別參數,但struct不可以。
2.從語法上來講class 和 typename沒有區別,但是用class會導致誤解,譬如會以為只有類才能作為型別參數,所以,盡量使用typename。
函數模板的使用:

int i = 1, j = 2;
float k = 1.0, l = 2.0;
Max(i, j);          //T 為int
Max(k,l);          //T為float
Max(i,l);           //不可以,無法確定T為哪種類型

模板實例化:
用具體型別替代模板參數T的過程叫實例化,從而產生一個模板實例。
綜上,得出一個結論模板被編譯了兩次
1.一次是實例化之前,檢查模板代碼本身是否有語法錯誤;
2.實例化期間,檢查對模板代碼的調用是否合法。

1.2C++類模板
與函數模板類似,類也是可以做成模板,但是類模板比函數模板相比有更多的特性,所以使用起來更加靈活。下面舉一個類模板的例子:

const std::size_t DefaultStackSize = 1024;
tmeplate <typenmae T, std::size_t n = DefaultStackSize> 
Class Stack
{
public:
    void Push(cosnt T &element);
    int Pop(T &element);
    int Top(T &element) const ;
private:
    std::vector<T> m_Member;
    std::size_t m_nMaxSize = n;
};

值得注意的是,類模板的聲明時,除了Copy constructor之外,如果在類模板中需要使用到這個類本身,比如定義operator=,那么應該使用其完整的定義(Stack<T,n>),而不是使用T。

tmeplate <typenmae T, std::size_t n = DefaultStackSize> 
Class Stack
{
public:
    ...
    Stack(Stack<T,n> const&);
    Stack<T>& operator= (Stack<T,n> const&);
    ...
};

如果在類外要定義一個類模板的成員函數,則要指明其是一個模板函數,例如Push函數的定義應當如下:

template<typename T, std::size_t nMaxSize>
void Stack<T, nMaxSize>::Push(const T &element)
{
...
}

類模板特化(specializations)
允許對一個類的某些模板形參類型做特化
特化的作用好處在于:
1.對于某些特俗的型別,可能可以做些特別的優化或提供不同的實現
2.避免在實例化類的時候引起一些可能產生的詭異行為
3.特化一個類模板的時候也意味著需要特化所有其他參數化的成員參數
如果要特化一個類,方法如下:

template<>                         //聲明一個帶template<>的類,即空參數列表
class Stack<std::wstring>          //在類名稱后面緊跟的尖括號中顯式指明類型
{
...
};

特化后的具體實現是可以和主模板的實現不一樣,類似函數重載。
偏特化(Partial specialization)
類模板也可以被偏特化,比如主模板定義為:

template<typename T1, typename T2>
class MyCalss
{
...
};

則由此可能產生以下幾種主模板的偏特化:
1.將模板參數偏特化為同樣類型:

template <typename T>
class MyClass<T, T>
{
...
};

2.也可以將第二個將第二個模板參數特化為int類型,不是泛化的T:

template <typename T>
class MyCalss<T, int>
{
...;
};

3.還可以將類型偏特化為指針:

template <typename T1, typename T2>
class MyClass<T1*, T2*>
{
...
};

那么知道定義后,我們該如何使用呢?請看下表:


偏特化使用示例.png

但使用時需要多加小心,防止出現二義性出現。
我們知道函數有默認參數,同樣,類模板與其也有異曲同工之處。例如:

template <typename T, typename TContainer = std::vector<T> > 
//使用std::vector<>作為默認實參
class Stack
{
private:
    TContainer m_Container;
    ...
};

當然,如果你傳入一個其他類型的類型時,則這是將以你輸入的那個類型為T2的類型。
總而言之
·模板類的性質是有一個或多個類型未被指定的模板。
·對于類模板而言,只有被調用到的成員函數才會被實例化
·類模板可以用特性的型別特化
·支持偏特化
·也允許有默認值
1.4運算符重載
對于運算符重載,由于前面兩門課已經有所探究,并且侯老師講解得也足夠詳細,故這里不再繼續重述。

2.泛型編程

2.1概述

泛型編程是一種方法,這種方式將類型以一種to-be-specified-later的方式給出。

2.2關聯特性

昨天學習C++ primer的第十章,遇到這么一個問題,程序如下:

#include<iostream>
#include<functional>
#include<vector>
#include<list>
#include<algorithm>
 
using namespace std;
bool check_size(const string &s1, const string &s2)
{
    return s1.size()>s2.size();
}
int main () 
{
    using std::placeholders::_1; 
    vector<string> s = {"the","quick","red","fox","jumps","ovet","the","red","slow","red","turtle"};
    string a = "red";
    auto p1 = bind(check_size,_1,"red");
    vector<string>::iterator p = find_if(s.cbegin(),s.cend(),p1);
    cout<<p-s.cbegin()+1<<endl;
}

發生報錯

[Error] conversion from '__gnu_cxx::__normal_iterator<const std::basic_string<char>*, std::vector<std::basic_string<char> > >' to non-scalar type 'std::vector<std::basic_string<char> >::iterator {aka __gnu_cxx::__normal_iterator<std::basic_string<char>*, std::vector<std::basic_string<char> > >}' requested```
后來在群里請教了別人,才知道原來我在find_if里使用了cbegin(),則find_if()傳出的迭代器也是不可修改指向的值的,所以應該是const_iterator。
所以改成
```C++
vector<string>::const_iterator p = find_if(s.cbegin(),s.cend(),p1);

就可以了。

附加內容:

一、輸出容器元素方法:
1.利用普通for循環輸出

vector<int> v = {1,2,3,4};
for(vector<int>::iterator it = v.begin( ); it!=v.end(); it++)
{
    cout<<*it;
}

2.利用范圍for循環輸出

//支持C++11的編譯器
vector<int> v = {1,2,3,4};
for(auto it:v)
{
    cout<<it;
}

可以看到,利用范圍for來遍歷元素是非常方便的。
3.利用標準庫for_each算法和lambda表達式(方便快捷,個人比較喜歡)

vector<int> v = {1,2,3,4};
for_each( v.cbegin(), v.cend(), [](int i) { cout << i << " " ; });

4.利用流迭代器和標準庫copy算法

ostream_iterator<int> out_iter(cout, " ");
vector<int> v = {1,2,3,4};
copy( vec.cbegin( ), vec.cend( ), out_iter);

二、關于copy算法
重要的一點是傳遞給copy的目的序列至少要包含與輸入序列一樣多的元素
即不能復制元素到一個空或者元素數量少于母板的數目。但有一點要注意,利用插入迭代器時,可以使用空的容器。

list<int> lst = {1, 2, 3, 4 };
list<int> v1, v2;
copy(lst.cbegin( ), lst.cend( ), front_inserter(lst2) );

由于有迭代器作為橋梁,因為copy函數是先將值傳遞給迭代器,再由迭代器來操作容器,這樣,容器即便是空的,經過插入迭代器處理后可以產生容納該被復制元素的空間,所以可以使用空容器。

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

推薦閱讀更多精彩內容