当前位置: 首页 > news >正文

C++函数模板详解

目录

如何使用函数模板?

模板的特化

全特化

偏特化

模板不要声明和定义分离


函数模板不是一个实在的函数,编译器不能为其生成可执行代码。定义函数模板后只是一个对函数功能框架的描述,当它具体执行时,将根据传递的实际参数决定其功能。

如何使用函数模板?

在我们学习模板之前,如果需要写一个整型数据交换的函数,我们会这样写

void Swap(int& x, int& y)
{int tmp = x;x = y;y = tmp;
}

运行结构如下

这样的确实现了整型变量数据的交换,但是如果我还需要交换其他类型呢?比如我又需要交换两个double类型的数据,我们又要是实现一个函数,如下

void Swap(double& x, double& y)
{double tmp = x;x = y;y = tmp;
}

如果我们还需要交换两个char类型或者string类型的变量值,我们需要不断地重复写这样类似的函数,非常非常麻烦,且显得冗余。于是我们的函数模板就在这里发挥巨大作用了。

此时只需要定义一个模板类型参数就可以完美解决这个问题,把Swap函数里的参数改为模板类型参数,那一个可以完成交换功能的模具就产生了,它可以交换我们想要交换的类型

template<class T>
void Swap(T& x, T& y)
{T tmp = x;x = y;y = tmp;
}

我们看看效果

此时我们看到只需要一个函数模板就可以实现多类型数据的交换。总的来说,函数模板就是编译器用使用方式产生特定具体类型函数的模具,所以其实模板就是将我们本来应该重复做的事情交给了编译器。

模板的特化

由上面内容我们可以知道模板函数会自动匹配传过来的参数类型,那我们尝试写一个比较大小的函数,通过我们对模板的初步认识,写一个比较大小的模板函数简直不要太简单,如下:

template<class T>
bool Less(T left, T right)
{return left < right;
}

我们比较两组数据看看效果,显然是完全符合我们的要求的

那假如我要比较指针指向的数据,也是如上面的案例一样直接把指针传过去让函数自己识别类型然后比较吗?那我们试一下测试以下代码看看是否可行

#include<iostream>
using namespace std;template<class T>
bool Less(T left, T right)
{return left < right;
}int main()
{int* a = new int(10), * b = new int(20);int* c = new int(8), * d = new int(3);int* f = new int(30), * g = new int(50);cout << Less(a, b) << endl;cout << Less(c, d) << endl;cout << Less(f, g) << endl;return 0;
}

其实我们可以想到,我传过去的参数是指针类型,那应该比较的是地址的大小,看看三次运行结果

嗯?三次结果均不一样,这是为什么?其实答案就藏在空间申请底层逻辑那里,我们平时定义的变量以及函数栈帧的调用销毁都是用栈上的空间,从栈拿的空间是自上往下的,也就是地址越来越小,而我们动态申请的空间比如上面new出来的指针变量指向的空间,是从堆上申请的,从堆上申请的空间是自下而上,地址越来越大的,但这仅仅是从整体上来看是如此,并非绝对的,我们想一想,可能我们刚刚在这里申请好空间,前面有部分的空间就被释放了呢,那我们是不是就可以复用前面被释放掉的空间?这是不是就会导致每次的空间地址不一样,才会导致我们运行了三次得到了三次截然不同的结果。那我们比较指针地址还有意义吗?显然是没有意义的。

那有人就问了,我们是要比较指针指向的数据才对啊,那比较前解引用不就好了吗,像这样

template<class T>
bool Less(T left, T right)
{return *left < *right;
}

我们再运行三次看看结果

OK,现在完全一致,符合要求,但这又面临了另一个大问题,我们在比较前对变量解引用了呀,解引用不是只能对指针变量使用吗?那我们如果要比较int、double、char等等这些类型是不是就会出错,那我们要比较指针变量就不要用比较前解引用的方法了,此时就要用到“模板的特化”了,所谓“特化”,其实就是特殊化,也就是“特殊对待”某个类型,那么指针类型就是我们现在要特殊关照的对象。我们对它单独处理,如下

template<class T>
bool Less(T left, T right)
{return left < right;
}template<>
bool Less<int*>(int* left, int* right)
{return *left < *right;
}

函数模板并没有实例化,需要根据我们传递的参数类型才能进行下一步编译,而特化就相当于告诉编译器,如果我传过去的参数是我特化的类型,那你就调用我特化的函数。

细心的朋友可能也会察觉到,为什么我第一个函数模板的形参这样写?这样写形参无疑要多拷贝,内置类型还好,如果是超级大的自定义类型呢?是不是十分影响运行效率啊?而且比较大小又不需要改变变量值,那我们是不是应该加const和引用呢?如下这样

template<class T>
bool Less(const T& left, const T& right)
{return left < right;
}

是的,的确可以且应该这样的,但这也涉及到一个十分坑的点,我们的函数模板参数改了,下面特化的函数参数也要跟着改变,有人就会这样子改,这是一个很难观察到的错误!

template<class T>
bool Less(const T& left, const T& right)
{return left < right;
}template<>
bool Less<int*>(const int* & left, const int* & right)//错误
{return *left < *right;
}

在说明错误位置之前,我们先来看一个前置知识点,下面三个const修饰的分别是什么

const int* p1; //修饰 *p1
int const* p2; //修饰 *p2
int* const p3; //修饰  p3

因此,const在*的左边和右边修饰的东西完全不同,再看上面错误的代码,我们真正const要修饰的应该是left和right,所以应该把const放在类型后面,如下

template<>
bool Less<int*>(int* const & left, int* const & right)//正确
{return *left < *right;
}

以上只是对int*类型的特化,如果我们想要比较所有指针变量指向的内容,可以对指针类型做一个函数模板,如下:

template<class T>
bool Less(T left, T right)
{return left < right;
}template<class T>
bool Less(T* left, T* right)
{return *left < *right;
}

全特化

前面我们传递的参数都是同一个类型的,那如果传递的是两个不同类型的参数,是怎么样的呢?先看看以下代码,传一个in和t一个double类型的参数

template<class T1, class T2>
void test1(T1 a, T2 b)
{cout << "template<class T1, class T2>" << endl;
}

模板类型可以写多个,因此要传多个不同类型的参数,只需要写同数量的模板参数即可。那什么是全特化呢?全特化就是两个模板类型都实例化,如下

template<class T1, class T2>
void test1(T1 a, T2 b)
{cout << "template<class T1, class T2>" << endl;
}//全特化
template<>
void test1(int a, char ch)
{cout << "template<>" << endl;
}

编译器会根据参数选择最匹配函数进行调用,我们来看看再次传一个int类型和double类型,会调用哪个函数,显然是全特化的那个,如下

偏特化

偏特化又称为半特化,与全特化的区别是什么呢?顾名思义,全特化是全部特殊化,偏特化是只特殊化一部分,比如我们明确了第二个参数的类型就是char类型,那就可以使用偏特化,如下

//偏特化
template<class T1>
void test1(T1 a, char ch)
{cout << "template<class T1>" << endl;
}

模板不要声明和定义分离

没有极其特殊的需求千万不要模板声明和定义分离,要写在同一个文件里,否则如常规函数一样声明和定义分离会导致链接错误,当然,如果实在是想要声明和定义分离,可以使用显示实例化的方法,也就是在模板下面写你要声明定义分离的实例化版本,这样就可以声明和定义分离了,如下

template<class T>
bool Less(const T& left, const T& right)
{return left < right;
}template<>
bool Less<int>(const int& x, const int& y);

这样int版本的模板就可以声明定义分离了,但是其他类型的依旧不可以分离,如果要兼容其他类型,那还得需要再继续写实例化版本,相当的麻烦,因此尽量不要分离

http://www.dtcms.com/a/424860.html

相关文章:

  • ros_control 中 hardware_interface 教程
  • 做视频网站教程wordpress页面添加描述
  • 青岛专业设计网站公司怎样做关键词排名优化
  • Spring 统一功能处理 - 拦截器与适配器
  • 浙江省建设厅干部学校门户网站vi设计与网站建设招标文件
  • 网站群建设方案黄金网站大全免费2023
  • C++指针与引用详解
  • 国内做免费视频网站哪些网站可以做淘宝店招
  • 生物化学Learning Track(9)核酸的结构和功能
  • 绿园区建设局网站惠州seo报价
  • 建设360导航网站的目的是什么意思网站推广的建议
  • 软考中级习题与解答——第十四章_UML建模(1)
  • 网易做相册的网站建站之星建出来的网站如何上传
  • 网站asp设计作品硬件开发平台是指什么
  • 深圳做网站报价高校建设网站的特色
  • 学习日报 20250929|缓存击穿及其解决方案
  • Dify 源码本地部署启动及完整步骤解析
  • 有效的字母异位词(二)
  • 简单大气食品农业网站源码站长如何做视频类网站
  • 滕州网站建设 助企网络公司管理系统怎么写
  • 做网站为什么用php网站建设遇到哪些危险
  • 基于扩散模型的任意尺度磁共振图像超分辨率重建:通过渐进式k空间重建与去噪实现|文献速递-文献分享
  • RT调度器
  • 网站生成工具百度域名多少钱
  • 网站移动端是什么问题网站开发属于商标哪个类别
  • 教师做课题可以参考什么网站建设银行网站上的的研究报告
  • 数据库事务中的脏读、不可重复读、幻读
  • 网站的绝对路径怎么做西安站
  • NuttX 实现细节指南
  • 苏州建行网站首页程序员和网站建设