C++ 型別轉換(conv.)

KinoluKaslana發表於2019-02-13

隱式型別轉換

總結自:隱式型別轉換&算數運算子

定義:隱式型別轉換是指使用了與表示式規定或當前語境不相符的型別時所進行的型別轉換,但是要注意,可能會存在轉換出現歧義,從而無法通過編譯;一切帶有explicit的轉換,建構函式,發生的型別轉換均不屬於隱式轉換.

概念總括:

標準轉換序列:

一個標準轉換順序可選的為以下:

  • 左值到右值的轉換
  • 數值提升或轉換
  • 函式指標轉換
  • 限定調整

使用者定義轉換:

非explicit的建構函式和轉換函式(與直接初始化不同)

算數轉換:

算數轉換是指一個表示式,運算子是算數運算子之一,此時有別於隱式型別轉換的轉換規則.

轉換順序:

  • 標準轉換序列
  • 使用者定義轉換
  • 標準轉換序列

接下來將對上述定義部分進行逐一說明:

  • 在語境中的隱式轉換:

    由於某語句中的表示式需要某種表示式型別,此時就會發生,比如if,邏輯運算子,switch,delete等,要求被轉換的表示式的型別遵循轉換順序.
    <
    值得注意的是:對於此處的使用者定義轉換,

    在C++ 14前,必須擁有一個轉換為指定型別(或轉換後可繼續隱式轉換為指定型別)的非explicit的轉換函式
    在C++ 14及之後則放寬為只要存在有可轉換為指定型別(或轉換後可繼續隱式轉換為指定型別)的限定或引用的轉換函式即可

    >

  • 左值到右值的轉換:

    這裡統一蓋稱為左值到右值的轉換;實際上,只是發生在表示式的運算數型別和當前運算數型別不同時,就會發生該轉換.
    此處轉換實際上細分了三大類,但是總是伴隨著泛左值向純右值的轉換以及純右值之間的轉換.

    細節上逐一介紹:

    • 泛左值向純右值的值型別轉換
      • 會將任何非函式,陣列的泛左值轉換為同型別的純右值,並且若非類型別,則會移除其cv限定,並以原左值的值作為右值的值,並且該轉換不會對以下表示式求值:
        • 不求值語境,為nullptr_t的型別(直接轉化為nullptr),不進行ODR訪問的表示式(此處關於ODR,會有另外的一篇文章做詳細描述).
      • 當其為類型別時則視為:
        • 在C++ 17前,以當前左值為引數所進行的一個複製建構函式的呼叫,建構函式返回的值為一個臨時物件,故為一個純右值表答式
        • 在C++ 17後,改稱,該結果物件以泛左值物件複製初始化
        • 並且有,泛左值物件中存在不確定值時,初始化後行為未定義,除非:
        • 該值存在有指向它的指標或存在引用
        • 該值位於靜態或TLS
  • 數值提升或轉換
    • 數值提升或轉換就是將原運算元的資料型別轉換為其他的資料型別(可能損失精度),或幾乎不會損失精度的型別提升;具體細節如下:
      • 整數型別提升:只需要保證,目標型別的有效值範圍是大於等於原型別的即可
      • 整數型別轉換:
        • 目標為無符號數發生溢位時,模目標型別的2nn為表示目標型別的位數
        • 目標為有符號數若發生溢位通常是實現定義.
          自C++20起,此時的值將會變成與源值對2n同餘的唯一目標型別值.
        • 源為bool時,true為1,false為0
        • 目標是bool時,零值,空指標為false,其餘的均為true
      • 浮點數:浮點數在發生型別轉換時,如果原值可表示,但是存在多種表示時,由實現定義,或可被精確表示時,值不變,否則未定義,若向整數轉換,要求整數對浮點部分截斷,無舍入,並且當無法表示時,行為未定義.
      • 指標, NULL可以轉換為任何型別的指標型別,void的(要求同cv等級)指標型別可以作為同cv等級的指標轉換的目標型別,在基類可訪問或無歧義時,可以將派生類的指標轉為為其基類的指標(CV等級相同)
  • 函式指標轉換:
    • 函式指標實際上很簡單,即任何非非靜態成員成員函式的id(一個型別為T的函式左值)都可以轉換為該函式型別的指標(一個指向改函式型別T的指標純右值),為什麼非靜態成員函式不可以呢?因為任何訪問非靜態成員函式的表示式都是被歸為純右值的,其僅只能作為呼叫運算子的運算元.
  • 臨時變數實質化:
    • 這個是一個純右值到亡值的過程,即當一個純右值出現在某些特定表示式時,會發生實質化,變為一個亡值(注,基本上,除了允許純右值直接作為運算元的表示式之外(例如,所有的隱式型別轉換均伴隨左值到純右值的轉化為第一步,其後再發生型別轉換的,此時型別改變,值型別任然為純右值,允許作為表示式運算元),均要發生該轉換),標準中有如下:
      • 成員訪問,下標,這類成員訪問的表示式
      • 作為棄值表示式的最終型別
      • 繫結到右值引用時
      • 特殊的:不求值表示式的運算元(sizeof,typeid)
  • 陣列到指標的轉換:該規則很簡答,直接轉換為指向陣列第一個元素型別的指標即可
  • 限定調整:
    • 對於任何一個帶有CV限定的型別T我們都可以做如下對應:
      CV0 P0 CV1 P1 CV2 P2 … CVn – 1 Pn – 1 CVn U
      例一:如型別const char * * 此時做對應實際上有:
const char * *
CV2 U CV1P1 CV0P0

  實際上觀察可以發現,從識別符號位置起,根據宣告檢視順序反向閱讀即可得到對應關係,再比如: char * A [];

char * []
CV2 U CV1P1 CV0P0

  當然,此處的U可以做任何包含,即可以包含N組(至少留一組)CVn Pn – 1

  由此,上述例1的CV分解實際上U有兩層,即U為char或U為const char *

  對於標準轉換中的限定調整,其規則實際上很簡單,一句話概括就是當且僅當目標的U和對應的CV層數相同且CV限定符一致,或當某級的cv限定符更多時,要求除0級外均有cv限定時才可以轉換,例如:

char ** p;
const char ** p1 = p; //錯誤,二級存在更多cv限定,但是1級沒有
const char * const * p2 = p; //正確

相關文章