【C++】模板进阶

一、非类型模板参数

模板参数分:类型形参非类型形参

类型形参:出现在模板参数列表中,跟在class或者typename之类的参数类型名称

非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用

下面size_t N中的N为非类型形参,class T中的T为类型参数。

template<class T, size_t N>
class Stack
{
public:
	//void func()
	//{
	//	N++;//N是常量不能修改,但是在编译的时候不会出现错误
	//}
private:
	int _a[N];
	int _top;
};

 C++20之前,只允许整形做非类型模板参数
 C++20,可以支持double(x64)等内置类型 

//C++20
template<double X,int* ptr>
class A
{};

 注意:
1. 浮点数(float(x86,x64),double(x86))、类对象以及字符串是不允许作为非类型模板参数的。
2. 非类型的模板参数必须在编译期就能确认结果。 

 这里介绍一个非常鸡肋的头文件

#include<array>

先说这个好处,这个头文件的好处。

看下面图片,如果我们使用静态数组难免会出现越界访问,且编译器也默认没有错误。 

如果使用array则会暴力检查出错误 

 array中使用了非类型模板参数

为什么说array有些鸡肋呢? 

因为vector可以完全替代array

下面的代码注意: 

template<class T>
void PrintVector(const vector<T>& v)
{
	// 没有实例化的类模板没实例化时,不能去里面查细节东西。
	typename vector<T>::const_iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
}
int main()
{
	vector<int> v1 = { 1, 2, 3 };
	PrintVector(v1);
	return 0;
}

这里需要添加typename来明确是类型,因为vector<T>没有实例化,不敢去查const_iterator所以编译器会发生报错

 二、模板的特化

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理。这就是模板的特化。

2.1函数模板特化 

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

template<class T>
bool Less(const T& left, const T& right)
{
	return left < right;
}
int main()
{
	cout << Less(1, 2) << endl; 
	Date d1(2022, 6, 7);
	Date d2(2022, 7, 8);
	cout << Less(d1, d2) << endl; 
	Date* p1 = &d1;
	Date* p2 = &d2;
	cout << Less(p1, p2) << endl; //这里会发生错误,因为是地址的比较而不是对象的比较
	return 0;
}

所以在函数模板下面进行特化

template<>
bool Less<Date*>( const Date* & left, const Date* & right)
{
	return *left < *right;
}

我们会发现上面的代码出现了语法不匹配,为什么?

首先,我们先看模板,模板的形参是const T& left,const修饰的是letf,而特化中const修饰的是* left出现了语法不匹配。所以我们要这样修改

template<>
bool Less<Date*>(Date* const & left, Date* const & right)
{
	return *left < *right;
}

 注意:一般函数特化会直接简化形成函数,给这个类型专门匹配一个函数。

2.2类模板特化

 类模板特化在实践当中是非常有用的

2.2.1全特化

//类模板
template<class T1, class T2>
class Data
{
public:
	//实现1功能
	Data() { cout << "Data<T1, T2>" << endl; }
};
//特化
template<>
class Data<int, char>
{
public:
	//实现2功能
	Data() { cout << "Data<int, char>" << endl; }
};
int main()
{
	Data<int, int> d1;
	Data<int, char> d2;
	return 0;
}

2.2.2偏特化与半特化

//偏特化/半特化
template <class T1>
class Data<T1, int>
{
public:
	Data() { cout << "Data<T1, int>->偏特化" << endl; }

};

// 两个参数偏特化为指针类型
template <class T1, class T2>
class Data <T1*, T2*>
{
public:
	Data() { 
		cout << "Data<T1*, T2*>->偏特化" << endl;
		cout << sizeof(T1) << endl;
		cout << sizeof(T2) << endl;
		cout << typeid(T1).name() << endl;
		cout << typeid(T2).name() << endl;
	}
};
// 限定引用
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:
	Data()	{	cout << "Data<T1&, T2&>" << endl;	}
};

我们不难发现在两个偏特化转化为指针类型,对象d4传的参数为double*,int*。偏特化T1为double,T2为int

三、模板分离编译 

首先我们要知道C/C++程序要运行,要经历预处理 ---> 编译 ----> 汇编 ----> 链接

编译:对程序按照语言特性进行词法、语法、语法分析,错误检查无误后生成汇编代码。

注意:头文件不参与编译,但其他.cpp包含的文件中展开。编译器对工程中的多个源文件是分离开单独编译的。

链接:将多个obj文件合并成一个,并处理没有解决的地址问题。

下图,我们不难发现,普通函数在.h文件进行函数声明且.cpp进行定义时不会在链接时找不到地址 

若是模板进行分离

 Func.cpp中的Add不会编译,生成指令,因为模板没有被实例化。当然也就,没有Add的地址放进符号表,所以链接也就找不到

main.cpp中调用的地方,知道模板实例化成什么,但是只有声明,没有定义。

那么如何进行模板分离?

在Func.cpp中我们进行了模板声明,我们不难发现,我们在声明的代码中把T替换成int与double,使其进行了实例化,但是当main函数中添加Add('x','y');我们又要在Func.cpp进行字符类型的实例化这样十分麻烦。

所以一般把模板定义在.h文件中,如果是模板类,就一般定义在.h中类的外面 

  • 21
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值