C++ —— 模板进阶

1. 非类型模板参数

模板参数分类类型形参和非类型形参。

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

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

cpp 复制代码
template<class T, size_t N = 10>
class mystack
{
	//...
private:
	T _a[N];
	int _top;
};

int main()
{
	//mystack<int,10> s1;      //10   
	mystack<int> s1;      //10   
	mystack<double,100> s2;   //100

	return 0;
}

注意:

1.非类型模板参数只能是整型,其他的都不行:浮点数、类对象以及字符串是不允许作为非类型模板参数的。

2.非类型的模板参数必须在编译期就能确认结果。

说到这里就要提一下STL中的容器:array(数组),少数场景可能会用到

cpp 复制代码
#include<array>
int main()
{
	array<int, 10> a1;
	array<int, 100> a2;
	return 0;
}

那么跟原生的数组有什么区别???

cpp 复制代码
int main()
{
	array<int, 10> a1;
	array<int, 100> a2;

	int arr1[10];
	int arr2[100];

	return 0;
}

除了迭代器的区别没有什么区别,但是容器array还有一点点作用,对越界的检查比较严格,而C语言对于越界的情况是抽查的行为,可能查到,也可能查不到具体要看平台和编译器,一般在数组的结束位置放一些检查位(本质上是在结束的位置放一些标志位,查看越没越界就看标志位修没修改,但是只能设置一部分的标志位),并且越界读是查看不了的:

cpp 复制代码
int main()
{

	int arr1[10];

	//越界检查抽查,只能检查越界写
	//arr1[10] = 10;  //检查的到
	//arr1[11] = 10;  //检查的到
	arr1[12] = 10;    //检查不到


	return 0;
}
cpp 复制代码
//越界读------检查不到
cout << arr1[12] << endl;

原生数组本身访问本质就是解引用

但是对于STL容器:array来说检查越界很敏感,上面两种情况都会检查到(运算符重载)

cpp 复制代码
cout << a1[10] << endl;  //越界读
cpp 复制代码
a1[12] = 10;  //越界写

而对于array容器来说检查输入的值是否小于n,小于n就可以,大于等于n就报错(断言在debug下才会报错,release下就不会报错了)

array除了上面的一点点小优势外,其实并不好用,因为array是静态的数组,意味着要占用当前栈帧的空间,实际上的栈的空间是不大的,容易栈溢出。在想定义一个静态数组并且又要有更好的越界就选择array。

2. 模板的特化

2.1 概念

模板的特化就是针对模板进行特殊化处理。通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如:实现一个专门用来进行小于比较的函数模板。模板特化分为函数模板特化和类模板特化。

2.2 函数模板特化

函数模板的特化步骤:

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

cpp 复制代码
//日期类的实现
class Date
{
	friend ostream& operator<<(ostream& _cout, const Date& d);
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);
	}

private:
	int _year;
	int _month;
	int _day;

};
ostream& operator<<(ostream& _cout, const Date& d)
{
	_cout << d._year << "-" << d._month << "-" << d._day;
	return _cout;
}
cpp 复制代码
template<class T>
bool Less(T left,T right)
{
	return left < right;
}

int main()
{

	cout << Less(1, 2) << endl;

	Date d1(2025, 12, 23);
	Date d2(2025, 12, 13);
	//Date d1(2025, 12, 30);
	cout << Less(d1, d2) << endl;

	cout << Less(new Date(2025, 12, 23), new Date(2025, 12, 13)) << endl;
	return 0;
}

运行结果:(多次运行就会出现问题)

这里的比较不是按照date来比较,而是按照指针来比较,new出来的指针就是不确定的一会大一会小,就会出现各种各样的问题。这个时候就可以用过模板特化来进行处理上面的情况。

特化需要注意的是,是在原模版的基础上进行特殊化处理,不能单独存在:

cpp 复制代码
//特化:
//针对某些特殊类型进行特殊化处理
template<>  //模板参数去掉了,后面也有不去掉模板参数的
bool Less<Date*>(Date* left, Date* right)
{
	return *left < *right;
}

运行结果:

注意:一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。(建议都用下面的普通函数)

cpp 复制代码
//一个完整的函数------更好更方便
bool Less(Date* left, Date* right)
{
	return *left < *right;
}

这种方式简单明了,代码的可读性高,容易书写 ,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化。

为什么函数特化可以用普通函数来替代,是因为函数大多数是不需要实例化的,自动匹配的。

函数的特化并没有多大的用,但是类模板的特化有用。

2.3 类模板特化

类模板是要显式实例化的,普通类是不存在传参数的概念,不存在自动匹配的概念,一定是要进行实例化的,所以特殊处理要特化。

2.3.1全特化

全特化:将全部的参数都进行特化(将模板参数列表中所有的参数都确定化)

cpp 复制代码
template<class T1,class T2>
class Data
{
public:
	Data()
	{
		cout << "Data<T1,T2>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};

//全特化
template<>
class Data<int*,int*>
{
public:
	Data()
	{
		cout << "Data<int*,int*>" << endl;
	}
};


int main()
{
	Data<int, int>d1;
	Data<int*, int*>d2;
	return 0;
}

运行结果:

2.3.2 偏特化:

偏特化有以下两种变现方式:

1.部分特化:对部分参数进行特化
cpp 复制代码
//全特化
template<>
class Data<int*,int*>
{
public:
	Data()
	{
		cout << "Data<int*,int*>" << endl;
	}
};

template<>
class Data<int, int>
{
public:
	Data()
	{
		cout << "Data<int,int>" << endl;
	}
};

//偏特化
template <class T1>
class Data<T1, int>
{
public:
	Data()
	{
		cout << "Date<T1,int>" << endl;
	}
private:
	T1 _d1;
	int _d2;
};


int main()
{
	Data<int, int>d1;
	Data<int*, int*>d2;
	Data<int, char>d3;
	Data<char, int>d4;
	return 0;
}

运行结果:(在特化之间也会存在最匹配的问题)

2.参数更进一步的限制

偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。

cpp 复制代码
template <typename T1,typename T2>
class Data<T1*,T2*>
{
public:
	Data()
	{
		cout << "Date<T1*,T2*>" << endl;
	}
private:
	T1 _d1;
	int _d2;
};
cpp 复制代码
int main()
{
	Data<char*, int*>d5;

	return 0;
}

运行结果:

有全特化走全特化,无全特化走偏特化。

偏特化中的两个参数为引用类型

cpp 复制代码
template <typename T1, typename T2>
class Data<T1&, T2&>
{
public:
	Data()
	{
		cout << "Date<T1&,T2&>" << endl;
	}
};

int main()
{

	Data<char&, int&>d6;

	return 0;
}

运行结果:

偏特化中的一个参数为引用类型,一直为指针类型:

cpp 复制代码
template <typename T1, typename T2>
class Data<T1*, T2&>
{
public:
	Data()
	{
		cout << "Date<T1*,T2&>" << endl;
	}
};

运行结果:

偏特化对于指针类型的比较:

cpp 复制代码
int main()
{
	ysy::priority_queue<int*> q3;
	q3.push(new int(3));
	q3.push(new int(1));
	q3.push(new int(2));
	while (!q3.empty())
	{
		std::cout << ' ' << *q3.top();
		q3.pop();
	}
	std::cout << '\n';

	return 0;
}
cpp 复制代码
	template<class T>
	class Less<T*>
	{
	public:
		bool operator()(T* const& x, T* const& y)
		{
			return *x < *y;
		}
	};

运行结果:

3.模板的分离编译

先看看普通函数支持声明和定义的分离:

模板(类模板和函数模板)不支持函数的声明和定义的分离:

cpp 复制代码
//Func.h
template<class T>
void func2(const T& x);
cpp 复制代码
//Func.cpp
template<class T>
void func2(const T& x)
{
	cout << x << endl;

}
cpp 复制代码
//Test.cpp
#include"Func.h"

int main()
{
	func2(200);

	return 0;
}

运行结果:

为什么模板不行呢???

模板距离实际被编译还差实例化。

在Func.i 里面不知道T被实例化成什么,Test.i 里面知道要被实例化成什么,但是它们不会交互,为了编译速度。知道要被实例化成什么的地方只有声明,有定义的地方不知道要被实例化成什么,所以就没有被编译成指令,没有放进符号表中,解决方法:

1.显式实例化(可以做到声明和定义分离,不是最优的解决方案,不推荐使用)

cpp 复制代码
//Func.cpp

#include"Func.h"



template<class T>
void func2(const T& x)
{
	cout << x << endl;

}

//显式实例化:可以做到声明和定义分离
template                                  //表示为一个模板
void func2(const int& x);

运行结果:

上述的显式实例化是不够用的,因为只实例化了int,如果是double呢?就不行了

  1. 直接在.h文件中定义
cpp 复制代码
//Func.h
template<class T>
void func2(const T& x)
{
    cout << x << endl;

}

函数只有声明没有定义才会在链接的时候去找地址,但是当前.h文件中有定义就不会在链接的时候去找。

4. 模板总结

【优点】

  1. 模板复用了代码,节省资源,更快的迭代器开发,C++的标准模板库(STL)因此而产生
  2. 增强了代码的灵活性

【缺陷型】

  1. 模板会导致代码膨胀问题,模板多了实例化的过程,也会导致编译时间变长
  2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误
相关推荐
萧鼎1 小时前
Python PyTesseract OCR :从基础到项目实战
开发语言·python·ocr
go_bai2 小时前
Linux-线程2
linux·c++·经验分享·笔记·学习方法
二川bro2 小时前
第57节:Three.js企业级应用架构
开发语言·javascript·架构
sali-tec2 小时前
C# 基于halcon的视觉工作流-章62 点云采样
开发语言·图像处理·人工智能·算法·计算机视觉
j_xxx404_3 小时前
C++:继承(概念及定义|作用域|基类与派生类转换|默认成员函数|与友元、静态成员关系|多继承|组合)
数据结构·c++
这人很懒没留下什么3 小时前
SpringBoot2.7.4整合Oauth2
开发语言·lua
ZHOUZAIHUI3 小时前
WSL(Ubuntu24.04) 安装PostgreSQL
开发语言·后端·scala
欧阳x天3 小时前
C++入门(二)
开发语言·c++
CappuccinoRose3 小时前
MATLAB学习文档(二十八)
开发语言·学习·算法·matlab