模板
什么是模板
模板是一种通用的编程工具,允许程序员编写通用的类或函数,以便在不同的数据类型上进行操作。模板可以让程序员编写一次代码,然后根据需要在编译时生成特定类型的代码实例。这种特性统称为泛型编程。
void Swap(int& a, int& b) { int temp = a; a = b; b = temp; } void Swap(double& a, double& b) { int temp = a; a = b; b = temp; }
在上面例子中可以看出,当我想要进行两个对象之间的交换时,由于每次交换对象的类型不同,所以要重复写多个
Swap
函数,为了解决这个问题,模板就出现了。
template <typename T> void Swap(T& a, T& b) { T temp = a; a = b; b = temp; }
使用模板的优势:
- 代码重用:可以编写通用的代码,减少重复编写类似功能的代码。
- 类型安全:编译器可以在编译时进行类型检查,确保类型安全性。
- 灵活性:可以在不同的数据类型上使用想同的模板,提高代码的灵活性和可扩展性。
函数模板
定义
函数模板:允许编写通用的函数,可以用于处理不同类型的数据。函数模板使用template <typename T>
或template <class T>
来定义,T
是模板参数,表示可以是任意类型。函数模板的实例化
函数模板的实例化是指在使用函数模板时,编译器根据具体的数据类型生成实际的函数代码。
隐式实例化:是指在程序中使用函数模板时,编译器自动根据传递的参数类型推断出模板参数的类型,并生成对应的函数。
template <typename T> T add(const T& a, const T& b) { return a + b; } int main() { int x1 = add(1, 2);// 隐式实例化为 int add(const int& a, const int& b) double x2 = add(3.1, 4.2);// 隐式实例化为 double add(const double& a, const double& b) return 0; }
显示实例化:在函数名后的<>中指定模板参数的实际类型
int main() { int x1 = add<int>(1, 2); double x2 = add<double>(3.1, 4.2); }
模板参数的匹配原则
精确匹配优先原则:如果存在完全匹配的模板实例化,编译器会优先选择完全匹配的模板函数。
#include <iostream> using namespace std; template <typename T> void foo(T a) { cout << "foo(T a) is called" << endl; } template <> void foo<int>(int a) { cout << "foo<int>(int a) is called" << endl; } int main() { foo(5); // 输出 "foo<int>(int a) is called" foo(3.14); // 输出 "foo(T a) is called" return 0; }
在上述代码中,定义了一个函数模板
foo
,同时还定义了一个特化版本foo<int>
。当调用foo(s)
时,由于存在精确匹配的特化版本,会选择特化版本而不是通用版本。模板参数推断:编译器会根据传递的参数类型推断模板参数的类型。在函数模板调用时,如果模板参数类型可以从函数参数类型推断出来,则编译器会自动推断模板参数的类型
#include <iostream> using namespace std; template <typename T> void foo(T a) { cout << "foo(T a) is called" << endl; } int main() { foo(5); // 输出 "foo(T a) is called" foo(3.14); // 输出 "foo(T a) is called" return 0; }
当调用
ffo(5)
时,由于5
是一个int
类型,函数模板会选择匹配int
类型的实例化版本;当调用foo(3.14)
时,由于3.14
是一个double类型,函数模板会选择匹配double
类型的实例化版本。注意:这种方式无法编译通过
template<class T> T Add(const T& left, const T& right) { return left + right; } int main() { Add(1, 1.1); return 0; }
由于参数列表中中有一个
T
,编译器无法确定此处到底该将T
确定为int
或者double
类型而报错。
类模板
示例
template <class T> class Container { public: Container(int size) { data = new T[size]; _size = size; } ~Container();//类中声明,类外定义 T& operator[](int index) { return data[index]; } int getSize() const { return size; } private: T* data; int _size; }; //注意:类模板中函数放在类外进行定义时,需要加模板参数列表 template <class T> Container<T>::~Container() { delete[] date; }
在上述代码中,我们定义了一个类模板
Container
,其中存储的元素类型由模板参数T
指定。当我们使用Container<int>
实例化类模板时,会生成一个存储int
类型元素的容器类;当我们使用Container<double>
实例化类模板时,会生成一个存储double
类型元素的容器类。类模板的实例化
类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板不是真正的类,而实例化的结果才是。
//Container是类名,Container<int>才是类型 Container<int> s1; Container<double> s2;
非类型模板参数
- 类型形参:出现在模板参数列表中,跟在
class
或typename
之后的参数类型名称。- 非类型参数:用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
namespace wzh{ template<class T, size_t N = 100> class Array { public: private: T _array[N]; }; }
模板特化
模板特化就是在原模版类的基础上,针对特殊类型所进行特殊化的实现方式
template<class T> bool less(T& left, T& right) { return left < right; } int main() { cout << less(1, 2) << endl;//可以比较,结果正确 //Date是自定义类型的一个日期类 Date d1(2024, 5, 20); Date d2(2024, 5, 21); cout << less(d1, d2) << endl;//可以比较,结果正确 Date* p1 = &d1; Date* p2 = &d2; cout << less(p1, p2) << endl;//可以比较,结果错误 }
由上可知,
less
在绝大多数的情况下是可以正常比较,但对于某种特殊的场景下任然会出错。在less(p1,p2)
的比较中,它们比较的是指针的地址,而并不是比较它们所指向的值。此时就需要模板的特化。
函数模板特化
1.必须要有一个基础的函数模板
2.关键字template后面接一对空的<>
template<class T> bool less(T& left, T& right) { return left < right; } //对less进行特化 template<> bool less<Date*>(Date* left, Date* right) { return *left < *right; } int main() { cout << less(1, 2) << endl;//可以比较,结果正确 //Date是自定义类型的一个日期类 Date d1(2024, 5, 20); Date d2(2024, 5, 21); cout << less(d1, d2) << endl;//可以比较,结果正确 Date* p1 = &d1; Date* p2 = &d2; cout << less(p1, p2) << endl;//这里调用的是特化的那个版本,可以比较,结果正确 }
注意:在一般的情况下,如果函数模板遇到不能处理的类型,为了实现简单通常都是将该函数直接给出
bool less(Date* left, Date* right) { return *left < *right; }
类模板
类模板又分为全特化和偏特化
全特化
template<class T1, class T2> class Date { public: Date() { cout << "Date<T1, T2>" << endl; } private: T1 _d1; T2 _d2; }; //全特化 template<> class Date<int, char> { public: Date() { cout << "Date<int, char>" << endl; } private: int _d1; char _d2; };
只有当Date的<>中的类型都匹配时,才会走全特化这条路
偏特化
template<class T1, class T2> class Date { public: Date() { cout << "Date<T1, T2>" << endl; } private: T1 _d1; T2 _d2; }; //偏特化 template<class T1> class Date<T1, int> { Date() { cout << "Date<T1, int>" << endl; } }; void Test() { Date<int, int> d1; Date<int, char> d2; }
模板的分离编译
模板的分离编译就是模板的声明与定义分离开,在头文件中进行声明,源文件中完成定义。
模板不能进行分离编译是因为编译器只在需要使用模板时才会对其进行实例化。
总的来说就是:
- 模板是一种在编译时生成代码的机制,编译器在遇到模板时并不会生成实际的代码,而是生成一个模板定义。
- 当需要使用模板时,才会进行模板实例化。
- 如果将模板的声明和定义分离开,编译器在遇到使用模板的地方时,无法找到模板的定义,无法进行实例化。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!