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.pswp.cn/news/443734.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/443734.shtml
英文地址,請注明出處:http://en.pswp.cn/news/443734.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Tomcat中JVM內存溢出及合理配置

Tomcat本身不能直接在計算機上運行&#xff0c;需要依賴于硬件基礎之上的操作系統和一個Java虛擬機。Tomcat的內存溢出本質就是JVM內存溢出&#xff0c;所以在本文開始時&#xff0c;應該先對Java JVM有關內存方面的知識進行詳細介紹。 一、Java JVM內存介紹 JVM管理兩種類型的…

俄羅斯農民乘法 | 快速乘

文章目錄概念概念 俄羅斯農民乘法經常被用于兩數相乘取模的場景&#xff0c;如果兩數相乘已經超過數據范圍&#xff0c;但取模后不會超過&#xff0c;我們就可以利用這個方法來拆位取模計算貢獻&#xff0c;保證每次運算都在數據范圍內。 A 和 B 兩數相乘的時候我們如何利用加…

Linux網絡編程 | socket選項設定 及 網絡信息API

文章目錄讀取和設置 socket 選項SO_REUSEADDRSO_RCVBUF 和 SO_SNDBUFSO_RCVLOWAT 和 SO_SNDLOWATSO_LINGER 選項網絡信息APIgethostbyname 和 gethostbyaddrgetservbyname 和 getservbyportgetaddrinfogetnameinfo讀取和設置 socket 選項 正如 fcntl 系統調用是控制文件描述符…

Linux | 高級I/O函數

文章目錄創建文件描述符的函數pipe函數dup函數、dup2函數讀取或寫入數據readv函數、writev函數零拷貝sendfile函數splice函數tee函數進程間通信——共享內存mmap函數 和 munmap函數控制文件描述符fcntl函數創建文件描述符的函數 pipe函數 不再贅述&#xff0c;詳情見我的另一…

分布式理論:CAP、BASE | 分布式存儲與一致性哈希

文章目錄分布式理論CAP定理BASE理論分布式存儲與一致性哈希簡單哈希一致性哈希虛擬節點分布式理論 CAP定理 一致性&#xff08;Consistency&#xff09;&#xff1a; 在分布式系統中的所有數據副本&#xff0c;在同一時刻是否一致&#xff08;所有節點訪問同一份最新的數據副…

Tomcat服務器性能優化

一、概述 本文檔主要介紹了Tomcat的性能調優的原理和方法。可作為公司技術人員為客戶Tomcat系統調優的技術指南&#xff0c;也可以提供給客戶的技術人員作為他們性能調優的指導手冊。 二、調優分類 由于Tomcat的運行依賴于JVM&#xff0c;從虛擬機的角度我們把Tomcat的調整分為…

分布式系統概念 | 分布式事務: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;句柄集、線程集、事件處理器工作流程兩種服…

香農信息熵之可憐的小豬

文章目錄題目解析香農熵公式樣例具體分析代碼題目 有 n 桶液體&#xff0c;其其中 正好 有一桶含有毒藥&#xff0c;其裝的都是水。它們從外觀看起來都一樣。為了弄清楚哪只水桶含有毒藥&#xff0c;你可以喂一些豬喝&#xff0c;通過觀察豬是否會死進行判斷&#xff0c;實驗對…

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

文章目錄最長相等前后綴next數組概念代碼實現圖解GetNext中的回溯改進代碼實現代碼復雜度分析最長相等前后綴 給出一個字符串 ababa 前綴集合&#xff1a;{a, ab, aba, abab} 后綴集合&#xff1a;{a, ba, aba, baba} 相等前后綴 即上面用同樣顏色標識出來的集合元素&#…

linux下tomcat6.0與jdk安裝詳細步驟

安裝Tomcat6.0和JDK1.6 在linux系統上安裝tomcat和jdk應該說是我學習linux知識的第一課了&#xff0c;之前只 是聽說過&#xff0c;從沒接觸過&#xff0c;但我們公司項目都是部署在linux系統上的&#xff0c;那天上司突 然給我發了幾個文檔&#xff0c;讓我看一下&#xff…

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;這兩個目錄下放置…