1. 程式人生 > >c++關鍵字詳解(volatile, mutable, explicit, dynamic_ cast(expression))等(轉)

c++關鍵字詳解(volatile, mutable, explicit, dynamic_ cast(expression))等(轉)

版權宣告:轉載時請以超連結形式標明文章原始出處和作者資訊及本宣告
http://cyinger-smiling.blogbus.com/logs/31041587.html


c++關鍵字詳解(volatile, mutable, explicit, dynamic_ cast<T>(expression))等
1  volatile
有些變數是用volatile關鍵字宣告的。當兩個執行緒都要用到某一個變數且該變數的值會被改變時,應該用volatile宣告,該關鍵字的作用是防止優化編譯器把變數從記憶體裝入CPU暫存器中。如果變數被裝入暫存器,那麼兩個執行緒有可能一個使用記憶體中的變數,一個使用暫存器中的變數,這會造成程式的錯誤執行。 volatile的意思是讓編譯器每次操作該變數時一定要從記憶體中真正取出,而不是使用已經存在暫存器中的值,如下:   
  volatile   BOOL   bStop   =   FALSE;   
    
  在一個執行緒中:   
  while(   !bStop   )   
  {   
    ...   
  }   
  bStop   =   FALSE;   
  return;     
    
  在另外一個執行緒中,要終止上面的執行緒迴圈:   
  bStop   =   TRUE;   
  while(   bStop   );   //等待上面的執行緒終止,如果bStop不使用volatile申明,那麼這個迴圈將是一個死迴圈,因為bStop已經讀取到了暫存器中,暫存器中bStop的值永遠不會變成FALSE,加上volatile,程式在執行時,每次均從記憶體中讀出bStop的值,就不會死迴圈了。 
這個關鍵字是用來設定某個物件的儲存位置在記憶體中,而不是暫存器中。因為一般的物件編譯器可能會將其的拷貝放在暫存器中用以加快指令的執行速度,例如下段程式碼中:   
  ...   
  int   nMyCounter   =   0;   
  for(;   nMyCounter<100;nMyCounter++)   
  {   
  ...   
  }   
  ...   
  在此段程式碼中,nMyCounter的拷貝可能存放到某個暫存器中(迴圈中,對nMyCounter的測試及操作總是對此暫存器中的值進行),但是另外有又段程式碼執行了這樣的操作:nMyCounter   -=   1;這個操作中,對nMyCounter的改變是對記憶體中的nMyCounter進行操作,於是出現了這樣一個現象:nMyCounter的改變不是同步的 
                                                                                            
2  extern
2A 在c++中,還可用來指定使用另一語言進行連結,這時需要與特定的轉換符一起使用。目前microsoft c/c++僅支援”c”轉換標記,來支援c編譯器連結。使用這種情況有兩種形式:
u       extern “c” 宣告語句
u       extern “c” { 宣告語句塊 }
2B  extern   LPDIRECTDRAW4       lpdd;  宣告lpdd,但是不分配記憶體,只說明他是你可以用的變數,在此程式之外其他的程式中已經聲名了他。 其實他就是防止名字衝突~   被其修飾的變數(外部變數)是靜態分配空間的,即程式開始時分配,結束時釋放
如果一個頭檔案被   #include   到一個以上的原始檔   裡,這個標頭檔案中所有的定義就會出現在每一個有關的原始碼檔案裡。這會使它們裡的符號被定義一次以上,從而出現連線錯誤。   
        解決辦法就是不要在標頭檔案裡定義變數。你只需要在標頭檔案裡宣告它們然後在適當的原始碼檔案(應該   #include   那個標頭檔案的那個)裡定義它們一次。extern告訴編譯器其所宣告的符號的存在,並不會使編譯器分配貯存空間。當做一個宣告而不是做定義的時候,在宣告前放一個關鍵字“extern”。 
extern關鍵字的作用是宣告變數和函式為外部連結,即該變數或函式名在其它檔案中可見。用其宣告的變數或函式應該在別的檔案或同一檔案的其它地方定義。 
例如語句:extern int a;
    僅僅是一個變數的宣告,其並不是在定義變數a,並未為a分配記憶體空間。變數a在所有模組中作為一種全域性變數只能被定義一次,否則會出現連線錯誤。
    通常,在模組的標頭檔案中對本模組提供給其它模組引用的函式和全域性變數以關鍵字extern宣告。例如,如果模組B欲引用該模組A中定義的全域性變數和函式時只需包含模組A的標頭檔案即可。這樣,模組B中呼叫模組A中的函式時,在編譯階段,模組B雖然找不到該函式,但是並不會報錯;它會在連線階段中從模組A編譯生成的目的碼中找到此函式
3  static
靜態變數作用範圍在一個檔案內,程式開始時分配空間,結束時釋放空間,預設初始化為0,使用時可改變其值。
靜態變數或靜態函式,即只有本檔案內的程式碼才可訪問它,它的名字(變數名或函式名)在其它檔案中不可見。
在函式體內生成的靜態變數它的值也只能維持
int max_so_far( int curr )//求至今(本次呼叫)為止最大值
{
   static int biggest; //該變數保持著每次呼叫時的最新值,它的有效期等於整個程式的有效期
   if( curr > biggest )
   biggest = curr;
   return biggest;
}
在c++類的成員變數被宣告為static(稱為靜態成員變數),意味著它為該類的所有例項所共享,也就是說當某個類的例項修改了該靜態成員變數,其修改值為該類的其它所有例項所見;而類的靜態成員函式也只能訪問靜態成員(變數或函式)。
類的靜態成員變數必須在宣告它的檔案範圍內進行初始化才能使用,private型別的也不例外。如,               
float savingsaccount::currentrate = 0.00154;   (注:currentrate是類savingsaccount的靜態成員變數)
4  register
  用register宣告的變數稱著暫存器變數,在可能的情況下會直接存放在機器的暫存器中;但對32位編譯器不起作用,當global optimizations(全域性優化)開的時候,它會做出選擇是否放在自己的暫存器中;不過其它與register關鍵字有關的其它符號都對32位編譯器有效。
5  auto
  它是儲存型別識別符號,表明變數(自動)具有本地範圍,塊範圍的變數宣告(如for迴圈體內的變數宣告)預設為auto儲存型別。
6  const
 const所修飾的物件或變數不能被改變,修飾函式時,該函式不能改變在該函式外面宣告的變數也不能呼叫任何非const函式。在函式的宣告與定義時都要加上const,放在函式引數列表的最後一個括號後。
 c++中,用const宣告一個變數,意味著該變數就是一個帶型別的常量,可以代替#define,且比#define多一個型別資訊,且它執行內連結,可放在標頭檔案中宣告;但在c中,其宣告則必須放在原始檔(即.c檔案)中,在c中const宣告一個變數,除了不能改變其值外,它仍是一具變數,如
const int maxarray = 255;
char store_char[maxarray];  //c++中合法,c中不合法
1.如果const位於星號左側,則const用來修飾指標所指向的變數,
       即指標指向的為不可變的.
2.如果const位於星號右側,const就是修飾指標本身,即指標本身是
不可變的.


7 mutable關鍵字的用法 
關鍵字mutable是C++中一個不常用的關鍵字,他只能用於類的非靜態和非常量資料成員
我們知道一個物件的狀態由該物件的非靜態資料成員決定,所以隨著資料成員的改變,
對像的狀態也會隨之發生變化! 
如果一個類的成員函式被宣告為const型別,表示該函式不會改變物件的狀態,也就是
該函式不會修改類的非靜態資料成員.但是有些時候需要在該類函式中對類的資料成員
進行賦值.這個時候就需要用到mutable關鍵字了
mutable關鍵字提示編譯器該變數可以被類的const函式修改
8 explicit
explicit關鍵字用於取消建構函式的隱式轉換,對有多個引數的建構函式使用explicit是個語法錯誤。


9 C++ 同時提供了四種新的強制轉型形式(通常稱為新風格的或 C++ 風格的強制轉型):
const_cast<T>(expression)
dynamic_cast<T>(expression)
reinterpret_cast<T>(expression)
static_cast<T>(expression)
每一種適用於特定的目的:
  ·const_cast 一般用於強制消除物件的常量性。它是唯一能做到這一點的 C++ 風格的強制轉型。
  ·dynamic_cast 主要用於執行“安全的向下轉型(safe downcasting)”,也就是說,要確定一個物件是否是一個繼承體系中的一個特定型別。它是唯一不能用舊風格語法執行的強制轉型。也是唯一可能有重大執行時代價的強制轉型。(過一會兒我再提供細節。)
  ·reinterpret_cast 是特意用於底層的強制轉型,導致實現依賴(implementation-dependent)(就是說,不可移植)的結果,例如,將一個指標轉型為一個整數。這樣的強制轉型在底層程式碼以外應該極為罕見。在本書中我只用了一次,而且還僅僅是在討論你應該如何為裸記憶體(raw memory)寫一個調諧分配者(debugging allocator)的時候。
  ·static_cast 可以被用於強制隱型轉換(例如,non-const 物件轉型為 const 物件(就像 Item 3 中的),int 轉型為 double,等等)。它還可以用於很多這樣的轉換的反向轉換(例如,void* 指標轉型為有型別指標,基類指標轉型為派生類指標),但是它不能將一個 const 物件轉型為 non-const 物件。(只有 const_cast 能做到。)
A:轉換的含義是通過改變一個變數的型別為別的型別從而改變該變數的表示方式。為了型別轉換一個簡單物件為另一個物件你會使用傳統的型別轉換操作符。比如,為了轉換一個型別為double的浮點數的指標到整型:
程式碼:
int i;
double d;
i = (int) d;
或者:
i = int (d);
對於具有標準定義轉換的簡單型別而言工作的很好。然而,這樣的轉換符也能不分皁白的應用於類(class)和類的指標。ANSI-C++標準定義了四個新的轉換符:'reinterpret_cast', 'static_cast', 'dynamic_cast' 和 'const_cast',目的在於控制類(class)之間的型別轉換。
程式碼:
reinterpret_cast<new_type>(expression)
dynamic_cast<new_type>(expression)
static_cast<new_type>(expression)
const_cast<new_type>(expression)
const_cast:允許新增或刪除表示式型別的const或volatile關鍵字.
static_cast:用於將一個繼承層次結構中的基類型別的指標或引用向下轉換為一個派生類的指標或引用。
dynamic_cast:僅適用於多型型別的向下轉換,被轉換的型別必須是一個指向含有虛擬函式的類型別的指標。
reinterpret_cast:從位的角度來看待一個物件,從而允許將一個東西看成是完全不同的另一個東西,最強的一種轉換。
1 reinterpret_cast
'reinterpret_cast'轉換一個指標為其它型別的指標。它也允許從一個指標轉換為整數型別。反之亦然。(譯註:是指標具體的地址值作為整數值?)
這個操作符能夠在非相關的型別之間轉換。操作結果只是簡單的從一個指標到別的指標的值的二進位制拷貝。在型別之間指向的內容不做任何型別的檢查和轉換。
如果情況是從一個指標到整型的拷貝,內容的解釋是系統相關的,所以任何的實現都不是方便的。一個轉換到足夠大的整型能夠包含它的指標是能夠轉換回有效的指標的。
程式碼:
class A {};
class B {};
A * a = new A;
B * b = reinterpret_cast<B *>(a);
'reinterpret_cast'就像傳統的型別轉換一樣對待所有指標的型別轉換。
2 static_cast
'static_cast'允許執行任意的隱式轉換和相反轉換動作。(即使它是不允許隱式的)
應用到類的指標上,意思是說它允許子類型別的指標轉換為父類型別的指標(這是一個有效的隱式轉換),同時,也能夠執行相反動作:轉換父類為它的子類。
在這最後例子裡,被轉換的父類沒有被檢查是否與目的型別相一致。
程式碼:
class Base {};
class Derived : public Base {};
Base *a    = new Base;
Derived *b = static_cast<Derived *>(a);
'static_cast'除了操作型別指標,也能用於執行型別定義的顯式的轉換,以及基礎型別之間的標準轉換:
程式碼:
double d = 3.14159265;
int    i = static_cast<int>(d);
3 dynamic_cast
'dynamic_cast'只用於物件的指標和引用。當用於多型型別時,它允許任意的隱式型別轉換以及相反過程。不過,與static_cast不同,在後一種情況裡(注:即隱式轉換的相反過程),dynamic_cast會檢查操作是否有效。也就是說,它會檢查轉換是否會返回一個被請求的有效的完整物件。
檢測在執行時進行。如果被轉換的指標不是一個被請求的有效完整的物件指標,返回值為NULL.
程式碼:
class Base { virtual dummy() {} };
class Derived : public Base {};
Base* b1 = new Derived;
Base* b2 = new Base;
Derived* d1 = dynamic_cast<Derived *>(b1);          // succeeds
Derived* d2 = dynamic_cast<Derived *>(b2);          // fails: returns 'NULL' ??必須要是完整的物件??
如果一個引用型別執行了型別轉換並且這個轉換是不可能的,一個bad_cast的異常型別被丟擲:
程式碼:
class Base { virtual dummy() {} };
class Derived : public Base { };
Base* b1 = new Derived;
Base* b2 = new Base;
Derived d1 = dynamic_cast<Derived &*>(b1);          // succeeds
Derived d2 = dynamic_cast<Derived &*>(b2);          // fails: exception thrown
4 const_cast
這個轉換型別操縱傳遞物件的const屬性,或者是設定或者是移除:
程式碼:
class C {};
const C *a = new C;
C *b = const_cast<C *>(a);
其它三種操作符是不能修改一個物件的常量性的。
注意:'const_cast'也能改變一個型別的volatile qualifier。
--------------------------------------------------------------------
C++的4種類型轉換
    一、C 風格(C-style)強制轉型如下:
    (T) expression // cast expression to be of type T 
    函式風格(Function-style)強制轉型使用這樣的語法:
    T(expression) // cast expression to be of type T 
    這兩種形式之間沒有本質上的不同,它純粹就是一個把括號放在哪的問題。我把這兩種形式稱為舊風格(old-style)的強制轉型。
   二、 C++的四種強制轉型形式:
  C++ 同時提供了四種新的強制轉型形式(通常稱為新風格的或 C++ 風格的強制轉型): 
  const_cast(expression) 
  dynamic_cast(expression) 
  reinterpret_cast(expression) 
  static_cast(expression)
  每一種適用於特定的目的:
  ·dynamic_cast 主要用於執行“安全的向下轉型(safe downcasting)”,也就是說,要確定一個物件是否是一個繼承體系中的一個特定型別。它是唯一不能用舊風格語法執行的強制轉型,也是唯一可能有重大執行時代價的強制轉型。
    
    ·static_cast 可以被用於強制隱型轉換(例如,non-const 物件轉型為 const 物件,int 轉型為 double,等等),它還可以用於很多這樣的轉換的反向轉換(例如,void* 指標轉型為有型別指標,基類指標轉型為派生類指標),但是它不能將一個 const 物件轉型為 non-const 物件(只有 const_cast 能做到),它最接近於C-style的轉換。
    
  ·const_cast 一般用於強制消除物件的常量性。它是唯一能做到這一點的 C++ 風格的強制轉型。
  ·reinterpret_cast 是特意用於底層的強制轉型,導致實現依賴(implementation-dependent)(就是說,不可移植)的結果,例如,將一個指標轉型為一個整數。這樣的強制轉型在底層程式碼以外應該極為罕見。
  
  舊風格的強制轉型依然合法,但是新的形式更可取。首先,在程式碼中它們更容易識別(無論是人還是像 grep 這樣的工具都是如此),這樣就簡化了在程式碼中尋找型別系統被破壞的地方的過程。第二,更精確地指定每一個強制轉型的目的,使得編譯器診斷使用錯誤成為可能。例如,如果你試圖使用一個 const_cast 以外的新風格強制轉型來消除常量性,你的程式碼將無法編譯。
==  
==  dynamic_cast .vs. static_cast 
==
class B { ... };
class D : public B { ... };
void f(B* pb)
{
   D* pd1 = dynamic_cast<D*>(pb);
   D* pd2 = static_cast<D*>(pb);
}
If pb really points to an object of type D, then pd1 and pd2 will get the same value. They will also get the same value if pb == 0.
If pb points to an object of type B and not to the complete D class, then dynamic_cast will know enough to return zero. However, static_cast relies on the programmer’s assertion that pb points to an object of type D and simply returns a pointer to that supposed D object.
    即dynamic_cast可用於繼承體系中的向下轉型,即將基類指標轉換為派生類指標,比static_cast更嚴格更安全。dynamic_cast在執行效率上比static_cast要差一些,但static_cast在更寬上範圍內可以完成對映,這種不加限制的對映伴隨著不安全性.static_cast覆蓋的變換型別除類層次的靜態導航以外,還包括無對映變換,窄化變換(這種變換會導致物件切片,丟失資訊),用VOID*的強制變換,隱式型別變換等...


==
==  static_cast .vs. reinterpret_cast 
==
    reinterpret_cast是為了對映到一個完全不同型別的意思,這個關鍵詞在我們需要把型別映射回原有型別時用到它.我們對映到的型別僅僅是為了故弄玄虛和其他目的,這是所有對映中最危險的.(這句話是C++程式設計思想中的原話)
    static_cast 和 reinterpret_cast 操作符修改了運算元型別. 它們不是互逆的; static_cast 在編譯時使用型別資訊執行轉換, 在轉換執行必要的檢測(諸如指標越界計算, 型別檢查). 其運算元相對是安全的. 另一方面, reinterpret_cast 僅僅是重新解釋了給出的物件的位元模型而沒有進行二進位制轉換, 例子如下:
    int n=9; double d=static_cast < double > (n);
    上面的例子中, 我們將一個變數從 int 轉換到 double. 這些型別的二進位制表示式是不同的. 要將整數 9 轉換到 雙精度整數 9, static_cast 需要正確地為雙精度整數 d 補足位元位. 其結果為 9.0. 而reinterpret_cast 的行為卻不同:
    int n=9;
    double d=reinterpret_cast<double & > (n);
    這次, 結果有所不同. 在進行計算以後, d 包含無用值. 這是因為 reinterpret_cast 僅僅是複製 n 的位元位到 d, 沒有進行必要的分析.