型別轉換和型別安全 (現代的 C++)
文件識別一般型別轉換問題並說明如何避免它們在您的 C++ 程式碼。
當您撰寫 C ++. 程式時,請確定是重要的是型別安全。這表示每個變數、函式引數和函式傳回值存放一種接受資料,因此,包含不同型別的值「的作業有道理」並不會導致資料遺失、位元組合的無效、解譯或記憶體損毀。從程式未明確或隱含地從某型別轉換的值與另一個根據定義為型別安全。不過,在某些情況下,需要型別轉換,甚至是不安全的轉換。例如,您可以在型別 int的變數可能必須存放浮點運算的結果,或者您可能必須以不帶正負號的 int 值至採用簽署的 int的函式。因為它們可能會導致資料值的遺失或重新定義,兩個範例說明不安全的轉換。
當編譯器偵測到不安全的轉換時,便會發生錯誤或警告。錯誤停止編輯;警告允許編輯繼續,但表示在程式碼中可能的錯誤。不過,,即使您的程式編譯,沒有警告,造成隱含型別轉換產生無效的結果的可能仍然包含程式碼。型別錯誤可能被明確轉換或轉型也引入,在程式碼。
隱含型別轉換
當運算式包含不同的內建型別的運算元,因此,明確轉換不存在,編譯器會使用內建的 標準 轉換其中一個運算元,讓型別相符。編譯器會嘗試在明確定義的轉換,直到其中一個成功。如果選取的轉換是提升,編譯器不會發出警告。如果轉換是縮小,編譯器發出有關可能發生資料遺失的警告。實際資料遺失出現是否取決於實際值時,的,但請建議您將這個警告視為錯誤。如果使用者定義型別時,則編譯器會嘗試使用您在類別定義所指定的轉換。如果找不到可接受的轉換,編譯器會發出錯誤,而無法編譯程式。如需管理標準轉換規則的詳細資訊,請參閱 標準轉換。如需使用者定義轉換的詳細資訊,請參閱 使用者定義轉換 (C++/CLI)。
擴展轉換 (加速)
在擴展轉換,以較小的變數的值指派給較大的變數沒有資料遺失。由於擴展轉換一定會是安全的,編譯器會自動執行它們並不會發出警告。下列轉換擴展轉換。
From |
若要 |
---|---|
任何帶正負號或不帶正負號的整數類資料型別 (除了 long long 或 __int64 |
double |
bool 或 char |
內建型別 |
short 或 wchar_t |
int, long, long long |
int, long |
long long |
float |
double |
縮小轉換 (強制型轉)
編譯器隱含地執行縮小轉換,不過,它會警告您可能在發生資料遺失。非常謹慎採取這些警告。如果您確定資料遺失不會發生,因為在較大的變數的值永遠符合較小的變數,則請將明確轉換,讓編譯器不會發出警告。如果您不確定轉換是安全的,加入至您的程式碼處理可能發生資料遺失的執行階段檢查,而導致程式產生無效的結果。如需如何的建議處理這種情況,請參閱 How to: 處理縮小轉換 (C++)。
因為浮點數值的分數部分捨棄而遺失,從浮點型別的所有轉換為整數型別是縮小轉換。
下列程式碼範例會顯示一些隱含縮小轉換和編譯器所發出的警告。
int i = INT_MAX + 1; //warning C4307:'+':integral constant overflow
wchar_t wch = 'A'; //OK
char c = wch; // warning C4244:'initializing':conversion from 'wchar_t'
// to 'char', possible loss of data
unsigned char c2 = 0xfffe; //warning C4305:'initializing':truncation from
// 'int' to 'unsigned char'
int j = 1.9f; // warning C4244:'initializing':conversion from 'float' to
// 'int', possible loss of data
int k = 7.7; // warning C4244:'initializing':conversion from 'double' to
// 'int', possible loss of data
簽署-不帶正負號的轉換
一個帶正負號的整數類資料型別和它未簽署的對應項目都是相同大小,不過,它們在位元模式如何為不同值轉換解譯。下列程式碼範例中,會發生哪些在同一個位元組合解譯為帶正負號的值和做為不帶正負號的值時。在 num 和 num2 中的這個位元組合哪些永遠不會變更在較早的圖所示。
using namespace std;
unsigned short num = numeric_limits<unsigned short>::max(); // #include <limits>
short num2 = num;
cout << "unsigned val = " << num << " signed val = " << num2 << endl;
// Prints: unsigned val = 65535 signed val = -1
// Go the other way.
num2 = -1;
num = num2;
cout << "unsigned val = " << num << " signed val = " << num2 << endl;
// Prints: unsigned val = 65535 signed val = -1
請注意值在兩個方向重新定義。如果您的程式產生值的正負號似乎來自的異常結果何種物件的預期,請查看帶正負號和不帶正負號的整數型別之間的隱含轉換。在下列範例中,運算式 (0 的結果– 1) 從 int 隱含地轉換成 unsigned int ,包含在 num中儲存了。這會導致這個位元模式重新定義。
unsigned int u3 = 0 - 1;
cout << u3 << endl; // prints 4294967295
編譯器不警告有關帶正負號和不帶正負號的整數型別之間的隱含轉換。因此,建議您完全避免簽署對未簽署的轉換。如果您不能避免它們,則新增至您的程式碼偵測到執行階段檢查轉換的值是否大於或等於零且小於或等於這個帶正負號型別的最大值。在這個範圍的值傳輸從簽署到不帶正負號或不帶正負號到簽署,不用重新定義。
指標轉換
在許多運算式,. C 樣式陣列隱含轉換為第一個元素的指標陣列,因此,常數轉換可能會以無訊息方式發生。雖然這很方便,也可能是容易發生錯誤。例如,下列非常所設計的程式碼中似乎沒有意義,,它仍然在 Visual C++ 中編譯並產生「p」的結果。首先,指向陣列的第一個項目的「Help」字串常數的文字轉換為 char* ;該指標由三個項目會加入,讓它出現在最後一個元素「p」的點。
char* s = "Help" + 3;
明確轉換 (轉換)
使用轉換作業,您可以指示編譯器將一個型別的值與另一個型別。編譯器在其他情況下會引發錯誤,如果兩個型別完全無關的,,但是在某些情況下不會引發錯誤,即使作業不是型別安全。因為從一個型別的任何型別轉換為另一個是潛在來源程式設計錯誤,請謹慎使用轉型。不過,有時候需要轉型,,並不是所有的轉換同樣危險。要轉型為的有效用法是您的程式碼執行縮小轉換,而且您知道轉換不會讓您的程式會導致不正確的結果。實際上,這會告訴編譯器您知道您正在執行或停止進行了您有警告的上面。另一個用途是從轉型為指標至衍生的類別加入至指標轉換為基底類別。另一個用途是轉型成 const-將變數的岬對要求非const 引數的函式。大部分轉型作業包含一些風險。
在 C-Style 程式設計中,同一 C-Style 轉型運算子為各種轉換使用。
(int) x; // old-style cast, old-style syntax
int(x); // old-style cast, functional syntax
C-Style 轉型運算子與呼叫運算子 () 相同因此不會使用程式碼和容易俯視窗。兩個是錯誤的,所以很難一看識別或搜尋,,而且有不同的叫用 static、 const和 reinterpret_cast的組合。假設哪一舊樣式轉換實際上可以不但困難而且容易發生錯誤。對於這些原因,,如果需要轉型,我們建議您使用下列 C++ 轉型運算子之一,在某些情況下會比較型別安全,因此,明確表示程式設計目的:
static_cast,會檢查只能在編譯時期轉換的。static_cast 傳回錯誤,如果編譯器偵測到嘗試將完全不相容的型別。您也可以用它將指標轉換為基底之間和指標,衍生,但是編譯器無法永遠分辨這類轉換是安全的執行階段。
double d = 1.58947; int i = d; // warning C4244 possible loss of data int j = static_cast<int>(d); // No warning. string s = static_cast<string>(d); // Error C2440:cannot convert from // double to std:string // No error but not necessarily safe. Base* b = new Base(); Derived* d2 = static_cast<Derived*>(b);
如需詳細資訊,請參閱 static_cast。
dynamic_cast,安全的,指標基礎執行階段已檢查轉換成指標至衍生的。dynamic_cast 要向下轉換的 static_cast 安全性,不過,執行階段檢查產生一些額外負荷。
Base* b = new Base(); // Run-time check to determine whether b is actually a Derived* Derived* d3 = dynamic_cast<Derived*>(b); // If b was originally a Derived*, then d3 is a valid pointer. if(d3) { // Safe to call Derived method. cout << d3->DoSomethingMore() << endl; } else { // Run-time check failed. cout << "d3 is null" << endl; } //Output: d3 is null;
如需詳細資訊,請參閱 dynamic_cast。
const_cast,轉換為 const-變數的岬或轉換的非const 變數是 const。轉換 const-使用這個運算子的岬一樣容易發生錯誤的像使用 C++. 樣式轉換,除了與 const-cast ,您就不太可能會不小心執行轉換。有時您必須轉換成 const-變數的岬,例如,透過 const 變數傳遞給接受非const 參數的函式。下列範例顯示如何執行這項工作。
void Func(double& d) { ... } void ConstCast() { const double pi = 3.14; Func(const_cast<double&>(pi)); //No error. }
如需詳細資訊,請參閱 const_cast。
reinterpret_cast,在不相關的型別之間的轉換 (為 int的 pointer 。
注意事項
這個轉型運算子相同通常不會像其他,,而且不保證是可攜式對其他編譯器。
以下範例說明 reinterpret_cast 與 static_cast不同。
const char* str = "hello"; int i = static_cast<int>(str);//error C2440: 'static_cast' : cannot // convert from 'const char *' to 'int' int j = (int)str; // C-style cast. Did the programmer really intend // to do this? int k = reinterpret_cast<int>(str);// Programming intent is clear. // However, it is not 64-bit safe.
如需詳細資訊,請參閱reinterpret_cast 運算子。