那些陌生的C++關鍵字
學過程序語言的人相信對關鍵字並不陌生。偶然間翻起了《C++ Primer》這本書,書中列舉了所有C++的關鍵字。我認真核對了一下,竟然發現有若干個從未使用過的關鍵字。一時間對一個學了六年C++的自己狠狠鄙視了一番,下決心一定要把它們搞明白!圖1紅色字體給出的是我個人感覺一般大家都會比較陌生的關鍵字,下邊我們逐個學習。
圖1 C++ 關鍵字
一、typeid
從名字直觀看來,該關鍵字應該是獲取語言元素的類型ID。其實它和sizeof類似,是一個類型運算符。有時候代碼可能需要獲取某個變量或者類型的名字,這時候使用typeid就比較合適。
使用格式:typeid(int)或typeid(i+1)
這樣操作后返回有個type_info類型的對象,比較常用的對象成員函數一般有比較是否相等和獲取類型名。
例如:
typeid( 1)!= typeid( 1.0); // 比較表達式類型,結果為true。
使用過Java的讀者想必發現該運算符和Java關鍵字instanceof功能比較類似,相比而言,instanceof可能使用起來更方便些。對typeid用法更詳細的內容請點擊參考博文。
二、typename
這個關鍵字和上邊的很相近,剛開始我還以為是這個關鍵字獲取類型的名字呢(想當然害死人啊~),但是他們之間一點關系都沒有!C++使用typename的情況有兩種:
第一種情況是在函數模板和類模板聲明中。一般模板聲明中,使用class關鍵字指定類型參數,后來C++支持使用typename代替class關鍵字。這里僅僅是在語義上強調模板使用的類型參數不一定是類類型,可以是所有類型。這里typename和class沒有任何區別。
使用格式:
template<class T, class Y>
和
template<typename T, typename Y>完全等價!
第二種情況使用情況比較特殊,簡單說起來就是在使用類內成員類型的時候。類內成員類型就是在類定義內聲明了一個類型,該類型屬於類型內部,可見性由權限訪問符限定。
下面就是一個類內的成員類型的聲明。
{
public:
typedef int MyType;
};
類內類型可以像類的靜態成員一樣使用,例如:
MyClass::MyType * pvar; // 定義指針
typedef MyClass::MyType MyType; // 重新命名類型
這些使用方式並沒有太大問題,問題可能出現在帶有模板的代碼中,例如:
void MyMethod( T my )
{
T::MyType * pvar;
typedef T:: MyType MyType;
}
函數參數類型來自於模板,如果MyClass對象是實際參數,那么函數內將聲明一個MyClass::MyType類型的指針,以及對MyClass::MyType類型重新命名為MyType。由於類內類型使用方式和類成員完全相同,對於第一種語句,可以解釋為一個指針聲明,也可以解釋為一個類成員和變量的乘法操作。第二種語句把T::MyType解釋為類型是沒有問題的,但是解釋為成員變量就產生了錯誤,因為typedef操作的對象只能是類型。這種二義性對於編譯器是不願意看到的,為了消除這種問題,就可以使用typename進行顯示的類型聲明。
使用格式:
typename T::MyType * pvar;
typedef typename T:: MyType MyType;
引發這種問題的本質原因來自於模板類型T的不確定性,和直接使用MyClass::MyType不同的是,后者能在編譯時期檢查出該引用的語法成分。通過typename明確的告訴編譯器,這里使用的是類型。這樣編譯器就明確類型T引出的成員是類型,而不是變量或者函數名。因此,typename的使用范圍也被限定在模板函數內部。
其實這些問題在目前的編譯器中並不存在,使用VC6.0和VS2010測試發現,無論是否加上typename程序都不會出錯。對該關鍵字的保留大概是為了兼容舊式編譯器的代碼。關於typename的用法讀者感興趣可以點擊參考鏈接。
三、mutable
Mutable的含義是可變的,它和const關鍵字是相對的。同樣是修飾變量的聲明,但是mutable的使用范圍比const要小。我們知道類的常成員函數在語義上是不允許修改類的成員變量的,但是有時候可能根據代碼的需要並不是這么絕對。那么就可以使用mutable聲明一個類的成員變量,它告訴編譯器類的常成員函數可以修改這個變量。
使用格式:
mutable int var;//類內聲明
例如:
{
mutable int member;
void constFun() const
{
member= 0;
}
};
如果不使用mutable修飾member定義,就會編譯報錯。
四、volatile
Volatile是易變的意思,編譯器在編譯時期可能不能獲取變量是否被多個線程或者進程修改的信息。這時候一個變量是否在兩次“讀操作”之間發生改變,編譯器肯定無法確定。然而編譯優化的技術針對一般的變量都會做出優化,例如:
int b=a;
int c=a+ 1;
編譯器極可能把a放在寄存器中,供b,c的計算使用。更有甚者,編譯器確定a的值是0,會直接計算出b=0,c=1!如果在實際運行中a的值被其他線程修改,這么做就改變了代碼的語意。
使用格式:
volatile int a;//這里對a是否初始化已經不再重要了
為了消除這種問題,使用volatile關鍵字告訴編譯器每次訪問a的時候都需要讀內存,而不對其優化。
五、explicit
Explicit的含義是顯式的,它和C++中的隱式轉換相關。例如:
編譯器會自動將整數100轉化為浮點類型。對於用戶數據類型,C++提供了轉換構造函數和類型轉換函數實現用戶數據類型和內置類型的相互轉換。而explicit是因為轉換構造函數而存在的。下面給出一個轉換構造函數的例子:
{
public:
A( int x)
{
}
};
void fun(A a)
{
}
fun( 1);
最后的函數調用語句是合法的,雖然fun只接受A類型的參數,但是因為A的構造函數除了初始化A外,還提供了整數轉換為A類型的方式——轉換構造函數。但是有些情況下,這樣做可能是不利的,比如fun可能有單獨處理整形參數的重載,或者fun根本不需要轉換構造函數生成的對象。
使用格式:
explicit A(int x)
{}
通過使用explicit限制構造函數必須是顯式調用,禁止隱式類型轉換就可以按照程序作者的需要限定構造函數的功能。
六、static_cast、const_cast、dynamic_cast、reinterpret_cast
之所以把這四個關鍵字放在一起,是因為它們處理相似的問題——顯式類型轉換。C++延續了C風格的強制類型轉換的語法:
(類型)表達式
但是C風格的轉換具體很大的風險性,為此,C++支持四種關鍵字對不同形式的類型轉換進行分別處理。
使用格式:
轉換關鍵字<類型>(表達式)
static_cast和C風格類型轉換功能完全相同,它屬於在編譯時期靜態的類型轉換。如果把一個double類型轉換為整形,形式如下:
static_cast功能有所限制,比如不能轉化struct類型到int,不能轉化指針到double等。另外,它不能在轉換中消除const和volatile屬性。
const_cast用於消除引用或者指針的const或者volatile屬性。
int &i= const_cast < int&>(ci);
通過這種方式,ci引用的內存單元雖然無法通過修改ci改變,但是可以修改i改變內存的值。這里是把const屬性消除,這里想多說一點的是把const加上的問題。例如:
const int &cx=x;
const int &cy=x+ 1;
對const對象的引用允許使用表達式初始化,比如cy引用的內存單元的值應該就是x+1的值即101。正因為此《C++ Primer》也假設了編譯器了的工作方式:
const int &cy=temp;
如果按照這種工作方式,cx引用的內存單元應該不是x的內存單元,但是在VS2010下測試結果表明cx和x的地址為同一內存單元!
顯然,使用單獨的變量初始化const引用的值不會產生額外的存儲空間,通過修改原先的變量是可以修改常量引用的值的。
dynamic_cast一般出現在類到子類或兄弟類的轉換,並要求基類有虛函數。而且它能提供轉換后的結果和狀態,一旦轉換失敗則返回空指針。如果沒有繼承關系的轉換一般使用static_cast。
對於dynamic_cast使用方式如下:
{
virtual void fun(){}; // 必須擁有虛函數
};
class A: public Base // 必須是供有繼承才能默認轉換
{
};
Base b;
A *a= dynamic_cast<A*>(&b); // 基類到子類,顯式轉換
Base*pb=a; // 子類到基類,默認轉換
reinterpret_casts一般用作函數指針的轉換,而且使用它的代碼可移植性很差,因為無法確定編譯器的函數調用方式等。有可能會導致函數調用出錯,一般不常用。例如:
int func() // 一個無參返回整數的函數定義
{
return 0;
}
FuncPtr pf= reinterpret_cast<FuncPtr>(func);
直接把func賦值給pf是不行的,使用reinterpret_cast將函數指針強制轉換即可。
至此,我們把那些陌生的C++關鍵字的“老底”摸了個遍,相信以后應該不會再碰到搞不清楚的C++關鍵字了,希望本文對你有所幫助!