【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 解决方法

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

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

相关推荐
Re.不晚7 分钟前
Java入门15——抽象类
java·开发语言·学习·算法·intellij-idea
老秦包你会9 分钟前
Qt第三课 ----------容器类控件
开发语言·qt
凤枭香12 分钟前
Python OpenCV 傅里叶变换
开发语言·图像处理·python·opencv
ULTRA??16 分钟前
C加加中的结构化绑定(解包,折叠展开)
开发语言·c++
远望清一色32 分钟前
基于MATLAB的实现垃圾分类Matlab源码
开发语言·matlab
confiself42 分钟前
大模型系列——LLAMA-O1 复刻代码解读
java·开发语言
XiaoLeisj1 小时前
【JavaEE初阶 — 多线程】Thread类的方法&线程生命周期
java·开发语言·java-ee
杜杜的man1 小时前
【go从零单排】go中的结构体struct和method
开发语言·后端·golang
幼儿园老大*1 小时前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go
半桶水专家1 小时前
go语言中package详解
开发语言·golang·xcode