🎉个人名片:
🐼作者简介:一名乐于分享在学习道路上收获的大二在校生
🙈个人主页🎉:GOTXX
🐼个人WeChat:ILXOXVJE
🐼本文由GOTXX原创,首发CSDN🎉🎉🎉
🐵系列专栏:零基础学习C语言----- 数据结构的学习之路----C++的学习之路
🐓每日一句:如果没有特别幸运,那就请特别努力!🎉🎉🎉 ————————————————
🎉文章简介:
🎉本篇文章将 C++模板进阶,全特化,偏特化,非类型模板参数,模板的分离编译 相关知识进行分享!
💕如果您觉得文章不错,期待你的一键三连哦,你的鼓励是我创作动力的源泉,让我们一起加 油,一起奔跑,让我们顶峰相见!!!🎉🎉🎉
——————————————————
一.文章前言
上次将模初阶的学习知识进行了分享(链接: link),今天在这篇文章中你将学习到一些关于C++模板进阶的一些知识,包括模板特化和偏特化:介绍如何通过特化和偏特化来为特定类型提供定制化的模板实现,以及如何处理模板的重载和优先级问题。
二.模板
一. 非类型模板参数
首先,模板参数可以分为类类型形参和非类型形参
类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称;
非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用;
就比如:我们实现一个静态的栈的时候,定义一个静态数组,假设数组大小为N,N被#define为20,那么当我们想要实现一个大小为20的栈和一个大小为1000的栈时,只能被迫去改N的大小,但是如果改为1000的,栈空间为20的那个栈空间就会浪费,这个时候就可以使用非类型模板参数;
例如:
//假设实现一个静态栈
#define N 20
template<class T>
class stack
{
public:
stack()
{
cout << "stack()" << endl;
}
private:
T _a[N];
size_t _size;
};
int main()
{
stack<int> stack1; //20
stack<int> stack2; //100
return 0;
}
解决方法:使用非类型模板参数
//使用非类型模板参数,整型常量
template<class T,size_t N>
class stack
{
public:
stack()
{
cout << "stack()" << endl;
}
private:
T _a[N];
size_t _size;
};
int main()
{
stack<int,20> stack1; //20
stack<int,1000> stack2; //1000
return 0;
}
注意:
1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
2. 非类型的模板参数必须在编译期就能确认结果
例如:
二. 模板的特化
使用模板实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要进行特殊处理的方法;
例子:
//实现了一个日期类
class Date
{
public:
Date(int year,int month,int day)
:_year(year)
,_month(month)
,_day(day)
{}
bool operator>(Date& dd)
{
if (_year > dd._year)
return true;
else if(_year == dd._year && _month > dd._month)
return true;
else if(_year == dd._year && _month == dd._month && _day > dd._day)
return true;
else
return false;
}
private:
int _year;
int _month;
int _day;
};
我们想要这样做比较时就会出错;
template<class T>
bool great(T x,T y)
{
return x > y;
}
int main()
{
Date d1(1010, 5, 3);
Date d2(1010, 1, 2);
cout << great(d1, d2)<< endl;
Date* p1 = &d1;
Date* p2 = &d2; //当我们只有日期类对象的指针时,想要比较时
cout << great(p1, p2) << endl;
return 0;
}
解析:
如图:当我们调用函数1时,参数传的是一个日期类对象,T是一个日期类对象,当在>比较的时候,因为Date是自定义类型,就会去调用他的>运算符重载,实现比较。
但是当在调用第二个函数时,我们想的是对指针指向的对象进行比较,但是函数参数传的是Date的指针,great函数会实例化生成一个Date*的函数,去调用生成的函数,达不到想要的效果;
一.函数模板的特化
为了解决上面的问题:
1.我们可以使用仿函数类解决(仿函数在上篇分享过,不知道的可以去看看链接: link)
2.函数模板的实例化
template<class T>
bool great(T& x,T& y)
{
return x > y;
}
//函数特化
template<>
bool great<Date*>(Date*& x, Date*& y)
{
return *x > *y;
}
3.根据编译器的匹配机制再写一个函数即可这 ;
种实现简单明了,代码的可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化。
template<class T>
bool great(T& x,T& y)
{
return x > y;
}
//
bool great(Date* x, Date* y)
{
return *x > *y;
}
类模板的特化
类模板的特化分为:全特化和偏特化
全特化
全特化:全特化即是将模板参数列表中所有的参数都确定化。
template<class T1, class T2>
class Data
{
public:
Data() { }
private:
T1 _d1;
T2 _d2;
};
//特化后
template<>
class Data<int, char>
{
public:
Data() { }
private:
int _d1;
char _d2;
};
void test()
{
Data<int, int> d1; //调用原模版
Data<int, char> d2; //调用特化后的
}
偏特化
偏特化:任何针对模版参数进一步进行条件限制设计的特化版本
偏特化也有两种:
第一种:部分特化
将模板参数列表中部分特化
//原模版
template<class T1, class T2>
class Data
{
public:
Data() { }
private:
T1 _d1;
T2 _d2;
};
//特化后
template<class T1>
class Data<T1,char>
{
public:
Data(){ }
private:
T1 _d1;
char _d2;
};
第二种:参数更进一步的限制
偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本;
例如下面的例子,特化后只能接受指针类型;
//原模版
template<class T1, class T2>
class Data
{
public:
Data() { }
private:
T1 _d1;
T2 _d2;
};
//特化后
template<class T1,class T2>
class Data<T1*,T2*>
{
public:
Data() { }
private:
T1 _d1;
T2 _d2;
};
三. 模板的分离编译
什么是分离编译?
一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
当我们写一个函数模板时,.h放声明,.cpp放定义时,例如:
最后编译器会报链接错误;
这是因为:
C/C++程序要运行,一般会经历预处理–>编译–>汇编–>链接这四个步骤,
预处理:
主要是头文件的展开,这里的话Test.h,会在main.cpp与Test.cpp里面展开,生成main.i与Test.i,因为头文件展开了,就没有头文件了;
展开了过后Test.i里面既有函数声明也有函数定义;main.i里面有函数声明,没有定义;
编译:
检查语法,实例化模板等操作,如果没有错误后会形成汇编代码;生成main.s与Test.s;实例化模板的时候,这里不知道实例化为什么类型的函数没所以这里并没有实例化函数;
汇编:
生成二进制的机器语言;生成mian.o与Test.o
链接:会将这两个文件合并到一起,到这里会发现函数找不到函数的地址,这两个文件前面3不都是分离的,没有交互,mian.i里面知道将func函数模板中T实例化为int,但是没有函数的定义,只有声明,Test.i可以实例化生成函数,但是不知道将T实例化为什么类型,所以到最后的合并后,没有函数地址,报链接错误;
最好的解决方法,函数模板就不要声明定义分离;