模板的语法
template<typename T>
template<class T>
以上两种语法都可以,在此处typename和class可互相替换,没有区别
模板是支持C++泛型编程的关键语法,泛型编程是C与C++的明显区别之一
这两种语法都可以在类模板或函数模板中使用
实例(类模板):
template <typename T>
class Box {
public:
Box(T val) : val_(val) {}
T getVal() const {
return val_;
}
private:
T val_;
};
int main() {
Box<int> intBox(123); // 使用int类型
Box<double> doubleBox(456.789); // 使用double类型
std::cout << "Box<int>: " << intBox.getVal() << std::endl;
std::cout << "Box<double>: " << doubleBox.getVal() << std::endl;
return 0;
}
实例(函数模板):
template <typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
int main() {
int i = max(3, 7); // 使用int类型
double d = max(6.34, 18.523); // 使用double类型
std::cout << "Max int: " << i << std::endl;
std::cout << "Max double: " << d << std::endl;
return 0;
}
以上即是模板的简单使用
关于模板的注意事项
-
模板一般不支持分离编译
i. 分离编译时,首先因为分离出去的定义也是模板,模板在编译时并未进行实例化,故此分离出去的定义(函数)是不进入符号表的
ii. .h文件和.cpp文件(即声明和定义)在编译成.obj文件前并不会进行交互
iii. 因此在进行链接时会出现声明找不到定义的链接错误-
虽然一般不支持分离编译,但如果硬要进行分离编译也可以,(极其不建议使用,大多时候没必要多此一举!)方法:使用显式模板实例化,即手动给出所有可能的模板参数类型,先对模板进行实例化,此时存在明确的定义了则不会发生链接错误
实例如下:
头文件中声明模板:// my_template.h #include <iostream> template <typename T> class Box { public: Box(T val); T getVal() const; private: T val_; };
另一个源文件中定义模板并显式实例化模板:
// my_template.cpp #include "my_template.h" template <typename T> Box<T>::Box(T val) : val_(val) {} template <typename T> T Box<T>::getVal() const { return val_; } // 显式实例化模板 template class Box<int>; template class Box<double>;
源文件中使用模板实例:
// main.cpp #include "my_template.h" int main() { Box<int> intBox(123); // 使用int类型 Box<double> doubleBox(456.789); // 使用double类型 std::cout << "Box<int>: " << intBox.getVal() << std::endl; std::cout << "Box<double>: " << doubleBox.getVal() << std::endl; return 0; }
这种方式可以实现模板的分离编译,但是它有一些限制。比如,必须在编译时知道所有可能的模板参数类型,因为需要为每种类型显式实例化模板。此外,这种方式可能会增加编译时间和生成的代码大小,因为编译器需要为每种类型生成模板的实例。
-
-
typename的其它用法:
-
在模板中指定依赖类型:当我们需要使用一个依赖模板参数的类型时则需要使用typename关键字来告诉编辑器这是一个类型而不是一个值。例如:
template <typename T> class MyClass { public: typedef typename T::SubType SubType; SubType* ptr; // ... }; // 在这个例子中,T::SubType是一个依赖于模板参数T的类型。如果我们不使用typename关键字, // 编译器可能会误认为T::SubType是一个静态成员变量,而不是一个类型。 // 因此,我们需要使用typename关键字来明确地告诉编译器T::SubType是一个类型。
-
在模板中使用嵌套模板: 我们需要使用一个依赖于模板参数的模板时,我们需要使用typename关键字来告诉编译器这是一个模板,而不是一个值。例如:
template <typename T> class MyClass { public: typename T::template MyNestedTemplate<int> myVar; // ... }; // 在这个例子中,typename std::vector<T>::iterator是一个依赖于模板参数T的模板。 // typename关键字告诉编译器std::vector<T>::iterator是一个模板,而不是一个值。
-
-
关于模板函数
- 模板函数其实本质只是类似图纸,实际函数调用时会根据类型生成对应新的函数并非直接调用该模板函数,而是编译器生成新的函数
- 模板函数传参时不能进行隐式类型转换,否则在推导类型T时会产生矛盾
- 调用模板函数时,可以直接进行传参,进行隐式实例化模板也可以通过函数名<类型>(参数)指定参数类型的方式进行显式实例化模板
- 当T未出现在参数列表里,仅出现在函数体内时则必须显示实例化该模板函数
- 如果有同名函数与模板函数同时存在时,则是先优先匹配调用已有的同名函数。使用模板函数时,都会优先使用更合适的模板函数
-
关于类模板
- 使用方式上与模板函数基本相同
- 类模板使用时则必须显式实例化模板
- 类模板一般不支持分离编译,故模板所在文件为.hpp后缀
-
关于非类型模板参数(仅做了解),仅支持整型,可提供默认参数
template<class T, size_t N = 10>
关于模板的进阶用法——模板的特化(针对某些类型进行特殊化处理)
其实特化目前不常用,针对特殊情况可以再了解,这里进行简单介绍
模板的特化的作用大多还是重在提高了代码的可读性,常用于在不同的类型上实现相同的逻辑和功能,例如:在写一个比较器时,我们预期是比较两个值的大小,但当传入的值是一个指针时,如日期的指针,我们直接比较大小显然是不对的,需要解引用比较日期的大小
关于语法细节,如下:
template<class T>
bool Greater(T left, T right){
return left > right;
}
//函数模板的特化
template<>//模板中不写东西
//将类型加在模板函数名与形参之间的尖括号中,当匹配的为该类型时则直接使用该模板
bool Greater<Date*>(Date* left, Date* right){
return *left > *right;
}
tips:模板的特化不能单独使用,必须得存在原模板才可进行特化。
上述进行具体类型特化的方式被称为全特化,当存在多个模板参数时,可以选择进行偏特化(即部分特化),如:
template <class T1, class T2>
class Date
{
private:
/* data */
public:
Date() { cout << "Date<T1,T2>" << endl; }
};
//针对某种类型进行特化
//例如针对指针类型进行特化(即偏特化)
template <class T1, class T2>
class Date<T1 *, T2 *>
{
private:
/* data */
public:
Date() { cout << "Date<T1*,T2*>" << endl; }
};
这里针对指针类型进行了部分特化,当传入的参数为指针类型时则会使用该模板
总结
- 模板的优势
- 模板将代码进行了复用,减少了重复代码的冗余——可重用性
- 提高了代码的灵活性,重复的代码生成工作交由了编译器
- 模板的局限性
- 模板的错误很复杂,一旦出错很难定位到具体的错误
- 模板是在编译时进行实例化,使用大量模板的代码可能会导致编译时间增加