C++模板特化:类型与常量的灵活掌控

一、模板参数再介绍

初级模板知识

模板参数是一个用来存放类型名称(int double

等内置类型和自定义类型名称)的变量。在代码实现中使用模板参数写代码(写一个函数或类),会增加代码复用的能力。

写出的函数或类被称为函数模板和类模板。 模板实例化:编译器根据使用模板时指定的类型名称,用这个类型名称来替代模板参数,生成相应的函数或类。

使用函数模板时,通过传入参数类型实例化出相应的模板。 而使用类模板时,需要显示说明模板参数类型,才能实例化出相应的类。

二、非类型模板参数

前面提及的模板参数接受的都是类型的名字,还有一种模板参数接受的是一个常数。

看下面一段代码:

cpp 复制代码
#include<iostream>
template<class T,size_t size=10>
class arr {
private:
	T arr[size];
	int len = size;
public:
	arr();
	~arr();
	T& operator [](const arr& a);
	bool empty();
};

这个是C++STL中对数组的改造array的模拟实现(未完成),与c风格数组相比,容器arr对越界访问更加严格。我们看到arr是一个类模板,有两个模板参数,第二个模板参数就是上面提及的非类型模板参数,相当于C语言中常量的宏定义。

但与宏定义相比较,这样的类模板可以根据传入参数的不同自动调整常量,不需要改变代码,但是宏定义恰恰相反。

举个arr的使用例子来体会非类型模板参数的用途:

cpp 复制代码
#include<iostream>
using namespace std;
#include<array>
int main()
{
	array<int, 20> arr = { 1,2,3,4,5 };
	for (int i = 0; i < arr.size(); i++)
	{
		cout<<arr[i]<<" ";//1 2 3 4 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
	}
	cout << endl;
	return 0;
 }

三、函数模板的特化

为什么需要引入特化

就函数模板而言:由于所有符合函数模板的参数列表中的每组参数并不都要根据函数模板实例化出的函数进行运算,总有一两个特例要实现不同的方法,所以引入模板特化。比如我想实现一个函数,当两个参数是一切参数时,判断第一个参数是否大于第二个参数。如果这样实现:

cpp 复制代码
#include<iostream>
using namespace std;
template<class T>
bool fun(T a, T b)
{
	return a>b

}

int main()
{
	int a = 10, b = 20;
	int* pb = &b, * pa = &a;
	cout << fun(a, b) << endl;
	cout << fun(pa, pb) << endl;
	
}

存在指针比较,是不是没有任何意义,当参数为int*时,我们特化一个函数模板,让它返回指针所指向的最大的数:

cpp 复制代码
#include<iostream>
using namespace std;
template<class T>
bool fun(T a, T b)
{
	return a>b

}
template<>
bool fun<int*>(int* a, int* b)
{
	return *a > *b;
}
int main()
{
	int a = 10, b = 20;
	int* pb = &b, * pa = &a;
	cout << fun(a, b) << endl;
	cout << fun(pa, pb) << endl;
	
}

函数模板特化的语法:

1.前提是有一个相关的函数模板

2.在特化函数模板之前加上template<>

3.在特化声明函数时在函数名之后参数表之前加上<特化的类型名称>

4.要求函数的其余部分除将模板参数特化替代外,必须和原来函数一模一样否则,就会编译报错。

函数模板特化的特点:

由于函数模板的性质,当定义一个将函数模板中所有模板参数替代成统一类型名称的函数fun时,调用一个既可以走函数模板,又可以直接调用fun时,编译器会不再实例化函数模板,直接调用fun。因此函数模板特化的作用不大,大多数情况都可以通过定义一个将函数模板中所有模板参数替代成统一类型名称的函数fun,调用fun直接解决问题,但也不排除只有使用函数模板特化的方法才能解决问题的项目。

使用上面提及的第二种常规方法解决之前需要函数模板特化才可以解决的问题:

cpp 复制代码
#include<iostream>
using namespace std;
template<class T>
bool fun(T a, T b)
{
	return a > b;

}

bool fun(int* a, int* b)
{
	return *a > *b;
}
int main()
{
	int a = 10, b = 20;
	int* pb = &b, * pa = &a;
	cout << fun(a, b) << endl;
	cout << fun(pa, pb) << endl;
	
}

四、类模板的特化

1.全特化

顾名思义就是将所有参数都特化,和函数模板特化一样,这里也强调一下:函数模板特化的方式只有一种,类模板特化可以全特化,也可以半特化。

示例代码:

cpp 复制代码
#include<iostream>
using namespace std;
template<class T1, class T2>
class c1
{private:
	T1 t1;
	T2 t2;
public:
	c1()
	{
		cout << "T1,T2" << endl;
	}
};
template<>
class c1<int,int>
{
private:
	int  t1;
	int t2;
public:
	c1()
	{
		cout << "int ,int " << endl;
	}
};
int main()
{
	c1<int, double> cc;
	c1<int, int> c1;
	//T1,T2
	//int, int

}

和函数模板特化一致,特化定义前要加上template<>

特化声明时l类的名字和{}之间要加上<特化的类型名称>

2.半特化(别称:偏特化)

先来看一下半特化的代码演示:

cpp 复制代码
#include<iostream>
using namespace std;
template<class T1, class T2>
class c1
{private:
	T1 t1;
	T2 t2;
public:
	c1()
	{
		cout << "T1,T2" << endl;
	}
};
template<class T1>
class c1<T1,int>
{
private:
	T1  t1;
	int t2;
public:
	c1()
	{
		cout << "T1 ,int " << endl;
	}
};
int main()
{
	c1<int, double> cc;
	c1<int, int> c1;
	//T1,T2
	//T1, int

}

半特化和全特化的不同在于:半特化定义之前,加的是template<未特化的模板参数声明>特化时类名称和{}之间加的是<未特化的模板参数,特化的模板参数>(这个排序要求和类模板定义之前的模板参数声明要一一对应下面的例子就是最好的说明)

cpp 复制代码
#include<iostream>
using namespace std;
template<class T1, class T2,class T3>
class c1
{private:
	T1 t1;
	T2 t2;
	T3 t3;
public:
	c1()
	{
		cout << "T1,T2,T3" << endl;
	}
};
template<class T1,class T3>
class c1<T1,int,T3>
{
private:
	T1  t1;
	int t2;
public:
	c1()
	{
		cout << "T1 ,int,T3 " << endl;
	}
};
int main()
{
	c1<int, double,int> cc;
	c1<int, int,double> c1;
	/*T1, T2, T3
		T1, int, T3*/

}

半特化还有一种特殊的例子,当被特化的类未指针或引用时:

cpp 复制代码
template<class T1, class T2>
class Data
{
public:
Data() {cout<<"Data<T1, T2>" <<endl;}
private:
T1 _d1;
T2 _d2;
};//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:
Data() {cout<<"Data<T1*, T2*>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
//两个参数偏特化为引用类型
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:
Data(const T1& d1, const T2& d2)
: _d1(d1)
, _d2(d2)
{
cout<<"Data<T1&, T2&>" <<endl;
}
private:
const T1 & _d1;
const T2 & _d2;
};
void test2 ()
{
Data<int , double> d2; // 调用基础的模板
Data<int *, int*> d3; // 调用特化的指针版本
Data<int&, int&> d4(1, 2); // 调用特化的指针版本
}
相关推荐
大圣编程14 分钟前
Java 多维数组详解
java·开发语言
殳翰3 小时前
下服务器端开发流程及相关工具介绍(C++)
开发语言·c++
落寞的星星3 小时前
这个对象就包含了已经转换好的DFA和各种词法分析器运转所需要的参数。下一步,我们就可以用ScannerInfo对象创建出Scanner对象,请看下面的代码:
开发语言·c#
nothing&nowhere4 小时前
用 Python 做问卷数据清洗:无效样本检测与处理实战
开发语言·python·数据清洗·数据处理·问卷星·问卷星脚本·刷问卷
青瓦梦滋4 小时前
协议定制/序列化-反序列化(Linux视角)
linux·服务器·网络·c++
2601_961593424 小时前
Rust 开发环境配置繁琐?RustRover 开箱即用搞定编码调试
开发语言·后端·macos·rust
HZZD_HZZD5 小时前
DL/T 645-2026新国标深度解读与智能电表协议适配实战:从帧结构变化到Java采集器升级的全链路改造方案
java·开发语言
多加点辣也没关系7 小时前
JavaScript|第4章:类型转换
开发语言·javascript
聪慧的水蜜桃7 小时前
【YFIOs】用C#开发硬件之设备上云
开发语言·c#
yqcoder7 小时前
httpOnly 是什么,又有什么用?
开发语言·前端·javascript