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.rhkb.cn/news/296034.html

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

相关文章

成都欣丰洪泰文化传媒有限公司引领电商新风向

在当今数字化时代&#xff0c;电子商务行业日新月异&#xff0c;竞争激烈。然而&#xff0c;在这股浪潮中&#xff0c;成都欣丰洪泰文化传媒有限公司凭借其独特的战略眼光和创新精神&#xff0c;正引领着电商领域的新浪潮。本文将探讨成都欣丰洪泰文化传媒有限公司如何在激烈的…

C++ //练习 11.12 编写程序,读入string和int的序列,将每个string和int存入一个pair中,pair保存在一个vector中。

C Primer&#xff08;第5版&#xff09; 练习 11.12 练习 11.12 编写程序&#xff0c;读入string和int的序列&#xff0c;将每个string和int存入一个pair中&#xff0c;pair保存在一个vector中。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#x…

Linux简单介绍

Linux简单介绍 编译器VMware虚拟机Ubuntu——LinuxOS为什么使用LinuxOS&#xff1f; 目录结构Windows目录结构Linux操作系统home是不是家目录&#xff1f; Linux常用命令终端命令行提示符与权限切换命令tab 作用&#xff1a;自动补全上下箭头pwd命令ls命令mkdir命令touch命令rm…

笔记: JavaSE day15 笔记

第十五天课堂笔记 数组 可变长参数★★★ 方法 : 返回值类型 方法名(参数类型 参数名 , 参数类型 … 可变长参数名){}方法体 : 变长参数 相当于一个数组一个数组最多只能有一个可变长参数, 并放到列表的最后parameter : 方法参数 数组相关算法★★ 冒泡排序 由小到大: 从前…

Docker之ruoyi-vue项目部署

文章目录 创建自定义网络安装redis安装mysql发布若依项目--后端使用Dockerfile自定义镜像运行容器 nginx 创建自定义网络 #搭建net-ry局域网&#xff0c;用于部署若依项目 docker network create net-ry --subnet172.68.0.0/16 --gateway172.68.0.1 注意1&#xff1a;关闭宿主…

Kubernetes(k8s):如何进行 Kubernetes 集群健康检查?

Kubernetes&#xff08;k8s&#xff09;&#xff1a;如何进行 Kubernetes 集群健康检查&#xff1f; 一、节点健康检查1、使用 kubectl 查看节点状态2、查看节点详细信息3、检查节点资源使用情况 2、Pod 健康检查2.1、 使用 kubectl 查看 Pod 状态2.2、 查看特定 Pod 的详细信息…

解决MySQL幻读?可重复读隔离级别背后的工作原理

什么是当前读和快照读 当前读&#xff1a;又称为 "锁定读"&#xff0c;它会读取记录的最新版本&#xff08;也就是最新的提交结果&#xff09;&#xff0c;并对读取到的数据加锁&#xff0c;其它事务不能修改这些数据&#xff0c;直到当前事务提交或回滚。"sele…

基于深度学习的机场航拍小目标检测系统(网页版+YOLOv8/v7/v6/v5代码+训练数据集)

摘要&#xff1a;在本博客中介绍了基于YOLOv8/v7/v6/v5的机场航拍小目标检测系统。该系统的核心技术是采用YOLOv8&#xff0c;并整合了YOLOv7、YOLOv6、YOLOv5算法&#xff0c;从而进行性能指标的综合对比。我们详细介绍了国内外在机场航拍小目标检测领域的研究现状、数据集处理…

工艺品wordpress外贸主题

工艺品wordpress外贸主题 简约大气的wordpress外贸主题&#xff0c;适合做工艺品进出品外贸的公司官网使用。 https://www.jianzhanpress.com/?p5377

超市销售数据-python数据分析项目

Python数据分析项目-基于Python的销售数据分析项目 文章目录 Python数据分析项目-基于Python的销售数据分析项目项目介绍数据分析结果导出数据查阅 数据分析内容哪些类别比较畅销?哪些商品比较畅销?不同门店的销售额占比哪个时间段是超市的客流高封期?查看源数据类型计算本月…

【数据库系统工程师】软考2024年5月报名流程及注意事项

2024年5月软考数据库系统工程师报名入口&#xff1a; 中国计算机技术职业资格网&#xff08;http://www.ruankao.org.cn/&#xff09; 2024年软考报名时间暂未公布&#xff0c;考试时间上半年为5月25日到28日&#xff0c;下半年考试时间为11月9日到12日。不想错过考试最新消息…

门控循环单元(GRU)

概述 门控循环单元&#xff08;Gated Recurrent Unit, GRU&#xff09;由Junyoung Chung等人于2014年提出&#xff0c;原论文为《Empirical Evaluation of Gated Recurrent Neural Networks on Sequence Modeling》。GRU是循环神经网络&#xff08;Recurrent Neural Network, …

PS从入门到精通视频各类教程整理全集,包含素材、作业等(7)复发

PS从入门到精通视频各类教程整理全集&#xff0c;包含素材、作业等 最新PS以及插件合集&#xff0c;可在我以往文章中找到 由于阿里云盘有分享次受限制和文件大小限制&#xff0c;今天先分享到这里&#xff0c;后续持续更新 PS敬伟01——90集等文件 https://www.alipan.com/s…

新兴势力展露头角? ERC-1111 协议能否开启下一个热潮

从 2024 年第一季度开始&#xff0c;加密资产市场迎来了新一轮狂热的上涨&#xff0c;比特币的价格突破了历史性的高点&#xff0c;为整个行业注入了强劲的动力。更为引人瞩目的是&#xff0c;NFT 市场正在经历一场革命性的变革&#xff0c;一些创新性的 NFT 协议开始崭露头角&…

1.Git是用来干嘛的

本文章学习于【GeekHour】一小时Git教程&#xff0c;来自bilibili Git就是一个文件管理系统&#xff0c;这样说吧&#xff0c;当多个人同时在操作一个文件的同时&#xff0c;很容易造成紊乱&#xff0c;git就是保证文件不紊乱产生的 包括集中式管理系统和分布式管理系统 听懂…

实验2:CLI的使用与IOS基本命令

1、实验目的 通过本实验可以掌握&#xff1a; CLI的各种工作模式个CLI各种编辑命令“?” 和【Tab】键使用方法IOS基本命令网络设备访问限制查看设备的相关信息 2、实验拓扑 CLI的使用与IOS基本命令使用拓扑如下图所示。 3、实验步骤 &#xff08;1&#xff09;CLI模式的切…

分享几个可以免费使用的GPT网站吧

1. ChatGAI ChatGAI是一个界面简洁的AI平台&#xff0c;提供App和网页版&#xff0c;每日均有免费使用机会。 2. ChatGPT 本网站向大家开放了ChatGPT 3.5和4.0版本的免费体验&#xff0c;特别适合新用户。每天都有免费次数&#xff0c;响应迅速&#xff0c;注册便捷&#xff0…

『Apisix系列』破局传统架构:探索新一代微服务体系下的API管理新范式与最佳实践

一、『Apisix安装部署』 &#x1f680; 1.1 手把手教你从零部署APISIX高性能API网关 二、『Apisix入门篇』 &#x1f680; 2.1 从零到一掌握Apache APISIX&#xff1a;架构解析与实战指南 三、『Apisix进阶篇』 &#x1f680; 3.1 动态负载均衡&#xff1a;APISIX的实战演练…

谷粒商城实战(008 缓存)

Java项目《谷粒商城》架构师级Java项目实战&#xff0c;对标阿里P6-P7&#xff0c;全网最强 总时长 104:45:00 共408P 此文章包含第151p-第p157的内容 简介 数据库承担落盘&#xff08;持久化&#xff09;工作 拿map做缓存 这种是本地缓存&#xff0c;会有一些问题 分布…

MATLAB 点云随机渲染赋色(51)

MATLAB 点云随机渲染赋色(51) 一、算法介绍二、算法实现1.代码2.效果总结一、算法介绍 为点云中的每个点随机赋予一种颜色,步骤和效果如图: 1、读取点云 (ply格式) 2、随机为每个点的RGB颜色字段赋值 3、保存结果 (ply格式) 二、算法实现 1.代码 代码如下(示例):…