C++模板进阶

C++教学总目录

1、模板初阶的补充

现在假设我们有一个vector对象,我们要遍历输出vector对象中的所有数据,代码如下:

void Print(const vector<int>& v)
{
	vector<int>::const_iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

但是接下来我还有一个list容器,我也要遍历输出list容器中的所有数据。并且之后可能还会有其他容器要遍历输出容器内的所有数据。基于这样的原因:我们可以直接把Print函数写成模板函数。

template<class Container>
void Print(const Container& v)
{
	Container::const_iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

但是编译之后发现报错了:
在这里插入图片描述
这是因为Container::const_iterator可能是类型,也可能是一个对象,如果是类型就没有问题,但是如果是一个对象就存在语法错误。
说白了这里就是因为类中可能有静态成员变量,如果是静态成员变量就是一个对象,那么就不符合语法,如果是类型就没有问题。

基于上面的原因,我们需要在Container前面加上typename告诉编译器这是一个类型,等到对象实例化的时候到类中去找const_iterator这个类型。
如下:对于vector对象和list对象都可以调用

template<class Container>
void Print(const Container& v)
{
	typename Container::const_iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

在这里插入图片描述
在优先级队列这里less的数据类型传的是Container::value_type,由于无法判断是对象还是类型,所以也需要在前面加上typename, 明确告诉编译器这里是类型。


2、非类型模板参数

在过去,我们实现一个栈的方式如下:

#define N 100
// 静态栈
template<class T>
class Stack
{
private:
	T _a[N];
	int _top;
};

有了非类型模板参数,我们可以这么写:

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

有了非类型模板参数N,需要多少容量我们可以自己传递。
但是需要注意两个点:
1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
2. 非类型的模板参数必须在编译期就能确认结果。


库里实现的有bitset(位图)后面我们会学习。这里讲一个array:
在这里插入图片描述
array是C++11的产物,包含于头文件<array>,类似数组,是一个固定长度的容器。需要传类型和容器的容量N。
实际上array和数组差不多,只不过对于越界的访问更加严格。
array重载operator[]中对pos位置进行断言检查,而普通数组越界编译器可能检查不出来。

用法如下:

array<int, 10> a;
a[0] = 0;
for (auto e : a)
{
	cout << e << " ";
}
cout << endl;

不过基本上没啥用,很鸡肋。


3、模板的特化

3.1、函数模板特化

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理。

实现一个模板函数,如果x1 < x2返回true。

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

int main()
{
	int a = 2, b = 1;
	int* p1 = &a;
	int* p2 = &b;
	cout << Less(a, b) << endl;
	cout << Less(p1, p2) << endl;
	return 0;
}

上面比较两个整形是没有问题的。但是如果比较的是两个指针呢?这样就变成了比较两个指针地址的大小了,是不符合我们需求的。这时候我们就需要使用模板的特化。

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

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

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

我们自己写了一个int*的特化Less函数,对于指针的比较就会走这个函数,不会再去走模板生成了。


但是写模板的特化,我不如直接实现函数重载,如下:

bool Less(int* x, int* y)
{
	return *x < *y;
}

或者直接实现指针模板的Less函数:

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

3.2、类模板特化

3.2.1、全特化

全特化就是将类模板中的所有参数都确定化,如下:


template<class T1, class T2>
class Test
{
public:
	Test() { cout << "Test<T1, T2>" << endl; }
private:
	T1 _a1;
	T2 _a2;
};

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

int main()
{
	Test<int, char> t1;
	Test<int, double> t2;
	return 0;
}

在这里插入图片描述
可以看到,t2走的是我们全特化的Test类。


3.2.2、偏特化

偏特化就是特化一部分参数,如下:

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

偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。
例如限制Test类的两个参数为指针和引用类型:

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

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

可以看下面main函数实例化出不同类型调用上面所写特化的不同类的构造函数。
在这里插入图片描述


3.2.3、类模板特化的应用

在这里插入图片描述


4、模板的分离编译

之前我们实现vector和list的时候,都是声明和定义写在一起。因为如果声明和定义分离在两个不同的文件中会出问题。
下面先给出代码:

// stack.h
#pragma once
#include <queue>

namespace zzy
{
	template<class T, class Container = std::deque<T>>
	class stack
	{
	public:
		void push(const T& x);
		void pop();

		T& top()
		{
			return _con.back();
		}

		size_t size()
		{
			return _con.size();
		}

		bool empty()
		{
			return _con.empty();
		}
	private:
		Container _con;
	};

}
// stack.cpp
#include "stack.h"

namespace zzy
{
	template<class T, class Container>
	void stack<T, Container>::push(const T& x)
	{
		_con.push_back(x);
	}

	template<class T, class Container>
	void stack<T, Container>::pop()
	{
		_con.pop_back();
	}
}

在main函数中调用push/pop函数,就会出现如下错误:
在这里插入图片描述
这是因为,找不到push/pop函数的地址。因为stack.h和stack.cpp经过预处理、编译、汇编、链接然后形成可执行程序文件,但是在链接的时候,在符号表中找不到push/pop函数的地址,因为这里是模板函数,没有实例化出具体的类,所以也就没有具体函数代码 ,所以没有地址。

解决方案有两个:
1、显示实例化模板类

在这里插入图片描述
但是这种方式只能针对某种类型,当我换成stack<double>还是会报错,这时候就需要继续添加stack<double>的显示实例化。因此不推荐这种方法。

2、将声明和定义写在一起或写在同一个文件中
在这里插入图片描述
可以像size和empty一样将声明和定义直接写在一起。

在这里插入图片描述

也可以声明定义分离,但是写在同一个文件中。
推荐使用第二种方式,可以发现前面的vector、list声明和定义都是写在一个文件中的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值