【C++】模板进阶 — 模板特化

📖前言

在我们之前的学习中,我们学习了初阶的模板,函数模板、类模板,并学会了简单的运用,本章将继续深入学习模板的内容,模板进阶…


1. typename的深一层应用

(1)先看一段代码:

void print_list(const list<int>& lt)
{
	list<int>::const_iterator cit = lt.begin();
	while (cit != lt.end())
	{
		cout << *cit << " ";
		cit++;
	}
	cout << endl;
}

上述代码为打印一个链表中节点数据的代码。

(2)我们将上述函数改为函数模板:

template<class T>
void print_list(const list<T>& lt)
{
	typename list<T>::const_iterator cit = lt.begin();

	while (cit != lt.end())
	{
		cout << *cit << " ";
		cit++;
	}
	cout << endl;
}
  • 用泛型写一个print函数
  • list确定了,但是list的模板参数没确定

STL中list的迭代器的实现:

在这里插入图片描述
以list为例子:

  • 这里的Iterator的类型是__list_iterator<T, Ref, Ptr>,
  • 虽然list中的迭代器类型被实例化成了假如是int类型的,在list的迭代器的类中T就是int
  • 但是这里却没有实例化,还是T,是个虚拟的类型
  • 取内嵌类型都要加上typename
  • 类模板 模板虚拟类型 没有实例化之前不能去它里面找内嵌定义的类型
  • 类模板没有实例化,找出来也是虚拟类型,后期无法处理 – 编译报错

typname 告诉编译器后面这一串是一个类型,等Iterator实例化之后,再去它里面找这个内嵌类型。

(3)接着用一下适配器模式:

template<class Container>
void print_container(const Container& lt)
{
	//typename Container::const_iterator cit = lt.begin();
	//用auto则不用加typename,因为auto是根据返回值类型自动推导
    //去类模板里面取的话都要加上typename

	auto cit = lt.begin();

	while (cit != lt.end())
	{
		cout << *cit << " ";
		cit++;
	}
	cout << endl;
}

上述代码是容器适配器的思想,适配一个Container容器,同样的道理,容器虽然确定了,但是容器内的迭代器却是虚拟的。

整体模板的虚拟类型都没确定:

  • 不是类模板不行,而是,模板包含中包含虚拟类型时,就需要用到typename
  • 类模板是不允许去取的,有可能还会取出来虚拟类型

注意:

  • 用auto则不用加typename,因为auto是根据返回值类型自动推导
  • 去类模板里面取的话都要加上typename

2. 非类型模板参数

2.1 非类型模板参数的引入:

定义一个静态的栈:

#define N 100

template<class T>
class Stack
{
private:
	T _a[N];
	int _pop;
};

这里有个缺陷,这里的栈的大小是固定死的,每次创建出来的栈都是一样的大小。

问题:

模板让我们实现了同时可以创建不同数据类型的栈,但是不能实现不同数据个数的栈。

如下就是上述代码不能实现的功能:(缺陷)

  • Stack< int > – 100个数据
  • Stack< char > – 500个数据

我们这时候就引入了非类型模板参数:

直接见代码:

template<class T, size_t N = 100>
class Stack
{
public:
	//非类型模板参数是不能修改的 -- 因为常量是不能修改的
	void f()
	{
		N = 10;
	}
private:
	T _a[N];
	int _pop;
};
  • 非类型模板参数 – 还可以给一个缺省值
  • <>里也可以不定义类型,也可以定义常量
  • N是常量不能修改
  • 模板不仅可以不定义类型,还可以定义常量
  • 比宏更好,可以传一个常量过去,定义不同的对象,可以传不同的常量过去
  • 非类型模板参数基本上是整形。
  • 非类型的模板参数必须在编译期就能确认结果。

2.2 array的特性和使用:

C++引入了一个新的类模板 —— array
在这里插入图片描述
这里便用到了非类型模板参数,size_t N

array和vector的区别:

  • array是一个静态的数组, 不支持插入删除
  • 而vector却是动态的
  • array的价值是:开大数组
  • array没有独特的地方,是C++11新增的

大小不一样:

  • vector中只存几个指针来维护数组,实际不存数据
  • 而array是直接存数据
    在这里插入图片描述

array与vector的区别:

  • array是封装过了的原生数组 - - 越界一定能查到(读写都能查得到)
  • array<int, 100> a1;
  • array是C++11新增的,相对于vector而言,没有独特的地方
  • operator [ ] 能严格检查越界
  • 而普通的数组检查越界不一定能查出来,因为是抽查
  • vector可以扩容,array不可以

总结:

  • array对比原生数组,还有一些越界检查的优势
  • 但是实际中,统一用vector更香
  • 类型模板参数 - 虚拟类型
  • 非类型模板参数 – 常量

3. 模板的特化

3.1 模板的特化的引入 + 函数模板的特化:

针对某些类型特殊化处理 - - 模板的特化

概念:

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

比较大小模板,直接见代码:

//模板的特化
template<class T>
bool Less(T left, T right)
{
	return left < right;
}

//针对某些类型特殊化处理 -- 模板的特化
//首先要有一个基础的函数模板
//针对Date*进行特化
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(2022, 7, 7);
	Date d2(2022, 7, 8);
	cout << Less(d1, d2) << endl; //可以比较,结果正确

	Date* p1 = new Date(2022, 7, 16);
	Date* p2 = new Date(2022, 7, 15);

	//这里比较的是地址
	cout << Less(p1, p2) << endl;
	//只用这种方式(p1, p2相比较)可以用模板的特化来解决

	return 0;
}

👉 日期类复习 – 传送门

此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化。

问题的解决:

我们比较的是日期的大小,直接传日期类的对象就可以调用日期类的运算符重载,但是如果传的是日期类对象的指针,比较的就是地址的大小,显然不是我们想要的比较日期类对象的大小。如果在我们只传指针还要解决问题的限制下,这时应用模板特化就可以很好的解决这种问题。

函数模板的特化步骤:

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

注意:

  • 上述三个同时存在的时候,先去匹配函数
  • 函数模板不一定要特化
  • 类模板就需要具体的特化了
  • 一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。
  • 因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化,直接写一个具体类型的函数更好

3.2 类模板的特化 + 全特化和偏特化(半特化):

(1)全特化:

  • 全特化即是将模板参数列表中所有的参数都确定化。
template<class T1, class T2>
class Data
{
public:
	Data() { cout << "Data<T1, T2>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};

//类模板的特化 -- 全特化(写死了)
template<>
class Data<int, double>
{
public:
	Data() { cout << "Data<int, double>" << endl; }
};
  • 制定好了两个模板参数 —— 写死了
  • 只能固定的类型走这个函数
  • 例如:只能是int 和 double走全特化的模板

(2)偏特化(半特化):

  • 任何针对模版参数进一步进行条件限制设计的特化版本。
//半特化 / 偏特化()半特化不是特化一半
//1、将部分模板参数列表中的一部分参数特化
template<class T1>
class Data<T1, char>
{
public:
	Data() { cout << "Data<T1, char>" << endl; }
};

//2、偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本
//只要T1 和 T2是指针就走这个 -- 针对指针特殊化处理
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, double> d2;

	//只要第二个是char都会匹配:半特化/偏特化
	Data<int, char> d3;
	Data<char, char> d4;

	//只要是两个指针
	Data<int*, int*> d5;
	Data<int*, char*> d6;
	Data<int*, string*> d7;
	Data<int*, void*> d8;
	//void不是类型,但是void*是一个类型,void*是不能解引用不能++

	Data<int*, int> d9;//匹配原生的指针
	Data<int&, char&> d10;

	return 0;
}

运行结果如下:

在这里插入图片描述

3.3 模板特化的应用:

//模板的特化 -- 应用
template<class T>
struct Less
{
	bool operator()(const T& x, const T& y) const
	{
		return x < y;
	}
};

template<>
struct Less<Date*>
{
	bool operator()(Date* x, Date* y) const
	{
		return *x < *y;
	}
};

//偏特化
//只要是指针都走这里
template<class T>
struct Less<T*>
{
	bool operator()(T* x, T* y) const
	{
		return *x < *y;
	}
};

int main()
{
	Date d1(2022, 7, 7);
	Date d2(2022, 7, 6);
	Date d3(2022, 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*>());

	vector<int*> v3;
	v3.push_back(new int(3));
	v3.push_back(new int(1));
	v3.push_back(new int(2));
	sort(v3.begin(), v3.end(), Less<int*>());

	return 0;
} 

4. 模板分离编译

这个问题在初识模板章节就已经讲解:

👉 初识模板 – 传送门

  • 48
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 32
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

yy_上上谦

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

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

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

打赏作者

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

抵扣说明:

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

余额充值