当前位置: 首页 > article >正文

模板的进阶

非类型模板参数

模板参数分类类型形参与非类型形参
类型形参即:出现在模板参数列表中,跟在 class 或者 typename 之类的参数类型名称
非类型形参,就是用一个常量作为类 ( 函数 ) 模板的一个参数,在类 ( 函数 ) 模板中可将该参数当成常量来使用

// 非类型模板参数,只能是整形常量
//template<class T, size_t N, double X>//err
template<class T, size_t N>
class Stack
{
public:
	void func()
	{
		//++N;常量不能++
	}
private:
	T _a[N];
	int _top;
};

int main()
{
	//Stack<int, 10, 1.1> st1;  // 10 err
	//Stack<int, 20, 2.2> st2;  // 20 err

	Stack<int, 10> st1;  // 10
	Stack<int, 20> st2;  // 20

	st1.func();

	return 0;
}

静态栈,非类型模板参数只能传整形常量,其他类型都不行

比如数组要开10个或者20个,有数量的时候可以这样传

注意:
1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的
2. 非类型的模板参数必须在编译期就能确认结果
例子
#include<array>
#include<vector>
using namespace std;

int main()
{
	int a2[10];

	array<int, 10> a1;
	cout << sizeof(a1) << endl;
	string a3[10];

	//a1[10];会检查越界
	a2[10];//检查不到越界

	vector<int> a4(10, 0);
	a4[10];//也可以检查越界

	return 0;
}

array是数组,第二个参数就是非类型模板参数,传10就是开辟10个空间不初始化

优势不明显,检查越界可以用vector定义,并且也能初始化,了解一下

模板的特化

函数模板特化

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);
private:
	int _year;
	int _month;
	int _day;
};

ostream& operator<<(ostream& _cout, const Date& d)
{
	_cout << d._year << "-" << d._month << "-" << d._day;
	return _cout;
}

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

// 函数模板的特化
template<>
bool Less<Date*>(Date* left, Date* right)
{
	return *left < *right;
}
/*
bool Less(Date* left, Date* right)
{
	return *left < *right;
}*///函数模板不建议特化,可以直接参数匹配来完成任务

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

	Date d1(2025, 7, 7);
	Date d2(2025, 7, 8);
	cout << Less(d1, d2) << endl; 

	Date* p1 = &d1;
	Date* p2 = &d2;
	cout << Less(p1, p2) << endl; 

	Date* p3 = new Date(2022, 7, 7);
	Date* p4 = new Date(2022, 7, 8);
	cout << Less(p3, p4) << endl; 

	return 0;
}

函数模板的特化可以理解成针对某个类型的特化,比如这里函数参数是Date*类型的时候,原来的参数匹配不行了,只能完成地址的比较,栈区地址相对大小是固定的,但是堆区地址是随机的,原生模板完成不了任务,就引入了函数模板的特化

函数模板的特化步骤:
1. 必须要先有一个基础的函数模板
2. 关键字template后面接一对空的尖括号<>
3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
根据步骤就能写出对应的特化,记住特化一定是在有原生模板的基础上才能用

可以看到这样就能完成普通模板完成不了的任务

最后不建议写函数模板的特化,因为这样写写死了,只能Date*指针才能使用

不如直接用参数匹配更简单,结果是一样的

类模板特化

全特化
template<class T1, class T2>
class Data
{
public:
	Data() { cout << "Data<T1, T2>" << endl; }
};

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

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

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

// 偏特化2:对参数类型进行一定限制,比如:限制是指针或者引用等
template <class T1, class T2>
class Data<T1*, T2*>
{
public:
	Data() { cout << "Data<T1*, T2*>" << endl; }
};

//两个参数偏特化为引用类型
template <class T1, class T2>
class Data <T1&, T2*>
{
public:
	Data()
	{
		cout << "Data<T1&, T2*>" << endl;
	}
};

 //匹配顺序
 //全特化 -> 偏特化 -> 原模板
int main()
{
	Data<int, int> d1;
	Data<int, char> d2;
	Data<char, int> d3;

	Data<char*, int*> d4;
	Data<int*, int*> d5;
	Data<int&, int*> d6;
	Data<int*, int&> d7;

	return 0;
}

结论,模板匹配顺序:全特化>偏特化>原模板

解析,首先要有一个原类模板

全特化也是template<>,类型后边跟两个特化类型,有传int,char的直接调用

也可以全特化为int,默认调两个int的

偏特化1:特化部分参数,保留T1,只特化第二个参数为int,只要有T,int的会直接调用

限制是两个指针,类型相同或者不同都可以

第一个传引用第二个传指针会匹配这个

好了,类模板的特化记住这些就够了,跟着敲一遍不难理解
类模板特化应用示例

template<class T>
struct Less
{
	bool operator()(const T& x, const T& y) const
	{
		return x < y;
	}
};

// 偏特化
template<class T>
struct Less<T*>
{
	bool operator()(const T* x, const T* y) const
	{
		return *x < *y;
	}
};
int main()
{
	Date d1(2024, 7, 7);
	Date d2(2025, 7, 6);
	Date d3(2026, 7, 8);
	vector<Date> v1;
	v1.push_back(d1);
	v1.push_back(d2);
	v1.push_back(d3);
	// 可以直接排序,结果是日期升序
	sort(v1.begin(), v1.end(), Less<Date>());//默认升序
	vector<Date*> v2;
	v2.push_back(&d1);
	v2.push_back(&d2);
	v2.push_back(&d3);

	// 可以直接排序,结果错误日期还不是升序,而v2中放的地址是升序
	// 此处需要在排序过程中,让sort比较v2中存放地址指向的日期对象
	// 但是走Less模板,sort在排序时实际比较的是v2中指针的地址,因此无法达到预期
	sort(v2.begin(), v2.end(), Less<Date*>());
	return 0;
}
v1和v2都完成了排序的任务,因为正常比较的是日期类对象而不是指针,原生模板无法完成任务,偏特化可以完成任务,比较指针指向的对象
注意sort第三个参数是对象,所以要加括号,表示匿名对象;priority_queue优先级队列第三个参数是类型,这两个的仿函数是一样的,只不过一个传递对象,一个传递类型
这里要注意,当参数Date* /int*传递过来的时候,传递给const Date*/int*,发生类型转换生成临时变量,临时变量具有常性,这里引用的是临时对象,要么引用之前再加一个const,要么把引用去掉
这样就对了

模板分离编译

什么是分离编译
一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
如果模板声明和定义分离,有两个cpp文件
       像这种情况因为 编译 的时候两个cpp文件互相不知道对方“长啥样”(两个cpp各自有头文件的包含,头文件展开只有声明),Stack.cpp找不到要实例化的类型(不正常),test.cpp找不到模板的定义(正常,可以链接找地址,如果链接再找不到定义就会报错)
       编译阶段符号汇总,汇编阶段符号表的生成,如果函数定义要实例化的类型都没有,函数声明却有类型,二者不匹配,链接的时候符号表的合并和重定位肯定有问题,所以报了链接错误(这部分我之前写过很详细的编译链接过程,感兴趣可以点开开)
程序环境及预处理-CSDN博客https://blog.csdn.net/eixjdj/article/details/144982435https://blog.csdn.net/eixjdj/article/details/144982435https://blog.csdn.net/eixjdj/article/details/144982435 解决办法1(不推荐)
可以在Stack.cpp文件显示实例化这个类型,实战中不建议这样,太呆了
  模板定义的位置显式实例化 。这种方法不实用,不推荐使用。
解决办法2(推荐)
直接在.h定义,这样编译就能实例化成功,不需要链接时候找函数定义,这种方式是最推荐的
将声明和定义放到一个文件 "xxx.hpp" 里面或者 xxx.h 其实也是可以的 。推荐使用这种。
.hpp和.h都行.hpp是c++的.h是c的,c++本身就兼容c,所以可以这样用

源代码示例

test.cpp

#include"Stack.hpp"

int main()
{
	Add(1, 2);
	func(1, 2);

	Add(1.1, 2.2);

	return 0;
}

 Stack.hpp

#pragma once
#include<iostream>
using namespace std;

template<class T>
T Add(const T& left, const T& right);
void func(int a, int b);

// 直接定义在.h ->调用的地方,直接就有定义,直接实例化,不需要链接时再去找
template<class T>
T Add(const T& left, const T& right)
{
	cout << "T Add(const T& left, const T& right)" << endl;

	return left + right;
}

Stack.cpp

#include"Stack.hpp"
//
//template
//double Add<double>(const double&, const double&); 
//template
//int Add<int>(const int&, const int&);


void func(int a, int b)
{
	cout << "void func(int a, int b)" << endl;
}

模板总结

【优点】
(模板的本质是,本来应该由你来写的多份类似代码,现在不需要你重复写了,你提供一个模板,编译器根据你的实例化,帮你写出来)
1. 模板复用了代码,节省资源,更快的迭代开发, C++ 的标准模板库 (STL) 因此而产生
2. 增强了代码的灵活性
【缺陷】
(编译错误不好定位发现,最佳实践解决方案:排除法,一段一段注释,日常建议写一部分编译一部分)
1. 模板会导致代码膨胀问题,也会导致编译时间变长
2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误

以上就是模板进阶的全部内容,欢迎互相交流


http://www.kler.cn/a/537120.html

相关文章:

  • 【电商系统架构的深度剖析与技术选型】
  • x64、aarch64、arm与RISC-V64:详解四种处理器架构
  • Baumer工业相机堡盟相机的相机传感器芯片清洁指南
  • camera光心检测算法
  • 深度学习 Pytorch 神经网络的学习
  • 学习日记-250207
  • android camera hal request
  • unity学习31:Video Player 视频播放相关基础
  • 【Axure教程】标签版分级多选下拉列表
  • makefile 的strip,filter,ifeq,ifneq基础使用
  • 朝天椒USB服务器在湖南电力的应用案例
  • docker run和docker exec的区别及开机启动
  • Centos执行yum命令报错
  • NSS-DAY1
  • 解决错误:CondaHTTPError: HTTP 000 CONNECTION FAILED for url
  • openCV函数使用(二)
  • mac环境下,ollama+deepseek+cherry studio+chatbox本地部署
  • 基于Spring Boot的历史馆藏系统设计与实现(LW+源码+讲解)
  • 通信易懂唠唠SOME/IP——SOME/IP-SD服务发现阶段和应答行为
  • 【大模型】DeepSeek与chatGPT的区别以及自身的优势
  • 软考教材重点内容 信息安全工程师 第15章 网络安全主动防御技术与应用
  • MySQL中datetime类型23:59:59变成下一天的00:00:00
  • 苍穹外卖-day12(工作台、数据导出)
  • 开箱即用的.NET MAUI组件库 V-Control 发布了!
  • 机器学习数学基础:17.矩阵初等变换
  • TCP/IP 邮件