C++ 运算符重载(二) | 类型转换运算符,二义性问题

文章目录

  • 类型转换运算符
    • 概念
    • 避免过度使用类型转换函数
    • 解决上述问题的方法
      • 转换为 bool
      • 显式的类型转换运算符
    • 类型转换二义性
    • 重载函数与类型转换结合导致的二义性
    • 重载运算符与类型转换结合导致的二义性


类型转换运算符

概念

类型转换运算符(conversion operator)是类的一种特殊成员函数。负责将一个类类型的值转换成其他类型。

operator type() const ;

其中 type 表示某种类型。类型转换运算符可以面向任意类型(除了 void 之外)进行定义,只要该类型能作为函数的返回类型。因此,我们不允许转换成数组或者函数类型,但允许转换成指针(包括数组指针及函数指针)或者引用类型。

一个类型转换函数必须是类的成员函数;它不能声明返回类型,形参列表也必须为空。类型转换函数通常不应该改变待转换对象的内容,因此,应该是const。

运用实例,定义一个简单的类,令其表示 0~255 之间的一个整数:
在这里插入图片描述
构造函数将算术类型的值转换成 SmallInt 对象,而类型转换运算符将 SmallInt 对象转换成 int

SmallInt si;
si = 4; // 将 4 隐式转换成 SmallInt,然后调用 SmallInt::operator=
si + 3; // 首先将 si 隐式地转换成 int,然后执行整数的加法

尽管编译器一次只能执行一个 我们定义的类型转换(如上面的构造函数/类型转换运算符),但可以将其搭配 内置类型转换(如double可以转换成int) 实现二次转换。

// 内置类型转换将 doulbe 实参转换成 int
SmallInt si = 3.14; // 调用 SmallInt(int) 构造函数,然后调用拷贝构造函数
// SmallInt 的类型转换运算符将 si 转换成 int
si + 3.14// 内置类型将所得的 int 继续转换成 double

尽管类型转换函数不负责指定返回类型,但实际上每个类型转换函数都会返回一个对应类型的值:
在这里插入图片描述


避免过度使用类型转换函数

  1. 类型转换可能具有误导性

例如,假设某个类表示 Date,我们也许会为它添加一个从 Dateint 的转换。然而,类型转换函数的返回值应该是什么?

  • 一种可能的解释是,函数返回一个十进制数,依次表示年、月、日,例如,July 30,1989 可能转换为 int19890730
  • 同时还存在另外一种合理的解释,即类型转换运算符返回的 int 表示的是从某个时间节点(比如 January 1,1970)开始经过的天数。

问题在于 Date 类型的对象和 int 类型的值之间不存在明确的一对一映射关系。因此在此例中,不定义该类型转换运算符也许会更好。作为替代的手段,类可以定义一个或多个普通的成员函数以从各种不同形式中提取所需的信息。

  1. 类型转换运算符可能产生意外结果

对于类来说,定义向 bool 的类型转换还是比较普遍的现象。

int i = 42;
cin << i; // 如果向 bool 的类型转换不是显式的,则该代码在编译器看来是合法的

因为 istream 本身并没有定义 <<,所以本来代码应该产生错误。然而,该代码能使用 istreambool类型转换运算符cin 转换成 bool ,而这个 bool值 接着会被提升成 int 并用作内置的左移运算符的左侧运算对象。这样一来,提升后的 bool值(1或0) 最终会 被左移42个位置。 这一结果显然与我们的预期大相径庭。


解决上述问题的方法

转换为 bool

  • 标准库的早期版本中,IO 类型定义了向 void* 的转换规则,以求避免上述问题。
  • C++11 标准中,IO 标准库通过定义一个向 bool 的显式类型转换实现同样的目的。

其实我们在编程中经常用到 IO 类型定义的 operator bool

while(std::cin >> value)

为了对条件求值,cinistream operator bool 类型转换函数隐式地执行了转换。如果 cin 的条件状态是 good,则该函数返回为真;否则该函数返回为假。(这部分知识可以看我之前的博客)

bool 的类型转换通常用在条件部分,因此 operator bool 一般定义成 explicit 的。


显式的类型转换运算符

为了防止上面第二点这样的异常情况发生,我们可以使用 explicit 关键字。
在这里插入图片描述

SmallInt  si = 3; // 正确:SmallInt 的构造函数不是显式的
si + 3; // 错误:explicit阻止隐式类型转换
static_cast<int>(si) + 3; // 正确:显式地请求类型转换

当类型转换运算符是显式的时,我们也能执行类型转换,不过必须通过显式的强制类型转换才可以。

该规定存在一个例外,即,如果表达式被用作条件,则编译器会将显式的类型转换自动应用于它。 换句话说,当表达式出现在下列位置时,显式的类型转换将被隐式地执行:

  • ifwhiledo 语句的条件部分
  • for 语句头的条件表达式
  • 逻辑非运算符(!)、逻辑或运算符(||)、逻辑与运算符(&&)的运算对象
  • 条件运算符(? :)的条件表达式。

类型转换二义性

如果类中包含一个或多个类型转换,则必须确保在类类型和目标类型之间只存在唯一一种转换方式。否则的话,我们编写的代码将很可能会具有二义性。

在两种情况下可能产生多重转换路径:

  1. 第一种情况是 两个类提供相同的类型转换: 例如,当 A类 定义了一个接受 B类 对象的转换构造函数,同时 B类 定义了一个转换目标是 A类 的类型转换运算符。
  2. 第二种情况是 类定义了多个转换规则,而某些转换规则可以通过其他类型转换实现。 这种情况多出现在算术运算符上。

通常情况下,不要为类定义相同的类型转换,也不要在类中定义两个及两个以上转换源或转换目标是算术类型的转换。

第一种情况举例:

在这里插入图片描述

解决方法是显式调用:

A a1 = f(b.operator A());
A a2 = f(A(b));

第二种情况举例:

在这里插入图片描述
我们使用两个用户定义的类型转换时,如果转换函数之前或之后存在标准类型转换,则标准类型转换将决定最佳匹配到底是哪个:

short s = 42;
// 把 short 提升成 int 优于 提升成 double
// 上面的 long 则没有int和double谁优于谁的规则,因此会有二义性
A a3(s); // A::A(int)

重载函数与类型转换结合导致的二义性

有时会出现这种情况:
在这里插入图片描述

或这种情况:
在这里插入图片描述

虽然我们可以通过显式地构造正确的类型而消除二义性:

manip(C(10)); // 调用 manip(const C&)
manip2(E(double(10))); // 调用 manip2(const E&)

但意味着程序的设计存在不足。


重载运算符与类型转换结合导致的二义性

重载的运算符也是重载的函数。因此也遵从通用的函数匹配规则。例如,如果 a 是一种类类型,则表达式 a sym b 可能是:

a.operatorsym(b); // a 有一个 operatorsym 成员函数
operatorsym(a, b); // operatorsym 是一个普通函数

和普通函数不同,我们无法通过调用的形式区分当前调用的是成员函数还是非成员函数。

举个例子:
在这里插入图片描述

  • 第一条加法语句接受两个 SmallInt 值并执行 + 运算符的重载版本。
  • 第二条加法语句具有二义性:因为我们可以把 0 转换成 SmallInt,然后使用 SmallInt+;或者把 s3 转换成 int,然后对于两个 int 执行内置的加法运算。

如果我们对同一个类既提供了转换目标是算术类型的类型转换,也提供了重载的运算符,则将会遇到重载运算符与内置运算符的二义性问题。

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

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

相关文章

分布式理论:CAP、BASE | 分布式存储与一致性哈希

文章目录分布式理论CAP定理BASE理论分布式存储与一致性哈希简单哈希一致性哈希虚拟节点分布式理论 CAP定理 一致性&#xff08;Consistency&#xff09;&#xff1a; 在分布式系统中的所有数据副本&#xff0c;在同一时刻是否一致&#xff08;所有节点访问同一份最新的数据副…

分布式系统概念 | 分布式事务:2PC、3PC、本地消息表

文章目录分布式事务2PC&#xff08;二阶段提交协议&#xff09;执行流程优缺点3PC&#xff08;三阶段提交协议&#xff09;执行流程优缺点本地消息表&#xff08;异步确保&#xff09;分布式事务 分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分…

数据结构算法 | 单调栈

文章目录算法概述题目下一个更大的元素 I思路代码下一个更大元素 II思路代码132 模式思路代码接雨水思路算法概述 当题目出现 「找到最近一个比其大的元素」 的字眼时&#xff0c;自然会想到 「单调栈」 。——三叶姐 单调栈以严格递增or递减的规则将无序的数列进行选择性排序…

最长下降子序列

文章目录题目解法DP暴搜思路代码实现贪心二分思路代码实现题目 给出一组数据 nums&#xff0c;求出其最长下降子序列&#xff08;子序列允许不连续&#xff09;的长度。&#xff08;类似于lc的最长递增子序列&#xff09; 示例&#xff1a; 输入&#xff1a; 6 // 数组元素个…

Linux 服务器程序规范、服务器日志、用户、进程间的关系

文章目录服务器程序规范日志rsyslogd 守护进程syslog函数openlog函数setlogmask函数closelog函数用户进程间的关系进程组会话系统资源限制改变工作目录和根目录服务器程序后台化服务器程序规范 Linux 服务器程序一般以后台进程&#xff08;守护进程[daemon]&#xff09;形式运…

IO模型 :阻塞IO、非阻塞IO、信号驱动IO、异步IO、多路复用IO

文章目录IO模型阻塞IO非阻塞IO信号驱动IO多路复用IO异步IOIO模型 根据各自的特性不同&#xff0c;IO模型被分为阻塞IO、非阻塞IO、信号驱动IO、异步IO、多路复用IO五类。 最主要的两个区别就是阻塞与非阻塞&#xff0c;同步与异步。 阻塞与非阻塞 阻塞与非阻塞最主要的区别就…

Tomcat服务器集群与负载均衡实现

一、前言 在单一的服务器上执行WEB应用程序有一些重大的问题&#xff0c;当网站成功建成并开始接受大量请求时&#xff0c;单一服务器终究无法满足需要处理的负荷量&#xff0c;所以就有点显得有点力不从心了。另外一个常见的问题是会产生单点故障&#xff0c;如果该服务器坏掉…

Linux服务器 | 事件处理模式:Reactor模式、Proactor模式

文章目录Reactor模式Proactor模式同步I/O模型模拟Proactor模式两者的优缺点ReactorProactor同步I/O模型通常用于实现 Reactor 模式&#xff0c;异步I/O模型通常用于实现 Proactor 模式。&#xff08;不是绝对的&#xff0c;同步I/O也可模拟出 Proactor 模式&#xff09; React…

Linux服务器 | 服务器模型与三个模块、两种并发模式:半同步/半异步、领导者/追随者

文章目录两种服务器模型及三个模块C/S模型P2P模型I/O处理单元、逻辑单元、存储单元并发同步与异步半同步/半异步模式变体&#xff1a;半同步/半反应堆模式改进&#xff1a;高效的半同步/半异步模式领导者/追随者模式组件 &#xff1a;句柄集、线程集、事件处理器工作流程两种服…

字符串匹配之KMP(KnuthMorrisPratt)算法(图解)

文章目录最长相等前后缀next数组概念代码实现图解GetNext中的回溯改进代码实现代码复杂度分析最长相等前后缀 给出一个字符串 ababa 前缀集合&#xff1a;{a, ab, aba, abab} 后缀集合&#xff1a;{a, ba, aba, baba} 相等前后缀 即上面用同样颜色标识出来的集合元素&#…

Android入门(一) | Android Studio的配置与使用

文章目录安装配置Android Studio使用Android Studio模拟器更改Android SDK的路径Hello World&#xff01;安装配置Android Studio 从这一步开始&#xff1a; 一直点 next 即可&#xff0c;直到存储路径的选择上&#xff0c;可以放到非 C 盘&#xff0c;这里我放到 D 盘了&am…

Android 入门(四) | Intent 实现 Activity 切换

文章目录Intent显式 Intent定义两个 xml 文件android:orientationmatch_parent 和 wrap_contentIntent函数定义两个 Activity隐式 Intent更多隐式 Intent 的用法用隐式 Intent 打开系统浏览器自建 Activity 以响应打开网页的 Intent向下一个活动传递数据返回数据给上一个活动In…

Android入门(二) | 项目目录及主要文件作用分析

文章目录项目目录分析app目录分析AndroidManifest.xml 分析MainActivity.kt 分析build.gradle 分析最外层目录下的 build.gradleapp 目录下的 build.gradle项目目录分析 我们来看一下 src/main/res 下的一些文件&#xff1a; .gradle 和 .idea &#xff1a;这两个目录下放置…

Android入门(三) | Android 的日志工具 Logcat

文章目录日志工具类 android.util.LogLogcat 中的过滤器日志工具类 android.util.Log Log 从属日志工具类 android.util.Log &#xff0c;该类提供了五个方法供我们打印日志&#xff1a; Log.v() &#xff1a;用于打印那些最为琐碎的、意义最小的日志信息。对应级别 verbose&…

Android入门(五) | Activity 的生命周期

文章目录Activity 的状态及生命周期实现管理生命周期FirstActivitySecondActivityDialogActivity运行结果旧活动被回收了还能返回吗&#xff1f;Activity 的状态及生命周期 Android 的应用程序运用 栈&#xff08;Back Stack&#xff09; 的思想来管理 Activity&#xff1a; …

Android入门(六) | Activity 的启动模式 及 生产环境中关于 Activity 的小技巧

文章目录Activity 的启动模式standardsingleTopsingleTasksingleInstance技巧了解当前界面是哪个 Activity随时随地退出程序启动活动的最佳写法Activity 的启动模式 standard&#xff1a;默认的启动方式&#xff0c;每次启动一个活动都会重新创建singleTop&#xff1a;如果该活…

Android入门(七) | 常用控件

文章目录TextView 控件&#xff1a;文本信息Button 控件&#xff1a;按钮EditText 控件&#xff1a;输入框ImageView 控件&#xff1a;图片ProgressBar 控件&#xff1a;进度条AlertDialog 控件&#xff1a;提示框ProgressDialog 控件&#xff1a;带有进度条的提示框TextView 控…

Android入门(八) | 常用的界面布局 及 自定义控件

文章目录LinearLayout &#xff1a;线性布局android:layout_gravity &#xff1a;控件的对齐方式android:layout_weight&#xff1a;权重RelativeLayout &#xff1a;相对布局相对于父布局进行定位相对于控件进行定位边缘对齐FrameLayout &#xff1a;帧布局Percent &#xff1…

Android入门(九)| 滚动控件 ListView 与 RecyclerView

文章目录ListView内置类型的简单运用定制数据类型提升效率点击事件RecyclerView布局管理器点击事件ListView 内置类型的简单运用 由于手机屏幕空间有限&#xff0c;能够一次性在屏幕上显示的内容不多&#xff0c;当我们的程序有大量数据需要显示的时候就可以借助 ListView 来…

Android入门(10)| Fragment碎片详解

文章目录为什么要使用碎片&#xff08;Fragment&#xff09;实例布局文件FragmentActivity动态添加碎片布局文件FragmentActivity碎片通信Fragment布局文件Activity生命周期为什么要使用碎片&#xff08;Fragment&#xff09; 我们在手机上看新闻可能是这样的&#xff1a; Re…