目录
1. 什么是模板
C++另一种编程思想称为泛型编程,主要利用的技术就是模板。
模板就是建立通用的摸具,大大提高复用性。
模板不能直接使用。
C++提供两种模板机制:函数模板和类模板。
2.函数模板
2.1 函数模板的作用
函数模板的作用:建立一个通用函数,其函数返回值类型和形参类型可以不具体确定,用一个虚拟的类型来代表。
2.2 函数模板语法
//函数模板 函数声明或定义
template<typename T>
template------声明创建模板
typename------表明其后面的符号是一种数据类型,可以用class代替。
T------通用数据类型,名称可以替换,通常为大写字母。
//函数模板
template<typename T> //声明一个函数模板,T为一个通用数据类型
void mySwap(T &a, T &b)
{
T temp = a;
a = b;
b = temp;
}
void test()
{
int a = 100;
int b = 200;
//两种方式适应函数模板
//方式1:自动类型推导
mySwap(a, b);
//方法2:显示指定类型
mySwap<int>(a, b); //<int>指定T为int型
}
总结:
- 函数模板用关键字template。
- 函数模板两种使用方式:自动类型推导、显示指定类型。
- 模板的目的是为了提高复用性,将类型参数化。
2.3 函数模板注意事项
注意事项:
- 自动类型推导必须要推导出一致的数据类型T,即推出的参数类型要一致。
- 模板必须要有确定的数据类型,不能有二义性。
//函数模板注意事项
template<typename T> //typename也可以替换成class
void mySwap(T &a, T &b)
{
T temp = a;
a = b;
b = a;
}
//自动类型推导必须要推导出一致的数据类型T,即推出的参数类型要一致,不能有二义性。
void test1()
{
int a = 100;
int b = 200;
char c = 'c';
mySwap(a,b); //正确!
//mySwap(b,c); //错误!推导不出一致的T类型(一个是int,一个是char)
}
//模板必须要有确定的数据类型。
template<typename T>
void func()
{
cout<<"func的调用"<<endl;
}
void test2()
{
//func(); //错误!若去掉template<typename T>使func变为普通函数则正确
func<int>(); //正确!不管有没有使用到,都必须将其模板函数的数据类型确定
}
2.4 函数模板案例
案列描述:
- 利用函数模板封装一个排序函数,可以对不同类型的数据进行排序
- 排序规则由大到小,排序算法为选择排序
template<typename T> //交换模板
void mySwap(T &a,T &b)
{
T temp = a;
a = b;
b = temp;
}
template<typename T> //打印模板
void myPrint(T arr,int len)
{
for(int i=0;i<len;i++)
{
cout<<arr[i]<<" ";
}
cout<<endl;
}
template<typename T> //排序模板
void mySort(T arr[], int len)
{ //选择排序
for(int i=0;i<len;i++)
{
int max = i;
for(int j=i+1;j<len;j++)
{
if(arr[max]<arr[j])
{
max=j;
}
}
if(max!=i)
{
mySwap(arr[max],arr[i]); //交换
}
}
}
void test1()//测试char类型数组
{
//char类型数组
char arr[]="abcdefghi";
//计算char数组长度
int len = sizeof(arr)/sizeof(char)
mySort(arr,len); //对char类型数组进行排序
myPrint(arr,len); //打印输出排序后的char数组
}
void test2() //测试double类型数组
{
//double类型数组
double doubleArr[]={1,3,2,6,8,3,9};
//double数组长度
int num = sizeof(doubleArr)/sizeof(double);
mySort(doubleArr,num); //对double类型数组进行排序
myPrint(doubleArr,num); //打印输出排序后的double类型数组
}
2.5 普通函数与函数模板的区别及调用规则
普通函数与函数模板的区别:
- 普通函数调用时可以发生自动类型转换(隐式类型转换)。
- 使用函数模板时,如果利用自动类型推导,则不会发生隐式类型转换。
- 如果利用显示指定类型的方式,则可以发生隐式类型转换。
//普通函数调用时可以发生自动类型转换(隐式类型转换)。
int myAdd1(int a,int b) //普通函数
{
return a+b;
}
//使用函数模板
template<class T>
T myAdd2(T &a, T &b)
{
return a+b;
}
void testAdd()
{
int a = 100;
int b = 200;
char c = 'c';
cout<<myAdd1(a,b)<<endl; //普通函数调用
cout<<myAdd1(a,c)<<endl; //普通函数调用 会发生隐式类型转换 将c转换为其对应的ASCII c的ASCII为99
//函数模板--自动类型转换
//使用函数模板时,如果利用自动类型推导,则不会发生隐式类型转换。
cout<<myAdd2(a,b)<<endl; //正确
cout<<myAdd2(a,c)<<endl; //错误
//函数模板--显示指定类型
//如果利用显示指定类型的方式,则可以发生隐式类型转换。
cout<<myAdd2<int>(a,c)<<endl; //正确
}
普通函数与函数模板的调用规则:
1. 如果函数模板与普通函数都可以实现,优先调用普通函数。
2. 可以通过空模板参数列表来强制调用函数模板。
3. 函数模板也可以发生重载。
4. 如果函数模板可以产生更好的匹配(如不用发生隐式转换),优先调用函数模板。
注:空模板 例如:myAdd1<>(a,b)
2.6 模板的局限性
模板的通用性并不是万能的,有些特定数据类型,需要用具体化方式做特殊实现。
//模板局限性
//自定义数据类型
class Person
{
public:
string m_name; //姓名
int m_age; //性别
Person(string name,int age)
{
this.m_name = name;
this.m_age = age;
}
}
template<typename T> //模板
bool myCompare(T &a, T &b) //比较函数 比较两个数据是否相等
{
if(a == b)
{
return true;
}
else
{
return false;
}
}
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 test()
{
int a = 100;
int b = 200;
bool ret = myCompare(a,b);
if(ret)
{
cout<<"a == b"<<endl;
}
else
{
cout<<"a != b"<<endl;
}
}
void testPerson()
{
Person per1("Tony",19);
Person per2("Tony",19);
bool ret = myCompare(p1,p2); //编译器不会报错,但是运行时会发生错误!
if(ret)
{
cout<<"a == b"<<endl;
}
else
{
cout<<"a != b"<<endl;
}
}
3. 类模板
类模板的作用:建立一个通用类,类中的成员、数据类型可以不具体制定,用一个虚拟的类型来代表。
语法:template<typename T>
类
//类模板
template<class NameType, class AgeType>
class Person
{
public:
NameType m_name; //姓名
AgeType m_age; //年龄
Person(NameType name,AgeType age)
{
this->m_name=name;
this->m_age=age;
}
void showPerson()
{
cout<<"name: "<<this->m_name<<endl;
cout<<"age: "<<this->m_age<<endl;
}
}
void test()
{
Person<string,int> p1("yujingtian","19"); //<string,int>为模板参数列表
p1.showPerson();
}
3.1 类模板与函数模板的区别
类模板与函数模板的区别:
1. 类模板没有自动类型推导的使用方式,只有显示指定类型。
2. 类模板在模板模板参数列表中可以有默认参数。
//类模板与函数模板的区别
//类模板
template<class NameType, class AgeType=int> //可以在此默认
class Person
{
public:
NameType m_name;
AgeType m_age;
Person(NameType name, AgeType age)
{
this->m_name=name;
this->m_age=age;
}
void showPerson()
{
cout<<"name: "<<this->m_name<<endl;
cout<<"age: "<<this->m_age<<endl;
}
}
//1.类模板没有自动类型推导的使用方式:
void test1()
{
Person p("YuTony",19); //错误!无法用自动类型推导
Person<string,int> p1("YuTony",19) //正确!只能用显示指定类型
p1.showPerson();
}
//2.类模板在模板的参数列表中可以有默认:
void test2()
{
Person<string> p2("xuchangjin",29);
p2.showPerson();
}
3.2 类模板中成员函数的创建时机
类模板中成员函数和普通类中成员函数的创建时机:
1. 普通类中的成员函数在开始就创建。
2.类模板中的成员函数在调用时才创建。
3.3 类模板对象做函数参数
类模板实例化出一个对象,向函数传参的方式。
三种传入方式:
1. 指定传入类型:直接显示对象的数据类型。
2. 参数模板化:将对象中的参数变为模板进行传递。
3.整个类模板化:将这个对象类型、类模板进行传递。
template<class T1,class T2>
class Person
{
public:
T1 m_name;
t2 m_age;
Person(T1 name,T2 age)
{
this->m_name=name;
this->m_age=age;
}
void showPerson
{
cout<<"name: "<<this->m_name<<endl;
cout<<"age: "<<this->age<<endl;
}
}
//1. 指定传入类型
void printPerson1(Peison<string,int> &p)
{
p.showPerson();
}
void test1()
{
Person<string,int> p1("孙悟空",99);
printPerson1(p1);
}
//2. 参数模板化
template<class T1,class T2>
void printPerson2(Peison<T1,T2> &p) //将string和int模板化
{
p.showPerson();
cout<<"T1的类型为:"<<typeid(T1).name()<<endl;
cout<<"T2的类型为:"<<typeid(T2).name()<<endl;
}
void test2()
{
Person<string,int> p2("猪八戒",199);
printPerson2(p2);
}
//3. 整个类模板
template<class T>
void printPerson3(T &p)
{
p.showPerson();
}
void test3()
{
Person<string,int> p3("唐僧",39);
printPerson3(p3);
}
3.4 类模板与继承
注意:
1. 当子类继承的父类是模板时,子类在声明的时候要指定出父类T的类型。
2. 不指定编译器将无法给子类分配内存。
3. 如想灵活指出父类中的T的类型,则需将子类变成类模板。
//类模板与继承
template<class T>
class Base
{
public:
T m;
}
class Son : public Base //错误!必须要知道父类中T的数据类型才能继承
class Son : public Base<int> //正确
{}
//子类变为类模板
template<class T1, calss T2>
class Son2 : public Base<T2> //正确
{
T s;
}
void test()
{
Son2<int,char> S2; //将char类型传给T2--父类,int类型传给T1--子类
}
3.5 类模板成员函数类外实现
类模板中的成员函数类外实现时需要加上模板参数列表。
//类模板成员函数的类外实现
template<class T1,class T2>
class Person
{public:
T1 m_name;
T2 m_age;
Person(T1 name,T2 age);
//类内实现
//{
// this->m_name=name;
// this->m_age=age;
//}
void showPerson();
//类内实现
//{
// cout<<"name: "<<this->m_name<<endl;
// cout<<"age: "<<this->m_age<<endl;
//}
}
//构造函数的类外实现:
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<<"name: "<<this->m_name<<endl;
cout<<"age: "<<this->m_age<<endl;
}
3.6 类模板与友元
全局函数类内实现:直接在类内声明友元。
全局函数类外实现:需要提前让编译器知道全局函数的存在。
//类模板与友元
//通过全局函数来打印Person的信息
//全局函数类外实现:需要提前让编译器知道全局函数的存在。
//(提前让编译器知道Person类的存在,类外实现才不会出错!)
template<class T1,class T2>
class Person;
//(要提前知道全局函数的存在)
template<class T1,class T2>
void printPerson2(Person<T1,T2> p)
{
cout<<"类外实现: "<<endl;
cout<<"name: "<<this->m_name<<endl;
cout<<"age: "<<this->m_age<<endl;
}
template<class T1,class T2>
class Person
{
//全局函数类内实现
//全局函数类内实现:直接在类内声明友元。
friend void printPerson(Person<T1,T2> &p)
{
cout<<"类内实现:"<<endl;
cout<<"name: "<<this->m_name<<endl;
cout<<"age: "<<this->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;
}
void test1() //全局函数类内实现测试
{
Person<string,int> p1("Tom",25);
printPerson(p1);
}
void test2() //全局函数类外实现测试
{
Person<string,int> p2("Jack",21);
printPerson2(p2);
}
4 总结
尝试先对所学知识点进行总结,之后针对知识点再利用具体的案例来实现,感觉这样理解更深刻些。此外,还有一个类模板的分文件编写注意事项:建议将.h文件和.cpp文件写在一起,并命名为.hpp文件。