模版
模版是一種架構,允許你用不同類型的資料套進架構中進而實作特定的功能,提高了代碼的可複用性。
在使用方面,将類型參數化,允許你使用未知的資料類型(參數類型模版化、傳回值類型模版化)
C++中另一種程式設計思想:泛型程式設計,其主要技術就是模版,模版包括函數模版和類模闆兩種。
函數模版
基本使用
參數類型/傳回值類型模版化(或者說 類型參數化),文法如下:
template<typename T>
函數聲明或定義
template<typename T>
T add(T& a, T& b) {
T tmp;
tmp = a+b;
return tmp;
}
int main() {
int a=1, b=2;
// 模版調用規則
// 1. 自動類型推導
int c = add(a,b);
// 2. 顯示指定規則
int c = add<int>(a,b); // 大概就這樣文法,可能會error
cout << "c="<< c<< endl; //c=3
}
注意事項:
- 自動類型推導,必須推導出一緻的資料類型T,才可以使用
- 模闆必須要确定出T的資料類型,才可以使用
//利用模闆提供通用的交換函數
template<class T>
void mySwap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}
// 1、自動類型推導,必須推導出一緻的資料類型T,才可以使用
void test01()
{
int a = 10;
int b = 20;
char c = 'c';
mySwap(a, b); // 正确,可以推導出一緻的T
//mySwap(a, c); // 錯誤,推導不出一緻的T類型
}
// 2、模闆必須要确定出T的資料類型,才可以使用
template<class T>
void func()
{
cout << "func 調用" << endl;
}
void test02()
{
//func(); //錯誤,模闆不能獨立使用,必須确定出T的類型
func<int>(); //利用顯示指定類型的方式,給T一個類型,才可以使用該模闆
}
int main() {
test01();
test02();
system("pause");
return 0;
}
普通函數和模版的差別
- 普通函數調用時可以發生自動類型轉換(隐式類型轉換)
- 函數模闆調用時,如果利用自動類型推導,不會發生隐式類型轉換
- 如果利用顯示指定類型的方式,可以發生隐式類型轉換
//普通函數
int myAdd01(int a, int b)
{
return a + b;
}
//函數模闆
template<class T>
T myAdd02(T a, T b)
{
return a + b;
}
//使用函數模闆時,如果用自動類型推導,不會發生自動類型轉換,即隐式類型轉換
void test01()
{
int a = 10;
int b = 20;
char c = 'c';
cout << myAdd01(a, c) << endl; //正确,将char類型的'c'隐式轉換為int類型 'c' 對應 ASCII碼 99
//myAdd02(a, c); // 報錯,使用自動類型推導時,不會發生隐式類型轉換
myAdd02<int>(a, c); //正确,如果用顯示指定類型,可以發生隐式類型轉換
}
函數模版和普通模版的調用規則
- 如果函數模闆和普通函數都可以實作,優先調用普通函數
- 可以通過空模闆參數清單來強制調用函數模闆
- 函數模闆也可以發生重載
- 如果函數模闆可以産生更好的比對,優先調用函數模闆
void print(int a, int b) {
cout << "調用普通函數..." << endl;
}
templete<typename T>
void print(T& a, T& b) {
cout << "調用模版函數..." << endl;
}
templete<typename T>
void print(T& a, T& b, T& c) {
cout << "調用重載模版函數..." << endl;
}
int main() {
int a = 1, b = 2;
// 優先調用普通函數
print(a,b);
// 強制調用模版函數
print<>(a,b);
// 如果函數模闆可以産生更好的比對,優先調用函數模闆
char c1 = 'a';
char c2 = 'b';
print(c1, c2); //調用函數模闆
return 0;
}
函數模版的局限性
函數模版不是萬能的,比如上面的
add
函數,當參數類型為數組、自定義資料類型時,函數調用會出錯(運作期間出錯)
C++為了解決這種問題,提供模闆的重載,可以為這些特定的類型提供具體化的模闆
#include<iostream>
using namespace std;
#include <string>
class Person
{
public:
Person(string name, int age)
{
this->m_Name = name;
this->m_Age = age;
}
string m_Name;
int m_Age;
};
//普通函數模闆
template<class T>
bool myCompare(T& a, T& b)
{
if (a == b)
{
return true;
}
else
{
return false;
}
}
//具體化,顯示具體化的原型和定意思以template<>開頭,并通過名稱來指出類型
//具體化優先于正常模闆
template<> bool myCompare(Person &p1, Person &p2)
{
if ( p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age)
{
return true;
}
else
{
return false;
}
}
void test01()
{
int a = 10;
int b = 20;
//内置資料類型可以直接使用通用的函數模闆
bool ret = myCompare(a, b);
if (ret)
{
cout << "a == b " << endl;
}
else
{
cout << "a != b " << endl;
}
}
void test02()
{
Person p1("Tom", 10);
Person p2("Tom", 10);
//自定義資料類型,不會調用普通的函數模闆
//可以建立具體化的Person資料類型的模闆,用于特殊處理這個類型
bool ret = myCompare(p1, p2);
if (ret)
{
cout << "p1 == p2 " << endl;
}
else
{
cout << "p1 != p2 " << endl;
}
}
int main() {
test01();
test02();
system("pause");
return 0;
}
類模闆
類模闆就是 類内成員資料類型參數化
基本使用
#include<iostream>
#include<string>
using namespace std;
template<class T1, class T2=string>
class Person {
public:
Person(T1 age, T2 name) {
this->age = age;
this->name = name;
}
void showPerson() {
cout << this->name << "'s age is" << this->age << endl;
}
public:
T1 age;
T2 name;
};
int main() {
//Person p("孫悟空", 1000); // 錯誤 類模闆使用時候,不可以用自動類型推導
Person<int,string> p(19, "zhangsan");//必須使用顯示指定類型的方式,使用類模闆
p.showPerson();
}
類模闆和函數模闆的差別:
- 類模闆沒有自動類型推導的使用方式
- 類模闆在模闆參數清單中可以有預設參數
類模闆中成員函數和普通類中成員函數建立時機是有差別的:
- 普通類中的成員函數一開始就可以建立
- 類模闆中的成員函數在調用時才建立
class Person1
{
public:
void showPerson1()
{
cout << "Person1 show" << endl;
}
};
class Person2
{
public:
void showPerson2()
{
cout << "Person2 show" << endl;
}
};
template<class T>
class MyClass
{
public:
T obj;
//類模闆中的成員函數,并不是一開始就建立的,而是在模闆調用時再生成
void fun1() { obj.showPerson1(); }
void fun2() { obj.showPerson2(); }
};
void test01()
{
MyClass<Person1> m;
m.fun1();
//m.fun2();//編譯會出錯,說明函數調用才會去建立成員函數
}
int main() {
test01();
system("pause");
return 0;
}
類模闆對象作函數參數
類模闆執行個體化出的對象,向函數傳參的方式有三種:
- 指定傳入的類型 — 直接顯示對象的資料類型
- 參數模闆化 — 将對象中的參數變為模闆進行傳遞
- 整個類模闆化 — 将這個對象類型 模闆化進行傳遞
template<class T1, class T2>
class Person {
public:
Person(T1 age, T2 name) {
this->age = age;
this->name = name;
}
void showPerson() {
cout << this->name << "'s age is" << this->age << endl;
}
public:
T1 age;
T2 name;
}
// 指定傳入類型方式
void printPerson(Person<int,string> &p) {
p.showPerson();
}
// 參數模版化
template<class T1, class T2>
void printPerson2(Person<T1,T2> &p) {
p.showPerson();
cout << "T1的類型為: " << typeid(T1).name() << endl;
cout << "T2的類型為: " << typeid(T2).name() << endl;
}
// 類模闆化
template<class T>
void printPerson3(T& p) {
p.showPerson();
cout << "T的類型為: " << typeid(T).name() << endl;
}
int main() {
Person<int,string> p(18,"zhangsan");
printPerson1(p);
printPerson2(p);
printPerson3(p);
}
類模闆與繼承
當類模闆碰到繼承時,需要注意一下幾點:
- 當子類繼承的父類是一個類模闆時,子類在聲明的時候,要指定出父類中T的類型
- 如果不指定,編譯器無法給子類配置設定記憶體
- 如果想靈活指定出父類中T的類型,子類也需變為類模闆
template<class T>
class Base
{
T m;
};
//class Son:public Base //錯誤,c++編譯需要給子類配置設定記憶體,必須知道父類中T的類型才可以向下繼承
class Son :public Base<int> //必須指定一個類型
{
};
void test01()
{
Son c;
}
//類模闆繼承類模闆 ,可以用T2指定父類中的T類型
template<class T1, class T2>
class Son2 :public Base<T2>
{
public:
Son2()
{
cout << typeid(T1).name() << endl;
cout << typeid(T2).name() << endl;
}
};
void test02()
{
Son2<int, char> child1;
}
int main() {
test01();
test02();
system("pause");
return 0;
}
類模闆成員函數類外實作
template<class T>
class Person {
public:
Person(T name) {};
public:
T name;
};
template<class T>
Person<T>::Person(T name) { // 類外實作需要加上模闆參數清單
this->name = name;
}
類模闆分檔案編寫
- 類模闆中成員函數建立時機是在調用階段,導緻分檔案編寫時連結不到
解決:
- 解決方式1:直接包含.cpp源檔案
- 解決方式2:将聲明和實作寫到同一個檔案中,并更改字尾名為.hpp,hpp是約定的名稱,并不是強制
person.hpp中代碼:
#pragma once
#include <iostream>
using namespace std;
#include <string>
template<class T1, class T2>
class Person {
public:
Person(T1 name, T2 age);
void showPerson();
public:
T1 m_Name;
T2 m_Age;
};
//構造函數 類外實作
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) {
this->m_Name = name;
this->m_Age = age;
}
//成員函數 類外實作
template<class T1, class T2>
void Person<T1, T2>::showPerson() {
cout << "姓名: " << this->m_Name << " 年齡:" << this->m_Age << endl;
}
類模闆分檔案編寫.cpp中代碼
#include<iostream>
using namespace std;
//#include "person.h"
#include "person.cpp" //解決方式1,包含cpp源檔案
//解決方式2,将聲明和實作寫到一起,檔案字尾名改為.hpp
#include "person.hpp"
void test01()
{
Person<string, int> p("Tom", 10);
p.showPerson();
}
int main() {
test01();
system("pause");
return 0;
}
總結:主流的解決方式是第二種,将類模闆成員函數寫到一起,并将字尾名改為.hpp
類模闆和友元
#include <string>
//2、全局函數配合友元 類外實作 - 先做函數模闆聲明,下方在做函數模闆定義,在做友元
template<class T1, class T2> class Person;
//如果聲明了函數模闆,可以将實作寫到後面,否則需要将實作體寫到類的前面讓編譯器提前看到
//template<class T1, class T2> void printPerson2(Person<T1, T2> & p);
template<class T1, class T2>
void printPerson2(Person<T1, T2> & p)
{
cout << "類外實作 ---- 姓名: " << p.m_Name << " 年齡:" << p.m_Age << endl;
}
template<class T1, class T2>
class Person
{
//1、全局函數配合友元 類内實作
friend void printPerson(Person<T1, T2> & p)
{
cout << "姓名: " << p.m_Name << " 年齡:" << p.m_Age << endl;
}
//全局函數配合友元 類外實作
friend void printPerson2<>(Person<T1, T2> & p);
public:
Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
private:
T1 m_Name;
T2 m_Age;
};
//1、全局函數在類内實作
void test01()
{
Person <string, int >p("Tom", 20);
printPerson(p);
}
//2、全局函數在類外實作
void test02()
{
Person <string, int >p("Jerry", 30);
printPerson2(p);
}
int main() {
//test01();
test02();
system("pause");
return 0;
}