模板(C++)

本文介绍了C++中的泛型编程、函数模板、类模板、非类型模板参数、模板特化以及模板分离编译的概念,通过实例详细展示了如何使用这些技术进行类型安全的代码设计和编译优化。
摘要由CSDN通过智能技术生成

一.泛型编程:

引例:交换两个数

void Swap(int& left, int& right)
{
	int temp = left;
	left = right;
	right = temp;
}
void Swap(double& left, double& right)
{
	double temp = left;
	left = right;
	right = temp;
}
void Swap(char& left, char& right)
{
	char temp = left;
	left = right;
	right = temp;
}

根据每次交换类型的不同,函数就不同很麻烦,所以祖师爷发明了模板

二.函数模板

这样子就很轻松了:

template <typename T>
void Swap(T& x,T& y)
{
	T tmp = x;
	x = y;
	y = tmp;
}

其中typename是关键字,也可以拿class代替,T是名字,取啥都行。
看以下代码:其中两个Swap调用的是不是同一个函数

#include <iostream>

using namespace std;

template <typename T>
void Swap(T& x,T& y)
{
	T tmp = x;
	x = y;
	y = tmp;
}

int main()
{
	int a = 1, b = 2;
	Swap(a,b);
	double d1 = 1.1, d2 = 2.2;
	Swap(d1,d2);
	return 0;
}

答案是完全不一样的,因为浮点型和整型的存储方式都不一样,交换方式肯定不一样。
编译器根据需要,推断类型,生成对应的函数,这个过程叫做模板实例化:
反汇编可以看出:
在这里插入图片描述
还有一个注意的点,C++库里面有swap,以后可以不用自己写:

#include <iostream>

using namespace std;

int main()
{
	int a = 1, b = 2;
	swap(a,b);
	double d1 = 1.1, d2 = 2.2;
	swap(d1,d2);
	return 0;
}

在这里插入图片描述
下面调用模板的方式不可取:两个类型不同,却用的同一个T类型,有歧义

#include <iostream>

using namespace std;

template <typename T>
T Add(const T& left,const T& right)
{
	return left + right;
}

int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 10.1, d2 = 20.2;
	cout << Add(a1,d1) << endl;
	return 0;
}

有以下几种解决方案:
第一种方案是强转类型

int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 10.1, d2 = 20.2;
	cout << Add((double)a1,d2) << endl;
	cout << Add(a1,(int) d2) << endl;
	return 0;
}

会发现答案不一样,因为用的模板不一样
在这里插入图片描述
第二种方案就是显示实例化
这个意思就是不用推了,直接明说T的类型

int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 10.1, d2 = 20.2;
	cout << Add<int>(a1,d2) << endl;
	cout << Add<double>(a1, d2) << endl;
	return 0;
}

第三种方案写两个参数,这样就不会有歧义了

template <typename T1,typename T2>
T1 Add(const T1& left,const T2& right)
{
	return left + right;
}

同名的模板和函数能同时存在吗?
答案是可以:

#include <iostream>

using namespace std;

template <typename T>
T Add(const T& left,const T& right)
{
	return left + right;
}

int Add(const int& left, const int& right)
{
	return left + right;
}
int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 10.1, d2 = 20.2;
	cout << Add(a1,a2) << endl;
	cout << Add(d1, d2) << endl;
	return 0;
}

通过调试可以得知:
1.有现成,吃现成的(匹配)
2.有现成的,但是不够匹配的,有模板,就会选择自己实例化模板

三.类模板

引例:

#include <iostream>

using namespace std;

template <typename T>
class Stack
{
public:
	void Push(const T& x)
	{
		;
	}
private:
	T* _a;
	int _top;
	int _capacity;
};

int main()
{
	Stack<int> str1;//显示实例化
	Stack<double> str2;//显示实例化
	return 0;
}

类模板不能推演实例化了,只能显示实例化,因为没有传参的问题,所以无法推演类型,只能自己指定,并且str1与str2两个类不是同一个类
可能就会想为什么不用typedef,这里讲以下,假设下面这个代码str1存int,str2存double怎么办。用只能用typedef改名,定义两份,太过于麻烦

int main()
{
	Stack s1;// int
	Stack s2;// double
	return 0;
}

然后我在学习list的时候遇到了一个模板的问题:ListNode< T >*到底是什么意思

template<class T>
struct ListNode
{
	ListNode<T>* _next;
	ListNode<T>* _prev;
	T _data;
};

我是这样理解的:比如我要用这个模板,创建了一个变量叫做Node,< int >表示我要用int模板啦,然后ListNode< T > * _next就变成了ListNode< int > * _next 。_next表示我的类型是ListNode*,_date类型是int。

int main()
{
	ListNode<int> Node;
	return 0;
}

四.非类型模板参数

函数参数(T 对象1,T 对象2)(T表示类型)
模板参数 <class 类型1,class 类型2>
引例:这里想要创建10个int大小的a1,和1000个int大小的a2

template<class T>
class array
{
private:
	T _array[N];
};
int main()
{
	array<int> a1;//10
	array<int> a2;//1000
	return 0;
}

常用的办法就是typedef或再写一个类

typedef N 1000//这样写反而委屈了a1,过多的空间溢出

所以有了非类型模板参数:类型 + 常量

template<class T,size_t N>
class array
{
private:
	T _array[N];
};
int main()
{
	array<int,10> a1;//10
	array<int,1000> a2;//1000
	return 0;
}

这里的大小已经在编译的时候已经确定了
在这里插入图片描述
非类型模板参数也有限制:

template<string str>
class A
{

};

int main()
{
	A<"1111111"> a1;
	return 0;
}

在这里插入图片描述
C++20之前只支持整型做非类型模板参数(size_t ,int ,char…)
模板也支持缺省参数的,比如栈,队列的适配容器,规则也与函数缺省相似
在这里插入图片描述

template<class T,size_t N = 10>
class array
{
private:
	T _array[N];
};

int main()
{
	array<int> a1
	return 0;
}

在这里插入图片描述
按需实例化:运行会发现以下代码没有报错

namespace bit
{
	template<class T, size_t N = 10>
	class array
	{
	public:
		T& operator[](size_t index)
		{
			size(1);//这里语法错了size()就没有参数
			return _array[index];
		}
		size_t size()const{return _size;}
		bool empty()const{return 0 == _size;}
	private:
		T _array[N];
		size_t _size;
	};
}

int main()
{
	bit::array<int,10> a1;
	cout << a1.empty() << endl;
	return 0;
}

根据模板实例化->半成品模板->实例化成具体的类/函数->语法编译
实例化类的时候,会按需求实例化(调用哪个成员函数就实例化哪个)
没有调用operator[],没有检查出来,只有调用,才会实例化,才会细致检查语法错误。

五.模板的特化

函数特化:

引例:指针的比较很麻烦,可能会出错

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

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

int main()
{
	Date d1(2022, 7, 7);
	Date d2(2022, 7, 8);
	cout << Less(d1, d2) << endl; // 可以比较,结果正确
	Date* p1 = new Date(2022,7,7);
	Date* p2 = new Date(2022, 7, 8);
	cout << Less(p1, p2) << endl; // 可以比较,结果错误
	return 0;
}

所以有模板的特化:这里有模板才能特化,不能只有特化

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

//针对某些特殊类型可以特殊处理
template<>
bool Less<Date*>(Date* left, Date* right)
{
	return *left < *right;
}

也可以这样:

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

如果想和别的类型共用模板也可以这样:(这里就不是特化了)

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

当然这里函数重载也行:(比较推荐用函数重载)

bool Less(Date* left,Date* right)
{
	return *left < *right;
}

类特化

跟函数差不多,但比函数精简

template<class T1, class T2>
class Date
{
public:
	Date() { cout << "Date<T1, T2>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};

template<>
class Date<char, char>
{
public:
	Date() { cout << "Date<char, char>" << endl; }
};

template<class T>//偏特化/半特化
class Date<T, char>
{
public:
	Date() { cout << "Date<T, char>" << endl; }
};

template<class T1, class T2>//注意这里别写成class T1*,是类型!!!!!
class Date<T1*,T2*>
{
public:
	Date() { cout << "Date<T1*, T2*>" << endl; }
};

template<>
class Date<char*, char*>
{
public:
	Date() { cout << "Date<char*, char*>" << endl; }
};

template<class T1,class T2>
class Date<T1&, T2>
{
public:
	Date() { cout << "Date<T1&, T2>" << endl; }
};

int main()
{
	Date<int, int> d1;
	Date<char, char> d2;
	Date<int, char>d3;
	Date<char*, char*> d4;
	Date<int*, string*> d5;
	Date<int&, string> d6;
	return 0;
}

在这里插入图片描述

六.模板分离编译:

这里先说以下结论:尽量声明和定义都写在.h文件里
引例:会发现他会报一个链接错误,func只是与函数size的一个对比

在这里插入图片描述在这里插入图片描述在这里插入图片描述

在这里插入图片描述

如果把func函数的定义注释掉,那么就会报两个链接错误
在这里插入图片描述
说明大概率就是size函数找不到定义
这里说一下编译的时候size和func都只有声明。编译的时候,检查一下函数名鹅参数匹配,没问题就暂时过了
func和size都是既有声明和定义的
那为什么size链接的时候找不到地址,func可以呢?
其主要原因没有实例化,定义的地方不知道T实例化成什么类型,调用的地方知道实例化什么类型,但没有定义。
所以实例化即可:这样把T为int,N=10实例化出来了
在这里插入图片描述
那么为什么模板的定义放到.h就不会出现链接错误了?
因为调用函数的时候,都会往上面找,.h预处理展开后,实例化模板时,既有声明也有定义,就直接实例化了。
编译时,有函数定义,就直接有地址,就不需要在链接时取符号表里面找

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浅碎时光807

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值