日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

C++Primer_Chap16_模板和泛型编程_List01_定义模板_笔记

發布時間:2023/12/20 c/c++ 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C++Primer_Chap16_模板和泛型编程_List01_定义模板_笔记 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

? 面向對象變成(OOP)和泛型編程都能處理在編寫程序時不知道類型的情況。不同之處在于:

  • OOP能處理類型在程序運行之前都未知的情況
  • 泛型編程中,在編譯時就能獲知類型。

函數模板

? 我們可以定義一個通用的函數模板(function template),一個函數模板就是一個公式,可生成針對特定類型的函數版本

template <typename T> int compare( const T &val1, const T &val2) {if( val1 < val2) return -1;if( val2 < val1)return 1;return 0; }

? 模板定義以關鍵字template開始,后跟一個模板參數列表(template parameter list),這是一個逗號分隔的一個或多個模板參數(template parameter)的列表,用<>包圍起來。

? 一般來說,可以將類型參數看做類型說明符,就想內置類型或類類型說明符一樣使用。特別是,類型參數可以用來指定返回類型或函數的參數,以及在函數體內用于聲明變量或類型轉換。類型參數前必須使用關鍵字class或typename(在模板參數列表中,class和typename沒有什么不同):

template<typename T, class U> calc(const T&, const U&);

非類型模板參數

? 除了定義類型參數,還可以在模板中定義非類型參數(nontype parameter)。一個非類型參數表示一個值而非一個類型。通過一個特定的類型名而非關鍵字class或typename來指定非類型參數。

? 當一個模板被實例化時,非類型參數被一個用戶提供的或編譯器推斷出的值所代替。這些值必須是常量表達式,從而允許編譯器在編譯時時實例化模板。

template<unsigned N, unsigned M> int compare(const char (&p1)[N], const char(&p2)[M]) {return strcmp(p1, p2); }compare("hi", "mom"); int compare( const char (&p1)[3], const char (&p2)[4]);

? 編譯器會使用字面常量的大小代替N和M,從而實例化模板。一個非類型參數可以是一個整型,或者是一個指向對象或函數類型的指針或(左值)引用。綁定到非類型整型參數的實參必須是一個常量表達式。綁定到指針或引用非類型參數實參必須具有靜態的生存期。我們不能用一個普通(非static)局部變量或動態變量作為指針或引用非類型模板參數的實參。指針參數也可以用nullptr或值為0的常量表達式來實例化。

inline和constexpr的函數模板

? inline和constexpr說明符放在模板參數列表之后,返回類型之前:

template<typename T> inline T min(const T&, const T&);

? 編寫泛型代碼的兩個重要原則:

  • 模板中的函數參數是const的引用(保證了函數可以用于不能拷貝的類型)
  • 函數體中的條件判斷僅使用<比較運算(降低compare對要處理的類型的要求,只需要有<,不必同時支持>)

? 實際上,如果真的關心類型無關和移植性,可能需要使用less來定義我們的函數.(彌補原版本針對兩個指針,且兩個指針未指向相同的數據時代碼行為未定義的問題)

template<typename T> int compare( const T &v1, const T &v2) {if( less<T>()(v1, v2))return -1;if( less<T>()(v2, v1))return 1;return 0; }

? 模板程序應該盡量減少對實參類型的要求。

模板編譯

? 當編譯器遇到一個模板定義時,它并不生成代碼。只有實例化出模板的一個特定版本時,編譯器才會生成代碼。即當我們使用而不是定義模板時,編譯器才生成代碼,該特性會影響如何組織代碼以及錯誤何時被檢測到。

? 為了生成一個實例化版本,編譯器需要掌握函數模板和類模板成員函數的定義。因此,與非模板代碼不同,模板的頭文件通常保護聲明和定義。

? 類模板

??類模板(class template)是用來生成類的狼途的。和函數模板不同之處是,編譯器不能為類模板推斷模板參數類型。為了使用類模板,我們必須在模板名后面的尖括號中提供額外信息——用來代替模板參數的模板實參列表

template <typename T> class Blob { public:typedef T value_type;typedef typename std::vector<T>::size_type size_type;Blob();Blob( std::initializer_list<T> i1);size_type size() const { return data->size(); }bool empty() const { return data->empty(); }void push_back( const T &t) { data->push_back(t); }void push_back( const T &&t) { data->push_back(std::move(t)); }void pop_back();T& back();T& operator[](size_type i);private:std::shared_ptr<std::vector<T>> data;void check(size_type i, const std::string &msg) const; };

? 實例化類模板時必須提供額外的信息。我們現在知道浙西額額外信息是顯式模板實參(explicit template argument)列表,他們被綁定到模板參數:

Blob<int> ia; Blob<int> ia2 = {0, 1, 2, 3, 4};

?實例化會讓編譯器實例化出一個與下面定義等價的類:

template <> class Blob<int> { public:typedef typename std::vector<int>::size_type size_type;Blob();Blob( std::initializer_list<int> i1);size_type size() const { return data->size(); }bool empty() const { return data->empty(); }void push_back( const int &t) { data->push_back(t); }void push_back( const int &&t) { data->push_back(std::move(t)); }void pop_back();T& back();T& operator[](size_type i);private:std::shared_ptr<std::vector<int>> data;void check(size_type i, const std::string &msg) const; };

? 一個類模板的每個實例都會形成一個獨立的類。類型Blob<string>和任何其他Blob類型都沒有關聯,也不會對任何其他Blob類型的成員有特殊訪問權限。

類模板的成員函數

? 類模板的成員函數具有和模板相同的模板參數。因此,定義在類模板之外的成員函數就必須以關鍵字template開始,后接類模板參數列表。

ret-type StrBlob::member-name(parm-list) {}template <typename T> ret-type Blob<T>::member-name(parm-list) {}template <typename T> Blob<T>::Blob() : data( std::make_shared<std::vector<T>>()) {}template <typename T> Blob<T>::Blob( std:: initializer_list<T> i1) : data( std::make_shared<std::vector<T>>(i1)){}template <typename T> void Blob<T>::pop_back() {check( 0, "back on empty Blob");return data->pop_back(); }template <typename T> T& Blob<T>::back() {check( 0, "back on empty Blob");return data->back(); }template <typename T> T& Blob<T>::operator[](size_type i) {check(i, "subscript out of range");return (*data)[i]; }template <typename T> void Blob<T>::check( size_type i, const std::string &msg) const {if( i >= data->size() )throw std::out_of_range(msg); }

? 如果一個成員函數沒有被使用,則它不會被實例化。成員函數只有在被用到時才進行實例化,這一特性使得即使某種類型不能完全符合模板操作的要求,我們仍然能用該類型實例化類。

在類代碼內簡化模板類名的使用

? 當我們使用一個類模板類型時必須提供模板實參,但例外是在類模板自己的作用域中,我們可以直接使用模板名而不提供實參:

template <typename T> class BlobPtr { public:BlobPtr():curr(0) {}BlobPtr( Blob<T> &a, size_t sz = 0):wptr(a.data),curr(sz) {}T& operator*() const{auto p = check( curr, "dereference past end");return (*p)[curr];}BlobPtr& operator++();BlobPtr& operator--(); private:std::shared_ptr<std::vector<T>> check(std::size_t, const std::string&) const; std::weak_ptr< std::vector<T>> wptr;std::size_t curr; };

? ? 前置遞增/遞減返回BlobPtr&而不是BlobPtr<T>&,當我們處于一個類模板的作用域中時,編譯器處理模板自身引用時就好像我們已經提供了與模板參數匹配的實參一樣。

? 在類模板外定義其成員時,必須記住并不在類的作用域中,直到遇到類名才表示進入類的作用域.

template <typename T> BlobPtr<T> BlobPtr<T>::operator++(int) {BlobPtr ret = *this;++*this;return ret; }

類模板和友元

? 當一個類包含一個有友元聲明時,類和友元各自是否是模板是相互無關的。如果一個類模板包含一個非模板友元,則友元被授權可以訪問所有模板示例。如果友元自身是模板,類可以授權給所有友元模板實例,也可以值授權給特定實例。

一對一友好關系

? 類模板與另一個(類或函數)模板間友好關系的最常見的形式是建立對應實例及其友元間的友好關系。為了引用(類或函數)模板的一個特定實例,我們必須首先聲明模板自身(一個模板的聲明包括模板參數列表):

template <typename> class BlobPtr; template <typename> class Blob; template <typename T>bool operator==(const Blob<T>&, const Blob<T>&);template <typename T> class Blob{friend class BlobPtr<T>;friend bool operator==<T>(const Blob<T>&, const Blob<T>&); };

? 前面三行的聲明是函數的參數聲明和Blob中的友元聲明所需要的。

? 友元聲明用Blob的模板形參作為它們自己的模板實參。因此,友好關系被限定在用相同類型實例化的Blob和BlobPtr相等運算符之間。

Blob<char> ca; Blob<char> operator==<char>Blob<int> ia; Blob<int> operator==<int>

通用和特定的模板友好關系

? 一個類也可以將另一個模板的每個實例都聲明為自己的友元,或者限定特定的示例為友元:

template <typename T> class Pal;class C{friend class Pal<C>; //用C實例化的Pal是C的一個友元//Pal2的所有示例都是C的友元,這種情況無需前置聲明template <typename T> friend class Pal2; };template <typename T> class C2 {//C2的每個實例將相同實例化的Pal聲明為友元friend class Pal<T>; //Pal的模板聲明必須在作用域之內//Pal2的所有示例都是C2的每個實例的友元,不需要前置聲明template <typename X> friend class Pal2;//Pal3是一個非模板類,它是C2所有示例的友元friend class Pal3; };

? 為了讓所有實例稱為友元,友元聲明中必須使用與類模板本身不同的模板參數。

令模板自己的類型參數稱為友元

? 可以將模板類型參數聲明成友元:

template <typename Type> class Bar{friend Type; };

模板類型別名

? 我們可以定義一個typedef來引用實例化的類,但不能定義一個typedef引用一個模板。不過,我們可以為類模板定義一個類型別名:

typedef Blob<string> StrBlob;template<typename T> using twin = pair<T, T>; twin<string> authors; //authors是一個pair<string, string> twin<int> win_loss; //win_loss是一個pair<int, int> twin<double> area;

類模板的static成員

? 類模板的static數據成員必須有且僅有一個定義。類模板的每個實例都有一個獨有的static對象。與定義模板的成員函數類似,我們將static數據成員也定義為模板:

template <typename T> class Foo { public:static std::size_t count() {return ctr;} private:static std::size_t ctr; };template <typename T> size_t Foo<T>::ctr = 0;Foo<int> fi; auto ci = Foo<int>::count(); ct = fi.count(); ct = Foo::count(); //錯誤

模板參數

? 類似函數參數的名字,一個模板參數的名字沒有什么內在含義,其可用范圍是在其什么之后,至模板聲明或定義結束之前(會隱藏外層作用域中聲明的相同名字)。參數名不能重用,但定義中的名字不必與聲明中的模板參數名字相同。

使用類的類型成員

? 假定T是一個模板類型參數,當編譯器遇到類似T::mem這樣的代碼時,它不會知道men是一個類型成員還是一個static數據成員,直到實例化。但為了處理模板,編譯器必須指定名字是否表示一個類型。

T::size_type * p; //是定義一個p的指針變量? //還是將一個名為size_type的static數據成員與p相乘

? 默認情況下,C++語言假定通過作用域訪問符(::)訪問的名字都不是類型。因此,如果希望使用一個模板類型參數的類型名字,必須顯式告訴編譯器該名字是一個類型(通過使用關鍵字typename來實現):

template <typename T> typename T::value_type top(const T& c) { if( !c.empty())return c.back();elsereturn typename T::value_type(); }

? 當我們希望通知編譯器一個名字表示類型時,必須使用關鍵字typename,而不能使用class

默認模板實參

? 我們可以提供默認模板實參(default template argument)。在C++11標準后,可以為函數和類模板提供默認實參。

template <typename T, typename F = less<T>> int compare(const T &v1, const T &v2, F f = F()) {if( f(v1, v2))return -1;if( f(v2, v1))return 1;return 0; }

模板默認實參和類模板

無論何時使用一個類模板,都必須在模板名之后接上尖括號。尖括號指出類必須從一個模板實例化而來。特別,如果一個類模板為其所有模板參數都提供了默認實參,且希望使用這些默認實參,就必須在模板名之后跟一個空尖括號對:

template<class T = int> class Numbers{//默認T為int public:Numbers(T v = 0) : val(v) {} private:T val; };Numbers<long double> lots_of_precision; Numbers<> average_precision;

成員模板

? 一個類(無論是普通類還是類模板)可以包含本身是模板的成員函數。這種成員稱為成員模板(member template)。成員模板不是虛函數。

? 重載的函數調用運算符希望刪除器適用于任何類型,所以將調用運算符定義為一個模板:

class DebugDelete { public:DebugDelete(std::ostream &os = std::cerr ) : os(s) {}template <typename T> void operator()(T *p) const{ os << "deleting unique_ptr" << std::endl;delete p; } private:std::ostream &os; };double *p = new double; DebugDelete d; //調用DebugDelete::operator()(double *),釋放p d(p);int *ip = new int; //在一個臨時DebugDelete對象上調用operator()(int *) DebugDelete()(ip);

? 由于調用一個DebugDelete對象會delete其給定指針,我們可以將其用作unique_ptr的刪除器。為了重載unique_ptr的刪除器,我們在尖括號內給出了刪除器類型,并提供一個這種類型的對象給unique_ptr的構造函數:

unique_ptr<int, DebugDelete> p(new int, DebugDelete()); unique_ptr<string, DebugDelete> sp(new string, DebugDelete());

類模板的成員模板

? 對于類模板,也可以為其定義成員模板:

template <typename T> class Blob {template <typename IT> Blob(It b, It e);};

? 與類模板的普通成員函數不同,成員模板是函數模板。當在類模板外定義一個成員模板時,必須同時為類模板和成員模板提供模板參數列表。類模板的參數列表在前,后跟成員自己的模板參數列表:

template <typename T> template <typename IT>Blob<T>::Blob(IT b, IT e) : data(std::make_shared<std::vector<T>>(b, e) ) { }

實例化和成員模板

? 為了實例化一個類模板的成員模板,我們必須同時提供類和函數模板的實參。與普通函數模板相同,編譯器通常根據傳遞給成員模板的函數實參來推斷它的模板類型:

int ia[] = {0,1,2}; vector<long> vi = {0, 1, 2, 3}; list<const char*> w = {"now", "is", "the", "time"};//實例化Blob<int>類及接受兩個int*參數的構造函數 Blob<int> a1(begin(ia), end(ia));//實例化Blob<int>類及接受兩個vector<long>::iterator的構造函數 Blob<int> a2(vi.begin(), vi.end());//實例化Blob<string>類及接受兩個list<const char*>::iterator的構造函數 Blob<string> a2(w.begin(), w.end());

控制實例化

當模板被使用時才進行實例化這特性意味著相同的實例可能會出現在多個對象文件中(.o)。在大系統中,在多個文件中實例化化相同模板的額外開銷可能非常嚴重。可以通過顯示實例化(explicit instantiation)來避免這種開銷:

extern template declaration; //實例化聲明 template declaration; //實例化定義

? declaration是一個類或函數聲明,其中所有模板參數已被替換成模板實參,例如:

extern template class Blob<string>; //聲明 template int compare(const int&, const int&); //定義

? 由于編譯器你在使用一個模板時自動對其實例化。因此extern聲明必須出現在任何使用此實例化版本的代碼之前。

// Application.cc //這些模板類型必須在程序其他位置進行實例化 extern template class Blob<string>; extern template int compare(const int&, const int&); Blob<string> sa1, sa2; //實例化會出現在其他位置//Blob<int>及其接受initializer_list的構造函數在本文件中實例化 Blob<int> a1 = {0, 1, 2, 3, 4, 5, 6}; Blob<int> a2(a1); //拷貝構造函數在本文件中實例化 int i = compare(a1[0], a2[0]); //實例化出現在其他位置

? ?文件Application.o將包含Blob<int>的實例及其接受initializer_list參數的構造函數和拷貝構造函數的實例。而compare<int>函數和Blob<string>類將不再本文件中進行實例化。這些模板的定義必須出現在程序的其他文件中:

//templateBuild.cc //實例化文件必須為每個在其他文件中聲明為extern的類型和函數提供一個(非extern)的定義 template int compare(const int&, const int&); template class Blob<string>;

? ?當編譯器遇到一個實例化定義(與聲明相對)時,它為其生存代碼。因此templateBuild.o將包含compare的int實例化版本的定義和Blob<string>類的定義。

? 對于每個實例化聲明,在程序中某個位置必須有其顯式的實例化定義。實例化定義會實例化所有成員:一個類模板的實例化定義會實例化該模板的所有成員,包括內聯的成員函數。當編譯器遇到一個實例化定義時,它不了解程序使用那些成員函數。因此與處理類模板的普通實例化不同,編譯器會實例化該類的所有成員。因此,我們用來顯示實例化一個類模板的類型,必須能用于模板的所有的成員。

總結

以上是生活随笔為你收集整理的C++Primer_Chap16_模板和泛型编程_List01_定义模板_笔记的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。