1. 什么是C++模板?
在C++中,模板(Template)是一种支持泛型编程的机制。
所谓泛型编程,就是:
编写一份通用代码,使它能够适用于多种数据类型,而不需要为每一种类型重复编写代码。
例如:
如果我们想写一个交换函数:
void swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
这个函数只能交换 int 类型。
如果换成:
double
char
string
就需要重新写:
void swap(double&, double&);
void swap(char&, char&);
void swap(string&, string&);
代码重复严重。
模板可以解决这个问题:
template<class T>
void Swap(T& a, T& b)
{
T tmp = a;
a = b;
b = tmp;
}
调用:
int a=10,b=20;
Swap(a,b);
double x=1.2,y=3.4;
Swap(x,y);
编译器会自动生成对应版本。
2. 模板的分类
C++模板主要分为两类:
2.1 函数模板
用于生成函数。
例如:
template<class T>
T Add(T a,T b)
{
return a+b;
}
2.2 类模板
用于生成类。
例如:
template<class T>
class Stack
{
};
STL中的:
vector<int>
vector<double>
vector<string>
本质都是类模板。
3. 函数模板
3.1 函数模板语法
格式:
template<typename 类型参数>
返回值 函数名(参数)
{
}
例如:
template<typename T>
T Max(T a,T b)
{
return a>b?a:b;
}
这里:
T
表示一个未知类型。
4. 函数模板实例化
当调用:
Max(10,20);
编译器会推导:
T=int
然后生成:
int Max(int a,int b)
{
return a>b?a:b;
}
调用:
Max(1.2,3.4);
生成:
double Max(double,double)
这个过程叫:
模板实例化(Template Instantiation)
5. 模板参数推导
例如:
template<class T>
void Print(T x)
{
cout<<x;
}
调用:
Print(10);
编译器:
T=int
调用:
Print("hello");
编译器:
T=const char*
6. typename 和 class区别
模板参数:
template<class T>
或者:
template<typename T>
两者完全一样。
例如:
template<typename T>
void func(T x)
{
}
等价:
template<class T>
void func(T x)
{
}
区别:
在模板内部:
typename
还有一个特殊作用。
例如:
template<class T>
void func()
{
typename T::value_type x;
}
告诉编译器:
T::value_type 是一个类型。
7. 类模板
7.1 类模板定义
语法:
template<class T>
class 类名
{
};
例如:
实现一个简单数组:
template<class T>
class Array
{
private:
T data[10];
public:
void set(int index,T value)
{
data[index]=value;
}
T get(int index)
{
return data[index];
}
};
使用:
Array<int> arr1;
Array<double> arr2;
编译器生成:
Array<int>
和:
Array<double>
8. 类模板成员函数定义
方式一:
直接写:
template<class T>
class Stack
{
public:
void push(T x)
{
}
};
方式二:
类外定义:
template<class T>
class Stack
{
public:
void push(T x);
};
template<class T>
void Stack<T>::push(T x)
{
}
注意:
必须再次写:
template<class T>
9. 模板非类型参数
模板参数不仅可以是类型。
还可以是:
-
整数
-
指针
-
枚举
例如:
template<class T,int N>
class Array
{
private:
T arr[N];
};
使用:
Array<int,10> a;
表示:
创建一个:
int[10]
10. 模板默认参数
例如:
template<class T=int>
class Test
{
};
使用:
Test<> t;
等价:
Test<int> t;
11. 函数模板重载
模板可以重载:
template<class T>
void print(T x)
{
cout<<"模板";
}
void print(int x)
{
cout<<"普通函数";
}
调用:
print(10);
优先:
普通函数
因为:
普通函数匹配优先级更高。
12. 模板特化(Template Specialization)
有时候:
模板生成的代码不适合某个类型。
例如:
template<class T>
bool Equal(T a,T b)
{
return a==b;
}
对于:
char*
比较的是地址。
我们希望比较字符串内容。
可以特殊处理:
template<>
bool Equal<char*>(char* a,char* b)
{
return strcmp(a,b)==0;
}
这叫:
全特化
13. 类模板特化
普通模板:
template<class T>
class Data
{
};
特殊版本:
template<>
class Data<int>
{
};
当:
Data<int>
出现时:
使用特化版本。
14. 模板偏特化
偏特化:
只限定部分参数。
例如:
template<class T,class U>
class Test
{
};
偏特化:
template<class T>
class Test<T,int>
{
};
表示:
第二个参数必须是:
int
15. 模板编译模型
C++模板采用:
两阶段编译模型
第一阶段
检查模板语法:
例如:
template<class T>
void func(T t)
{
}
第二阶段
实例化时检查:
func(10);
如果:
T
没有对应操作:
才报错。
例如:
template<class T>
void Print(T x)
{
x.test();
}
调用:
Print(10);
错误:
int没有test函数
16. 为什么模板函数一般放头文件?
普通函数:
.h
声明
.cpp
实现
模板:
通常:
.h
声明+实现
原因:
编译器实例化模板时:
必须看到完整代码。
例如:
main.cpp:
Add(1,2);
编译器需要看到:
template<class T>
T Add(...)
{
}
否则无法生成代码。
17. 模板和宏区别
很多人会比较:
模板:
template<class T>
T add(T a,T b)
{
return a+b;
}
宏:
#define ADD(a,b) a+b
区别:
| 模板 | 宏 | |
|---|---|---|
| 类型检查 | 有 | 无 |
| 调试 | 容易 | 困难 |
| 安全性 | 高 | 低 |
| 运行效率 | 高 | 高 |
| 作用阶段 | 编译期 | 预处理 |
现代C++:
优先模板。
18. 模板和继承区别
模板:
解决:
类型不同,但逻辑相同
例如:
vector<int>
vector<double>
继承:
解决:
类型之间存在关系
例如:
Animal
|
Dog
Cat
19. STL中的模板应用
C++标准库大量使用模板。
例如:
vector
vector<int>
vector<string>
底层:
template<class T>
class vector
{
};
map
map<string,int>
本质:
template<class Key,class Value>
class map
{
};
smart pointer
unique_ptr<int>
shared_ptr<string>
都是模板。
20. C++11以后模板新特性
可变参数模板
例如:
template<class... Args>
void Print(Args... args)
{
}
可以接收任意数量参数。
模板别名
以前:
typedef vector<int> Vec;
现在:
using Vec=vector<int>;
constexpr模板
编译期计算:
template<int N>
constexpr int factorial()
{
return N*factorial<N-1>();
}
21. 模板优缺点
优点
1. 代码复用
减少重复代码。
2. 类型安全
编译期检查。
3. 高性能
没有运行时开销。
缺点
1. 编译时间增加
模板代码复杂:
编译慢。
2. 错误信息复杂
模板报错:
可能几十页。
3. 代码膨胀
不同类型:
生成不同函数。
例如:
vector<int>
vector<double>
vector<char>
都会生成代码。
22. 总结
C++模板是C++泛型编程的核心。
掌握模板需要理解:
-
函数模板
-
类模板
-
模板参数推导
-
模板实例化
-
typename
-
模板特化
-
偏特化
-
非类型模板参数
-
可变参数模板
-
STL模板设计思想
一句话总结:
模板让C++能够写出与类型无关的通用代码,在编译阶段生成高性能代码,是STL和现代C++泛型编程的基础。