c++ —— 模板的进阶使用


前言: 模板是常考点的,内容知识点也很多,本文意在总结模板的高级使用,至于初级使用可以查看我以前的博客 —— 模板初阶的使用


1. 非类型的模板参数

模板的参数一定是类型吗?答案:不是。

模板参数:

  • 类型模板参数: class或typename 后为类型
  • 非类型模板参数:可以是常量整数 int或size_t ,包括枚举常量等 所以浮点数,字符型,自定义类类型都是不能做非类型参数的

举例:
我们可以搞一个模板类型的静态数组。

template <class T, size_t n = 10>
class my_array
{
public:
	my_array()
		:_size(n)
	{

	}
	
	T& operator [](size_t index)
	{
		return My_array[index];
	}

	size_t size()
	{
		return _size;
	}

	bool empty()
	{
		return _size == 0;
	}
		
private:
	T My_array[n];
	
	size_t _size;
};

这个数组的默认大小我设置为10,当然我们可以自己来定义数组的大小,

// 默认是10
my_array<int> a;
// 传模板参数100
my_array<int,100> b;

我们也可以验证一下,如果非类型模板参数我们不设置为常量,而是设置成float,看看会是什么情况

在这里插入图片描述
c++20,会支持非类型模板参数为float,之前的版本都不支持的。


2. 模板的特化

2.1 什么是模板特化

模板的特化:

  • 函数模板特化
  • 类模板特化

特化的目的是实现特殊的功能,如果按照原本模板的推导,并不能实现我们预期的功能,这就需要我们进行模板特化。

定义:
模板特化(template specialization)不同于模板的实例化,模板参数在某种特定类型下的具体实现称为模板的特化。 模板特化有时也称之为模板的具体化,分别有函数模板特化和类模板特化。

2.2 函数模板特化

比如我们要进行一个比大小的功能,如果传的是int ,long 等都还好说,要是传的是一个int* 指针,我们改怎么操作呢?比地址的高低是没有意义的,所以我们要去比 int*所以指向的内容。

template<class T>

bool compare(T& x,T& y)
{
	return x > y;
}

这就是一个简单的比较大小的函数模板:

int main()
{
	cout << compare(10, 2) << endl;
	cout <<compare(2, 10)<<endl;
	int a = 10;
	int b = 2;
	int* x = &a;
	int* y = &b;
	cout << compare(a, b) << endl;
}

前俩个compare()是不需要函数特化的,模板去推导的就满足功能;但是最后的compare(),比较的是指针,模板推导比的是地址的高低,我想要去比较指针所指向的内容,该怎么办?模板特化。

template<class T>
bool compare( T x, T y)
{
	return x > y;
}

template<>
bool compare<int*>(int* x,int* y)
{
	return *x > *y;
}

这就是函数模板的特化。

函数模板特化的使用规则:

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

2.3 类模板的特化

类模板的特化分类:

  • 全特化:类模板的参数全部确定化
  • 偏特化:类模板的参数部分确定化,类模板的参数进一步进行限制。

(1) 全特化

我拿下面的类进行举例:

template<class T1,class T2>
class two_val
{
public:
	two_val()
	{
		cout << "<T1 x, T2 y>" << endl;
	}
private:
	T1 x;
	T2 y;
};

全特化为类模板的参数全部确定化

template<>
class two_val<char,int>
{
public:
	two_val()
	{
		cout << "<char x, int y>" << endl;
	}
private:
	char x;
	int y;
};

我们来创建类对象:

int main()
{
	two_val<int, int> s;
	two_val<char, int> ss;
}

在这里插入图片描述

(2) 偏特化
偏特化首先可以只特化一部分:
以下两种都是偏特化:

template<class T1>
class two_val<T1,int>
{
public:
	two_val()
	{
		cout << "<T1 x, int y>" << endl;
	}
private:
	T1 x;
	int y;
};

template<class T2>
class two_val<char,T2>
{
public:
	two_val()
	{
		cout << "<char x, T2 y>" << endl;
	}
private:
	char x;
	T2 y;
};

偏特化其次是一种对特化的进一步限制:
比如:将模板参数,特化成指针 / 特化成引用,这也是一种偏特化

template<class T1, class T2>
class two_val<T1*,T2*>
{
public:
	two_val()
	{
		cout << "<T1* x, T2* y>" << endl;
	}
private:
	T1* x;
	T2* y;
};

3. 模板的分离编译

为什么要分离编译?我们在做一个较大的项目时,多人协作,头文件一般用于看框架,源文件去实现功能,这个功能的实现需要多人协作,每个人负责实现一部分的功能。

但是模板声明和定义分开的话,会导致链接错误。这是因为分开定义导致模板函数或类,没有实例化。

我可以演示一下:
以一个加法模板函数为例:

add.h头文件:

#pragma once
template <class T>
T add(T x, T y);

add.c源文件:

#include"add.h"
template <class T>
T add(T x, T y)
{
	return x + y;
}

main函数源文件:

#include"add.h"
int main()
{
	add(1, 2);
	return 0;
}

我们看看运行情况:
很明显的链接错误、
在这里插入图片描述
该如何解决呢?

  1. 显示的实例化:链接错误的原因是没有实例化,那么我们可以手动实例化。
    在头文件中加入我们具体的实例化:

add.h头文件:

#pragma once
template <class T>
T add(T x, T y);

template
int add<int>(int x, int y);

这样就支持了 int类型的加和。如果想要支持double类型的加和呢?还得手动实例化,这种方法是比较累人的。

  1. 在头文件中定义,使得声明和定义不分离

这种方法是常用的,定义在头文件里就好了,简单省事:

#pragma once

template <class T>
T add(T x, T y);

template <class T>
T add(T x, T y)
{
	return x + y;
}

类模板和函数模板是一样的。


4. 模板总结

优点:

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

缺陷:

  1. 模板会导致代码膨胀问题,也会导致编译时间变长
  2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

动名词

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

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

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

打赏作者

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

抵扣说明:

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

余额充值