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指向的值
		else
			ret << " 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指向的值
		else
			ret << " 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指向的值
		else
			ret << " 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指向的值
		else
			ret << " 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指向的值
		else
			ret << " 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指向的值
		else
			ret << " 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指向的值
		else
			ret << " 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由于未遇到你希望调用的函数而实例化一个并非你所需的版本。
 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

双机 Cartogtapher 建图文件配置

双机cartogtapher建图 最近在做硕士毕设的最后一个实验&#xff0c;其中涉及到多机建图&#xff0c;经过调研最终采用cartographer建图算法&#xff0c;其中配置多机建图的文件有些麻烦&#xff0c;特此博客以记录 非常感谢我的同门 ”叶少“ 山上的稻草人-CSDN博客的帮助&am…

计算机网络:数据链路层 - 点对点协议PPP

计算机网络&#xff1a;数据链路层 - 点对点协议PPP PPP协议的帧格式透明传输字节填充法零比特填充法 差错检测循环冗余校验 对于点对点链路&#xff0c;PPP协议是目前使用最广泛的数据链路层协议。比如说&#xff0c;当用户想要接入互联网&#xff0c;就需要通过因特网服务提供…

高分卫星助力台湾省花莲县地震应急救援

4月3日7时58分&#xff0c;在台湾省花莲县海域&#xff08;北纬23.81度&#xff0c;东经121.74度&#xff09;发生7.3级地震&#xff0c;震源深度12公里。接中国地震局地震预测研究所应急需求&#xff0c;国家航天局对地观测与数据中心&#xff08;以下简称“中心”&#xff09…

Kubernetes探索-Pod面试

本篇及此系列文章只针对面试相关问题做了简单总结&#xff0c;后续会出比较详细的系列文章.... 1. 创建Pod的底层逻辑 1&#xff09;创建单个Pod时&#xff1a;组件间的交互流程和描述如下图&#xff0c;该过程中controller-manager组件不工作。 流程描述 ① 客户端提交创建请…

揭开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搭建NTP服务器

搭建 NTP&#xff08;Network Time Protocol&#xff09;服务器可以帮助你在局域网内提供时间同步服务&#xff0c;让网络中的设备都使用统一的时间。以下是在 Linux 系统上搭建 NTP 服务器的基本步骤&#xff1a; 安装 NTP 服务器软件&#xff1a; 在终端中执行以下命令安装 N…

Webpack部署本地服务器

Webpack部署本地服务器 目录 Webpack部署本地服务器目的认识模块热替换&#xff08;HMR&#xff09;什么是 HMRHMR 通过如下几种方式, 来提高开发的速度如何使用 HMRhost 配置 目的 完成自动编译 常用方式: webpack-dev-server webpack-dev-server 是一个用于开发环境的 Web 服…

Class类

1. Class类的理解 针对于编写好的 .java 源文件进行编译(使用 javac.exe)&#xff0c;会生成一个或多个 .class 字节码文件。接着&#xff0c;我们使用 java.exe 命令对指定的 .class 文件进行解释运行。这个解释运行的过程中&#xff0c;我们需要将 .class 字节码文件加载到内…

本地储存、jQuery

文章目录 1. 本地储存1. window.sessionStorage2. window.localStorage案例&#xff1a;记住用户名 2. jQuery入门jQuery 的概念jQuery 的入口函数jQuery 的顶级对象 $jQuery 对象和 DOM 对象 3. jQuery 常用API1. jQuery 选择器1.基础选择器2.层级选择器隐式迭代&#xff08;重…

C++(set和map详解,包含常用函数的分析)

set set是关联性容器 set的底层是在极端情况下都不会退化成单只的红黑树,也就是平衡树,本质是二叉搜索树. set的性质:set的key是不允许被修改的 使用set需要包含头文件 set<int> s;s.insert(1);s.insert(1);s.insert(1);s.insert(1);s.insert(2);s.insert(56);s.inser…

Vue.js---------Vue基础

能够说出Vue的概念和作用能够使用vue/cli脚手架工程化开发能够熟练Vue指令 一.vue基本概念 1.学习vue Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化。 渐进…

2024 ccfcsp认证打卡 2022 09 01 如此编码

2022 09 01 如此编码 题解1题解2 题解1 import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);int n sc.nextInt(); // 天数int m sc.nextInt(); // 科目数int[] b new int[n 1]; // 存放结果的数…

笔记: JavaSE day15 笔记

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

Paddle实现人脸对比

人脸对比 人脸对比&#xff0c;顾名思义&#xff0c;就是对比两个人脸的相似度。本文将用Paddle实现这一功能。 PS&#xff1a;作者肝了整整3天才稍微搞明白实现方法 数据集准备 这里使用百度AI Studio的开源数据集&#xff1a; 人脸数据_数据集-飞桨AI Studio星河社区 (b…

【React】vite + react 项目,配置项目路径别名 @

vite react 项目&#xff0c;配置项目路径别名 1 安装 types/node2 在 vite.config.ts 中添加配置&#xff1a;3 配置路径别名的提示 使用 vite 开发 react 项目时&#xff0c;可以通过一下步骤配置路径别名&#xff1a; 1 安装 types/node npm i -D types/node2 在 vite.con…

Lumos学习王佩丰Excel第一讲:认识Excel

最近发现自己在操作excel的一些特殊功能时会有些不顺手&#xff0c;所以索性找了一个比较全的教程&#xff08;王佩丰excel24讲&#xff09;拿来学习&#xff0c;刚好形成文档笔记&#xff0c;分享给有需要但没有时间看视频的朋友们。整体笔记以王老师授课的知识点去记录&#…

Spring拓展点之SmartLifecycle如何感知容器启动和关闭

Spring为我们提供了拓展点感知容器的启动与关闭&#xff0c;从而使我们可以在容器启动或者关闭之时进行定制的操作。Spring提供了Lifecycle上层接口&#xff0c;这个接口只有两个方法start和stop两个方法&#xff0c;但是这个接口并不是直接提供给开发者做拓展点&#xff0c;而…

算法基础--递推

&#x1f600;前言 递推算法在计算机科学中扮演着重要的角色。通过递推&#xff0c;我们可以根据已知的初始条件&#xff0c;通过一定的规则推导出后续的结果&#xff0c;从而解决各种实际问题。本文将介绍递推算法的基础知识&#xff0c;并通过一些入门例题来帮助读者更好地理…

力扣 392. 判断子序列

题目来源&#xff1a;https://leetcode.cn/problems/is-subsequence/description/ C题解1&#xff1a;在t中按顺序一个一个寻找s的元素。 class Solution { public:bool isSubsequence(string s, string t) {bool flg false;int m s.size(), n t.size();if(m 0) return tr…

vue项目打包优化之-productionSourceMap设置

productionSourceMap 是一个用于配置生产环境下是否生成 source map 文件的选项。在 webpack 中&#xff0c;source map 文件是一种映射关系文件&#xff0c;可以将编译后的代码映射回原始源代码&#xff0c;方便开发者在调试时定位问题。 在生产环境中&#xff0c;通常不建议暴…