前言
hi~ 大家好呀,欢迎点进我的这篇学习笔记!这篇笔记主要是对我之前C++写的模板笔记进行的一个补充,主要补充模板的非类型模板参数、模板的特化、模板的分离编译等相关的C++知识~如果有误欢迎大佬在评论区或者私信补充呀~Thanks♪(・ω・)/。
前一篇模板博客链接在这里:【C++】了解模板--泛型编程基础_柒海啦的博客-CSDN博客
前一篇C++笔记链接在这里: 【C++】反向迭代器--迭代器适配器_柒海啦的博客-CSDN博客
目录
一、非类型的模板参数
一般,会存在给定数组空间的例子。在以前的例子中,一般会使用宏去定义数组空间大小,单这些终究是静态的,变成动态的话仍然需要申请新的空间。
1.引入
比如如下程序就是典型的利用宏去定义数组空间大小的例子:
#include <iostream>
using namespace std;
#define N 10
template<class T>
class A
{
public:
T _arr[N];
};
void test1()
{
A<int> a;
a._arr[9] = 1;
cout << a._arr[9] << endl;
}
但是,如果存在这样的场景:要求你实例化不同的对象,让每个不同的对象申请的数组的空间大小不一致。
此时,宏定义就一点用处都没有了。因为宏的话一旦定下来整个编译下来也就一个确定值,除非创建其他的类,但是这样就不能实现同类进行实例化对象了。但是,非类型的模板参数刚好就可以完成这个任务:
2.定义
非类型模板参数:
格式:
template<[class T......][,整型 N......]>
class .....
注意:
<>中可以单独包括整型 N,当然,非特化下必须包括class/typename或者非类型模板参数一种。
非类型模板参数必须是整型(char、int、long、unsigned int...)
实例化对象时必须传入常量属性的参数(字面值常量以及常变量(const))
和函数参数类似,可以给缺省值,满足和缺省一样的从右到左连续。
3.举例
根据上面的提示,我们就可以对之前提出的问题进行解答了:
#include <iostream>
using namespace std;
template<int N>
class A
{
public:
int _arr[N];
};
template<class T, int N = 4>
class B
{
public:
T _arr[N];
};
void test1()
{
A<10> a;
a._arr[9] = 1;
cout << a._arr[9] << endl;
B<int> b;
b._arr[3] = 0;
cout << b._arr[3] << endl;
}
另外,必须要传常属性的值,常变量也是可以的,如下:
非模板参数只能是整型哦,其余类型比如自定义类型以及浮点类型均不可如此,这是语法规定。
二、模板的特化
1.引入
在一些特殊情况下,比如传入一些特殊的类型就会出现错误的结果。
如下程序就是一个例子:
template<class T>
bool Less(T a, T b)
{
return a < b;
}
void test2()
{
int a = 1;
int b = 2;
cout << Less(a, b) << endl;
cout << Less(&a, &b) << endl;
}
运行结果:
可以发现两次结果不一样,原因就是第二次错误的传入类型为指针类型,所以比较的时候就变成指针进行比较了。
针对此,C++引入了模板特化,上述问题就可以通过特化进行解决:
template<>
bool Less(int* a, int* b)
{
return *a < *b;
}
运行结果:
这就是特化的效果。 这样如果我们传入指针类型的也能通过模板特化用来比较我们想要的结果。
2.定义
模板特化:
某些场景下,对传入的类型进行特殊化处理。前提:必须存在对应的模板函数或者模板类--返回值类型必须一致(两者定义方法一致)
全特化:
template<>
class ... <指定具体类型...>{} / 类型 函数名(指定类型)(之前用到模板的均可直接指定类型)
偏特化:
template<class T.....>
class ... <模板类型, 指定类型......>{}/ 类型 函数名 (模板类型, 指定类型...)
(偏特化即存在模板类型,并且此模板类型可以不是原本的单纯的T(T*, T&...)还可以混杂的有具体的类型,并且也可以对部分模板参数进行特化)
3.举例
用一下的类模板举例可以更好的理解:
// 原类模板
template<class T1, class T2>
class Test3
{
public:
Test3()
{ cout << "Test3(T1, T1)" << endl; }
};
1.全特化:即template<> class后需要指定类型(类型数量必须和原类模板一致)
template<>
class Test3<int, char>
{
public:
Test3()
{ cout << "Test3(int, char)" << endl; }
};
2.偏特化:
模板参数和指定类型混合在一起:
template<class T>
class Test3<T, int>
{
public:
Test3()
{ cout << "Test3(T, int)" << endl; }
};
单独对部分模板参数特化:
template<class T>
class Test3<T&, T&>
{
public:
Test3()
{ cout << "Test3(T&, T&)" << endl; }
};
template<class T>
class Test3<T*, T*>
{
public:
Test3()
{ cout << "Test3(T*, T*)" << endl; }
};
不同模板参数进行特化
template<class T1, class T2>
class Test3<T1&, T2*>
{
public:
Test3()
{ cout << "Test3(T1&, T2*)" << endl; }
};
综上,就可以总结出模板特化是为了适应不同的场景而生编译器会找语法最匹配的从而实例化对象出来,通俗一点说就是找最像的。
三、模板的分离编译
我们知道,为了直观的看定义的结构,一般情况下在.h文件中是声明,具体的代码实现就是在对应的.cpp文件中去实现。
C语言里面不存在模板这一说法,自然也就不会出现类似的问题。但是C++是存在模板的,泛型编程自然离不开模板,有了模板我们想要分离编译的话该如何进行呢?
1.定义
模板的分离编译:
1.分文件:声明在.h 实现在.cpp(不推荐)
.cpp处必须显示实例化模板类,和调用此类时的参数一致
template class 类名<具体类型>;
(想要定义多个就重复上述两行)
2.同文件:(推荐)
写在定义class后即可,类中声明函数。
注意:当引用类中的内嵌类型的时候,要加上typename 否则容易和类中的静态成员发生冲突,编译器会报错。
2.举例
以一下的自定义类Test4作为例子:
1.分文件进行分离编译:
// Test.h
#pragma once
template<class T>
class Test4
{
public:
Test4(const T& val)
:_val(val)
{}
T Print(); // 声明
typedef T* iterator; // 举例
iterator callcval();
private:
T _val;
};
// Test.cpp
#include "Test.h"
template<class T>
T Test4<T>::Print()
{
return _val;
}
template<class T>
typename Test4<T>::iterator Test4<T>::callcval()
{
return &_val;
}
// 显示实例化
template class Test4<int>;
template class Test4<char>;
分离的文件必须要显示实例化,否则分离的文件在编译过程就不会被实例化,自然就不会进符号表,最后链接的时候也就找不到了。
2.同文件分离:
在上述test.cpp不变的情况下,舍弃Test.cpp文件,只需在.h文件定义即可:(就不用显示实例化了)
//同文件的分离编译
template<class T>
T Test4<T>::Print()
{
return _val;
}
template<class T>
typename Test4<T>::iterator Test4<T>::callcval()
{
return &_val;
}
3.总结
模板分离编译之所以不像之前那样方便就是模板分离后,本身就是一个模板类,并没有实例化,实例化才会成为具体的类型,才能分配空间和编译。而在main文件传入类型的时候另一个分离文件接收不到,所以必须要显示实例化使其与.h文件同步进入符号表,链接才会找的到。但是这种一般不推荐,推荐的就是就写在一个文件里面,这样就不会存在问题。为了观感就分出类即可。
模板的优缺点:
优点:模板复用了代码,节省资源,更快的迭代开发,STL库因此诞生。增加了代码的灵活性,重复的动作交给编译器做。
缺点:模板会导致代码膨胀问题,导致编译时间变长。出现模板编译错误时,错误信息非常凌乱,不易定位错误。