前面講了C++繼承並擴展C語言的傳統類型轉換方式,最後留下了一些關於指針和引用上的轉換問題,沒有做詳細地講述。C++相比於C是一門面向對象的語言,面向對象最大的特點之一就是具有“多態性(Polymorphism)”。
要想很好的使用多態性,就免不了要使用指針和引用,也免不了會碰到轉換的問題,所以在這一篇,就把導師講的以及在網上反復查閱了解的知識總結一下。
C++提供了四個轉換運算符:
const_cast <new_type> (expression)
static_cast <new_type> (expression)
reinterpret_cast <new_type> (expression)
dynamic_cast <new_type> (expression)
它們有著相同的結構,看起來像是模板方法。這些方法就是提供給開發者用來進行指針和引用的轉換的。
其實我很早就想寫這篇內容的,自己不斷地查看導師發來的資料,也在網上不停地看相關的知識,卻一直遲遲不能完全理解C++轉換運算符的用法,倒是看了那些資料後先寫了一篇傳統轉換方面的內容。雖然從字面上很好理解它們大致是什麼作用,但是真正像使用起來,卻用不知道他們具體的用途,只會不斷的被編譯器提醒Error。所以如果出現理解不到位或錯誤的地方,還希望前人或來者能夠指正。
在我看來這些標准運算符的作用就是對傳統運算符的代替,以便做到統一。就像我們用std::endl來輸出換行,而不是'\n'。我會用代碼來說明相應的傳統轉換可以如何這些標准運算符。當然,這這是大致的理解,在標准運算符上,編譯器肯定有做更多的處理,特別是dynamic_cast是不能用傳統轉換方式來完全實現的。
在這一篇文章裡,我會先講講我對const_cast運算符的理解。
const_cast (expression)
const_cast轉換符是用來移除變量的const或volatile限定符。對於後者,我不是太清楚,因為它涉及到了多線程的設計,而我在這方面沒有什麼了解。所以我只來說const方面的內容。
用const_cast來去除const限定
對於const變量,我們不能修改它的值,這是這個限定符最直接的表現。但是我們就是想違背它的限定希望修改其內容怎麼辦呢?
下邊的代碼顯然是達不到目的的:
const int constant = 10;
int modifier = constant;
因為對modifier的修改並不會影響到constant,這暗示了一點:const_cast轉換符也不該用在對象數據上,因為這樣的轉換得到的兩個變量/對象並沒有相關性。
只有用指針或者引用,讓變量指向同一個地址才是解決方案,可惜下邊的代碼在C++中也是編譯不過的:
const int constant = 21;
int* modifier = &constant
// Error: invalid conversion from 'const int*' to 'int*'
(上邊的代碼在C中是可以編譯的,最多會得到一個warning,所在在C中上一步就可以開始對constant裡面的數據胡作非為了)
把constant交給非const的引用也是不行的。
const int constant = 21;
int& modifier = constant;
// Error: invalid initialization of reference of type 'int&' from expression of type 'const int'
於是const_cast就出來消滅const,以求引起程序世界的混亂。
下邊的代碼就順利編譯功過了:
const int constant = 21;
const int* const_p = &constant;
int* modifier = const_cast<int*>(const_p);
*modifier = 7;
傳統轉換方式實現const_cast運算符
我說過標:准轉換運算符是可以用傳統轉換方式實現的。const_cast實現原因就在於C++對於指針的轉換是任意的,它不會檢查類型,任何指針之間都可以進行互相轉換,因此const_cast就可以直接使用顯示轉換(int*)來代替:
const int constant = 21;
const int* const_p = &constant;
int* modifier = (int*)(const_p);
或者我們還可以把他們合成一個語句,跳過中間變量,用
const int constant = 21;
int* modifier = (int*)(&constant);
替代
const int constant = 21;
int* modifier = const_cast<int*>(&constant);
為何要去除const限定
從前面代碼中已經看到,我們不能對constant進行修改,但是我們可以對modifier進行重新賦值。
但是但是,程序世界真的混亂了嗎?我們真的通過modifier修改了constatn的值了嗎?修改const變量的數據真的是C++去const的目的嗎?
如果我們把結果打印出來:
cout << "constant: "<< constant <<endl;
cout << "const_p: "<< *const_p <<endl;
cout << "modifier: "<< *modifier <<endl;
/**
constant: 21
const_p: 7
modifier: 7
**/
constant還是保留了它原來的值。
可是它們的確指向了同一個地址呀:
cout << "constant: "<< &constant <<endl;
cout << "const_p: "<< const_p <<endl;
cout << "modifier: "<< modifier <<endl;
/**
constant: 0x7fff5fbff72c
const_p: 0x7fff5fbff72c
modifier: 0x7fff5fbff72c
**/
這真是一件奇怪的事情,但是這是件好事:說明C++裡是const,就是const,外界千變萬變,我就不變。不然真的會亂套了,const也沒有存在的意義了。
IBM的C++指南稱呼“*modifier = 7;”為“未定義行為(Undefined Behavior)”。所謂未定義,是說這個語句在標准C++中沒有明確的規定,由編譯器來決定如何處理。
位運算的左移操作也可算一種未定義行為,因為我們不確定是邏輯左移,還是算數左移。
再比如下邊的語句:v[i] = i++; 也是一種未定義行為,因為我們不知道是先做自增,還是先用來找數組中的位置。
對於未定義行為,我們所能做的所要做的就是避免出現這樣的語句。對於const數據我們更要這樣保證:絕對不對const數據進行重新賦值。
如果我們不想修改const變量的值,那我們又為什麼要去const呢?
原因是,我們可能調用了一個參數不是const的函數,而我們要傳進去的實際參數確實const的,但是我們知道這個函數是不會對參數做修改的。於是我們就需要使用const_cast去除const限定,以便函數能夠接受這個實際參數。
#include <iostream>
using namespace std;
void Printer (int* val,string seperator = "\n")
{
cout << val<< seperator;
}
int main(void)
{
const int consatant = 20;
//Printer(consatant);//Error: invalid conversion from 'int' to 'int*'
Printer(const_cast<int *>(&consatant));
return 0;
}出現這種情況的原因,可能是我們所調用的方法是別人寫的。還有一種我能想到的原因,是出現在const對象想調用自身的非const方法的時候,因為在類定義中,const也可以作為函數重載的一個標示符。有機會,我會專門回顧一下我所知道const的用法,C++的const真的有太多可以說的了。
在IBM的C++指南中還提到了另一種可能需要去const的情況:
#include <iostream>
using namespace std;
int main(void) {
int variable = 21;
int* const_p = &variable;
int* modifier = const_cast<int*>(const_p);
*modifier = 7
cout << "variable:" << variable << endl;
return 0;
}
/**
variable:7
**/
我們定義了一個非const的變量,但用帶const限定的指針去指向它,在某一處我們突然又想修改了,可是我們手上只有指針,這時候我們可以去const來修改了。上邊的代碼結果也證實我們修改成功了。
不過我覺得這並不是一個好的設計,還是應該遵從這樣的原則:使用const_cast去除const限定的目的絕對不是為了修改它的內容,只是出於無奈。(如果真像我說是種無奈,似乎const_cast就不太有用到的時候了,但的確我也很少用到它)