浅谈模板及模板推导

 / 正序浏览   © 文章版权由 shizheng_123 解释,禁止匿名转载

作者:shizheng_123 2024-5-24 00:45:26
跳转到指定楼层

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x

假设我们需要一个求和函数,我们可能会写成这样:

double Add(int a, int b){ return a + b; }

当用户传递给该函数两个整型实参时,函数正常运行。

但是当用户传递两个double类型,比如Add(2.5, 3.6),或者一个int一个double,比如Add(2, 3.6),或者更多其它类型的组合的时候,编译器便找不到相匹配的函数。

于是,我们不得不使用重载,将可能出现的情况一一列举:

double Add(int a, int b) { return a + b; } double Add(double a, double b) { return a + b; } double Add(int a, double b) { return a + b; } double Add(double a, int b) { return a + b; } ... ...

我们发现,上面那么多的重载函数,除了参数的类型不同以外,其它的所有部分都是一样的。

那么有没有一种方法,可以代替我们进行这些廉价的体力劳动呢?

答案就是:模板

模板的一般形式如下

template<typename T, typename U> // 定义了两种类型 T U double Add(T a, U b) // 使用类型 T U 分别定义了形参 { return a + b; }

其中template<>里面typename X的个数是可选的,可以是一个,也可以是多个。

模板是怎么实现自定义类型的呢?

具体的流程可以这样理解:

当我们在IDE写下这样的代码:

#include <iostream> template<typename T, typename U> double Add(T a, U b) { return a + b; } int main(int argc, char *argv[]) { int a = 1; int b = 2; double c = 3.6; Add(a, b); Add(b,c); }

当我们点下编译按钮的时候,编译器所做的事情可以理解为一下两步。

第一遍编译器发现有一个模板函数Add(),然后编译器在整个代码里面找,在哪里调用了这个求和函数。最终编译器在mian找到了两处调用,编译器一看,这两处调用,一次是传了两个int,一次是传了一个int和一个double。

第一遍分析完了,编译器要做的第二步就是,将源码中的模板删掉,然后用具体的参数类型替换掉模板中的T和U类型,也就是说,上面的代码,经过编译器处理之后,就变成了这样:

#include <iostream> double Add(int a, int b) { return a + b; } double Add(int a, double b) { return a + b; } int main(int argc, char *argv[]) { int a = 1; int b = 2; double c = 3.6; Add(a, b); Add(b,c); }

最后,编译器将重写之后的代码再进行编译,所有的函数调用都可以找到相匹配的实现了。

简单来说,编译器做的事情有两件:第一个是找到模板并找到哪些地方调用了模板,第二个就是根据调用模板的实际类型替换掉模板,生成没有模板的代码。最后就可以再次编译运行了。

模板推导

模板推导就是根据传递给模板的实参类型,推导出应该生成的模板代码。

比如:

template<typename T, typename U> // 定义了两种类型 T U double Add(T a, U b) // 使用类型 T U 分别定义了形参 { return a + b; } Add(1,2); // T被推导为int类型

模板推导是在编译器期间做的事情,而推导的规则基本上是符合我们预料的情况。但是具体来说,可以分为三大类型:

按值传递的模板推导按引用传递的模板推导万能引用

本节主要介绍前面两点。

按值传递

按值传递的模板形式如下:

template<typename T> ReturnType Function(T param) { // 函数实现 }

注意模板参数的形式,必须是T param的形式才是表达的按值推导。

因为按值传递的实质是对实参对象进行拷贝,得到实参的一个副本。所以,按值传递的时候,实参的常量性和引用性都会被忽略。

下面是几个模板推导的实例

#include <iostream>using namespace std; template<typename T> void Function(T param) { // 函数实现 } int main(int argc, char *argv[]) { int a = 1; const int b = 2; int &ra = a; const int &rb = b; int *pa = &a; const int *pb = &b; const int *const cpb = &b; Function(a); // T 推导为int Function(b); // T 推导为int,忽略b的常量性 Function(ra); // T 推导为int,忽略引用性 Function(rb); // T 推导为int,忽略常量性和引用性 Function(pa); // T 推导为int*,int*和int是不同的类型, Function(pb); // T 推导为const int * // 这里的const并不是pb本身的常量性,而是pb所指对象的常量性,所以得以保留 Function(cpb); // T 推导为const int *,和上一条对比,cpb本身的常量性被忽略了 }

按引用传递

如果你想要模板推导为引用类型,那么你的模板应该是这样的:

template<typename T> ReturnType Function(T& param) { // 函数实现 }

以下是几个实例:

#include <iostream>using namespace std; template<typename T> void Function(T& param) // 传递引用的模板 { // 函数实现 } int main(int argc, char *argv[]) { int a = 1; const int b = 2; int &ra = a; const int &rb = b; int *pa = &a; const int *pb = &b; const int *const cpb = &b; Function(a); // T 推导为int Function(b); // T 推导为const int Function(ra); // T 推导为int,忽略ra引用性 Function(rb); // T 推导为const int,忽略rb引用性 Function(pa); // T 推导为int*, Function(pb); // T 推导为const int * Function(cpb); // T 推导为const int *const,和上一条对比,cpb本身的常量性被忽略了 }

以上推导的规则就是,如果传递进去的是一个值,那么就生成对该值类型的引用模板,因为引用参数可以接受值类型参数。如果传递进去的是一个引用类型,那么模板将忽略实参的引用性(因为模板里已经明确写了T&,所以实参的引用性可以忽略不看),然后再进行推导。比如,传递进去是rb,rb的类型是const int &,忽略引用性就剩下const int,这就是 T 被推导的类型了。

关于推导数组类型和函数指针类型

对于这两种,不作更多的解释。大家记住就好,模板推导这东西,本身就没啥固定的规则可言,基本的原则就是要符合用户的预期。

数组模板:

#include <iostream>using namespace std; template<typename T> void ArrayPattern1(T& param) // 数组模板 { // ... } template<typename T, size_t N> void ArrayPattern2(T (&param)[N]) //数组模板,并推导出数组元素个数 { // ... } template<typename T> void PrintType(T param) { } int main(int argc, char *argv[]) { int a[10]{ 0 }; ArrayPattern1(a); // T的类型为 int[10] , param的类型为 int(&)[10] ArrayPattern2(a); // T的类型为 int, param的类型为 int(&)[10] PrintType(a); // 按值传递,a退化为指针,丢失了数组元素个数的信息 }

函数模板

函数模板既可以按值传递,也可以按引用传递。效果是一样的。

分享:

成为第一个回答人

高级模式 评论
您需要登录后才可以回帖 登录 | 立即注册
Archiver 手机版 小黑屋
Copyright © 2001-2024 Comsenz Inc.  Powered by Discuz! X3.5  沪ICP备2021008143号-5