1.函数模板
1.1 函数模板的概念
写一次逻辑,让编译器针对不同类型/值生成具体代码。
模板本身不是函数或类,实例化后才产生真正的代码。
1.2函数模板格式,比如:
template <typename T> T add(T a, T b) { return a + b; }add 本身不是函数,而是函数模板。调用 add(1,2) 时,编译器生成 int 版本的函数;
调用 add(1.0 , 2.0) 时,生成 double 版本的函数。
(typename 也能写成 class,但是不能写成struct)
多个模板参数:
template <typename T1, typename T2> void printPair(T1 a, T2 b) { std::cout << a << ", " << b << '\n'; }1.3函数模板的原理:
编译器看到函数模板定义 ->根据实参推导模板参数,确定 T 的类型->生成对应具体函数-> 对该具体函数进行类型检查和代码生成。
例如:
template <typename T> T myMax(T a, T b) { return a > b ? a : b; } int main() { myMax(1, 2); // 生成 int myMax(int, int) myMax(1.5, 2.5); // 生成 double myMax(double, double) }1.4函数模板的实例化
函数模板实例化是指编译器根据模板实参,把“模板”变成“具体函数的过程。
隐式实例化-调用时由编译器自动推导模板参数的类型并生成函数
template<typename T> T add(const T& a,const T& b) { return a + b; } int main() { int a1 = 1, a2 = 2; double b1 = 1.1, b2 = 2.2; add(a1, a2);// 调用时会隐式实例化 add(b1, b2); return 0; }显式实例化-手动写出模板形参
上面的代码如果在调用时写add(a1,b2); 就会报错,因为第一个参数传给模板,识别出类型为int,但第二个参数类型却是double,而模板函数不允许自动类型转换,所以编译器最终无法确定T到底是int 还是double 类型。
- 要想解决这个问题,可以手动强制类型转换,如 add(a1,(int)b2);
- 或者用显式实例化,这样写:add<int>(a1,b2);
1.5非模板函数与模板函数重载:
(1)一个非模板函数和一同名的模板函数可以同时存在,并且该函数模板还会实例化出这个非模板函数,比如:
template<typename T> void print(T x) // 模板函数 { //... } void print(int x) // 非模板函数 { //... }函数模板 print( T ) 在 T = int 时,会实例化出一个函数: void print<int>(inx) {...} ,这个函数可以和 void print(int x) {...} 同时存在。
(2)模板函数和非模板函数同时存在时,会优先调用非模板函数,比如:
template<typename T> // 模板 T add(const T& a, const T& b) { return a + b; } int add(const int& a, const int& b) // 非模板 { return a + b; }如果传的参数都是整型,会优先调用 int add 函数,但如果就是想调用模板,可以用显式实例化,比如:
int main() { //int r1 = add(1,2); // 调用非模板的函数 int r2 = add<int>(1, 2); // 调用模板 return 0; }2.类模板:
2.1 类模板的格式和实例化
template<typename T> class Stack { vector<T> data; // 用 vector 存数据 public: // 入栈 void push(const T& val) { data.push_back(val); } // ... }; int main() { //Stack s1; // err //Stack s2; // err // 存 int 的栈 Stack<int> s1; // 存 double 的栈 Stack<double> s2; return 0; }注意:类模板必须显式实例化
模板不建议声明和定义分离