【C++】模板进阶

✨✨欢迎大家来到Celia的博客✨✨

🎉🎉创作不易,请点赞关注,多多支持哦🎉🎉

所属专栏:C++

个人主页Celia's blog~

目录

一、非类型模板参数

二、模板的特化

[2.1 特化的概念](#2.1 特化的概念)

[2.2 函数模板特化](#2.2 函数模板特化)

[2.3 类模板特化](#2.3 类模板特化)

[2.3.1 全特化](#2.3.1 全特化)

[2.3.2 偏特化](#2.3.2 偏特化)

三、模板的分离编译

[3.1 分离编译](#3.1 分离编译)

[3.2 模板不能分离编译的原因](#3.2 模板不能分离编译的原因)

[3.3 解决方法](#3.3 解决方法)


一、非类型模板参数

模板参数分为两种:类型模板参数非类型模板参数

  • 类型模板参数:在参数列表中,跟在class或者typename后的类型名称。例如:
cpp 复制代码
template<class T>
class A
{
	T data;
};
  • 非类型模板参数:在参数列表中, 声明一个常量,作为参数列表的一部分。在类中可以当作常量来使用。例如:
cpp 复制代码
template<class T, size_t N>
class A
{
	T data;
	T arr[N];
};

注意:

  1. 浮点数、类对象、字符串不能作为非类型模板参数。

  2. 非类型模板参数的值必须在编译时就能够确定

  3. 模板中,类型参数、非类型参数都可以给定缺省值

    cpp 复制代码
    template<class T = int, size_t N = 10>
    class A
    {
    	T data;
    	T arr[N];
    };
    int main()
    {
    	A<> a;
    	return 0;
    }

二、模板的特化

2.1 特化的概念

在模板类中,我们可以借助类型参数实现任意类型的大小比较:

cpp 复制代码
template<class T>
class Great
{
public:
	bool operator()(T n1, T n2)
	{
		return n1 > n2;
	}
};

class Date
{
public:
	Date(int year = 2000, int month = 8, int day = 10)
		:year(year),
		month(month),
		day(day)
	{}

	bool operator>(const Date& date)
	{
		//实现了日期比较的逻辑...
		if (year > date.year)
		{
			return true;
		}
		else if (year == date.year)
		{
			if (month > date.month)
			{
				return true;
			}
			else if (month == date.month)
				return day > date.day;
		}

		return false;
	}
private:
	int year;
	int month;
	int day;
};

int main()
{
	Great<Date> great;
	Great<Date*> pgreat;

	Date d1(2005, 10, 21);
	Date d2(2005, 10, 11);

	cout << great(d2, d1) << endl;//比较成功

	Date* dd1 = &d1;
	Date* dd2 = &d2;

	cout << pgreat(dd2, dd1) << endl;//比较失败,比较的是地址


	return 0;
}

但是当比较的类型变成指针的时候,我们会发现结果不符合我们的预期。原因是当类型为指针的时候,比较的逻辑是地址的大小,而不是指针指向的对象的大小关系。 如果想解决这种问题,就需要用到特化。特化就是:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特 化与类模板特化

2.2 函数模板特化

要求:

  1. 必须有一个现有的函数模板。
  2. 关键字template后面接一对空的尖括号<>。
  3. 函数名字后跟一对尖括号<>,尖括号中指定需要特化的类型。
  4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。

例如:

cpp 复制代码
template<class T>
bool Great(T left, T right)
{
	return left > right;
}

//对Great函数模板进行特化
template<>
bool Great<Date*>(Date* left, Date* right) //left,right和模板函数的基础参数类型完全相同
{
	return *left > *right;
}

这样一来,当模板参数为Date指针类型的时候,比较逻辑就会按照特化后的方式进行比较。

2.3 类模板特化

2.3.1 全特化

全特化就是将类模板中的所有参数都特化。

cpp 复制代码
template<class T>
class Great
{
public:
	bool operator()(T n1, T n2)
	{
		return n1 > n2;
	}
};

template<class T>
class Great<T*>
{
public:
	bool operator()(T* n1, T* n2)
	{
		return *n1 > *n2;
	}
};

2.3.2 偏特化

偏特化就是任何针对模版参数进一步进行条件限制设计的特化版本。共有两种形式:

假设有以下类:

cpp 复制代码
template<class T1, class T2>
class C
{
private:
	T1 data1;
	T2 data2;
};
  1. 对于模板参数进行部分特化:

    cpp 复制代码
    template<class T1>
    class C<T1,int>//将第二个参数特化为int
    {
    public:
    	C() { cout << "C()(T1 int)" << endl; }
    private:
    	T1 data1;
    	int data2;
    };
  2. 对于模板参数进行进一步的限制,比如:

    cpp 复制代码
    template<class T1, class T2>
    class C<T1*, T2*>  //指针特化
    {
    public:
    	C() { cout << "C()(T1* T2*)" << endl; }
    private:
    	T1 data1;
    	T2 data2;
    };
    cpp 复制代码
    template<class T1, class T2>
    class C<T1&, T2&> //引用特化
    {
    public:
    	C() { cout << "C()(T1& T2&)" << endl; }
    private:
    	T1 data1;
    	T2 data2;
    };
    cpp 复制代码
    template<class T1, class T2>
    class C<T1*, T2&>  //指针 + 引用特化
    {
    public:
    	C() { cout << "C()(T1* T2&)" << endl; }
    private:
    	T1 data1;
    	T2 data2;
    };
cpp 复制代码
int main()
{
	C<char, int> c1;	//调用<T1, int>特化
	C<int*, int*> c2;	//调用<T1*, T2*>特化
	C<int&, int&> c3;	//调用<T1&, T&>特化
	C<int*, int&> c4;	//调用<T1*, T2&>特化
	return 0;
}

三、模板的分离编译

3.1 分离编译

在工程中,一般会将头文件(.h)、函数实现文件(.cpp)、测试文件(.cpp)分为三个文件来进行管理。头文件进行函数的声明,函数实现文件定义函数的具体实现过程,测试文件中有main函数,进行测试。而编译器将会对除头文件以外的文件 进行编译(之所以不编译头文件,是因为其他文件中包含了头文件),形成目标文件(.obj)。再将这些编译后的文件进行链接处理,形成可执行程序(.exe)。

3.2 模板不能分离编译的原因

每个文件在进行编译后,会附带一个符号表 ,方便链接器在其中寻找相应的函数名函数地址。如果模板分离在两个文件中进行声明和定义:

这种情况下,编译器会去之前包含的头文件中(Text.h)去寻找该函数的定义,但头文件中只有函数的声明,没有函数的定义,所以编译器无法实例化该函数。由于无法实例化该函数,所以编译器无法生成具体的函数调用指令,也就没有办法为这个函数分配地址。故该函数并没有在该文件的符号表中,编译器链接时在不同文件的符号表中查找Add函数的时候,就会找不到这个函数,生成链接错误。

3.3 解决方法

问题出现的原因是没有实例化相应的模板参数,那么只要在函数具体实现的位置实例化函数就可以解决问题:

但是这样非常不方便,每用一个类型就要实例化一次。建议直接在头文件中进行声明和定义,其他文件直接包头文件就好了。这样编译器就能够向上寻找到头文件中的函数定义,进行相应的实例化。

相关推荐
hopetomorrow4 分钟前
学习路之PHP--使用GROUP BY 发生错误 SELECT list is not in GROUP BY clause .......... 解决
开发语言·学习·php
小牛itbull14 分钟前
ReactPress vs VuePress vs WordPress
开发语言·javascript·reactpress
请叫我欧皇i22 分钟前
html本地离线引入vant和vue2(详细步骤)
开发语言·前端·javascript
闲暇部落25 分钟前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
GIS瞧葩菜34 分钟前
局部修改3dtiles子模型的位置。
开发语言·javascript·ecmascript
chnming198739 分钟前
STL关联式容器之set
开发语言·c++
熬夜学编程的小王1 小时前
【C++篇】深度解析 C++ List 容器:底层设计与实现揭秘
开发语言·数据结构·c++·stl·list
GIS 数据栈1 小时前
每日一书 《基于ArcGIS的Python编程秘笈》
开发语言·python·arcgis
Mr.131 小时前
什么是 C++ 中的初始化列表?它的作用是什么?初始化列表和在构造函数体内赋值有什么区别?
开发语言·c++
陌小呆^O^1 小时前
Cmakelist.txt之win-c-udp-server
c语言·开发语言·udp