一. 非类型模板参数:让模板支持 “编译期常量配置”

我们平时写的模板参数(比如template <class T>)都是 “类型形参”,但实际开发中,有时需要给模板传常量(比如固定数组大小、指定缓存默认容量)—— 这时候 “非类型模板参数” 就派上用场了。

本文所有代码示例前置头文件

代码语言:javascript

AI代码解释

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<list>
#include<queue>
using namespace std;
1.1 什么是非类型模板参数?

非类型模板参数,就是用编译期可确定的常量作为模板的参数,在模板内部可以直接当常量使用。其中比较典型的例子就是 STL 中的array(静态数组),它用非类型参数固定数组大小,避免动态内存开销: 实际案例1.固定数组大小

代码语言:javascript

AI代码解释

//#define N 10
#define N 1000
//模板进阶
template<class T>
class Stack
{
private:
	T _a[N];
	int _top;
};

int main()
{
	Stack<int> st1;//10
	Stack<int> st2;//1000,那是不是就不够,只能改上面的定义,但是改的之后上面的st1就很浪费

	return 0;
}

用非类型模板参数进行改进

代码语言:javascript

AI代码解释

//非类型模板参数--很好的解决了上面的问题
template<class T,size_t N>
class Stack
{
private:
	T _a[N];
	int _top;
};

//C++20才开始支持这些类型
//template<double N,int * ptr>
//class AA
//{};

//std::string 不是非类型模板参数 str 的有效的类型
//template<string str>
//class BB
//{ };

int main()
{
	Stack<int,10> st1;//10
	Stack<int,1000> st2;//1000

	return 0;
}

2.array array - C++ Reference

代码语言:javascript

AI代码解释

#include<array>

void func(int* a)
{
	////不能使用范围for
	//for (auto e : a)
	//{
	//	cout << e << " ";
	//}
	//cout << endl;
}

void func(array<int, 10>& a)
{
	//能使用范围for
	for (auto e : a)
	{
		cout << e << " ";
	}
	cout << endl;
}
//静态数组array就使用了这个非类型模板参数
int main()
{
	//但是这里内置类型默认是不会初始化的
	array<int, 10> a1;
	a1.fill(0);
	a1[3] = 3;
	a1[9] = 9;
	for (auto e : a1)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << sizeof(a1) << endl;

	//那么array和我这样定义有啥区别呢
	int a2[10];
	a2[3] = 3;
	a2[9] = 9;
	for (auto e : a2)
	{
		cout << e << " ";
	}
	cout << endl;

	//区别:再去做其容器类型,或者传参,array都有普通数组达不到的优势
	list<array<int, 10>> lt;
	func(a1);//不能使用范围for,因为我们的这种静态数组作为形参会退化成指针
	func(a2);//可以使用范围for

	//还有个越界的检查问题
	//数组只能检查越界写,并且是抽查
	//a2[10]=1  //可以查出来
	//a2[15] = 1;//不能查出来
	//cout << a2[10] << endl;//越界读那是一点办法都没有

	//上面那些对于array都不是问题,都可以检查出来,因为他是运算符重载调用,内存严格检查
	/*a1[15] = 1;
	cout << a1[10] << endl;*/
}

在这里插入图片描述

在这里插入图片描述

1.2 必须遵守的 2 个关键规则

非类型模板参数看似灵活,但有严格限制,踩错直接编译报错:

  • 支持的类型有限:只能是整数类型(intsize_t)、指针、引用,不支持浮点数、类对象、字符串。比如template <double D>template <string S>都会报错,但是C++20之后支持了浮点数。
  • 必须是编译期常量:参数值必须在编译时就能确定,不能传运行时变量。比如int n = 5; array<int, n>会报错,因为n是运行时才能确定的变量。

二. 模板特化:解决 “特殊类型” 的适配问题

模板的核心是 “通用”,但遇到特殊类型(比如指针、自定义类)时,通用逻辑可能失效。比如用模板比较指针时,默认会比较地址而非指针指向的内容 —— 这时候就需要 “模板特化”,为特殊类型写专属逻辑。

特化步骤

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

我们写一个通用的Less比较模板,比较普通类型没问题,但比较指针时就会出错:所以我们需要单独特化一个。

代码语言:javascript

AI代码解释

//函数模板的特化
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{
	}
	bool operator<(const Date& d)const
	{
		return (_year < d._year) ||
			(_year == d._year && _month < d._month) ||
			(_year == d._year && _month == d._month && _day < d._day);
	}
	bool operator>(const Date& d)const
	{
		return (_year > d._year) ||
			(_year == d._year && _month > d._month) ||
			(_year == d._year && _month == d._month && _day > d._day);
	}
	friend ostream& operator<<(ostream& _cout, const Date& d)
	{
		_cout << d._year << "-" << d._month << "-" << d._day;
		return _cout;
	}
private:
	int _year;
	int _month;
	int _day;
};

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

 //const 在*的左边都是修饰指针指向对象不能修改
 //const 在*的右边都是修饰指针本身

//函数模板特化版本形参结构必须和原模板保持一致,比如说原模板是const的形参,特化版本也必须是
//对上述函数模板实现一个特化版本
//特化:针对某些类型进行特殊化处理
template<>
//bool Less<Date*>(const Date*& left, const Date*& right)//这样写就错了,这里const修饰的指向的对象,而不是本身
bool Less<Date*>(Date* const& left, Date* const& right)
{
	return *left < *right;
}

int main()
{
	cout << Less(1, 2) << endl;

	Date* p1 = new Date(2025, 1, 1);
	Date* p2 = new Date(2025, 1, 3);
	cout << Less(p1, p2) << endl;//不使用特化版本的话比较就会结果错误

	return 0;
}

注意:函数模板特化不如 “直接写重载函数” 简单。比如直接定义bool Less(Date* left, Date* right),逻辑更清晰,还不用记特化语法。

代码语言:javascript

AI代码解释

//但是这样特化起来有时候涉及到指针啥的很麻烦,所以我们直接写成函数
bool Less(Date* left, Date* right)
{
	return *left < *right;
}
2.2 类模板特化:比函数特化更常用

类模板特化分为 “全特化” 和 “偏特化”,是 STL 的核心设计技巧(比如vector<bool>就是vector的特化版本),比函数特化更灵活。

2.2.1 全特化:所有模板参数都确定

全特化是把类模板的所有参数都指定为具体类型,相当于为特定类型写一个专属类:

代码语言:javascript

AI代码解释

// 通用类模板(两个类型参数)
template<class T1, class T2>
class Data
{
public:
	Data() { cout << "Data<T1,T2>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};

//类模板的特化,对内部成员没有要求,也就是说原模板定义的,特化版本可以不定义,也可以新增
//全特化
template<>
class Data<int,double>
{
public:
	Data() { cout << "Data<int,double> 全特化" << endl; }
	void func() {}
};

int main()
{
	Data<int, int> d1;
	//d1.func();//d1不行,因为没有
	cout << endl;

	Data<int, double> d2;
	d2.func();//d2新增的可以使用
	cout << endl;

	return 0;
}

在这里插入图片描述

在这里插入图片描述

2.3.2 偏特化:对模板参数做 “条件限制”

偏特化不是只特化部分参数,而是对参数做进一步的条件限制,常见两种场景: 场景 1:部分参数特化 比如特化第二个参数为double,第一个参数保留通用:

代码语言:javascript

AI代码解释

// 通用类模板(两个类型参数)
template<class T1, class T2>
class Data
{
public:
	Data() { cout << "Data<T1,T2>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};

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

int main()
{
	Data<int, int> d1;
	//d1.func();//d1不行,因为没有
	cout << endl;

	Data<char, double> d3;
	cout << endl;

	return 0;
}

在这里插入图片描述

在这里插入图片描述

场景 2:参数类型进一步限制 比如把参数限制为 “指针类型” 或 “引用类型”,这是解决 “指针比较” 的关键:

代码语言:javascript

AI代码解释

// 通用类模板(两个类型参数)
template<class T1, class T2>
class Data
{
public:
	Data() { cout << "Data<T1,T2>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};

//偏特化
//参数更进一步限制
//两个参数偏特化为指针类型
template<class T1, class T2>
class Data<T1*, T2*>
{
public:
	Data() { cout << "Data<T1*,T2*> 偏特化--参数更进一步限制" << endl; }
	void func()
	{
		cout << typeid(T1).name() << endl;//T1
		cout << typeid(T2).name() << endl;//T2
	}
};

//两个参数偏特化为引用类型
template<class T1, class T2>
class Data<T1&, T2&>
{
public:
	Data() { cout << "Data<T1&,T2&> 偏特化--参数更进一步限制" << endl; }
	void func()
	{
		cout << typeid(T1).name() << endl;//T1
		cout << typeid(T2).name() << endl;//T2
	}
};

template<class T1>
class Data<T1*, int>
{
public:
	Data() { cout << "Data<T1*,int> 偏特化--参数更进一步限制" << endl; }
	void func()
	{
		cout << typeid(T1).name() << endl;//T1
	}
};

int main()
{
	Data<int, int> d1;
	//d1.func();//d1不行,因为没有
	cout << endl;

	Data<char*, double*> d4;
	d4.func();
	cout << endl;

	Data<char&, double&> d5;
	d5.func();
	cout << endl;

	Data<char*, int> d6;
	d6.func();
	cout << endl;

	return 0;
}

在这里插入图片描述

在这里插入图片描述

2.3.3 类模板特化的实战场景

–我们就拿上篇博客中priority_queue比较Date类的那个例子来看看吧

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐