模板提供了一种通用的方法来开发可重用的代码,即可创建参数化的C++类型。通俗来讲,模板就是建立通用的模具,大大提高复用性。
模板的特点:
模板的分类:
C++包含一种编程思想——泛型编程,主要利用的技术就是模板。C++提供两种模板机制:函数模板和类模板。
建立一个通用函数,其函数返回值类型和形参类型可以不具体定制,用一个虚拟的类型来代表。
template<typename T>
函数声明或定义
template – 声明创建模板
typename – 表明其后面的符号是一种数据类型,可以用class代替
T – 通用的数据类型,名称可以替换,通常为大写字母
#include
using namespace std;
//函数模板:实现两个数交换的函数
template<typename T> //声明一个模板,告诉编译器T是一个通用的数据类型
void mySwap(T &a,T &b){
T temp=a;
a=b;
b=temp;
}
void test01(){
//函数模板实现有两种方法
//1.自动类型推导
int a=10;
int b=20;
cout<<"a="<<a<<" "<<"b="<<b<<endl;
mySwap(a,b);
cout<<"交换后:"<<endl;
cout<<"a="<<a<<" "<<"b="<<b<<endl;
cout<<endl;
//2.显示指定类型
double c=3.2;
double d=4.5;
cout<<"c="<<c<<" "<<"d="<<d<<endl;
mySwap<double>(c,d);
cout<<"交换后:"<<endl;
cout<<"c="<<c<<" "<<"d="<<d<<endl;
}
int main(){
test01();
system("pause");
return 0;
}
自动类型推导必须推导出一致的数据类型T,才可以使用
模板必须要确定出T的数据类型,才可以使用
举例:
#include
using namespace std;
template<typename T>
void func(){
cout<<"func() 调用"<<endl;
}
void test01(){
//func();//没有与参数列表匹配的 函数模板 "func" 实例
func<int>();//应显示指定类型
}
int main(){
test01();
system("pause");
return 0;
}
小结:
使用模板时必须要确定出通用数据类型T,并且能够推导出一致的类型。
举例:
#include
using namespace std;
int myAdd01(int a,int b){ //普通函数
return a+b;
}
template<typename T> //函数模板
T myAdd02(T a,T b){
return a+b;
}
void test01(){
int a=10;
char b='c';
cout<<"普通函数发生隐式类型转换:"<<myAdd01(a,b)<<endl; //普通函数:变量b发生隐式类型转换,将字符型隐式地转化为整型
//cout<
cout<<"函数模板显示指定类型中发生了隐式类型转换:"<<myAdd02<int>(a,b)<<endl; //函数模板中的显示指定类型:发生隐式类型转换
}
int main(){
test01();
system("pause");
return 0;
}
举例:
#include
using namespace std;
void myPrint(int a,int b){
cout<<"调用普通函数"<<endl;
}
template<typename T>
void myPrint(T a,T b){
cout<<"调用函数模板"<<endl;
}
template<typename T>
void myPrint(T a,T b,T c){
cout<<"调用重载的函数模板"<<endl;
}
void test01(){
int a=10;
int b=20;
myPrint(a,b);//1.如果函数模板和普通函数都可以实现,优先调用普通函数
myPrint<>(a,b);//2.通过空模板参数列表来强制调用函数模板
myPrint(a,b,100);//3.函数模板也可以发生重载
char c1='a';
char c2='b';
myPrint(c1,c2);//4.如果函数模板可以产生更好的匹配,优先调用函数模板
//编译器认为将c1和c2推导出T为char型,比调用普通函数进行强制类型转换简单
}
int main(){
test01();
system("pause");
return 0;
}
小结:
既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性。
模板的通用性并不是万能的。
比如:
template<typename T>
T sum(T data[],int nSize){
T sum=0;
for (int i = 0; i < nSize; i++)
{
sum+=data[i];
}
return sum;
}
上述代码具有一定的局限性,那就是它只能对数组元素求和,假如想要实现对链表、集合等元素的求和,它就不能实现了,这也是STL的思维方式想要解决的问题。为了解决这一问题,我们提出了模板特化的概念。
概念:
在实例化模板时,对特定类型的实参进行特殊处理,即实例化一个特殊的版本。
举例:
#include
#include
using namespace std;
//对比两个数据是否相等的函数
class Person{
public:
string m_name;
int m_age;
public:
Person(string name,int age){
m_name=name;
m_age=age;
}
};
template<typename T>
bool myCompare(T &a,T &b){
if (a==b)
{
return true;
}
else{
return false;
}
}
//利用模板特化实现Person类的比较,模板特化会优先调用
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",20);
Person p2("Tom",30);
bool ret=myCompare(p1,p2);
if (ret)
{
cout<<"p1=p2"<<endl;
}
else{
cout<<"p1!=p2"<<endl;
}
}
int main(){
test01();
test02();
system("pause");
return 0;
}
小结:
类模板作用:
建立一个通用类,类中成员的数据类型可以不具体指定,用一个虚拟的类型来代表,在创建对象时再去指定类中成员的数据类型。
语法:
template<class T>
类
template – 声明创建模板
class – 表明其后面的符号是一种数据类型,可以用typename代替
T – 通用的数据类型,名称可以替换,通常为大写字母
举例:
#include
#include
using namespace std;
template<class NameType,class AgeType>
class Person{
public:
NameType m_name;
AgeType m_age;
public:
Person(NameType name,AgeType age){
m_name=name;
m_age=age;
}
void showPerson(){
cout<<"name:"<<m_name<<" "<<"age:"<<m_age<<endl;
}
};
void test01(){
Person<string,int> p1("Tom",20);//在将类模板实例化时,必须显式定义具体类型,编译器无法为我们自动推导
p1.showPerson();
}
int main(){
test01();
system("pause");
return 0;
}
小结:
template
后面加类,即为类模板。区别:
举例:
#include
#include
using namespace std;
template<class NameType,class AgeType=int>//类模板在模板参数列表中可以有默认参数,默认为int类型
class Person{
public:
NameType m_Name;
AgeType m_Age;
public:
Person(NameType name,AgeType age){
m_Name=name;
m_Age=age;
}
void showPerson(){
cout<<"name:"<<m_Name<<" "<<"age:"<<m_Age<<endl;
}
};
void test01(){
//Person p1("Tom",20);//报错,因为类模板没有自动类型推导的使用方式
Person<string,int> p1("Tom",20);
p1.showPerson();
Person<string> p2("Lisa",24);//因为m_age的默认类型为int,故这里不用显示指定参数类型
p2.showPerson();
}
int main(){
test01();
system("pause");
return 0;
}
类模板中成员函数和普通类中成员函数创建时机是有区别的:
举例:
#include
using namespace std;
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;
public:
void func1(){
obj.showPerson1();
}
void func2(){
obj.showPerson2();
}
};
void test01(){
MyClass<Person1> m;
m.func1();
//m.func2();//报错,Person1中没有函数showPerson2。因为类模板中的成员函数在调用时才创建
//给类模板传入Person1类型的对象,类模板确定了T为Person1时,才创建自己的成员函数
}
int main(){
test01();
system("pause");
return 0;
}
当类模板实例化出的对象,向函数传参时,一共有三种传入方式:
举例:
#include
#include
using namespace std;
template<class T1,class T2>
class Person{
public:
T1 m_name;
T2 m_age;
Person(T1 name,T2 age){
m_name=name;
m_age=age;
}
void showPerson(){
cout<<"name:"<<m_name<<" "<<"age:"<<m_age<<endl;
}
};
//1.传入指定类型
void printPerson1(Person<string,int> &p){
p.showPerson();
}
void test01(){
Person<string,int> p1("Tom",20);
printPerson1(p1);
}
//2.参数模板化
template<typename T1,typename T2>
void printPerson2(Person<T1,T2>&p){
p.showPerson();
}
void test02(){
Person<string,int>p2("Lisa",24);
printPerson2(p2);
}
//3.整个类模板化
template<typename T>
void printPerson3(T &p){
p.showPerson();
}
void test03(){
Person<string,int>p3("David",30);
printPerson3(p3);
}
int main(){
test01();
test02();
test03();
system("pause");
return 0;
}
当类模板碰到继承时,需要注意以下几点:
举例:
#include
#include
using namespace std;
template<class T>
class Base{
T m;
};
class Son1:public Base<int>{//指定出父类中T的类型
};
void test01(){
Son1 s1;
}
template<class T1,class T2>
class Son2:public Base<T2>{//想灵活指定出父类中T的类型,子类也需变为类模板
T1 obj;
public:
void showSon2(){
cout<<"T1的数据类型为:"<<typeid(T1).name()<<endl;
cout<<"T2的数据类型为:"<<typeid(T2).name()<<endl;
}
};
void test02(){
Son2<int,char> s2;//将int传给T1,应用于Son2类;将char传给T2,应用于Base类
s2.showSon2();
}
int main(){
test01();
test02();
system("pause");
return 0;
}
小结:
如果父类是类模板,子类需要指定出父类中的数据类型。
#include
#include
using namespace std;
template<class T1,class T2>
class Person{
public:
T1 m_Name;
T2 m_Age;
Person(T1 name,T2 age);
void showPerson();
};
//构造函数的类外实现
template<class T1,class T2>
Person<T1,T2>::Person(T1 name,T2 age){
m_Name=name;
m_Age=age;
}
//成员函数的类外实现
template<class T1,class T2>
void Person<T1,T2>::showPerson(){
cout<<"name:"<<m_Name<<endl;
cout<<"age:"<<m_Age<<endl;
}
void test01(){
Person<string,int> p("Tom",20);
p.showPerson();
}
int main(){
test01();
system("pause");
return 0;
}
小结:
类模板中的成员函数类外实现时,需要加上模板参数列表。
问题提出:
类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到
解决方式:
.cpp
源文件.hpp
,.hpp
是约定的名称,并不是强制举例:
解决方式1:直接包含.cpp
源文件
Person.h:
#pragma once
#include
using namespace std;
template<class T1,class T2>
class Person{
T1 m_Name;
T2 m_Age;
public:
Person(T1 name,T2 age);
void showPerson();
};
Person.cpp:
#include
#include"Person.h"
using namespace std;
template<class T1,class T2>
Person<T1,T2>::Person(T1 name,T2 age){
m_Name=name;
m_Age=age;
}
template<class T1,class T2>
void Person<T1,T2>::showPerson(){
cout<<"name:"<<m_Name<<" "<<"age:"<<m_Age<<endl;
}
Demo12.cpp:
#include
using namespace std;
#include"Person.cpp"
void test01(){
Person<string,int> p("Tom",20);
p.showPerson();
}
int main(){
test01();
system("pause");
return 0;
}
解决方式2:将声明和实现写到同一个文件中,并更改后缀名为.hpp
:
Person.hpp:
#pragma once
#include
using namespace std;
template<class T1,class T2>
class Person{
T1 m_Name;
T2 m_Age;
public:
Person(T1 name,T2 age);
void showPerson();
};
template<class T1,class T2>
Person<T1,T2>::Person(T1 name,T2 age){
m_Name=name;
m_Age=age;
}
template<class T1,class T2>
void Person<T1,T2>::showPerson(){
cout<<"name:"<<m_Name<<" "<<"age:"<<m_Age<<endl;
}
Demo12.cpp:
#include
using namespace std;
#include"Person.hpp"
void test01(){
Person<string,int> p("Tom",20);
p.showPerson();
}
int main(){
test01();
system("pause");
return 0;
}
全局函数类内实现:
直接在类中声明友元即可。
#include
using namespace std;
template<class T1,class T2>
class Person{
//全局函数类内实现
friend void printPerson(Person<T1,T2> p){
cout<<"姓名:"<<p.m_name<<" 年龄:"<<p.m_age<<endl;
}
string m_name;
int m_age;
public:
Person(T1 name,T2 age){
m_name=name;
m_age=age;
}
};
void test01(){
Person<string,int> p("tom",20);
printPerson(p);
}
int main(){
test01();
system("pause");
return 0;
}
全局函数类外实现:
需要提前让编译器知道全局函数的存在。
#include
using namespace std;
template<class T1,class T2>
class Person;//提前告知编译器类模板的存在
template<class T1,class T2>//提前让编译器知道全局函数的存在
void printPerson(Person<T1,T2> p){
cout<<"姓名:"<<p.m_name<<" 年龄:"<<p.m_age<<endl;
}
template<class T1,class T2>
class Person{
//全局函数类外实现(记得加空模板)
friend void printPerson<>(Person<T1,T2> p);
string m_name;
int m_age;
public:
Person(T1 name,T2 age){
m_name=name;
m_age=age;
}
};
void test01(){
Person<string,int> p("tom",20);
printPerson(p);
}
int main(){
test01();
system("pause");
return 0;
}
小结:建议全局函数做类内实现,用法简单,而且编译器可以直接识别。
参考视频:https://www.bilibili.com/video/BV1et411b73Z?p=167