前言
类封装了属性和方法,而这些属性和方法都有他们自己的数据类型,在有些特殊场景,我们希望我们的类里的这些属性和方法的类型能够在使用的时候再指定,因为我们并无法事先判断这个类的使用者会传什么类型给到这个类,例如,一个集合类,用户可以往集合中存任意的数据类型,我们不可能为用户可能传的数据类型都做一个实现。那样太麻烦了,所以就出现了类模板。
1.类模板
语法:
template<class T>
类
解释:
template: 声明创建的模版
typename: 表面其后的类型是一种数据类型,让编译器不要报错
T:通用数据类型,名称可以自定义,通常为大写字母
注意:class 可以使用typename 代替,即声明模版时也可写成template<typename T>的方式,效果是一样的,只是有的人会在函数模版的情况下使用typename,在类模版下使用class.
示例:
#include<iostream>
#include<string>
using namespace std;
template<class NameType, class AgeType>
class Person
{
public:
NameType mName;
AgeType mAge;
Person(NameType name,AgeType age){
this->mName = name;
this->mAge = age;
}
void showPerson(){
cout << "name=>" << this->mName << " ,age=>"<<this->mAge<<endl;
}
};
void test(){
Person<string,int> p1("walt",28);
p1.showPerson();
}
int main()
{
test();
return 0;
}
在上面的示例中,我们定义了一个Person类,类中有name和age两个属性,我们使用类模板的方式定义后可以使用类模版中定义的虚拟型来表示name和age的数据类型,调用的时候就可以传入任何想用的数据类型了,如果你想让名字以Int型表示,你就传int值,如:9527也可以表示一个人名字
注:此处的Person类只是作为演示使用,并不具备通用性
2.类模板和函数模版的区别
类模板和函数模板的区别有两点:
(1)类模板没有自动类型推导,函数模板有自动类型推导
(2)类模板在模板参数列表中可以有默认参数,而函数模板不行
两者的区别如下列所示:
#include<iostream>
using namespace std;
// 类模版和函数模版的区别
template<class NameType,class AgeType = int>
class Person
{
public:
Person(NameType name,AgeType age){
this->mName = name;
this->mAge = age;
}
void showPerson()
{
cout << "name=>"<<mName << " ,age=>" << mAge<<endl;
}
NameType mName;
AgeType mAge;
};
// 1,类模版没有自动类型推导的使用方式
void test()
{
// Person p("walt",28);//错误,无法自动类型推导
Person<string,int> p("walt",28);// 正确,只能使用显示指定类型
p.showPerson();
}
// 2,类模版在模版参数列表中可以有默认参数
void test1()
{
Person<string> p("zhong",29);
p.showPerson();
}
int main()
{
//test();
test1();
return 0;
}
3.类模板的使用
3.1 类模板对象作为函数参数
类模板对象作为函数参数有三种情况分别是指定传入类型,参数模板化和整个类模板化,其中指定传入类型最常用,具体的请看代码示例:
#include<iostream>
#include<string>
using namespace std;
// 类模版对象做函数参数
template<class T1,class T2>
class Person{
public:
Person(T1 name,T2 age){
this->mName = name;
this->mAge = age;
}
void showPerson()
{
cout<< "name=>"<< mName << " ,age=>" << mAge<<endl;
}
T1 mName;
T2 mAge;
};
//1.指定传入类型: 最常用
void printPerson(Person<string,int> &p)
{
p.showPerson();
}
// 2.参数模版化
template<class T1,class T2>
void printPerson1(Person<T1,T2> &p)
{
p.showPerson();
cout<<"type of T1 is: " << typeid(T1).name() << endl;
cout<<"type of T2 is: " << typeid(T2).name() << endl;
}
// 3.整个类模版化
template<class T>
void printPerson2(T &p){
p.showPerson();
cout<< "the type of T is:" << typeid(T).name() << endl;
}
void test(){
Person<string,int> p("walt",28);
printPerson(p);
}
void test1(){
Person<string,int> p("zhong",33);
printPerson1(p);
}
void test2()
{
Person<string,int> p("xianjian",29);
printPerson2(p);
}
int main()
{
//test();
//test1();
test2();
return 0;
}
3.2 类模板与继承
当父类是类模板的时候,必须知道父类中定义的类型,才能继承给子类,并且如果想要灵活指定父类中的T类型,那么子类也需要变成类模板,请看示例代码:
#include<iostream>
using namespace std;
// 类模版与继承
template<class T>
class Base{
T m;
};
//class Son : public Base{//错误,必须知道父类中T的类型,才能继承给子类
class Son:public Base<int>{//正确
};
// 如果想要灵活指定父类中的T类型,子类也需要变类模版
template<class T1,class T2>
class Son2:public Base<T2>{
public:
Son2()
{
cout<< "the type of T1 is: " << typeid(T1).name() <<endl;
cout<< "the type of T2 is: " << typeid(T2).name() <<endl;
}
T1 obj;
};
void test()
{
Son s1;
}
void test1()
{
Son2<int,char> s1;// int 给了T1,char 给了T2,给了父类
}
int main()
{
test1();
return 0;
}
3.3 类模板与友元
在C++ 中,外部想要访问类中的私有属性时,可以使用友元的方式,及将访问者添加friend关键字,标记为“朋友”。当访问的全局函数在类的内部实现时,直接访问就行,但是如果访问私有属性的友元函数在类外实现时,需要做到首先加空模板参数列表
friend void printPerson1<>(Person<T1,T2> p);
然后让编译器提前知道这个类模版的存在,即在文件的最前面声明下类模板
template<class T1, class T2>
class Person;
代码示例:
#include<iostream>
using namespace std;
// 提前让编译器知道Person模版类的存在
template<class T1, class T2>
class Person;
// 通过全局函数 打印Person信息
template<class T1, class T2>
void printPerson1(Person<T1,T2> p)
{
cout<<"out of class implement: name=>"<<p.mName << " ,age=>"<< p.mAge << endl;
}
template<class T1, class T2>
class Person
{
// 全局函数类内实现
friend void printPerson(Person<T1,T2> p)
{
cout<<"name=>"<<p.mName << " ,age=>"<< p.mAge << endl;
}
// 全局函数类外实现
// 加空模版参数列表
// 全局函数如果是类外实现,需要让编译器提前知道这个函数的存在
friend void printPerson1<>(Person<T1,T2> p);
public:
Person(T1 name,T2 age){
this->mName = name;
this->mAge = age;
}
private:
T1 mName;
T2 mAge;
};
// 全局函数类内实现
void test()
{
Person<string,int> p("Tom",13);
printPerson(p);
}
// 全局函数的类外实现
void test1()
{
Person<string,int> p("jerry",16);
printPerson1(p);
}
int main()
{
//test();
test1();
return 0;
}
3.4 类模板分文件编写
在某些场景下我们需要将类模板的定义和实现分文件编写。类模板的分文件编写步骤为:
- 先将类模板的定义和实现放到一起,命名为xxx.hpp
例如本例中的Person.hpp
#include<iostream>
#include<string>
using namespace std;
template<class T1,class T2>
class Person
{
public:
Person(T1 name,T2 age);
void showPerson();
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=>"<< mName << " ,age=>"<<mAge<<endl;
}
- 需要使用时,使用
include
关键字引入这个xxx.hpp这个文件就行
示例:
#include<iostream>
#include<string>
#include "header/Person.hpp"
using namespace std;
// 类模版分文件编写
void test()
{
Person<string,int> p("jerry",18);
p.showPerson();
}
int main()
{
test();
return 0;
}
总结
本文介绍了类模板的语法,类模板和函数模板的区别,以及类模板的使用,其中类模板的使用需要注意的是当类模板和继承,友元,函数的类内实现和类外实现,分文件编写一起使用的时候的注意点。本篇文章重点在于看懂,能使用类模板就行,不要求会写。函数模板和类模板知识的学习是为了更好的使用系统提供的模板进行开发。