09.07 誰說 C++ 的強制類型轉換很難懂?

誰說 C++ 的強制類型轉換很難懂?

作者 | 櫻雨樓

出品 | CSDN(ID:CSDNnews)

在上篇與中篇中,我們討論了隱式類型轉換及其與函數重載之間的相關話題。本篇將要討論的即為類型轉換的另一大分支——強制類型轉換。

谁说 C++ 的强制类型转换很难懂?

C風格的強制類型轉換

在C語言中,強制類型轉換存在兩種等價形式:Type(Value)或(Type)Value。

參考以下代碼:

<code>int main
{
(int *) malloc(0); // (Type)Value形式的強制類型轉換
int(0.); // Type(Value)形式的強制類型轉換
}
/<code>

上述代碼中,我們分別使用了C語言的提供的兩種強制類型轉換的等價形式將void *轉為了int *,以及將double轉為了int。

谁说 C++ 的强制类型转换很难懂?

static_cast

在C++中,staticcast相當於C語言中的強制類型轉換語法。staticcast用於在編譯期對某種類型的變量進行強制類型轉換。

參考以下代碼:

<code>int main
{
static_cast(malloc(0));
static_cast(0.);
}
/<code>

上述代碼中,我們使用了static_cast分別將void *轉為了int *,以及將double轉為了int。

谁说 C++ 的强制类型转换很难懂?

const_cast

constcast是C++中專用於處理與const相關的強制類型轉換關鍵字,其功能為:為一個變量重新設定其const描述。即:constcast可以為一個變量強行增加或刪除其const限定。

需要明確的是,即使用戶通過constcast強行去除了const屬性,也不代表當前變量從不可變變為了可變。constcast只是使得用戶接管了編譯器對於const限定的管理權,故用戶必須遵守“不修改變量”的承諾。如果違反此承諾,編譯器也不會因此而引發編譯時錯誤,但可能引發運行時錯誤。

下面討論const_cast的主要用途。

考察以下代碼:

<code>struct A { A &test { return *this; } };

int main
{
A.test;
}/<code>

這段代碼看上去運行正常。但如果:

<code>struct A { A &test { return *this; } };

int main
{
const A a;
a.test; // Error!
}/<code>

我們試圖用一個const對象去調用非const成員函數,此時,為了調用此成員函數,const A *this就需要轉換為A *this,這顯然是不行的。

經過上述討論,我們可以將代碼修改為如下:

<code>struct A { const A &test const { return *this; } };

int main
{
const A a;
a.test;
}/<code>

我們將this指針聲明為const A *,解決了此問題。但不難發現,如果我們通過一個非const對象調用此方法,其返回值也會被轉為const,從而不再可以繼續調用任何接受A *this的成員函數。這明顯不是我們想要的結果:

<code>struct A
{
const A &test const { return *this; }
A &test2 { return *this; }
};

int main
{
A.test.test2; // Error!
}/<code>

怎麼解決此問題呢?根據C++函數重載的規則,我們可以為test成員函數同時定義const與非const版本:

<code>struct A
{
A &test { return *this; }
const A &test const { return *this; }

A &test2 { return *this; }
};

int main
{
A.test.test2;

const A a;
a.test;
}/<code>

對於A的非const實例而言,test的非const版本是精確匹配,故編譯器將選擇此版本,從而返回一個A &;同時,對於A的const實例而言,const版本的test是其唯一可用的版本,返回一個const A &。

至此,問題解決了。我們基於const的有無重載出了兩個版本的成員函數,從而使得const對象與非const對象能夠各自調用不同的版本,互不影響。

在實際情況中,我們定義的兩個版本的重載函數除了有無const以外往往沒有任何區別,此時就可以使用const_cast定義第二個重載版本,而無需寫兩遍一模一樣的函數體。

參考以下代碼:

<code>struct A
{
A &test { ... }

// 通過const_cast強行去除this的const限定後調用非const版本
// 返回值通過隱式類型轉換再轉回const A &
const A &test const { return const_cast/<code>

上述代碼中,我們首先定義了一個非const版本的test成員函數,這個成員函數將提供給A *this調用;在定義test成員函數的const版本時,我們通過const_cast\\

由此可見,通過const_cast,我們僅需一行代碼就可以完成第二個函數重載版本的定義。

谁说 C++ 的强制类型转换很难懂?

dynamic_cast

上文提到,動態類型為繼承類的指針或引用可以存儲在靜態類型為基類的變量中,且不會發生隱式類型轉換。對於一個變量而言,雖然其動態類型確實是繼承類,但由於編譯期與運行期的差別,其也無法跨越“可使用的成員名稱由靜態類型決定”這一規則。

雖然繼承類可以通過虛函數的方式一定程度上解決此種情況,但是,如果某個成員函數不是基類虛函數,而只存在於繼承類中呢?dynamic_cast為我們提供瞭解決方案。

當一個靜態類型為基類指針或引用的變量確實存放了繼承類指針或引用時,從基類向繼承類的類型轉換,即向下類型轉換理論上是可行的,dynamic_cast即用於在運行時實現向下類型轉換。

需要注意的是,dynamic_cast的使用必須同時滿足以下所有條件:

  1. 被轉換的變量的類型為基類指針或引用,且其確實存放了一個繼承類指針或引用

  2. 基類具有虛表,即基類必須至少定義了一個虛函數

參考以下代碼:

<code>struct A { virtual void test {} }; // 基類含有虛函數
struct B: A { void test2 {} }; // 繼承類特有函數

int main
{
// 靜態類型為基類指針的變量存放繼承類指針
A *b = new B;

// 通過向下類型轉換調用繼承類特有函數
dynamic_cast(b)->test2;
}
/<code>

上述代碼中,我們首先定義了具有虛函數的基類A,然後定義了具有繼承類特有函數的類B。此時,由於test2成員函數並未在基類中註冊為虛函數,我們將無法通過靜態類型為A *的變量b調用此函數。但由於我們可以確定變量b的動態類型為B *,則可以於運行時通過dynamic_cast將變量b的靜態類型轉為B *,然後調用繼承類的特有函數test2。

谁说 C++ 的强制类型转换很难懂?

reinterpret_cast

reinterpret,即“重新解釋”,顧名思義,這個強制類型轉換的作用是提供某個變量在底層數據上的重新解釋。當我們對一個變量使用reinterpretcast後,編譯器將無視任何不合理行為,強行將被轉換變量的內存數據重解釋為某個新的類型。需要注意的是,reinterpretcast要求轉換前後的類型所佔用內存大小一致,否則將引發編譯時錯誤。

參考以下代碼:

<code>int main
{
reinterpret_cast(0); // 強行將一個整數的內存數據解釋為一個int *
}
/<code>
谁说 C++ 的强制类型转换很难懂?

討論

編程語言的強類型與弱類型相關話題,多年來業界一直討論不休,有的語言發展出了高度弱類型的語法體系,而有的語言則相對嚴謹,要求用戶儘可能多的使用顯式類型轉換。C++作為一門經典的弱類型語言,其類型轉換的相關話題自然十分龐大。

縱觀C++的類型轉換語法體系,其延續了C++一貫的包羅萬象風格,不僅為用戶提供了自定義類型轉換的極大自由度,也在語法層面為類型轉換可能會帶來的各種錯綜複雜的情況作出了嚴謹的規定。

保守看來,如果對C++的類型轉換沒有深入的理解,或不希望大量使用隱式類型轉換時,我們不應過度的依賴諸如非explicit轉換構造函數,自定義的類型轉換操作符,以及涉及隱式類型轉換的各種重載確定等語法組分。但作為C++語法體系的一個重要部分,深入理解C++關於類型轉換的各種話題,必定是十分重要的。

作者簡介:櫻雨樓,畢業於生物信息學專業,是一枚Python/C++/Perl開發,自稱R語言黑粉,GitHub勾搭:https://github.com/yingyulou

【END】


分享到:


相關文章: