C++重载和模板

重载与模板

函数模板可以被另一个模板或一个普通非模板函数重载。

与往常一样,名字相同的函数必须具有不同数量或类型的参数。

如果涉及函数模板,则函数匹配规则会在以下几方面受到影响:

  1. 对于一个调用,其候选函数包括所有模板实参推断成功的函数模板实例。
  2. 候选的函数模板总是可行的,因为模板实参推断会排除任何不可行的模板。
  3. 与往常一样,可行函数(模板与非模板)按类型转换(如果对此调用需要的话)来排序。当然,可以用于函数模板调用的类型转换是非常有限的。
  4. 与往常一样,如果恰有一个函数提供比任何其他函数都更好的匹配,则选择此函数。但是,如果有多个函数提供同样好的匹配,则:

              ——如果同样好的函数中只有一个是非模板函数,则选择此函数,

               ——如果同样好的函数中没有非模板函数,而有多个函数模板,且其中一个模板比其他                          模板更特例化,则选择此模板。

               ——否则,此调用有歧义。

正确定义一组重载的函数模板需要对类型间的关系及模板函数允许的有限的实参类型转换有深刻的理解。

编写重载模板

作为一个例子,我们将构造一组函数,它们在调试中可能很有用。

我们将这些调试函数命名为debug_rep,每个函数都返回一个给定对象的string表示。

我们首先编写此函数的最通用版本,将它定义为一个模板,接受一个const对象的引用:

//打印任何我们不能处理的类型
template <typename T> 
string debug_rep(const T& t)
{ostringstream ret;ret << t; // 使用T的输出运算符打印t的一个表示形式return ret.str();// 返回ret 绑定的string的一个副本
}

此函数可以用来生成一个对象对应的string表示,该对象可以是任意具备输出运算符的类型。

接下来,我们将定义打印指针的debug_rep版本:


template <typename T> 
string debug_rep(T* p)
{cout << "使用了T*p版本" << endl;ostringstream ret;  // 打印指针本身的值ret << "pointer: " << p;if (p)ret << "" << debug_rep(*p);// 打印p指向的值elseret << " null pointer"; // 或指出ρ为空return ret.str();// 返回ret 绑定的string的一个副本
}

此版本生成一个string,包含指针本身的值和调用debug_rep获得的指针指向的值。

注意此函数不能用于打印字符指针,因为IO库为char+值定义了一个<<版本。此<版本假定指针表示一个空字符结尾的字符数组,并打印数组的内容而非地址值。

我们可以这样使用这些函数:

#include<iostream>
#include<sstream>
using namespace std;
//打印任何我们不能处理的类型
template <typename T> 
string debug_rep(const T& t)
{cout << "使用了const T&t版本" << endl;ostringstream ret;ret << t; // 使用T的输出运算符打印t的一个表示形式return ret.str();// 返回ret 绑定的string的一个副本
}template <typename T> 
string debug_rep(T* p)
{cout << "使用了T*p版本" << endl;ostringstream ret;  // 打印指针本身的值ret << "pointer: " << p;if (p)ret << "" << debug_rep(*p);// 打印p指向的值elseret << " null pointer"; // 或指出ρ为空return ret.str();// 返回ret 绑定的string的一个副本
}int main()
{string s("hi");cout << debug_rep(s)<<endl;
}

对于这个调用,只有第一个版本的debug_rep是可行的。

第二个debug_rep版本要求一个指针参数,但在此调用中我们传递的是一个非指针对象。

因此编译器无法从一个非指针实参实例化一个期望指针类型参数的函数模板,因此实参推断失败。

由于只有一个可行函数,所以此函数被调用。

如果我们用一个指针调用 debug_rep:

cout << debug_rep(&s)<<endl;

两个函数都生成可行的实例:

  1. debug _rep (const string*&),由第一个版本的debug_rep实例化而来,被绑定到string*。
  2. debug_rep(string*),由第二个版本的debug_rep实例化而来,T被绑定到string。

第二个版本的debug rep的实例是此调用的精确匹配。

第一个版本的实例需要进行普通指针到 const指针的转换。

正常函数匹配规则告诉我们应该选择第二个模板,实际上编译器确实选择了这个版本。

#include<iostream>
#include<sstream>
using namespace std;
//打印任何我们不能处理的类型
template <typename T> 
string debug_rep(const T& t)
{cout << "使用了const T&t版本" << endl;ostringstream ret;ret << t; // 使用T的输出运算符打印t的一个表示形式return ret.str();// 返回ret 绑定的string的一个副本
}template <typename T> 
string debug_rep(T* p)
{cout << "使用了T*p版本" << endl;ostringstream ret;  // 打印指针本身的值ret << "pointer: " << p;if (p)ret << "" << debug_rep(*p);// 打印p指向的值elseret << " null pointer"; // 或指出ρ为空return ret.str();// 返回ret 绑定的string的一个副本
}
int main()
{string s("hi");string result = debug_rep(&s); // 将函数返回值存储在result变量中  cout << result << endl; // 打印result变量,即debug_rep函数的返回值  
}

我们打开调试面板,在执行函数调用语句时,直接跳进了第二个模板函数

有人就好奇了为什么会出现第二行的提示

这里的关键在于模板函数的实例化和递归调用。当您调用 debug_rep(&s) 时,由于传递了一个指针,所以编译器会选择第二个模板函数 debug_rep(T* p) 进行实例化。在这个函数内部,当指针 p 不为空时,会递归调用 debug_rep(*p)

递归调用 debug_rep(*p) 时,传递的是指针 p 所指向的值,即字符串 s 的一个引用。因此,编译器会选择第一个模板函数 debug_rep(const T& t) 进行实例化,其中 T 被推导为 std::string

在第一个模板函数内部,cout 语句会首先执行,打印出 "使用了const T&t版本"。接着,函数会使用输出运算符 << 将 t(即字符串 s)插入到一个 ostringstream 对象中,并最终返回这个对象转换成的字符串。

因此,当您运行程序时,会看到 "使用了const T&t版本" 被打印出来,这是因为在递归调用中第一个模板函数被实例化并执行了。

多个可行模板

作为另外一个例子,考虑下面的调用:

const string *sp = &s;
cout << debug_rep(sp) << endl;

此例中的两个模板都是可行的,而且两个都是精确匹配:

  • debug_rep(const string*&),由第一个版本的debug_rep实例化而来,T被绑定到string*。
  • debug_rep(const string*),由第二个版本的debug rep 实例化而来,T被绑定到 const string。

在此情况下,正常函数匹配规则无法区分这两个函数。我们可能觉得这个调用将是有歧义的。

但是,根据重载函数模板的特殊规则,此调用被解析为debug rep(T*),即,更特例化的版本。

设计这条规则的原因是,没有它,将无法对一个const的指针调用指针版本的debug_rep。

问题在于模板 debug rep(const T&)本质上可以用于任何类型,包括指针类型。此模板比debug_rep(T*)更通用,后者只能用于指针类型。没有这条规则,传递const的指针的调用永远是有歧义的。

当有多个重载模板对一个调用提供同样好的匹配时,应选择最特例化的版本。

#include<iostream>
#include<sstream>
using namespace std;
//打印任何我们不能处理的类型
template <typename T> 
string debug_rep(const T& t)
{cout << "使用了const T&t版本" << endl;ostringstream ret;ret << t; // 使用T的输出运算符打印t的一个表示形式return ret.str();// 返回ret 绑定的string的一个副本
}template <typename T> 
string debug_rep(T* p)
{cout << "使用了T*p版本" << endl;ostringstream ret;  // 打印指针本身的值ret << "pointer: " << p;if (p)ret << "" << debug_rep(*p);// 打印p指向的值elseret << " null pointer"; // 或指出ρ为空return ret.str();// 返回ret 绑定的string的一个副本
}
int main()
{string s("hi");const string* sp = &s;cout << debug_rep(sp) << endl;
}

事实证明,确实是调用了T*版本

非模板和模板重载

作为下一个例子,我们将定义一个普通非模板版本的debug_rep来打印双引号包围的string:

string debug_rep(const string& s)
{return '"' + s + '"';
}


现在,当我们对一个string 调用debug_rep时:

string s("hi");cout << debug_rep(s) << endl;


有两个同样好的可行函数:

  • debug_rep<string>(const string&),第一个模板,T被绑定到string*。
  • debug_rep(const string&),普通非模板函数。

在本例中,两个函数具有相同的参数列表,因此显然两者提供同样好的匹配。但是,编译委会选择非模板版本。

当存在多个同样好的函数模板时,编译器选择最特例化的版本,出于相同的原因, 一个非模板函数比一个函数模板更好。

#include<iostream>
#include<sstream>
using namespace std;
//打印任何我们不能处理的类型
template <typename T> 
string debug_rep(const T& t)
{cout << "使用了const T&t版本" << endl;ostringstream ret;ret << t; // 使用T的输出运算符打印t的一个表示形式return ret.str();// 返回ret 绑定的string的一个副本
}template <typename T> 
string debug_rep(T* p)
{cout << "使用了T*p版本" << endl;ostringstream ret;  // 打印指针本身的值ret << "pointer: " << p;if (p)ret << "" << debug_rep(*p);// 打印p指向的值elseret << " null pointer"; // 或指出ρ为空return ret.str();// 返回ret 绑定的string的一个副本
}//打印双引号包围的string
string debug_rep(const string& s)
{return '"' + s + '"';
}int main()
{string s("hi");cout << debug_rep(s) << endl;
}

对于一个调用,如果一个非函数模板与一个函数模板提供同样好的匹配,则选择非模板版本。

重载模板和类型转换

还有一种情况我们到目前为止尚未讨论:C风格字符串指针和字符串字面常量。

现在有了一个接受string的debug_rep版本,我们可能期望一个传递字符串的调用会匹配这个版本。但是,考虑这个调用:
 

cout << debug_rep("hi world!") << endl; // 调用debug_rep(T*)

本例中所有三个debug rep版本都是可行的:

  1. debug rep(const T&),T被绑定到char[10]。
  2. debug rep(T*),T被绑定到const char。
  3. debug rep(const string&),要求从const char*到string的类型转换。

对给定实参来说,两个模板都提供精确匹配——第二个模板需要进行一次(许可的)数组到指针的转换,而对于函数匹配来说,这种转换被认为是精确匹配。

非模板版本是可行的,但需要进行一次用户定义的类型转换,因此它没有精确匹配那么好,所以两个模板成为可能调用的函数。

与之前一样,T*版本更加特例化,编译器会选择它。

#include<iostream>
#include<sstream>
using namespace std;
//打印任何我们不能处理的类型
template <typename T> 
string debug_rep(const T& t)
{cout << "使用了const T&t版本" << endl;ostringstream ret;ret << t; // 使用T的输出运算符打印t的一个表示形式return ret.str();// 返回ret 绑定的string的一个副本
}template <typename T> 
string debug_rep(T* p)
{cout << "使用了T*p版本" << endl;ostringstream ret;  // 打印指针本身的值ret << "pointer: " << p;if (p)ret << "" << debug_rep(*p);// 打印p指向的值elseret << " null pointer"; // 或指出ρ为空return ret.str();// 返回ret 绑定的string的一个副本
}//打印双引号包围的string
string debug_rep(const string& s)
{return '"' + s + '"';
}int main()
{cout << debug_rep("hi world!") << endl; // 调用debug_rep(T*)
}

如果我们希望将字符指针按string处理,可以定义另外两个非模板重载版本:

//将字符指针转换为string,并调用string版本的debug_reg
string debug_rep(char* p)
{return debug_rep(string(p));
}
string debug_rep(const char* p)
{return debug_rep(string(p));
}

缺少声明可能导致程序行为异常

值得注意的是,为了使 char*版本的 debug_rep 正确工作,在定义此版本时,debug_rep (const string)的声明必须在作用域中。否则,就可能调用错误的debug_rep版本:

template <typename T> string debug_rep(const T& t);
template<typename T> string debug_rep(T* p);// 为了使debug_rep(char*)的定义正确工作,下面的声明必须在作用域中
string debug_rep(const string&);string debug_rep(char* p)
{// 如果接受一个const string&的版本的声明不在作用域中,// 返回语句将调用 debug_rep(const T&)的T实例化为string的版本return debug_rep(string(p));
}


通常,如果使用了一个忘记声明的函数,代码将编译失败。

但对于重载函数模板的函数而言,则不是这样。

如果编译器可以从模板实例化出与调用匹配的版本,则缺少的声明就不重要了。

在本例中,如果忘记了声明接受string参数的debug_rep版本,编译器会默默地实例化接受const T&的模板版本。

#include<iostream>
#include<sstream>
using namespace std;
//打印任何我们不能处理的类型
template <typename T> 
string debug_rep(const T& t)
{cout << "使用了const T&t版本" << endl;ostringstream ret;ret << t; // 使用T的输出运算符打印t的一个表示形式return ret.str();// 返回ret 绑定的string的一个副本
}template <typename T> 
string debug_rep(T* p)
{cout << "使用了T*p版本" << endl;ostringstream ret;  // 打印指针本身的值ret << "pointer: " << p;if (p)ret << "" << debug_rep(*p);// 打印p指向的值elseret << " null pointer"; // 或指出ρ为空return ret.str();// 返回ret 绑定的string的一个副本
}string debug_rep(char* p)
{
// 如果接受一个const string&的版本的声明不在作用域中,
// 返回语句将调用 debug_rep(const T&)的T实例化为string的版本
return debug_rep(string(p));
}int main()
{cout << debug_rep("hi world!") << endl; // 调用debug_rep(T*)
}

 

在定义任何函数之前,记得声明所有重载的函数版本。这样就不必担心编译器T由于未遇到你希望调用的函数而实例化一个并非你所需的版本。
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/790062.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024第八届全国青少年无人机大赛暨中国航空航天科普展览会

2024第八届全国青少年无人机大赛暨中国航空航天科普展览会 邀请函 主办单位&#xff1a; 中国航空学会 重庆市南岸区人民政府 招商执行单位&#xff1a; 重庆港华展览有限公司 为更好的培养空航天产业人才&#xff0c;汇聚航空教育产业创新科技&#xff0c;丰富和完善航…

某音a_bogus 流程vmp分析

声明 本文章中所有内容仅供学习交流,抓包内容、敏感网址、数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除! 目标网站 仅研究。网站链接自己去找。 前言 这里a_bogus 又是个vmp。 还是个多层嵌套…

​LeetCode解法汇总1379. 找出克隆二叉树中的相同节点

目录链接&#xff1a; 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目&#xff1a; https://github.com/September26/java-algorithms 原题链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 给你两棵二叉树&#xff0c;原始树 origi…

[Arduino学习] ESP8266读取DHT11数字温湿度传感器数据

目录 1、传感器介绍 2、接线 3、DHT.h库 1、传感器介绍 DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器&#xff0c;是简单环境监测项目的理想选择。 温度分辨率为1C&#xff0c;相对湿度为1&#xff05;。温度范围在0C到50C之间&#xff0c;湿度的测…

Protobuf 二进制文件学习及解析

0. 简介 protobuf也叫protocol buffer是google 的一种数据交换的格式&#xff0c;它独立于语言&#xff0c;独立于平台。google 提供了多种语言的实现&#xff1a;java、c#、c、go 和 python&#xff0c;每一种实现都包含了相应语言的编译器以及库文件。 由于它是一种二进制的…

新人程序员必备在线工具推荐(cron、加解密、JSON、AI)

程序员必备在线工具推荐 俗话说的好&#xff0c;工欲善其事必先利其器&#xff0c;下面我就来给大家分享一下我个人常用的工具。也欢迎大家在评论区分享自己喜欢的工具。✈️ 1 在线cron表达式&#xff1a;cron 在日常开发中&#xff0c;我们难免会遇到一些定时任务的场景&…

「每日跟读」句型公式 第2篇

「每日跟读」句型公式 第2篇 1. I’m thinking about____ 我在考虑____ I’m thinking about my future career (我正在思考我未来的职业) I’m thinking about our marriage (我在考虑我们的婚姻) I’m thinking about taking a vacation (我在考虑度一个假) I’m think…

tcpdump + wireshark 服务器抓包分析

tcpdump wireshark 服务器抓包分析 1.tcpdump安装2.tcpdump使用3.安装wireshark4.使用wireshark 本文用以总结使用tcpdump进行抓包&#xff0c;然后使用wireshark工具打开抓包出来的pacp文件进行分析。通过tcpdump可以实时监控到linux服务器中tcp和http、https等通讯的内容和信…

人工智能上手 Pytorch

人工智能上手 Pytorch 1、人工智能框架历史走向 2015年&#xff0c; caffe&#xff0c;优势配置简单&#xff0c;缺点安装麻烦&#xff0c;且不更新维护 2016年&#xff0c;tensorflow 1.x&#xff0c;定义太严格&#xff0c;很复杂。开发成本高。简单的任务&#xff0c;也很…

[Python学习篇] Python创建项目

新建项目 打开开发工具 PyCharm 选择 New Project 目录结构如下 运行 hello world 选中项目&#xff0c;右键 New -> Python File 进行创建文件 运行项目

MYSQL数据库:告别慢查询,优化性能大揭秘

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》《MYSQL应用》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 MYSQL数据库&#xff1a;告别慢查询&#xff0c;优化性能大揭秘 文章目录 一、揭秘…

Makefile:调用shell脚本和嵌套调用多项目编译(九)

1、Makefile中调用shell脚本 Makefile中可以通过使用$(shell 指令)的方式调用shell脚本a指令&#xff1a;输出当前文件夹下的所有文件b指令&#xff1a;输出当前路径c指令&#xff1a;如果当前目录下不存在abc文件那么创建一个abc的文件 a$(shell ls ./) b$(shell pwd) filen…

关于简单又挣钱的冷门美团项目,美团圈圈

大家好&#xff0c;最近美团又开始搞事情了。接连推出了好几个网推项目&#xff0c;让一大波人都吃上了肉了。 美团的项目很简单&#xff0c;就是给它们的活动做推广。用户只需要拿到它推广的链接&#xff0c;然后去扫码进群就可以了。只要用户保持8天不退就行了。 下面是体验…

揭开AI编程语言Mojo比Pyhon快6.8万倍的5个秘密!

最近&#xff08;2024年3月29日&#xff09;&#xff0c;号称比Python快6.8万倍的Mojo编程语言开源啦&#xff01;6.8万倍&#xff1f;你敢相信这个数字是真的吗&#xff1f;不过&#xff0c;就连Mojo官网都把这个结果贴了出来&#xff08;见下图&#xff09;&#xff0c;这就很…

线程池小项目【Linux C/C++】(踩坑分享)

目录 前提知识&#xff1a; 一&#xff0c;线程池意义 二&#xff0c;实现流程 阶段一&#xff0c;搭建基本框架 1. 利用linux第三方库&#xff0c;将pthread_creat线程接口封装 2. 实现基本主类ThreadPool基本结构 阶段二&#xff0c;完善多线程安全 1. 日志信息打印…

若依框架时间比较的坑(DATE_FORMAT)

背景 - 想做生日的比较 若依自带的比较 <if test"params.beginTime ! null and params.beginTime ! "><!-- 开始时间检索 -->AND date_format(u.create_time,%y%m%d) > date_format(#{params.beginTime},%y%m%d)</if><if test"params…

AJAX —— 学习(三)

目录 一、jQuery 中的 AJAX &#xff08;一&#xff09;get 方法 1.语法介绍 2.结果实现 &#xff08;二&#xff09;post 方法 1.语法介绍 2.结果实现 &#xff08;三&#xff09;通用型的 AJAX 方法 1.语法介绍 2.结果实现 二、AJAX 工具库 axios &#xff08;…

【进阶六】Python实现SDVRPTW常见求解算法——遗传算法(GA)

基于python语言&#xff0c;采用经典遗传算法&#xff08;GA&#xff09;对 带硬时间窗的需求拆分车辆路径规划问题&#xff08;SDVRP&#xff09; 进行求解。 目录 往期优质资源1. 适用场景2. 代码调整2.1 需求拆分2.2 需求拆分后的服务时长取值问题 3. 求解结果4. 代码片段参…

【Qt】Ubuntu20.04.6+Qt5.15.2+QtCreator10.0.1无法输入中文

1、前提条件 1)已经安装了fcitx sudo apt install fcitx sudo apt install fcitx-pinyin sudo apt install fcitx-bin fcitx-table-all sudo apt install fcitx-qt52)系统已经配置fcitx 3)将系统下 /usr/lib/x86_64-linux-gnu/qt5/plugins/platforminputcontexts/libfcitx…

计算机考研408有向无环图描述表达式可靠构造方法

目录 前言目标&#xff08;以王道书为例&#xff09;构造方法1. 建树2. 后序遍历1. a2. b3. 4. b5. c6. d7. 8. *9. *10. c 前言 对王道视频中的分层合并思想不是很满意&#xff0c;笔者提出自己的构造方法。 目标&#xff08;以王道书为例&#xff09; 构造方法 笔者通过王…