第五章 C++模板
5.1 模板概論
c++提供了函數(shù)模板(function template.)所謂函數(shù)模板梧油,實際上是建立一個通用函數(shù)撵儿,其函數(shù)類型和形參類型不具體制定再榄,用一個虛擬的類型來代表蝙叛。這個通用函數(shù)就成為函數(shù)模板乳绕。 凡是函數(shù)體相同的函數(shù)都可以用這個模板代替绞惦,不必定義多個函數(shù),只需在模板中定義一次即可洋措。在調(diào)用函數(shù)時系統(tǒng)會根據(jù)實參的類型來取代模板中的虛擬類型济蝉,從而實現(xiàn)不同函數(shù)的功能。
- c++提供兩種模板機制:函數(shù)模板和類模板
- 類屬 - 類型參數(shù)化菠发,又稱參數(shù)模板
總結(jié):
- 模板把函數(shù)或類要處理的數(shù)據(jù)類型參數(shù)化王滤,表現(xiàn)為參數(shù)的多態(tài)性,成為類屬滓鸠。
- 模板用于表達(dá)邏輯結(jié)構(gòu)相同雁乡,但具體數(shù)據(jù)元素類型不同的數(shù)據(jù)對象的通用行為。
5.2 函數(shù)模板
5.2.1 什么是函數(shù)模板糜俗?
//交換int數(shù)據(jù)
void SwapInt(int& a,int& b){
int temp = a;
a = b;
b = temp;
}
//交換char數(shù)據(jù)
void SwapChar(char& a,char& b){
char temp = a;
a = b;
b = temp;
}
//問題:如果我要交換double類型數(shù)據(jù)踱稍,那么還需要些一個double類型數(shù)據(jù)交換的函數(shù)
//繁瑣,寫的函數(shù)越多吩跋,當(dāng)交換邏輯發(fā)生變化的時候寞射,所有的函數(shù)都需要修改渔工,無形當(dāng)中增加了代碼的維護難度
//如果能把類型作為參數(shù)傳遞進(jìn)來就好了锌钮,傳遞int就是Int類型交換,傳遞char就是char類型交換
//我們有一種技術(shù)引矩,可以實現(xiàn)類型的參數(shù)化---函數(shù)模板
//class 和 typename都是一樣的梁丘,用哪個都可以
template<class T>
void MySwap(T& a,T& b){
T temp = a;
a = b;
b = temp;
}
void test01(){
int a = 10;
int b = 20;
cout << "a:" << a << " b:" << b << endl;
//1. 這里有個需要注意點,函數(shù)模板可以自動推導(dǎo)參數(shù)的類型
MySwap(a,b);
cout << "a:" << a << " b:" << b << endl;
char c1 = 'a';
char c2 = 'b';
cout << "c1:" << c1 << " c2:" << c2 << endl;
//2. 函數(shù)模板可以自動類型推導(dǎo)旺韭,那么也可以顯式指定類型
MySwap<char>(c1, c2);
cout << "c1:" << c1 << " c2:" << c2 << endl;
}
用模板是為了實現(xiàn)泛型氛谜,可以減輕編程的工作量,增強函數(shù)的重用性区端。
5.2.2 課堂練習(xí)
使用函數(shù)模板實現(xiàn)對char和int類型數(shù)組進(jìn)行排序值漫?
//模板打印函數(shù)
template<class T>
void PrintArray(T arr[],int len){
for (int i = 0; i < len;i++){
cout << arr[i] << " ";
}
cout << endl;
}
//模板排序函數(shù)
template<class T>
void MySort(T arr[],int len){
for (int i = 0; i < len;i++){
for (int j = len - 1; j > i;j--){
if (arr[j] > arr[j - 1]){
T temp = arr[j - 1];
arr[j - 1] = arr[j];
arr[j] = temp;
}
}
}
}
void test(){
//char數(shù)組
char tempChar[] = "aojtifysn";
int charLen = strlen(tempChar);
//int數(shù)組
int tempInt[] = {7,4,2,9,8,1};
int intLen = sizeof(tempInt) / sizeof(int);
//排序前 打印函數(shù)
PrintArray(tempChar, charLen);
PrintArray(tempInt, intLen);
//排序
MySort(tempChar, charLen);
MySort(tempInt, intLen);
//排序后打印
PrintArray(tempChar, charLen);
PrintArray(tempInt, intLen);
}
5.3 函數(shù)模板和普通函數(shù)區(qū)別
- 函數(shù)模板不允許自動類型轉(zhuǎn)化
- 普通函數(shù)能夠自動進(jìn)行類型轉(zhuǎn)化
//函數(shù)模板
template<class T>
T MyPlus(T a, T b){
T ret = a + b;
return ret;
}
//普通函數(shù)
int MyPlus(int a,char b){
int ret = a + b;
return ret;
}
void test02(){
int a = 10;
char b = 'a';
//調(diào)用函數(shù)模板,嚴(yán)格匹配類型
MyPlus(a, a);
MyPlus(b, b);
//調(diào)用普通函數(shù)
MyPlus(a, b);
//調(diào)用普通函數(shù) 普通函數(shù)可以隱式類型轉(zhuǎn)換
MyPlus(b, a);
//結(jié)論:
//函數(shù)模板不允許自動類型轉(zhuǎn)換织盼,必須嚴(yán)格匹配類型
//普通函數(shù)可以進(jìn)行自動類型轉(zhuǎn)換
}
5.4 函數(shù)模板和普通函數(shù)在一起調(diào)用規(guī)則
- c++編譯器優(yōu)先考慮普通函數(shù)
- 可以通過空模板實參列表的語法限定編譯器只能通過模板匹配
- 函數(shù)模板可以像普通函數(shù)那樣可以被重載
- 如果函數(shù)模板可以產(chǎn)生一個更好的匹配杨何,那么選擇模板
//函數(shù)模板
template<class T>
T MyPlus(T a, T b){
T ret = a + b;
return ret;
}
//普通函數(shù)
int MyPlus(int a, int b){
int ret = a + b;
return ret;
}
void test03(){
int a = 10;
int b = 20;
char c = 'a';
char d = 'b';
//如果函數(shù)模板和普通函數(shù)都能匹配,c++編譯器優(yōu)先考慮普通函數(shù)
cout << MyPlus(a, b) << endl;
//如果我必須要調(diào)用函數(shù)模板,那么怎么辦?
cout << MyPlus<>(a, b) << endl;
//此時普通函數(shù)也可以匹配沥邻,因為普通函數(shù)可以自動類型轉(zhuǎn)換
//但是此時函數(shù)模板能夠有更好的匹配
//如果函數(shù)模板可以產(chǎn)生一個更好的匹配危虱,那么選擇模板
cout << MyPlus(c,d);
}
//函數(shù)模板重載
template<class T>
T MyPlus(T a, T b, T c){
T ret = a + b + c;
return ret;
}
void test04(){
int a = 10;
int b = 20;
int c = 30;
cout << MyPlus(a, b, c) << endl;
//如果函數(shù)模板和普通函數(shù)都能匹配,c++編譯器優(yōu)先考慮普通函數(shù)
}
5.5 模板機制剖析
思考:為什么函數(shù)模板可以和普通函數(shù)放在一起?c++編譯器是如何實現(xiàn)函數(shù)模板機制的唐全?
5.5.1編譯過程
hello.cpp
程序是高級c語言程序埃跷,這種程序易于被人讀懂。為了在系統(tǒng)上運行hello.c
程序,每一條c語句都必須轉(zhuǎn)化為低級的機器指令弥雹。然后將這些機器指令打包成可執(zhí)行目標(biāo)文件格式垃帅,并以二進(jìn)制形式存儲于磁盤中。
預(yù)處理(Pre-processing) -> 編譯(Compiling) ->匯編(Assembling) -> 鏈接(Linking)
5.5.2 模板實現(xiàn)機制
函數(shù)模板機制結(jié)論:
- 編譯器并不是把函數(shù)模板處理成能夠處理任何類型的函數(shù)
- 函數(shù)模板通過具體類型產(chǎn)生不同的函數(shù)
- 編譯器會對函數(shù)模板進(jìn)行兩次編譯缅糟,在聲明的地方對模板代碼本身進(jìn)行編譯挺智,在調(diào)用的地方對參數(shù)替換后的代碼進(jìn)行編譯。
5.6模板的局限性
假設(shè)有如下模板函數(shù):
template<class T>
void f(T a, T b)
{ … }
如果代碼實現(xiàn)時定義了賦值操作 a = b
窗宦,但是T為數(shù)組赦颇,這種假設(shè)就不成立了
同樣,如果里面的語句為判斷語句 if(a>b)
,但T如果是結(jié)構(gòu)體赴涵,該假設(shè)也不成立媒怯,另外如果是傳入的數(shù)組,數(shù)組名為地址髓窜,因此它比較的是地址扇苞,而這也不是我們所希望的操作。
總之寄纵,編寫的模板函數(shù)很可能無法處理某些類型鳖敷,另一方面,有時候通用化是有意義的程拭,但C++語法不允許這樣做定踱。為了解決這種問題,可以提供模板的重載恃鞋,為這些特定的類型提供具體化的模板崖媚。
class Person
{
public:
Person(string name, int age)
{
this->mName = name;
this->mAge = age;
}
string mName;
int mAge;
};
//普通交換函數(shù)
template <class T>
void mySwap(T &a,T &b)
{
T temp = a;
a = b;
b = temp;
}
//第三代具體化,顯示具體化的原型和定意思以template<>開頭恤浪,并通過名稱來指出類型
//具體化優(yōu)先于常規(guī)模板
template<>void mySwap<Person>(Person &p1, Person &p2)
{
string nameTemp;
int ageTemp;
nameTemp = p1.mName;
p1.mName = p2.mName;
p2.mName = nameTemp;
ageTemp = p1.mAge;
p1.mAge = p2.mAge;
p2.mAge = ageTemp;
}
void test()
{
Person P1("Tom", 10);
Person P2("Jerry", 20);
cout << "P1 Name = " << P1.mName << " P1 Age = " << P1.mAge << endl;
cout << "P2 Name = " << P2.mName << " P2 Age = " << P2.mAge << endl;
mySwap(P1, P2);
cout << "P1 Name = " << P1.mName << " P1 Age = " << P1.mAge << endl;
cout << "P2 Name = " << P2.mName << " P2 Age = " << P2.mAge << endl;
}
5.7 類模板
5.7.1 類模板基本概念
類模板和函數(shù)模板的定義和使用類似畅哑,我們已經(jīng)進(jìn)行了介紹。有時水由,有兩個或多個類荠呐,其功能是相同的,僅僅是數(shù)據(jù)類型不同砂客。
- 類模板用于實現(xiàn)類所需數(shù)據(jù)的類型參數(shù)化
template<class NameType, class AgeType>
class Person
{
public:
Person(NameType name, AgeType age)
{
this->mName = name;
this->mAge = age;
}
void showPerson()
{
cout << "name: " << this->mName << " age: " << this->mAge << endl;
}
public:
NameType mName;
AgeType mAge;
};
void test01()
{
//Person P1("德瑪西亞",18); // 類模板不能進(jìn)行類型自動推導(dǎo)
Person<string, int>P1("德瑪西亞", 18);
P1.showPerson();
}
5.7.2 類模板做函數(shù)參數(shù)
//類模板
template<class NameType, class AgeType>
class Person{
public:
Person(NameType name, AgeType age){
this->mName = name;
this->mAge = age;
}
void PrintPerson(){
cout << "Name:" << this->mName << " Age:" << this->mAge << endl;
}
public:
NameType mName;
AgeType mAge;
};
//類模板做函數(shù)參數(shù)
void DoBussiness(Person<string,int>& p){
p.mAge += 20;
p.mName += "_vip";
p.PrintPerson();
}
int main(){
Person<string, int> p("John", 30);
DoBussiness(p);
system("pause");
return EXIT_SUCCESS;
}
5.7.3 類模板派生普通類
//類模板
template<class T>
class MyClass{
public:
MyClass(T property){
this->mProperty = property;
}
public:
T mProperty;
};
//子類實例化的時候需要具體化的父類泥张,子類需要知道父類的具體類型是什么樣的
//這樣c++編譯器才能知道給子類分配多少內(nèi)存
//普通派生類
class SubClass : public MyClass<int>{
public:
SubClass(int b) : MyClass<int>(20){
this->mB = b;
}
public:
int mB;
};
5.7.4 類模板派生類模板
//父類類模板
template<class T>
class Base
{
T m;
};
template<class T >
class Child2 : public Base<double> //繼承類模板的時候,必須要確定基類的大小
{
public:
T mParam;
};
void test02()
{
Child2<int> d2;
}
5.7.5 類模板類內(nèi)實現(xiàn)
template<class NameType, class AgeType>
class Person
{
public:
Person(NameType name, AgeType age)
{
this->mName = name;
this->mAge = age;
}
void showPerson()
{
cout << "name: " << this->mName << " age: " << this->mAge << endl;
}
public:
NameType mName;
AgeType mAge;
};
void test01()
{
//Person P1("德瑪西亞",18); // 類模板不能進(jìn)行類型自動推導(dǎo)
Person<string, int>P1("德瑪西亞", 18);
P1.showPerson();
}
5.7.6 類模板類外實現(xiàn)
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;
template<class T1, class T2>
class Person{
public:
Person(T1 name, T2 age);
void showPerson();
public:
T1 mName;
T2 mAge;
};
//類外實現(xiàn)
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age){
this->mName = name;
this->mAge = age;
}
template<class T1, class T2>
void Person<T1, T2>::showPerson(){
cout << "Name:" << this->mName << " Age:" << this->mAge << endl;
}
void test()
{
Person<string, int> p("Obama", 20);
p.showPerson();
}
int main(){
test();
system("pause");
return EXIT_SUCCESS;
}
5.7.7 類模板頭文件和源文件分離問題
Person.hpp
#pragma once
template<class T1,class T2>
class Person{
public:
Person(T1 name,T2 age);
void ShowPerson();
public:
T1 mName;
T2 mAge;
};
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age){
this->mName = name;
this->mAge = age;
}
template<class T1, class T2>
void Person<T1, T2>::ShowPerson(){
cout << "Name:" << this->mName << " Age:" << this->mAge << endl;
}
main.cpp
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#include<string>
#include"Person.hpp"
//模板二次編譯
//編譯器編譯源碼 逐個編譯單元編譯的
int main(){
Person<string, int> p("Obama", 20);
p.ShowPerson();
system("pause");
return EXIT_SUCCESS;
}
結(jié)論:
案例代碼在qt
編譯器順利通過編譯并執(zhí)行鞭盟,但是在Linux
和vs
編輯器下如果只包含頭文件圾结,那么會報錯鏈接錯誤,需要包含cpp
文件齿诉,但是如果類模板中有友元類筝野,那么編譯失斏我Α!
解決方案: 類模板的聲明和實現(xiàn)放到一個文件中歇竟,我們把這個文件命名為.hpp(這個是個約定的規(guī)則挥唠,并不是標(biāo)準(zhǔn),必須這么寫).
原因:
- 類模板需要二次編譯焕议,在出現(xiàn)模板的地方編譯一次宝磨,在調(diào)用模板的地方再次編譯。
- C++編譯規(guī)則為獨立編譯盅安。
5.7.8 模板類碰到友元函數(shù)
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#include <string>
template<class T1, class T2> class Person;
//告訴編譯器這個函數(shù)模板是存在
template<class T1, class T2> void PrintPerson2(Person<T1, T2>& p);
//友元函數(shù)在類內(nèi)實現(xiàn)
template<class T1, class T2>
class Person{
//1. 友元函數(shù)在類內(nèi)實現(xiàn)
friend void PrintPerson(Person<T1, T2>& p){
cout << "Name:" << p.mName << " Age:" << p.mAge << endl;
}
//2.友元函數(shù)類外實現(xiàn)
//告訴編譯器這個函數(shù)模板是存在
friend void PrintPerson2<>(Person<T1, T2>& p);
//3. 類模板碰到友元函數(shù)模板
template<class U1, class U2>
friend void PrintPerson(Person<U1, U2>& p);
public:
Person(T1 name, T2 age){
this->mName = name;
this->mAge = age;
}
void showPerson(){
cout << "Name:" << this->mName << " Age:" << this->mAge << endl;
}
private:
T1 mName;
T2 mAge;
};
void test01()
{
Person <string, int>p("Jerry", 20);
PrintPerson(p);
}
// 類模板碰到友元函數(shù)
//友元函數(shù)類外實現(xiàn) 加上<>空參數(shù)列表唤锉,告訴編譯去匹配函數(shù)模板
template<class T1 , class T2>
void PrintPerson2(Person<T1, T2>& p)
{
cout << "Name2:" << p.mName << " Age2:" << p.mAge << endl;
}
void test02()
{
Person <string, int>p("Jerry", 20);
PrintPerson2(p); //不寫可以編譯通過,寫了之后别瞭,會找PrintPerson2的普通函數(shù)調(diào)用窿祥,因為寫了普通函數(shù)PrintPerson2的聲明
}
int main(){
//test01();
test02();
system("pause");
return EXIT_SUCCESS;
}
5.8 類模板的應(yīng)用
設(shè)計一個數(shù)組模板類(MyArray
),完成對不同類型元素的管理
#pragma once
template<class T>
class MyArray
{
public:
explicit MyArray(int capacity)
{
this->m_Capacity = capacity;
this->m_Size = 0;
// 如果T是對象,那么這個對象必須提供默認(rèn)的構(gòu)造函數(shù)
pAddress = new T[this->m_Capacity];
}
//拷貝構(gòu)造
MyArray(const MyArray & arr)
{
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
this->pAddress = new T[this->m_Capacity];
for (int i = 0; i < this->m_Size;i++)
{
this->pAddress[i] = arr.pAddress[i];
}
}
//重載[] 操作符 arr[0]
T& operator [](int index)
{
return this->pAddress[index];
}
//尾插法
void Push_back(const T & val)
{
if (this->m_Capacity == this->m_Size)
{
return;
}
this->pAddress[this->m_Size] = val;
this->m_Size++;
}
void Pop_back()
{
if (this->m_Size == 0)
{
return;
}
this->m_Size--;
}
int getSize()
{
return this->m_Size;
}
//析構(gòu)
~MyArray()
{
if (this->pAddress != NULL)
{
delete[] this->pAddress;
this->pAddress = NULL;
this->m_Capacity = 0;
this->m_Size = 0;
}
}
private:
T * pAddress; //指向一個堆空間蝙寨,這個空間存儲真正的數(shù)據(jù)
int m_Capacity; //容量
int m_Size; // 大小
};
測試代碼:
class Person{
public:
Person(){}
Person(string name, int age){
this->mName = name;
this->mAge = age;
}
public:
string mName;
int mAge;
};
void PrintMyArrayInt(MyArray<int>& arr){
for (int i = 0; i < arr.getSize(); i++){
cout << arr[i] << " ";
}
cout << endl;
}
void PrintMyPerson(MyArray<Person>& personArr)
{
for (int i = 0; i < personArr.getSize(); i++){
cout << "姓名:" << personArr[i].mName << " 年齡: " << personArr[i].mAge << endl;
}
}
MyArray<int> myArrayInt(10);
for (int i = 0; i < 9; i++)
{
myArrayInt.Push_back(i);
}
myArrayInt.Push_back(100);
PrintMyArrayInt(myArrayInt);
MyArray<Person> myArrayPerson(10);
Person p1("德瑪西亞", 30);
Person p2("提莫", 20);
Person p3("孫悟空",18);
Person p4("趙信", 15);
Person p5("趙云", 24);
myArrayPerson.Push_back(p1);
myArrayPerson.Push_back(p2);
myArrayPerson.Push_back(p3);
myArrayPerson.Push_back(p4);
myArrayPerson.Push_back(p5);