1、背景
为了解决强类型语言的严格性与灵活性的冲突,将类型抽象
- 带参数的宏定义(原样替换)
- 函数重载(函数名字相同,参数不同)
- 模板(将数据类型作为参数)
语言类型
- 强类型语言:C++/C/Java,有类型定义,能进行类型安全检查,编译型语言,安全但不灵活
- 弱类型语言:js/python,没有严格的类型,解释型语言,错误留到运行时,灵活但不安全
2、模板的特征
模板的定义形式
template <class/typename T>
模板的类型
- 函数模板
- 类模板
2.1、函数模板
template <模板参数表> //模板参数列表不能为空 返回类型 函数名(参数列表) { 函数体 } template <class T> T func(T t)
函数模板与模板函数的关系
函数模板通过模板参数推导(实例化)为模板函数。
// 隐式实例化:编译器自动推导。 cout << add(i1, i2) << endl; // 由i1,i2都是int,自动生成函数 int add(int,int) // 显式实例化 cout << add<double>(d1, d2) << endl;
函数模板与普通函数间的关系
- 函数模板可以与普通函数进行重载,而且普通函数优先于函数模板执行
- 函数模板与函数模板间也是可以进行重载的
模板的特化:统一的函数模板不能适用的类型时,模板在特定类型下的实现
- 偏特化:特化部分参数。
- 全特化:特化所有参数。函数模板只有全特化。
#include <string.h> #include <iostream> using std::cout; using std::endl; template <class T> T add(T x, T y) { cout << "T add(T,T) " << endl; return x + y; } //模板的全特化,是函数模板。也可以函数重载,普通的函数 template <> const char * add<const char *>(const char * px, const char * py) { char * ptmp = new char[strlen(px) + strlen(py) + 1](); strcpy(ptmp, px); strcat(ptmp, py); return ptmp; } int main() { const char * ps1 = "hello"; const char * ps2 = "world"; const char * pstr = add(ps1, ps2); cout << pstr << endl; delete pstr; int i1 = 1, i2 = 2; double d1 = 1.1, d2 = 2.2; cout << add(i1, i2) << endl; cout << add(d1, d2) << endl; return 0 }
模板头文件与实现文件
C++头文件都是使用模板进行编写的,而模板的的特点是必须知道所有实现才能进行正常编译。模板不能将声明与实现分开,模板的类型是inline
函数。模板的运行机制是在编译时,通过实参传递时,进行参数推导。当头文件与实现文件分离时,实现文件中没有函数的调用,所以在编译时并不会进行参数推导,没有函数的产生。
函数声明与实现分离:
#ifndef __ADD_H__ #define __ADD_H__ template <class T> T add(T x, T y); #include "add.tcc" //实现函数声明与实现分离 #endif
模板的参数类型
- 类型参数,class T
- 非类型参数,常量表达式,即整型 bool / char / short / int / long (float, double不行)
#include <iostream> using std::cout; using std::endl; template <class T, int kBase=10> //double报错 T multiply(T x, T y) { return x * y * kBase; } void test0() { int i1 = 10, i2 = 11; //常量的传递是在函数调用时完成的 cout << multiply<int, 10>(i1, i2) << endl; cout << multiply<int, 20>(i1, i2) << endl; cout << multiply(i1, i2) << endl; // 若没有给int给默认参数,则无法推导报错 } int main(){ test0(); return 0; }
成员函数模板
class Point { public: template <typename T = int> T func() { return (T)_dx; } private: double _dx; double _dy; };
2.2、类模板
注意类模板的嵌套,一层一层写,成员函数类外定义必须加上模板参数列表。
#include <string> #include <iostream> using std::cout; using std::endl; using std::string; template <class T, size_t kSize =10> class Stack { public: Stack() : _top(-1) , _pdata(new T[kSize]()) {} ~Stack(); T top() const; bool empty() const; bool full() const; void push(const T & t); void pop(); private: int _top; T * _pdata; }; // 如果是类模板,其成员函数在类外定义时,必须加上模板参数列表 template <class T, size_t kSize> Stack<T,kSize>::~Stack() { if(_pdata) { delete [] _pdata; } } template <class T, size_t kSize> T Stack<T, kSize>::top() const { return _pdata[_top]; } template <class T, size_t kSize> bool Stack<T, kSize>::empty() const { return _top == -1; } template <class T, size_t kSize> bool Stack<T, kSize>::full() const { return (kSize - 1) == _top; } template <class T, size_t kSize> void Stack<T, kSize>::push(const T & t) { if(full()) { cout << "stack is full, cannnot push data any more!" << endl; } else { _pdata[++_top] = t; } } template <class T, size_t kSize> void Stack<T, kSize>::pop() { if(empty()) { cout << "stack is empty, no data!" << endl; } else { --_top; } } void test1() { Stack<string> stack; cout << "此时栈是否为空? " << stack.empty() << endl; stack.push(string(2, 'a')); cout << "此时栈是否为空? " << stack.empty() << endl; cout << stack.top() << endl; for(int idx = 1; idx < 11; ++idx) { stack.push(string(2, 'a' + idx)); } cout << "此时栈是否已满? " << stack.full() << endl; while(!stack.empty()) { cout << stack.top() << endl; stack.pop(); } cout << "此时栈是否为空? " << stack.empty() << endl; } int main(void) { test1(); return 0; }
3、可变模板参数
C++ 11 新特性,对参数进行了高度的泛化,可表示 0 到任意个数、任意类型的参数。
- 模板参数包 Args:可以接受任意多个参数作为模板参数
template <typename... Args>
- 函数参数包 args:函数可以接受多个任意类型的参数。要求函数参数包必须唯一,且是函数的最后一个参数。
template<typename... T> void f(T... args)
省略号的作用
- 位于参数左边:打包
... args
- 位于参数右边:解包
args...
获取可变模板参数的个数
sizeof...(Args) sizeof...(args)
3.1、可变模板参数的展开
- 通过递归函数来展开参数包
- 是通过逗号表达式来展开参数包。
3.1.1、通过递归函数展开参数包
参数包在展开的过程中递归调用自己,每调用一次参数包中的参数就会少一个,直到所有的参数都展开为止,当没有参数时,则调用递归终止函数终止递归过程。
#include <iostream> using namespace std; // 递归终止函数 void print() { cout << endl; } // 展开函数 template <class T, class ...Args> void print(T head, Args... rest) { cout << head << " "; // 调用过程,对参数包的展开过程 print(rest...); } // 递归终止函数 template<typename T> T sum(T t) { return t; } // 展开函数 template<typename T, typename ... Types> T sum(T first, Types ... rest) { return first + sum<T>(rest...); } int main() { print(1, 2, 3, "hello", "world"); cout << sum(1, 2, 3, 4) << endl; //10 return 0; }
3.1.2、通过逗号表达式展开参数包
逗号表达式:表达式1, 表达式2
,先执行表达式1
,再执行表达式2
,返回的结果只与表达式2
有关。利用初始化列表来初始化变长数组,在数组构造的过程中展开参数。
#include <iostream> using namespace std; // 1、处理参数包中每一个参数的函数 template <class T> void printarg(T t) { cout << t << endl; } // 2、展开参数包 template <class ...Args> void expand(Args... args) { // 先执行printarg(args)展开参数包并打印参数,再执行逗号表达式得到结果0 // 1、int arr[] = {(printarg(1), 0), (printarg(2), 0), (printarg(3), 0)} // 2、int arr[] = {0, 0 ,0} int arr[] = {(printarg(args), 0)...}; } int main() { expand(1, "hello", 3); return 0; }
将函数作为参数,改写成 lambda
表达式
#include <iostream> using namespace std; template<class F, class... Args> void expand(const F& f, Args&&...args) { //std::initializer_list 接收任意长度的初始化列表,这里用到了完美转发 initializer_list<int>{(f(std::forward< Args>(args)), 0)...}; } int main() { // 只能参数列表中类型的参数 expand([](int i) { cout << i << endl; }, 1, 2, 3); return 0; }