模板
模板是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 类名 { … }

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::vector、std::pair、std::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)会在编译期直接消掉不成立的分支。