相關(guān)章節(jié)
「C++類的特殊成員函數(shù)(1):構(gòu)造函數(shù)」中“3 隱式類類型轉(zhuǎn)換”
考慮下面的例子:
int ival = 0;
ival = 3.541 + 3; //編譯器會給出警告
在計算ival的值時,C++不會把兩個不同類型的值直接相加,而是提供了一套轉(zhuǎn)換規(guī)則,在做加法計算前,先將兩個操作數(shù)轉(zhuǎn)換為同一種數(shù)據(jù)類型。
這里ival最終的值為6。在賦值操作中,不可能更改左操作數(shù)對象的類型。如果賦值操作符的左右操作數(shù)類型不同,那么右操作數(shù)會被轉(zhuǎn)換為左操作數(shù)的類型。
在本例中,從double類型轉(zhuǎn)換為int的過程中,會導致精度損失,大多數(shù)編輯器會給出警告:
warning: assignment to 'int' from 'double'
在上面的過程中,為了確保表達式本身的合法性,編譯器對操作數(shù)的類型按照一定的規(guī)則進行了轉(zhuǎn)換。這些轉(zhuǎn)換規(guī)則由編譯器自動執(zhí)行,無需開發(fā)人員介入——有時甚至不需要開發(fā)人員了解。這種轉(zhuǎn)換過程,我們稱之為隱式類型轉(zhuǎn)換(implicit type conversion)。
此外,由開發(fā)人員通過編碼的方式進行的類型轉(zhuǎn)換,我們稱之為顯示轉(zhuǎn)換,也叫強制類型轉(zhuǎn)換(cast)。
雖然有時候確實需要強制類型轉(zhuǎn)換,但是它們本質(zhì)上是非常危險的。
如果兩個類型之間可以相互轉(zhuǎn)換(conversion),則稱這兩個類型相關(guān)。
下面我們分別對這兩種轉(zhuǎn)換方式進行深入的介紹:
1 隱式類型轉(zhuǎn)換
為了理解隱式類型轉(zhuǎn)換,我們需要知道它們在什么時候發(fā)生,以及可能出現(xiàn)哪些類型的轉(zhuǎn)換。
1.1 何時發(fā)生隱式類型轉(zhuǎn)換
- 在混合類型的表達式中,其操作數(shù)被轉(zhuǎn)換為相同的類型;
- 用作條件的表達式被轉(zhuǎn)換為bool類型;
- 用一表達式初始化某個變量,或?qū)⒁槐磉_式賦值給某個變量,則該表達式被轉(zhuǎn)換為該變量類型;
- 在函數(shù)調(diào)用傳遞實參的過程中,其操作數(shù)被轉(zhuǎn)換為形參的類型。
第一點和第三點已經(jīng)在上面的例子中有過說明,下面我們針對第二點和第四點舉例說明:
bool func(int ival)
{
bool flag = false;
if (ival) //ival轉(zhuǎn)換為bool類型,上述第二點
{
flag = true;
}
return flag;
}
int main()
{
double dval = 3.541;
func(dval); //dval轉(zhuǎn)換為int類型,上述第四點
return 1;
}
1.2 隱式類型轉(zhuǎn)換的種類和規(guī)則
1.2.1 算數(shù)轉(zhuǎn)換(arithmetic conversion)
算數(shù)轉(zhuǎn)換,保證在執(zhí)行操作數(shù)之前,將二元操作符(如算數(shù)和邏輯操作符)的兩個操作數(shù)轉(zhuǎn)換為同一類型,并使表達式的值也具有相同的類型。
算數(shù)轉(zhuǎn)換規(guī)則的核心,是保護計算值的精度不降低。在規(guī)則的內(nèi)容中,定義了一個類型轉(zhuǎn)換的層次,該層次規(guī)定了操作數(shù)應按什么次序轉(zhuǎn)換為表達式中最寬的類型。
由于要保證計算值的精度,因此這類轉(zhuǎn)換本質(zhì)上依賴于機器對各數(shù)據(jù)類型的具體實現(xiàn)。
整形提升(integer promotion)規(guī)則:對于所有比int小的整形,包括char、signed char、unsigned char、short和unsigned short,如果該類型的所有可能的值都能包容在int內(nèi),它們就會被提升為int型,否則,它們將被提升為unsigned int。
如果將bool值提升為int,則false轉(zhuǎn)化為0,而true則轉(zhuǎn)化為1。
下面通過例子,我們可以理解的更透徹:
bool flag;
char cval;
short sval;
unsigned short usval;
int ival;
unsigned int uival;
long lval;
unsigned long ulval;
float fval;
double dval;
3.14159L + 'a'; //'a'轉(zhuǎn)換為int,而后再轉(zhuǎn)換為double long
dval + ival; //ival轉(zhuǎn)換為double
dval + fval; //fval轉(zhuǎn)換為double
ival = dval; //dval轉(zhuǎn)換為int(截斷)
flag = dval; //如果dval是0,那么flag為false,否則為true
cval + fval; //cval轉(zhuǎn)換為int,而后再轉(zhuǎn)換為float
sval + cval; //sval和cval都轉(zhuǎn)換為int
cval + lval; //cval轉(zhuǎn)換為long
ival + ulval; //ival轉(zhuǎn)換為unsigned long
usval + ival; //轉(zhuǎn)換取決于unsigned short和int的位寬
uival + lval; //轉(zhuǎn)換取決于unsigned int和long的位寬
最后兩條語句,單獨說明一下。為了保證計算值的精度不降低,倒數(shù)第二條,如果int型足夠表示所有unsigned short型的值,則將unsigned short轉(zhuǎn)換為int,否則,將兩個操作數(shù)均轉(zhuǎn)換為unsigned int。相同的,對于最后一條語句,如果long型足夠表示所有unsigned int型的值,則將unsigned int轉(zhuǎn)換為long,否則,將兩個操作數(shù)均轉(zhuǎn)換為unsigned long。
1.2.2 指針轉(zhuǎn)換
指針轉(zhuǎn)換主要存在于下列幾種情況中:
- 使用數(shù)組時,大多數(shù)情況下會將數(shù)組轉(zhuǎn)換為指向第一個元素的指針;
- 指向任意數(shù)據(jù)類型的指針都可轉(zhuǎn)換為void*類型;
- 整型數(shù)值常量0可轉(zhuǎn)換為任意指針類型(轉(zhuǎn)換后為空指針)。
對于第一條,在下列幾種情況下,不會進行類型轉(zhuǎn)換:
- 數(shù)組用作取地址操作符(&)的操作數(shù)時;
- 數(shù)組用作sizeof操作符的操作數(shù)時;
- 用數(shù)組對數(shù)組的引用進行初始化時。
1.2.3 轉(zhuǎn)換為bool類型
算數(shù)值和指針都可以轉(zhuǎn)換為bool類型。以下幾種情況,轉(zhuǎn)換的bool值為false:
- 算數(shù)值為0;
- 指針值為NULL;
- char型值為空字符(null)。
本質(zhì)上,上述情況都可以轉(zhuǎn)換為算數(shù)值0。其他情況下,均為true。
1.2.4 轉(zhuǎn)換與枚舉類型
C++自動將枚舉類型的對象或枚舉成員轉(zhuǎn)換為整型,其轉(zhuǎn)換結(jié)果可用于任何要求使用整數(shù)值的地方。
將enum對象或枚舉成員提升為什么類型,由機器實現(xiàn)和枚舉成員最大值共同決定,且該類型至少是int。
1.2.5 轉(zhuǎn)換為const對象
- 使用非const對象初始化const對象的引用;
- 可以將非const對象的地址(或指針)轉(zhuǎn)換為指向相關(guān)const類型的指針。
int i;
const int &j = i; //第一種情況
int ci = 0;
const int *p = &ci; //第二種情況
1.2.6 由標準庫類型定義的轉(zhuǎn)換
類類型可以定義由編譯器自動執(zhí)行的類型轉(zhuǎn)換。該部分超出本章的討論范圍,將會在C++類的介紹中進行描述。
2 強制類型轉(zhuǎn)換
建議:避免使用強制類型轉(zhuǎn)換,因為其關(guān)閉或掛起了正常的類型檢查。
2.1 何時需要強制類型轉(zhuǎn)換
- 覆蓋通常的標準轉(zhuǎn)換;
- 可能存在多種轉(zhuǎn)換時,需要選擇一種特定的轉(zhuǎn)換類型。
2.2 命名的強制類型轉(zhuǎn)換
其一般形式如下:
cast-name<type>(expression);
- cast-name為static_cast、dynamic_cast、const_cast和reinterpret_cast之一;
- type為轉(zhuǎn)換的類型目標;
- expression為被強制轉(zhuǎn)換的值。
強制轉(zhuǎn)換的類型,指定了在expression上執(zhí)行某種特定類型的轉(zhuǎn)換:
2.2.1 dynamic_cast
支持運行時識別指針或引用所指向的對象。
2.2.2 const_cast
顧名思義,可以轉(zhuǎn)換掉表達式的const性質(zhì)。例如:
const char *pc_str;
char* pc = string_copy(const_cast<char*>(pc_str));
這里,假設有函數(shù)string_copy,我們對其唯一的char* 類型參數(shù)只讀不寫。當我們要傳入的實參是const char* 類型時,可以通過const_cast轉(zhuǎn)換掉參數(shù)的const性質(zhì)。
2.2.3 static_cast
編譯器隱式執(zhí)行的任何類型轉(zhuǎn)換都可以有static_cast顯示完成;如果編譯器不提供自動轉(zhuǎn)換,也可以通過static_cast來實現(xiàn)。
//前一種情況
double d = 97.0;
char ch = static_cast<char>(d); //編譯器不再報精度丟失的錯誤
//后一種情況
void* p = &d;
double *dp = static_cast<double*>(p);
2.2.4 reinterpret_cast
通常為操作數(shù)的位模式提供較低層次的重新解釋。
int *ip;
char* pc = reinterpret_cast<char*>(ip);
2.3 舊式強制類型轉(zhuǎn)換
在引入命名的強制類型轉(zhuǎn)換操作符之前,顯示強制轉(zhuǎn)換用圓括號將類型括起來實現(xiàn):
type (expression); //函數(shù)風格的強制類型轉(zhuǎn)換寫法
(type) expression; //C語言風格的強制類型轉(zhuǎn)換寫法
與舊式相比較,命名的強制類型轉(zhuǎn)換的優(yōu)點是:加強強制類型轉(zhuǎn)換的可視性,開發(fā)人員可清晰辨別每個強制類型轉(zhuǎn)換潛在的風險級別。
支持舊式強制轉(zhuǎn)換符號,是為了對“在標準C++之前編寫的程序”保持向后兼容性,并保持與C語言的兼容性。