浅谈C++ templates 函数模板、类模板以及非类型模板参数

我自己搭建了博客,以后可能不太在CSDN上发博文了,https://www.qingdujun.com/


最近打算挑选几个STL容器做个简单实现,发现里面牵涉到不少模板知识。这里算提前学习一下C++模板的相关知识吧。这次主要学习了什么是函数模板(这个最简单),类模板以及非类型模板参数。下面挨个举例说明。


1. 函数模板

函数模板是最简答的一个,下面就定义一个返回两个值中最大者的函数模板:

namespace og {

template<typename T>
inline const T& max(const T& a, const T& b) {
	return a > b ? a : b;
}

}

我有个习惯,喜欢用和系统相关库函数同样的函数名。那么,为了避免不必要的麻烦,用上了自己的命名空间。我比较喜欢og这两个字母。

咯,使用这种方式就可以调用了…

cout << og::max(100, 123) << endl;
cout << og::max(1.23, 999.9) << endl;

可以在控制台上看到这样的内容,
123
999.9

2. 类模板

翻了翻侯捷先生的STL书籍,准备动一下手。那么,第一步就是需要弄清楚类模板。就算原理不太清楚,至少要能看懂类模板,知道其表达的是什么意思,从而模仿着学会如何写类模板。与函数模板一样,这里也举个例子,使用类模板定义一个栈类:

第一步: 声明一个stack类,并加上类模板。

namespace og {

template<typename T>
class stack {
public:
	void push(const T&);
	void pop();
	T top() const;
	bool empty() const {
		return elems.empty();
	}
private:
	std::vector<T> elems;
};

}//end namespace og

第二步: 实现类成员函数。这里需要注意,上面的stack声明是在.h文件中,如果在.cpp中实现编译器会报错。这个问题,等我研究清楚了再补充。

目前的解决方式是,同样在.h文件中将类成员函数实现。实现的内容就是下面这个样子,

namespace og {

template<typename T>
void stack<T>::push(const T& elem) {
	elems.push_back(elem);
}

template<typename T>
void stack<T>::pop() {
	if (elems.empty()) {
		throw std::out_of_range("stack<T>::pop() - empty stack");
	}
	elems.pop_back();
}

template<typename T>
T stack<T>::top() const {
	if (elems.empty()) {
		throw std::out_of_range("stack<T>::top() - empty stack");
	}
	return elems.back();
}

}//end namespace og

同样,这里补充一下——如何使用以上的类模板定义的类。你可以这样,

try {
	og::stack<char> sc;
	sc.push('a');
	cout << sc.top() << endl;
	sc.pop();
	sc.pop();  //exception
}
catch (const std::exception& msg) {
	cout << msg.what() << endl;
}

之所以加上个异常捕获,因为我这里想测试一下throw有没有效果。当然,如果不加上trycatch,当出现throw时程序会很不友好的崩溃掉。理论上,你的控制台应该会打印出,
a
stack::pop() - empty stack

3. 非类型模板参数

这种模板参数,我初次见到时,觉得十分怪异。你们提前感受一下,

og::array<int, 5> ai;

从形式上来看,就是给类模板指定了类型(这个我能理解),可是后面那个5是什么意思?
在我一番学习之后,我才知道——原来这就是非类型模板参数。同样,我也定义了一个数组类作为例子:

namespace og {

template<typename T, unsigned int N>
class array {
public:
	array();
	T& operator[] (unsigned int index);
	constexpr unsigned int size() noexcept;
private:
	T elems[N];
	int length;
};

}//end namespace og

是的,这次模板的声明变成这样了template<typename T, unsigned int N>后面多了一个unsigned int N。而且让人奇怪的是非类型模板参数好像还只能是整型的。

我尝试了整了一个double N参数,给我报错了——告诉我“浮点模板参数是非标准的”。

这个等以后研究清楚了,再补充。那么,继续放上以上类声明的实现部分:

namespace og {

template<typename T, unsigned int N>
array<T, N>::array() : length(N) {
	memset(elems, 0, sizeof(T)*N);
}

template<typename T, unsigned int N>
T& array<T, N>::operator[](unsigned int index) {
	if (index >= size()) {
		throw std::out_of_range("array<T, N>::operator[]() - index out of range");
	}
	return elems[index];
}

template<typename T, unsigned int N>
constexpr unsigned int array<T, N>::size() noexcept {
	return length;
}

}//end namespace og

这里,主要是实现了一个operator[]函数,当然也还有其它两个函数。

咯,再一次看看怎么用,又和老伙伴见面了——og::array<int, 5> ai;

try {
	og::array<int, 5> ai;
	ai[0] = 4;
	ai[2] = 123;
	for (unsigned int i = 0; i < ai.size(); ++i) {
		cout << ai[i] << endl;
	}
	cout << ai[7] << endl; //exception
}
catch (const std::exception& msg) {
	cout << msg.what() << endl;
}

最后,再来看看控制台是什么样子?
4
0
123
0
0
array<T, N>::operator - index out of range

到此处还意犹未尽的话,推荐阅读《对C++ templates类模板的几点补充(Traits类模板特化)》这篇文章——主要讲述了模板的缺省参数和类模板特化的相关知识,当然还涉及一些STL迭代器的内容。



©为径
2018-12-22 北京 海淀


Reference: C++ Templates(中文版)

©️2020 CSDN 皮肤主题: 精致技术 设计师:CSDN官方博客 返回首页