文章目录
- 前言
- 一、非类型模板参数
-
- 1.引出
- 2.概念
- 3.注意
-
- (1)非类型模板参数必须是整型
- (2)非类型模板参数是常量
- (3)缺省模板参数
- 二、模板的特化
-
- 1.概念
- 2.函数模板的特化
- 3.类模板的特化
- 4.偏特化
- 三、模板分离编译
-
- 1.什么是分离编译
- 2.模板的分离编译
- 四、模板的优缺点
-
- 1.优点
- 2.缺点
- 感谢阅读,如有错误请批评指正
前言 这篇文章是【C++】模板初阶 的进阶部分。
一、非类型模板参数 1.引出 现在需要一个定长100的数组类,很容易设计出如下的类。
#define N 100template
class Array
{
public:
//...
private:
T _a[N];
};
如上设计是没有问题的,但如果还需要定长10、1000的数组呢?定长为10的数组虽然浪费内存,但勉强还可以用上面的类;定长为1000的数组就需要再重新一个逻辑完全同上、只是N的值不同的类。这显然代码很冗余。
对于上面的问题,用非类型模板参数可很好的解决。
2.概念 模板参数分为:类型模板参数、非类型模板参数。
- 类型模板参数:出现在模板参数列表中,跟在class或typename之后的参数类型名称。
- 非类型模板参数:用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。非类型模板参数只能是整型。
//N是非类型模板参数
template
class Array
{
public:
//...
private:
T _a[N];
};
int main()
{
Array a10;
//定长10的数组
Array a100;
//定长100的数组
Array a1000;
//定长1000的数组 return 0;
}
3.注意 (1)非类型模板参数必须是整型
下面以double为例,传入其他类型也会报错。
文章图片
(2)非类型模板参数是常量
如下代码通过Modify函数修改N的值,但由于N是常量,所以会报错。
template
class Array
{
public:
void Modify()
{
N = 50;
}
private:
T _a[N];
};
int main()
{
Array a10;
Array a100;
Array a1000;
a10.Modify();
return 0;
}
编译结果如下:
文章图片
(3)缺省模板参数
实际上,不管是类型模板参数还是非类型模板参数,都是可以给缺省值的。
代码如下(示例):
//默认是类型为int,大小为10的数组
template
class Array
{
public:
//...
private:
T _a[N];
};
int main()
{
Array<> a1;
//用缺省值
Array a2;
//类型为double,大小为10的数组
Array a3;
//类型为int,大小为1000的数组 return 0;
}
二、模板的特化 1.概念 一些情况下,函数模板或类模板并不能正确处理所需的逻辑,这时就需要对一些情况进行特殊的处理,这就是模板的特化。
2.函数模板的特化 以下面的比较相等函数为例。
代码如下(示例):
template
bool IsEqual(const T& left, const T& right)
{
return left == right;
}int main()
{
cout << IsEqual(1, 1) << endl;
cout << IsEqual(1.5, 1.5) << endl;
const char p1[] = "hello";
const char p2[] = "hello";
cout << IsEqual(p1, p2) << endl;
return 0;
}
如果传入两个数字,逻辑没有问题,但如果传入两个字符串,如果不加以修改就会默认是比较两个指针变量p1和p2的地址,这时字符串相同,但由于指针地址不同,就会输出不符合所需逻辑的判断结果。
文章图片
这时用将函数模板特化即可解决这一问题。
代码如下(示例):
template
bool IsEqual(const T& left, const T& right)
{
return left == right;
}//指明参数的类型,当匹配时会调用这个函数而不是模板,可以看做是一种重载
bool IsEqual(const char* left, const char* right)
{
return strcmp(left, right) == 0;
}int main()
{
cout << IsEqual(1, 1) << endl;
cout << IsEqual(1.5, 1.5) << endl;
const char p1[] = "hello";
const char p2[] = "hello";
cout << IsEqual(p1, p2) << endl;
return 0;
}
运行结果如下,两个相同的字符串判等时结果为真,符合需要的逻辑。
文章图片
3.类模板的特化 代码如下(示例):
//除了int,int都用普通模板实例化
template
class A
{
public:
A()
{
cout << "A" << endl;
}
private:
T1 _a1;
T2 _a2;
};
//将int,int特化
template<>
class A
{
public:
A()
{
cout << "A" << endl;
}
private:
int _a1;
int _a2;
};
int main()
{
A a1;
A a2;
return 0;
}
上述代码运行结果如下:
文章图片
4.偏特化 前面讲到的都全特化,实际上特化的模板参数也可以有部分缺省。下面以类模板为例进行说明,函数模板同理。
//最普通的函数模板
template
class A
{
public:
A()
{
cout << "A" << endl;
}
private:
T1 _a1;
T2 _a2;
};
//全特化的函数模板
template<>
class A
{
public:
A()
{
cout << "A" << endl;
}
private:
int _a1;
int _a2;
};
//偏特化的函数模板
template
class A
{
public:
A()
{
cout << "A" << endl;
}
private:
int _a1;
int _a2;
};
int main()
{
A a1;
A a2;
A a1;
return 0;
}
注意,编译器匹配时总是找最接近的,完全相同是最好的。a1的两个参数符合普通的函数模板,但更符合偏特化的函数模板,所以打印的是A
文章图片
三、模板分离编译 1.什么是分离编译 一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来,再形成单一的可执行文件的过程称为分离编译模式。
2.模板的分离编译 图中Swap函数模板的声明在a.h中,实现在a.cpp中,调用在test.cpp中。
文章图片
在a.cpp中,只有函数模板的定义,在编译时无法确定T是什么类型,无法实例化;在test.cpp中,只有函数模板的调用,虽然知道所需的参数类型,但是没有函数的实现,同样无法实例化。
这导致在链接前两个cpp文件中的函数模板都无法实例化,于是在链接时没有生成函数的具体代码,链接报错。
【C++|【C++】模板进阶】这种问题推荐的解决办法是:将模板的声明和定义放到同一个.h或.hpp文件中。
四、模板的优缺点 1.优点
- 模板复用了代码,可以节省资源,更快的迭代开发。最重要的是,C++的标准模板库(STL)因此而产生
- 增强了代码的灵活性
- 模板会导致代码膨胀问题(每有一个新的实例化对象,模板的代码就会整个多出来一份),也会导致编译时间变长。
- 出现模板编译错误时,错误信息非常凌乱,不易定位错误。
- 模板不支持分离编译。
推荐阅读
- C++编程学习指导|C++初阶(模板进阶)
- c++初阶 函数模板
- 以分号结尾的诗(C++|C++之模板初阶:甩锅编译器)
- C++学习|C++模板进阶
- C++|C++模板初阶——函数模板与类模板
- C++|Cpp基础语法(二)
- 算法|C语言版扫雷(递归实现自动展开)
- c语言|C语言指针进阶详解
- C++|希尔排序——C++实现