?作者主頁
📚lovewold少個r博客主頁
? ???本文重點:c++模板初階知識點講解
👉【C-C++入門系列專欄】:博客文章專欄傳送門
😄每日一言:花有重開日,人無再少年
目錄
前言
泛型編程
函數模板
函數模板概念
函數模板格式
函數模板的原理
函數模板的實例化
模板參數的匹配原則
類模板
類模板的定義格式
類模板的實例化
總結
前言
? ? ? ? C++是一門面向對象的語言,很多情況下我們不需要在編寫程序時候去過多的考慮底層。而在前面我們學習C++的輸入輸出好像就是這樣子,編譯器會自動幫我們做很多的事情,而不需要自己去傳遞輸入輸出的變量類型等因素。這種方式肯定對于編寫程序的人來講是輕松的。世界上的各種科技的進步其實都離不開人對于懶惰的追求,對于方便的執著,而今天我要講解的模板好像就算是一種特殊的產物。
? ? ? ? 首先我們先寫一個比較簡單的程序來細致的探究一下我們為何需要模板。這里我們寫了一個用于整形變量交換的函數,交換函數無論是在什么排序亦或者一些計算的時候都是常用函數。
void swap(int& x, int& y) {int temp = x;x = y;y = temp; } int main() {int a = 10;int b = 5;cout << "a=" << a << " " << "b=" << b << endl;swap(a, b);cout << "swap~" << endl;cout << "a=" << a << " " << "b=" << b << endl;return 0; }
????????問題來了,我們這里需要特別強調這里是用于整形類型的交換函數,因此這對于其他類型變量并不合適。那么我們如何去實現長整形,短整形,浮點型,字符型······。函數重載?
????????C++提供了函數重載的方式,對于一些傳參會直接以其傳遞參數而決定對應的函數。
????????但是缺陷也很明顯,我們的函數僅僅是類型不同,那么多類型我們都需要去重載么。亦或者我們先創建我們需要的類型,等到新類型出現的時候,用戶再自己去增加對應的函數重載么。
????????另一個關鍵點是,我們這里僅僅只是交換函數,比較簡單,而對于一份各種函數相互嵌套的代碼,一份代碼出錯,其他重載函數全得改,代碼的可維護性比較低。
泛型編程
? ? ? ? 我們再看先前的代碼,會發現僅僅只是類型的不同罷了,我們可不可以提供一種方式和cout以及cin一樣,把類型識別的任務交給編譯器去完成,自己只需要給他傳遞參數變量即可。也就是說我們只需要提供一份代碼作為模具,編譯器可以根據不同的類型利用這個模具生成相應的代碼。
void swap(int& x, int& y)
{int temp = x;x = y;y = temp;
}
void swap(double& x, double& y)
{double temp = x;x = y;y = temp;
}
void swap(char& x, char& y)
{char temp = x;x = y;y = temp;
}
int main()
{int a = 10;int b = 5;cout << "a=" << a << " " << "b=" << b << endl;swap(a, b);cout << "swap~" << endl;cout << "a=" << a << " " << "b=" << b << endl;return 0;
}
????????無論是活字印刷術還是現在的模具澆筑技術,其根本的目的就是維持其功能一致即可,你可以注入不同的材料以改變其最后成品的效果,但本質上實現的功能是一樣的,外形是一致的。
? ? ? ? 之所以cv工程師能有獨特的cv大法,也是因為前人擁有特定的已經可以使用的板子,而只需要去改吧改吧然后切合自己的工程內容即可實現一個全新的項目成果。這也就是一種代碼復用的常規手段而已。
? ? ? ? ?話不多說,接下來直入正題。我們先談一談何為泛型編程。
????????泛型編程是一種編程范式,其目標是編寫與特定數據類型無關的通用代碼,以便更廣泛地重用代碼。泛型編程使得程序員可以編寫與數據類型無關的算法和數據結構,從而提高代碼的靈活性、可重用性和可維護性。
????????具體而言,泛型編程通過使用參數化類型(parameterized types)來實現。參數化類型是一種允許在代碼中使用未指定具體類型的抽象類型。這樣,可以編寫算法和數據結構,而不必在編寫時指定具體的數據類型。在需要使用這些算法和數據結構的地方,可以通過提供具體的類型來實現參數的具體化。
????????在C++中,泛型編程主要通過模板來實現。模板允許程序員編寫與數據類型無關的代碼,可以用于不同的數據類型。這使得在不同的上下文中重用代碼成為可能。例如,可以編寫通用的排序算法、容器類、以及其他算法和數據結構,而不必為每種數據類型都編寫一套特定的代碼。而模板就是泛型編程的基礎。
函數模板
函數模板概念
????????函數模板是C++中用于創建通用函數的一種機制,允許程序員編寫與特定數據類型無關的函數代碼。函數模板通過使用參數化類型來實現,使得可以在編寫代碼時使用未指定具體類型的抽象類型。這樣,函數模板可以適用于多種數據類型,提高了代碼的靈活性和重用性。
函數模板格式
template<typename T> void Swap(T& x,T& y) {T temp = x;x = y;y = temp; }
template T 表示模板參數,T是一個占位符,代表任意數據類型。在函數模板中,可以使用T作為函數的參數類型、返回類型,以及在函數體中進行通用的操作。
注意:typename是用來定義模板關鍵字,也可以使用class(不能使用struct代替class)
函數模板的原理
? ? ? ? 人類從農業時期到工業時期,很多重復機械的工作直接交給了機器去完成,極大的解放的生產力。機器生產淘汰了很多手工創作的東西,其本質上就是把這些工作交給了機器去完成。
? ? ? ? 函數模板本身并不是一個函數,而是像類一樣作為一個藍圖,是編譯器用使用方式產生具體類型函數的一個模具。
在編譯器編譯階段,對于模板函數的使用,編譯器需要根據傳入的實參類型來推演生成對應類型的函數以供調用。比如int類型使用函數模板時,編譯器根據傳遞的實參類型的推演將T確定為int類型,然后再專門產生一份處理int類型的代碼,對于double類型還是字符類型皆是這樣
函數模板的實例化
用不同類型的參數使用函數模板的時候稱之為函數模板的實例化。模板參數實例化分為隱式實例化和顯式實例化。
隱式實例化:讓編譯器根據傳遞的實參類型推演模板參數的實際類型。
template <class T>
T Add(const T& left, const T& right)
{return left + right;
}int main()
{int a1 = 10;int a2 = 5;Add(a1, a2);double b1 = 10.0;double b2 = 5.0;Add(b1, b2);return 0;
}
????????而當調用的時候將不同類型混合傳參是不被允許的,因為在編譯期間編譯器需要推演,對于a1可以推演為int,b1可以推演為double,但是模板參數列表中只有一個T,編譯器無法確定T被換為int還是double而報錯。在模板中,編譯器一般不會去做類型轉換,否則出錯了編譯器就會承擔不小的后果。
????????因此對于這種問題我們通常有兩種方式進行解決:用戶自己進行強制類型轉換或者使用顯示實例化
顯示實例化:在函數名的后面指定模板參數的實際類型
int main()
{int a1 = 10;int a2 = 5;Add(a1, a2);double b1 = 10.0;double b2 = 5.0;Add(b1, b2);Add<int>(a1, b1);//顯示實例化return 0;
}
模板參數的匹配原則
????????一個非模板函數和模板函數可以同時存在,而且該模板函數還可以實例化為這個非模板函數。(這種實例化為非模板函數指在參數傳遞上可能維持一樣,但是依據優先匹配的原則,在調用函數的時候可以做一些特殊處理)。這里我們通過不同函數之間打印信息而進行調用優先級的查看。
//專門處理整形類型加法函數 int Add(int left, int right) {cout << "非模板函數" << endl;return left + right; } //通用加法模板 template<class T> T Add(T left, T right) {cout << "模板函數" << endl;return left + right; } void test() {Add(1,2);//與非模板函數優先匹配Add(1.0, 2.0);//模板函數Add<int>(1, 2);//調用特定版本的Add版本,走模板函數 } int main() {test();return 0; }
????????對于非模板函數與同名函數模板,如果有其他條件相同,在調用的時候會優先去調用非函數模板而不會從該函數模板中生成實例化函數,如果一個函數可以產生一個具有更好匹配的函數,那么選擇模板。
int Add(int left, int right)
{cout << "非模板函數" << endl;return left + right;
}
//通用加法模板
template<class T1 , class T2>
T1 Add(T1 left, T2 right)
{cout << "模板函數" << endl;return left + right;
}
void test()
{Add(1, 2);//與非模板函數優先匹配Add(1, 2.0);//具備更加匹配的版本而不需要類型轉換,編譯器優先生成更加匹配的Add函數版本
}
int main()
{test();return 0;
}
????????模板函數不能進行自動類型轉換(幫你推演就夠忙了,類型轉換發生錯誤你還得罵他自然就不會幫你做這件沒意義的事情)。但是普通函數可以進行自動類型轉換
類模板
類模板的定義格式
template<class T1, class T2,class T3>//參數列表可以定義多個模板變量
class 類模板名
{//類成員定義
};
????????我們前面學習順序表提到過一點,使用typedef對類型名進行重命名。C語言中的類型重命名是指通過使用typedef關鍵字來為已有的類型創建一個新的別名。這樣可以簡化代碼,提高可讀性,并且方便批量修改具體類型,便于維護代碼。但是當我們學習了類模板之后我們發現我們并不需要這樣做,而是使用類模板。這里我們簡要的構造一個動態順序表的類模板來體會。
template<class T>
class Vector
{
public:Vector(size_t capacity = 10): _pData(new T[capacity]), _size(0), _capacity(capacity){}~Vector();// 其他成員函數在這里...size_t Size() { return _size; };T& operator[](size_t pos){assert(pos < _size);return _pData[pos];}private:T* _pData;size_t _size;size_t _capacity;
};// 類模板函數定義可以放在類定義外面。
template<class T>
Vector<T>::~Vector()
{if (_pData){delete[] _pData;}_size = _capacity = 0;
}
類模板的實例化
????????類模板的實例化與函數模板實例化不同,類模板實例化需要在類模板的名字前面跟<>,然后需要講實例化的類型放在<>中即可(就像必須顯式的實例化)。類模板名字不是真正的類,而實例化的結果才是真正的類(藍圖和用藍圖進行建筑的關系)。實例化時,編譯器會生成針對具體數據類型的類定義,從而使得類模板變得具體化,可以像普通類一樣使用。
Vector類名,Vector<int>才是類型
Vector<int> s1;
Vector<double> s2;
總結
????????泛型編程是一種編程范式,其目標是編寫可重用、通用的代碼,以便能夠適應多種數據類型而無需針對每種類型重復編寫相似的代碼。在C++中,泛型編程主要通過函數模板和類模板來實現。其優勢如下:
-
提高代碼的重用性和可維護性。
-
允許在不同數據類型上進行抽象,減少代碼冗余。
函數模板(Function Templates):
概念:函數模板是一種定義通用函數的方式,其中函數的參數或返回類型可以是通用的類型參數。
語法:
template <class T> //typename也可以 T Add(T a, T b) {return a + b; }
實例化:通過指定具體的數據類型,編譯器會生成對應類型的函數定義。
實例化用法:
int r_int = Add(3, 4); // 實例化為 Add<int>(3, 4) double r_double = Add(3.14, 2.5);// 實例化為 Add<double>(3.14, 2.5)
類模板(Class Templates):
概念:類模板是一種定義通用類的方式,其中類的成員或行為可以依賴于通用的類型參數。
語法:
?? ?template <class T> class MyClass {public:MyClass(T value) : data(value) {}void play() { /* ... */ }private:T data;};
實例化:通過指定具體的數據類型,編譯器會生成對應類型的類定義。
使用:
? ?MyClass<int> intObject(42);MyClass<double> doubleObject(3.14);
? ? 作者水平有限,如有錯誤歡迎指正!
? ??