C++模板特化

前言

模板特化对函数和函数都可以使用。它的作用是以某一模板函数或某个模板类为例,大部分情况下需要写的函数或内容是一致的,但是有些特别情况,所以我们需要单独拎出来。

模板参数

  • 模板参数可分为类型形参非类型形参
    类型形参: 出现在模板参数列表中,跟在class或typename关键字之后且class和typename可以混用。
  • 如下,是一个非类型模板参数的使用实例。这样传参使得更简洁的方式定义一个数组大小。
template<class T, size_t N> //N:非类型模板参数
class StaticArray
{
public:
	size_t arraysize()
	{
		return N;
	}
private:
	T _array[N]; //利用非类型模板参数指定静态数组的大小
};
  • 使用:
int main()
{
	StaticArray<int, 10> a1; //定义一个大小为10的静态数组
	cout << a1.arraysize() << endl; //10
	return 0;
}

注意:

  1. 非类型模板参数只能用整型类型。字符串、类对象、浮点数都不行。
  2. 非类型模板参数需要在编译阶段确认结果。即初始化时就给值。

模板特化

函数模板特化

  模板用在函数上使得多种类型的参数都能用同一种方式处理,但是存在某一些参数情况下需要用不同的处理方式,所以需要模板特化。
如下代码,普通数值类型能比较,但是字符串类型就比较不了,所以我们需要模板特化。

template<class T>
bool IsEqual(T x, T y)
{
	return x == y;
}
  • 函数模板特化的方式:
  1. 首先必须要有一个基础的函数模板。
  2. 关键字template<> 关键字template空的尖括号<>。
  3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型。
  4. 函数形参表必须要和模板函数的基础参数类型完全相同,否则不同的编译器可能会报一些奇怪的错误。

处理string类型的比较,需要按字典序比较,所以特化出能比较string类型的模板特化函数,函数特化代码如下:

//基础的函数模板
template<class T>
bool IsEqual(T x, T y)
{
	return x == y;
}
//对于char*类型的特化
template<>
bool IsEqual<char*>(char* x, char* y)
// 不写也可以
{
	return strcmp(x, y) == 0;
}

类模板的特化:

类模板特化分全特化和偏特化(半特化)。区别是全部特化和部分特化。

  • 全特化:
    有类模板代码如下:
template<class T1, class T2>
class Dragon
{
public:
	//构造函数
	Stu()
	{
		cout << "Stu" << endl;
	}
private:
	T1 _D1;
	T2 _D2;
};

如果想对T1、T2是double和int时做特殊处理,则可写如下代码:

//对于T1是double,T2是int时进行特化
template<>
class Stu<double, int>
{
public:
	//构造函数
	Stu()
	{
		cout << "Stu" << endl;
	}
private:
	double _D1;
	int _D2;
};
  • 调用:
Stu<int, double> s1; // 调用特化
Stu<int, int> s2; // 调用普通
  • 总结类全特化的写法:
  1. 要先写普通类模板。
  2. 写类前先加:template<>。
  3. 类名后跟一对尖括号,尖括号中指定需要特化的类型。
  • 半特化:参数更进一步限制。
    代码如下:我们对T1做限制
template<class T2>
class Dragon<double, T2>
{
public:
	//构造函数
	Stu()
	{
		cout << "Stu" << endl;
	}
private:
	double _D1;
	T2 _D2;
};
  • 总结偏特化的方式:
  1. template 偏特化类之前,先声明哪个参数不特化
  2. 类定义的<>中在适当位置加入类型
  • 参数更进一步的限制:偏特化不仅仅是特化部分参数,还可以根据模板参数比如使用T1、T2时候,我们限定传参为T1*、T2*时使用特殊化的类模板。
    代码如下:
//两个参数偏特化为指针类型
template<class T1, class T2>
class Stu<T1*, T2*>
{
public:
	//构造函数
	Stu()
	{
		cout << "Stu" << endl;
	}
private:
	T1 _D1;
	T2 _D2;
};
//两个参数偏特化为引用类型
template<class T1, class T2>
class Stu<T1&, T2&>
{
public:
	//构造函数
	Dragon()
	{
		cout << "Stu" << endl;
	}
private:
	T1 _D1;
	T2 _D2;
};

T1、T2同时为指针类型或为引用类型时,会分别调用两个特化类模板。

Stu<int, int> s1; // T1 T2
Stu<int*, int*> s2; // T1* T2*
Stu<int&, int&> s3; // T1& T2&

这里理解为,如果我们要特化关于指针或引用类型时,特化方式和普通默认类型稍有不一样,需要template,全写出来,
class Stu,类后的两个参数也全写出来,且涉及指针和引用类型算一种偏特化。

模板分离编译

  • (不是模板分离编译)分离编译指的是:一个程序(项目)由若干个源文件共同实现,每个源文件编译生成目标文件,最后将所有模板文件链接起形成单一可执行文件的过程叫分离编译,
  • 模板的分离编译:头文件只声明模板,而模板函数的实现在cpp。此外,任何程序都需要main.cpp(规范,当然放在某个别的cpp中也行)。如下图三部分:
    C++模板特化_第1张图片
      使用这三个文件生成可执行文件,链接阶段会产生报错。
    回忆程序运行经过四个步骤:
  1. 预处理: 头文件展开、去注释、宏替换、条件编译等。
  2. 编译: 检查代码的规范性、是否有语法错误等,确定代码实际要做的工作,在检查无误后,将代码翻译成汇编语言。
  3. 汇编: 把编译阶段生成的文件转成目标文件。
  4. 链接: 将生成的各个目标文件进行链接,生成可执行文件。

因为上面三个被干成了两部分,没有连接起来。
模板分离编译失败的原因:
  在函数模板定义的地方(Add.cpp)没有进行实例化,而在需要实例化函数的地方(main.cpp)没有模板函数的定义,无法进行实例化。
所以:为什么在链接阶段错误?
  答:在链接时候main中调用的两个Add实际上没有被真正定义,main发现Add没有定义是因为cpp中的实现了的函数模板没有生成对应函数,因为没有做实例化,因为函数模板不知道T实例化为什么类型。

避坑方案

  1. 在模板定义时显示实例化,如下:不建议,用到一个类型就要实例化一个类型,干不完的
    C++模板特化_第2张图片
  2. 将模板的声明和定义放一起。推荐

模板优缺点:

优点:灵活、复用简单,STL的重要工具。
缺点:代码膨胀、编译时间过长、报错信息难看、不容易定位。

题目

下列的模板声明中,其中几个是正确的( )

1)template

2)template<T1,T2>

3)template<class T1,T2>

4)template<class T1,class T2>

5)template<typename T1,T2>

6)template<typename T1,typename T2>

7)template<class T1,typename T2>

8)<typename T1,class T2>

9)template<typeaname T1, typename T2, size_t N>

10)template<typeaname T, size_t N=100, class _A=alloc<T>>

11)template<size_t N>

9、11中size_t N是非类型形参,9和11都是模板声明,关键点是跟在class和typename中即可。
共有:4\6\7\8\10\11共6个。

非模板的函数的调用优先级更高。

模板的编译:
模板不支持分离编译,分离后的模板实现需要自己写实例化,很麻烦。

你可能感兴趣的:(C/C++,c++,开发语言,算法)