- 💂 个人主页:风间琉璃
- 🤟 版权: 本文由【风间琉璃】原创、在CSDN首发、需要转载请联系博主
- 💬 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)和订阅专栏哦
目录
目录
一、模板的引出
在C++中,函数重载使得用于交换不同类型变量的函数可以拥有相同的函数名,并且传参使用引用传参。但是,这种代码仍然存在它的不足之处:
①重载的多个函数仅仅只是类型不同,代码的复用率比较低,只要出现新的类型需要交换,就需要新增对应的重载函数。
②代码的可维护性比较低,其中一个重载函数出现错误可能意味着所有的重载函数都出现了错误。
C++引入模板,建立通用的模具,提升了代码的复用性,实现类型通用,降低代码的冗余度。模板可以为一种算法定义不同类型的版本。
实现机制:
①使用类型参数突破类型的限制,丧失一定的类型安全
②模板需要实例化才能使用,实例化由编译器完成
模板特点:模板不可以直接使用,它只是一个框架。模板的通用性不是万能的。
模板一般分为函数模板和类模板。
二、函数模板
函数模板是带类型参数的函数,函数的返回值,形参,局部变量都可以使用类型参数,函数模板支持类型推断(形参)。
1.函数模板语法
建立一个通用的函数,其返回类型和参数类型可以不指定,可以通过一个虚拟的类型来替代。
语法:
//函数模板的声明
template<typename T/*类型参数*/,typename T2.....>
返回值类型 函数模板名(形参列表)
{
.......//可以使用T作为类型
}
template :是用来定义模板类型的关键字,也可以用class(不能用struct代替
typename :后面的字符表示为一个数据类型,可以被class替换
T :通用数据类型,可以被替换,但一般使用大写字母
template<typename T>
void Swap(T& x, T& y)
{
T tmp = x;
x = y;
y = tmp;
}
2.函数模板原理
函数模板是一个蓝图,它本身并不是函数。是编译器产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。
在编译器编译阶段,对于函数模板的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。
比如,当用int类型使用函数模板时,编译器通过对实参类型的推演,将T确定为int类型,然后产生一份处理int类型的代码,对于double类型也是如此。
3.函数模板实例化(调用)
用不同类型的参数使用模板时,称为模板的实例化。模板实例化分为隐式实例化和显示实例化。
①显示实例化
显示实例化:在函数名后的<>中指定模板参数的实际类型
template<typename T>
T Add(const T& x, const T& y)
{
return x + y;
}
int main()
{
int a = 10;
double b = 1.1;
int c = Add<int>(a, b); //指定模板参数的实际类型为int
return 0;
}
使用显示实例化时,如果传入的参数类型与模板参数类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功,则编译器将会报错。
②隐式实例化
隐式实例化:让编译器根据实参推演模板参数的实际类型。只有函数模板支持类型推断。
template<typename T>
T Add(const T& x, const T& y)
{
return x + y;
}
int main()
{
int a = 10, b = 20;
int c = Add(a, b); //编译器根据实参a和b推演出模板参数为int类型
return 0;
}
当推导方式为自动推导时,推导的类型必须是同一个类型才可以使用。 当传入两个不同数据类型的值,编译器则分辨不出T应该表示哪种数据类型,因此要避免写出这样的代码。否则只要使用①的方式不仅要传入模板参数类型还要传入形参。
函数模板的调用:
函数模板名<类型...>(实参);
如果函数模板的类型参数可以通过实参来判断,传递的类型可以省略。
4.函数模板的匹配原则
①一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
//专门用于int类型加法的非模板函数
int Add(const int& x, const int& y)
{
return x + y;
}
//通用类型加法的函数模板
template<typename T>
T Add(const T& x, const T& y)
{
return x + y;
}
int main()
{
int a = 10, b = 20;
int c = Add(a, b); //调用非模板函数,编译器不需要实例化
int d = Add<int>(a, b); //调用编译器实例化的Add函数
return 0;
}
②对于非模板函数和同名的函数模板,如果其他条件都相同,在调用时会优先调用非模板函数,而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么选择模板。
//专门用于int类型加法的非模板函数
int Add(const int& x, const int& y)
{
return x + y;
}
//通用类型加法的函数模板
template<typename T1, typename T2>
T1 Add(const T1& x, const T2& y)
{
return x + y;
}
int main()
{
int a = Add(10, 20); //与非模板函数完全匹配,不需要函数模板实例化
int b = Add(2.2, 2); //函数模板可以生成更加匹配的版本,编译器会根据实参生成更加匹配的Add函数
return 0;
}
③模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
template<typename T>
T Add(const T& x, const T& y)
{
return x + y;
}
int main()
{
int a = Add(2, 2.2); //模板函数不允许自动类型转换,不能通过编译
return 0;
}
因为模板函数不允许自动类型转换,所以不会将2自动转换为2.0,或是将2.2自动转换为2。
三、类模板
类模板是带类型参数的类,类的成员变量,成员函数可以使用类型参数,类模板不支持类型推断。
1.类模板语法
语法:
//类模板的声明
template <typename T/*类型参数*/...>
class 类模板名{
//...类中可以直接使用T类型
};
template :是用来定义模板类型的关键字,也可以用class(不能用struct代替
typename :后面的字符表示为一个数据类型,可以被class替换
T :通用数据类型,可以被替换,但一般使用大写字母
注意:这样定义出来的仅仅是一个类的模板,而不是具体的类。
//类模板的声明
template <typename T>
class Data{
public:
Data(T a):dt(a){}
T get_dt()
{
return this->dt;
}
void set_dt(T a)
{
this->dt = a;
}
void show()
{
cout<<this->dt<<endl;
}
private:
T dt;
};
int main()
{
//类模板的使用
Data<int> di(10);
di.show();
Data<double> dd(10.5);
dd.show();
Data<string> ds("hello");
ds.show();
return 0;
}
注意:类模板中函数放在类外进行定义时,需要加模板参数列表。
若在类内声明show(),在类外实现。
template <typename T>
void Data<T>::show()
{
cout<<this->dt<<endl;
}
除此之外,类模板不支持分离编译,即声明在xxx.h文件中,而定义却在xxx.cpp文件中。 所以说,Data不是真正的类,Data<int>和Data<double>才是真正的类。
2.类模板原理
类模板名字不是真正的类,而实例化的结果才是真正的类。Data不是真正的类,Data<int>和Data<double>才是真正的类。
3.类模板的实例化(调用)
类模板的调用:
类模板名<类型...> 对象;
类模板实例化与函数模板实例化相似,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。类模板不支持类型推断,需要显示指定类型。
四、模板的特化
1.函数模板特化
通常情况下,使用模板可以实现与类型无关的通用逻辑,但在特定情况下,模板实例化出的类型会导致逻辑错误。
//函数模板 ---- 冒泡排序
template <typename T>
void mysort(T *arr,int n)
{
cout<<"原始模板"<<endl;
for(int i=0;i<n-1;i++){
bool flag = true;
//两两比较
for(int j=0;j<n-1-i;j++){
if(arr[j]>arr[j+1]){
T tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
flag = false;
}
}
//没有发生交换,排序完成
if(flag)
break;
}
}
//打印
template <typename T>
void print(T *arr,int n)
{
for(int i=0;i<n;i++){
cout<<arr[i]<<" ";
}
cout<<endl;
}
int main()
{
int a[] = {5,9,6,8,4,7,1,15,2};
double d_a[] = {3.9,6.8,4.1,7.5,3.6,8.9,2.4,9.7,1.3};
string str_a[] = {"welcome","template","UK","byebye","Japan"};
const char *str_a1[] = {"welcome","template","UK","byebye","Japan"};
mysort(a,9); //正确
print(a,9);
mysort(d_a,9); //正确
print(d_a,9);
mysort(str_a,5); //逻辑错误
print(str_a,5);
mysort(str_a1,5); //逻辑错误
print(str_a1,5);
}
函数模板在大多数情况下是没有问题的,但当比较的对象是字符串或数组时就会出现问题。因为想要比较字符串和数组时,编译器会把模板参数实例化成指针,比较地址的大小,从而导致逻辑错误。 这个时候就要用到模板的特化。
//函数模板的特化 ----- const char *
template <>
void mysort<const char *>(const char **arr,int n)
{
cout<<"特化模板"<<endl;
for(int i=0;i<n-1;i++){
bool flag = true;
//两两比较
for(int j=0;j<n-1-i;j++){
if(strcmp(arr[j],arr[j+1])>0){
const char *tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
flag = false;
}
}
//没有发生交换,排序完成
if(flag)
break;
}
}
如果模板第某些特殊类型的行为需要重新定义,此时可以进行模板的特化。
template <>
返回值类型 函数模板名<特化类型...>(参数列表)
{
//......重定义特化类型的行为
}
编译器在对函数模板实例化时,如果有特化模板,优先选择特化的模板实例化。函数模板不允许特化一部分参数,必须全特化。
函数模板的特化:
①必须要有一个基础的函数模板
②关键字template后面接一对空的尖括号<>
③函数名后跟一对尖括号,尖括号中指定需要特化的类型
④函数形参表必须要和模板函数的基础参数类型完全相同,否则不同的编译器可能会报错误
2.类模板的特化
不仅函数模板可以进行特化,类模板也可以针对特殊类型进行特殊化实现,并且类模板的特化又可分为全特化和局部特化。
①全特化
全特化即是将模板参数列表中所有的参数都确定化。
语法:
template <>
class 类模板名<特化类型...>{
//...类中的内容重定义位特化类型的行为
};
函数模板的特化步骤:
①必须要有一个基础的类模板。
②关键字template后面接一对空的尖括号<>。
③类名后跟一对尖括号,尖括号中指定需要特化的类型。
template<class T1, class T2>
class dog
{
public:
//构造函数
dog()
{
cout << "dog<T1, T2>" << endl;
}
private:
T1 t1;
T2 t2;
};
//对于T1是double,T2是int时进行特化
template<>
class dog<double, int>
{
public:
//构造函数
dog()
{
cout << "dog<double, int>" << endl;
}
private:
double t1;
int t2;
};
int main()
{
Dog<int,int> d1; //基础模板
Dog<double,int> d2; //优先调用特化模板
return 0;
}
编译器在对类实例化时,如果有特化模板,优先选择特化的模板实例化。
②类模板的成员特化
对于类模板而言,既可以进行全类特化,也可以只针对部分与特化类型相关的成员函数进行特化。成员函数特化时要保持特化接口和原通用模板一致。
语法:
//类外进行成员特化
template <>
返回值类型 类模板名<特化类型...>::成员函数名(形参列表) //将T换成要特化的类型
{
//......使用特化类型重定义函数的行为
}
//类模板实现函数对象排序
template <typename T>
class Sortor{
public:
//函数对象 --- 快速排序
void operator()(T *arr,int n)
{
int L = 0;
int R = n-1;
//退出条件
if(n<=1)
return;
T tmp = arr[L];
while(L<R){
//以左边作为基准,比较右边
while(L<R&&arr[R]>=tmp)
R--;
arr[L] = arr[R];
//比较左边
while(L<R&&arr[L]<=tmp)
L++;
arr[R] = arr[L];
}
//将基准放回重合位置
arr[L] = tmp;
//递归前面的分组和后面的分组
operator()(arr,L);
operator()(arr+L+1,n-L-1);
}
void print(T *arr,int n)
{
for(int i=0;i<n;i++){
cout<<arr[i]<<" ";
}
cout<<endl;
}
};
//针对类模板的成员特化 ----- 特化重载函数对象的成员函数
template <>
void Sortor<const char *>::operator()(const char **arr,int n)
{
int L = 0;
int R = n-1;
//退出条件
if(n<=1)
return;
const char *tmp = arr[L];
while(L<R){
//以左边作为基准,比较右边
while(L<R&&strcmp(arr[R],tmp)>=0)
R--;
arr[L] = arr[R];
//比较左边
while(L<R&&strcmp(arr[L],tmp)<=0)
L++;
arr[R] = arr[L];
}
//将基准放回重合位置
arr[L] = tmp;
//递归前面的分组和后面的分组
operator()(arr,L);
operator()(arr+L+1,n-L-1);
}
int main()
{
int a[] = {5,9,6,8,4,7,1,15,2};
double d_a[] = {3.9,6.8,4.1,7.5,3.6,8.9,2.4,9.7,1.3};
string str_a[] = {"welcome","template","UK","byebye","Japan"};
const char *str_a1[] = {"welcome","template","UK","byebye","Japan"};
Sortor<int> si;
si(a,9);
si.print(a,9);
Sortor<double> sd;
sd(d_a,9);
sd.print(d_a,9);
Sortor<string> ss;
ss(str_a,5);
ss.print(str_a,5);
Sortor<const char *> sc;
sc(str_a1,5);
sc.print(str_a1,5);
return 0;
}
③局部特化
针对有多个类型参数的类模板,可以只特化其中一部分参数,编译器优先选择特化程度最高的模板。局部特化是指任何针对模板参数进一步进行条件限制设计的特化版本。
1.特化一部分参数:对模板参数列表中的部分参数进行确定化。
//基础模板
template <T1,T2,T3>
class xxx{...}//局部特化
template <T1,T2>
class xxx<T1,T2,int>{...}template <T1>
class xxx<T1,double,int>{...}
xxx<char,double,int> a; ------ 选择第三个版本进行实例化
xxx<char,long,int> b; ------- 选择第二个版本进行实例化
xxx<char,long,float> c; ------ 选择第一个版本进行实例化
对于有多个类型参数的类模板,可以对部分参数进行特化。编译器优先选择特化程度最高的版本 。
//通用类模板
template <typename T1,typename T2,typename T3>
class Trib{
public:
Trib(){cout<<"Trib<T1,T2,T3>"<<endl;}
};
//特化一部分类型参数
template <typename T1,typename T2>
class Trib<T1,T2,int>{
public:
Trib(){cout<<"Trib<T1,T2,int>"<<endl;}
};
template <typename T1>
class Trib<T1,double,int>{
public:
Trib(){cout<<"Trib<T1,double,int>"<<endl;}
};
int main()
{
Trib<char,double,int> t1;
Trib<char,long,int> t2;
Trib<char,long,double> t3;
return 0;
}
在实例化对象时指定T3为int,使用第一个特化的类模板来实例化对象。指定T2为double,T3为int,使用第二个特化的类模板来实例化对象。
2.特化类型参数之间的关系:如使用T1赋值给T2
template <T1,T2,T3>
class xxx{...}
//局部特化
template <T1,T2>
class xxx<T1,T2,T2>{...} //T3 = T2
template <T1>
class xxx<T1,T1,T1>{...} //T2 = T3 = T1
对于有多个类型参数的类模板,可以对类型参数的关系进行特化。编译器优先选择特化程度最高的版本 。
//通用类模板
template <typename T1,typename T2,typename T3>
class Trib{
public:
Trib(){cout<<"Trib<T1,T2,T3>"<<endl;}
};
//特化类型参数的关系
template <typename T1,typename T2>
class Trib<T1,T2,T2>{
public:
Trib(){cout<<"Trib<T1,T2,T2>"<<endl;}
};
template <typename T1>
class Trib<T1,T1,T1>{
public:
Trib(){cout<<"Trib<T1,T1,T1>"<<endl;}
};
int main()
{
Trib<char,double,double> t4;
Trib<double,double,double> t5;
return 0;
}
当T2和T3指定为double时,使用第一个特化的类模板来实例化对象。当全部指定为double时,使用第二个特化的类模板来实例化对象。
3.针对指针和数组类型的特化:指定当T1和T2为某种类型时,使用特殊化的类模板。
template <T1,T2,T3>
class xxx{...}
//局部特化
template <T1,T2,T3>
class xxx<T1*,T2*,T3*>{...}
template <T1,T2,T3>
class xxx<T1[],T2[],T3[]>{...}
对于有多个类型参数的类模板,可以针对指针和数组类型进行特化。编译器优先选择特化程度最高的版本 。
//通用类模板
template <typename T1,typename T2,typename T3>
class Trib{
public:
Trib(){cout<<"Trib<T1,T2,T3>"<<endl;}
};
//特化数组和指针类型
template <typename T1,typename T2,typename T3>
class Trib<T1*,T2*,T3*>{
public:
Trib(){cout<<"Trib<T1*,T2*,T3*>"<<endl;}
};
template <typename T1,typename T2,typename T3>
class Trib<T1[],T2[],T3[]>{
public:
Trib(){cout<<"Trib<T1[],T2[],T3[]>"<<endl;}
};
int main()
{
Trib<char *,long *,double *> t6;
Trib<char[],long[],double[]> t7;
return 0;
}
当实例化对象的T1和T2同时为指针类型或数组时,就会分别调用特化的两个类模板。
五、模板参数
1.模板参数的默认值
类模板的参数可以有默认值,有默认值的类型参数必须靠右。如果实例化不提供参数的类型,就使用默认值。右边类型参数的默认值可以是左边的类型参数。
template <typename T1,typename T2=short,typename T3=int/*模板参数的默认值*/>
class Trib{
public:
static void print_type()
{
cout<<typeid(T1).name()<<" "
<<typeid(T2).name()<<" "
<<typeid(T3).name()<<endl;
}
};
template <typename T1,typename T2,typename T3=T2/*模板参数的默认值*/>
class B{
public:
static void print_type()
{
cout<<typeid(T1).name()<<" "
<<typeid(T2).name()<<" "
<<typeid(T3).name()<<endl;
}
};
int main()
{
Trib<double,char,short>::print_type();
Trib<double,char>::print_type();
Trib<double>::print_type();
B<double,char,short>::print_type();
B<double,char>::print_type();
return 0;
}
2.类模板的非类型参数
类模板可以接收非类型参数,但是非类型参数只能是常量,常量表达式,常属性的变量。非类型参数也可以有默认值。函数模板也可以接收非类型参数和默认值。
template <typename T1,int T2=10/*非类型参数*/>
class A{
public:
static void print_type()
{
cout<<typeid(T1).name()<<" "
<<T2<<endl;
}
};
int main()
{
const int num = 99;
A<int,100>::print_type();
A<int,num>::print_type();
A<int>::print_type();
return 0;
}
3.模板的模板参数
模板除了可以接收类型参数和非类型参数以外,也可以接收模板作为参数,语法如下:
template <template <typename T> class xxx>
class Crab{
//...可以使用参数类模板,传入的模板保持统一
};
//类模板必须可以用T类型构造,必须带有show成员函数
template <typename T>
class King{
public:
King(T t):t(t){}
void show()const
{
cout<<this->t<<endl;
}
private:
T t;
};
//模板型模板参数
template <template <typename T> class Thing=King/*模板参数*/,typename K=int>
class Crab{
public:
//Thing模板可以使用T类型构造
Crab(int i=0,double d=0.0):t1(i),t2(d){}
//Thing show成员函数
void show()const
{
t1.show();
t2.show();
}
private:
Thing<int> t1;
Thing<double> t2;
K k;
};
int main()
{
Crab<King> cb(14,31.4);
cb.show();
return 0;
}
用类模板作为模板参数,传入的模板要保持统一。
六、模板成员
模板可以作为类模板或类的成员/友元,STL的实现使用了该语法。
①类模板成员:类模板实例化的类型参数可以来自于外部类模板,也可以直接指定。
②函数模板成员:函数模板实例化的类型可以来自于外部类模板或直接指定,并且支持类型推断。
template <typename T>
class Beta{
public:
Beta(int i,T t):t1(i),t2(t){}
void show()const
{
t1.show();
t2.show();
}
//函数模板成员
template <typename V>
V bobo(V v,T t)
{
return t1.get_value()+t2.get_value()+v/t;
}
private:
//类模板成员
template <typename K>
class Test{
public:
Test(K k):value(k){}
void show()const
{
cout<<this->value<<endl;
}
K get_value()const
{
return this->value;
}
private:
K value;
};
//使用类模板成员
Test<int> t1;
Test<T> t2;
};
int main()
{
//使用带有类模板成员的类模板
//T ---> double K--->int
Beta<double> bt(14,44.5);
bt.show();
//V---int
cout<<bt.bobo<int>(5,3.14)<<endl;
//V---double
cout<<bt.bobo(7.8,2.64)<<endl;
return 0;
}
③函数模板和类模板作为友元。如果友元使用了所在类模板的类型参数,属于约束性友元模板 ,否则属于非约束型友元模板。
//类模板
template <typename T>
class FriendTp{
public:
FriendTp(T t):t(t){};
//友元函数模板
//约束型 ----- 使用T类型参数
friend void count<T>();
friend void report</*FriendTp<T>*/>(FriendTp<T> &tt);
//非约束型 ----- 不使用T类型
template<typename T1,typename T2>
friend void show(T1 &t1,T2 &t2);
private:
T t;
};
//函数模板
template <typename K>
void count()
{
cout<<"size = "<<sizeof(FriendTp<K>)<<endl;
}
template <typename N>
void report(N &n)
{
cout<<n.t<<endl;
}
template<typename T1,typename T2>
void show(T1 &t1,T2 &t2)
{
cout<<t1.t<<","<<t2.t<<endl;
}
int main()
{
count<int>();
FriendTp<int> ft1(10);
report(ft1);
FriendTp<double> ft2(22.2);
report(ft2);
//非约束型
show(ft1,ft2);
return 0;
}
七、类模板的继承
类模板的继续一般有四种情况:类继承类模板、类模板继承类、类模板继承类模板、继承中使用模板参数。
1.类继承类模板
类继承类模板语法:
//类模板
template <typename T>
class BaseA{
public:
//......
private:
T data;
};
//类继承类模板
class Derived:public BaseA<int>{
//......
};
//类模板
template <typename T>
class BaseA{
public:
BaseA(T t):data(t){}
void show()const
{
cout<<this->data<<endl;
}
private:
T data;
};
//类继承类模板
class Derived:public BaseA<int>{
public:
Derived(int i=0):BaseA<int>(i){}
};
int main()
{
Derived d1(100);
d1.show();
return 0;
}
类在基础类模板时,必须要将类模板进行实例化成类(编译式),然后类才可以基础。 在子类构造的时候也可以向父类传参,这里的父类是经过实例化后的类"模板名<类型>"。
2.类模板继承类
类模板继承类语法:
//基础类
class BaseA{
public:
//......
private:
int data;
};
//类模板继承类
template <typename T>
class Derived:public BaseA{
public:
//.....
private:
T data2;
};
类模板继承类和普通类继承是一样的,只不过子类变成了类模板实例化后的"模板名<类型>"。在子类构造的时候也可以向父类传参。
//基础类
class BaseA{
public:
BaseA(int t):data(t){}
void show()const
{
cout<<this->data<<endl;
}
private:
int data;
};
//类模板继承类
template <typename T>
class Derived:public BaseA{
public:
Derived(int i,T t):BaseA(i),data2(t){}
void show()const
{
this->BaseA::show();//父类数据
cout<<this->data2<<endl;
}
private:
T data2;
};
int main()
{
Derived<double> d2(100,22.2);
d2.show();
return 0;
}
3.类模板继承类模板
类模板继承类模板语法:
template <typename T>
class BaseA{
public:
//......
private:
T data;
};
//类模板继承类模板
template <typename N>
class Derived:public BaseA<N>{
public:
//.....
private:
N data2;
};
子类继承以及子类向父模板类传参,父模板类都必须是经过实例化的。
//父类模板
template <typename T>
class BaseA{
public:
BaseA(T t):data(t){}
void show()const
{
cout<<this->data<<endl;
}
private:
T data;
};
//类模板继承类模板
template <typename N>
class Derived:public BaseA<N>{
public:
Derived(N n1,N n2):BaseA<N>(n1),data2(n2){}
void show()const
{
this->BaseA<N>::show();//父类数据
cout<<this->data2<<endl;
}
private:
N data2;
};
int main()
{
Derived<double> d3(11.1,33.3);
d3.show();
return 0;
}
4.继承中使用模板参数
继承中使用模板参数语法:
template <typename T>
class Derived:public T{
//......
};
其中的参数T必须是一个普通类或者模板类(实例化),因为只要类之间才有继承。
class BaseA{
public:
BaseA(){cout<<"BaseA()"<<endl;}
};
class BaseB{
public:
BaseB(){cout<<"BaseB()"<<endl;}
};
template <typename K,K n>
class BaseC{
public:
BaseC():data(n){cout<<"BaseC()"<<endl;}
private:
K data;
};
//继承模板参数
template <typename T>
class Derived:public T{
public:
Derived():T(){cout<<"Derived()"<<endl;}
};
int main()
{
Derived<BaseA> d4;
Derived<BaseC<int,10>> d4;
return 0;
}
八、模板的递归实例化
所谓的模板的递归实例化就是使用类模板实例化后作为参数继续实例化本模板
①可以使用任何类型来实例化类模板。
②由类模板实例化产生的类也可以用来实例化自身,这种做法称之为类模板的递归实例化。
③通过这种方法可以构建具有递归特性的数据结构(如多维数组)。
如下所示:
MyArray<int> arr;
MyArray<MyArray<int>> arr;//递归实例化
MyArray<MyArray<MyArray<int>>> arr;//递归实例化
#include<iomanip>
template<class T>
class Array
{
private:
T arr[10];
public:
T& operator[](int i) {//重载[]运算符
return arr[i];
}
};
int main()
{
Array<Array<int>> m;//递归实例化类模板
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
m[i][j] = i + j;//类模板的数组赋值
}
}
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
cout << setw(3) << m[i][j] << " ";//打印setw()控制数字输出格式
}
cout << endl;
}
return 0;
}
九、模板的划分
在实际的C++开发中,我们会将类的声明(模板的声明)写在头文件,类/类模板中的函数实现写在源文件,由于模板的实例化是在编译时完成的,所以必须把模板的声明和实现写在一个编译单元中,此时需要使用模板的划分来实现。
模板的分离编译:在分离编译模式下,一般创建三个文件,一个头文件用于进行函数声明,一个源文件用于对头文件中声明的函数进行定义,最后一个源文件用于调用头文件当中的函数。
按照此方法,若是对一个加法函数模板进行分离编译,其三个文件当中的内容大致如下:
但是,对应模板来说,但是使用这三个文件生成可执行文件时,却会在链接阶段产生报错。
程序要运行起来一般要经历以下四个步骤:
①预处理: 头文件展开、去注释、宏替换、条件编译等。
②编译: 检查代码的规范性、是否有语法错误等,确定代码实际要做的工作,在检查无误后,将代码翻译成汇编语言。
③汇编: 把编译阶段生成的文件转成目标文件。
④链接: 将生成的各个目标文件进行链接,生成可执行文件。
模板分离编译失败的原因:
在函数模板定义的地方(Add.cpp)没有进行实例化,而在需要实例化函数的地方(main.cpp)没有模板函数的定义,无法进行实例化。
可以在模板声明的头文件中用以下语法将实现加进来:
#include "xxx.cpp" //注意这里是加的类模板的成员函数的实现源文件
头文件
#ifndef COMPARETOR_H
#define COMPARETOR_H
//类模板的声明
template <typename T>
class Comparetor{
public:
Comparetor(T mx,T my);
T max()const;
T min()const;
private:
T x;
T y;
};
//模板划分
#include "comparetor.cpp"
#endif // COMPARETOR_H
源文件
#include <cstring>
template <typename T>
Comparetor<T>::Comparetor(T mx,T my):x(mx),y(my)
{
}
template <typename T>
T Comparetor<T>::max()const
{
return x>y?x:y;
}
template <typename T>
T Comparetor<T>::min()const
{
return x<y?x:y;
}
//成员特化
template<>
const char *Comparetor<const char *>::max()const
{
return strcmp(x,y)>0?x:y;
}
template<>
const char *Comparetor<const char *>::min()const
{
return strcmp(x,y)<0?x:y;
}
主程序
#include <iostream>
#include "comparetor.h"
using namespace std;
int main()
{
Comparetor<int> c1(100,200);
cout<<c1.max()<<endl;
Comparetor<const char *> c2("byebye","hello");
cout<<c2.max()<<endl;
return 0;
}
类类型的空指针访问成员函数:
可以使用类类型的空指针去调用成员函数,因为成员函数不属于对象,也不存储在对象中,存储在代码段,调用成员函数只需要找到成员函数的地址即可。
但是如果成员函数中访问了成员变量,就会发生段错误。
class A{
public:
void show()
{
cout<<"show A"<<endl;
}
};
int main()
{
A *pa = NULL;
pa->show();
}
因此,pa会成功调用A类的成员函数show。