Contents

模板

模板是C++泛型编程的基础。一个模板就是一个创建类或者函数的蓝图。 模板就是实现代码重用机制的一种工具,它可以实现类型参数化,即把类型定义为参数,从而实现了真正的代码可重用性。模版分为两类,一个是函数模版,另外一个是类模版。

1
2
3
4
5
6
7
//用函数模板,只写一套代码解决问题
template<typename T>
void swap(T& a, T& b) {
	T tmp = a;
	a = b;
	b = tmp;
}

函数模板

函数模板:模板类型参数

函数模板的格式:

template < typename 形参名1, typename 形参名2, ……>typename可以写为class

返回类型 函数名(参数列表) { 函数体 } < >括号中的参数叫模板形参( 类似函数形参),编译器根据传入的参数(或实参推导)确定实际的模板实参类型,并实例化

函数模板:非类型模板参数

参数类型: 非类型模板参数。

如: template<typename T, int N> void fun(T t){}; 其中int N就是非类型的模板形参。

在模板的内部是常量。 只能是整型、对象的指针或引用, 必须能在编译时确定。 (局部变量、局部对象的 地址和引用都不行, const类型的整形变量可以,全局对象或static对象的地址和引用可以)

函数模板:实例化

当调用一个函数模板时,编译器通常会利用给定的函数实参来推断模板参数,用此实际实参来代替模板参数来创建出模板的一个新的“实例”,也就是一个真正可以调用的函数,这个过程称为实例化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//模仿std::find,在两个迭代器之间找到匹配的元素,返回迭代器
//T为迭代器类型,V为元素类型
template<typename T,typename V>
T my_find(const T& b,const T& e,const V& value) {
	T it = b;
	while (it != e && *it != value)
		++it;
	return it;
}

//编写my_begin,my_end,返回数组的头尾指针
template<typename T,unsigned N>
T* my_begin(T(&arr)[N]) {
	cout << "size: " << N << endl;
	return &arr[0];
}
template<typename T,unsigned N>
T* my_end(T(&arr)[N]) {
	return &arr[N - 1];
}

//实例化的结果:
//vector<int>::iterator my_find(const vector<int>::iterator& b, const vector<int>::iterator& e, const int& value);
//list<string>::iterator my_find(const list<string>::iterator& b, const list<string>::iterator& e, const string& value);
//int* my_begin(int(&arr)[5]);
//int* my_end(int(&arr)[5]);

类模板

类模板:用来生成类的蓝图。编译器不能为类模板推断模板参数类型,必须提供实参列表。

类模板的格式: template < typename 形参名1, typename 形参名2, ……> class 类名 { … }

image/类模板.png

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
template <typename T,int MAXSIZE>
class MyStack{
public:
	MyStack():size(0){}
	MyStack(const MyStack& other):size(other.size){ //类内实现
		for (int i = 0; i < size; i++)
			data[i] = other.data[i];
	}
	MyStack& operator=(const MyStack& other);
	bool empty()const { return size == 0; }//类内实现,隐式内联
	bool full()const { return size == MAXSIZE; }
	void push(const T& elem) {
		assert(!full()); data[size++] = elem;
	}
	void pop() { assert(!empty()); size--; }
	inline T& top();
private:
	T data[MAXSIZE]; //栈数组
	int size;		 //元素个数
};
template<typename T,int MAXSIZE>
MyStack<T, MAXSIZE>& MyStack<T, MAXSIZE>::operator=
			(const MyStack/*<T, MAXSIZE>*/ &other) {
	if (this != &other) {
		size = other.size;
		for (int i = 0; i < size; i++)
			data[i] = other.data[i];
	}
	return *this;
}
template<typename T,int MAXSIZE>
T& MyStack<T,MAXSIZE>::top() {
	assert(!empty());
	return data[size - 1];
}
int main() {
	MyStack<int, 10> st1;
	st1.push(1);
	st1.pop();
	MyStack<int, 20> st2;
	st2.empty();
	//st1和st2是同一类型的对象吗?
	//MyStack<int, 10> 和
	//MyStack<int, 20>实例化的成员函数?
	return 0;
}

类模板: static成员、默认参数

类模板中的static成员,这些成员由相同类型的实例共享。
模板可以设置默认实参,如: template class A{},使用时A<> a;(尖括号不能省) 默认参数的位置习惯上靠右。


现代 C++ 对模板的扩展(补充)

类模板参数推导(CTAD,C++17)

在 C++17 中,很多标准库类模板(例如 std::vectorstd::pairstd::tuple 等)支持类模板参数推导(Class Template Argument Deduction):

1
2
3
4
5
// 传统写法
std::pair<int, double> p1(1, 3.14);

// C++17 之后,可以省略模板参数,由编译器推导
std::pair p2(1, 3.14);   // 推导为 std::pair<int, double>

自定义类模板也可以通过**推导指引(Deduction Guide)**支持类似用法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
template <typename T>
struct Box {
    T value;
};

// 推导指引:给出“构造参数 → 模板实参”的规则
template <typename T>
Box(T) -> Box<T>;

Box b{42};      // 被推导为 Box<int>
Box c{"text"};  // 被推导为 Box<const char*>

别名模板与可变参数模板

现代 C++ 常用**别名模板(alias template)**为复杂类型建立统一别名:

1
2
3
4
template <typename T>
using Vec = std::vector<T>;

Vec<int> v;     // 等价于 std::vector<int>

结合可变参数模板,可以写出更通用的别名:

1
2
3
4
template <typename... Ts>
using Tuple = std::tuple<Ts...>;

Tuple<int, double, std::string> t;

constexpr 模板与编译期计算

在 C++14/17 之后,constexpr 的能力更强,可以配合模板实现真正的编译期计算

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
template <std::size_t N>
constexpr std::size_t factorial() {
    if constexpr (N == 0) {
        return 1;
    } else {
        return N * factorial<N - 1>();
    }
}

static_assert(factorial<5>() == 120, "编译期计算失败");

这里的 if constexpr(C++17)会在编译期直接消掉不成立的分支。