類型轉換與類型安全
本檔會識別常見的類型轉換問題,並說明如何在 C++ 程式碼中避免這些問題。
當您撰寫 C++ 程式時,請務必確保其型別安全。 這表示每個變數、函式引數和函式傳回值都會儲存可接受的資料類型,而且涉及不同類型值的作業「有意義」,而且不會造成資料遺失、位模式的不正確解譯或記憶體損毀。 絕對不會明確或隱含地將值從某個型別轉換成另一個型別的程式,依定義是型別安全。 不過,有時需要類型轉換,甚至不安全的轉換。 例如,您可能必須將浮點運算的結果儲存在 類型的 int
變數中,或者您可能必須將 值 unsigned int
傳遞至採用 signed int
的函式。 這兩個範例都說明不安全的轉換,因為它們可能會導致資料遺失或重新解譯值。
當編譯器偵測到不安全的轉換時,它會發出錯誤或警告。 錯誤會停止編譯;警告允許繼續編譯,但表示程式碼中可能發生的錯誤。 不過,即使您的程式在沒有警告的情況下編譯,它仍然可能包含會導致產生不正確結果的隱含型別轉換的程式碼。 在程式碼中,也可以透過明確轉換或轉換來引入類型錯誤。
隱含類型轉換
當運算式包含不同內建類型的運算元,而且沒有明確的轉換時,編譯器會使用內 建標準轉換來轉換 其中一個運算元,讓類型相符。 編譯器會嘗試在定義完善的序列中轉換,直到成功為止。 如果選取的轉換是升階,編譯器不會發出警告。 如果轉換是縮小範圍,編譯器會發出有關可能遺失資料的警告。 實際資料遺失是否取決於所涉及的實際值,但我們建議您將此警告視為錯誤。 如果涉及使用者定義的類型,則編譯器會嘗試使用您在類別定義中指定的轉換。 如果找不到可接受的轉換,編譯器就會發出錯誤,而且不會編譯器。 如需管理標準轉換之規則的詳細資訊,請參閱 標準轉換 。 如需使用者定義轉換的詳細資訊,請參閱 使用者定義的轉換(C++/CLI)。
擴大轉換(促銷)
在擴大轉換中,較小變數中的值會指派給較大的變數,且不會遺失資料。 因為擴大轉換一律是安全的,編譯器會以無訊息方式執行它們,而且不會發出警告。 下列轉換會擴大轉換。
從 | 至 |
---|---|
或 以外的 long long 任何 signed 或 unsigned 整數類型__int64 |
double |
bool 或 char |
任何其他內建類型 |
short 或 wchar_t |
int , long , long long |
int , long |
long long |
float |
double |
縮小轉換 (強制)
編譯器會隱含地執行縮小轉換,但會警告您潛在的資料遺失。 非常認真地對待這些警告。 如果您確定不會發生任何資料遺失,因為較大變數中的值一律會符合較小的變數,請新增明確轉換,讓編譯器不再發出警告。 如果您不確定轉換是否安全,請將 新增至程式碼某種執行時間檢查來處理可能的資料遺失,以免程式產生不正確的結果。
從浮點類型到整數類型的任何轉換都是縮小轉換,因為浮點值的分數部分會捨棄並遺失。
下列程式碼範例顯示一些隱含縮小轉換,以及編譯器為其發出問題的警告。
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"
請注意,值會以兩個方向重新解譯。 如果您的程式產生奇數結果,該值的正負號似乎與預期值反轉,請尋找帶正負號與未帶正負號整數型別之間的隱含轉換。 在下列範例中,當運算式儲存在 num
中時,運算式的結果會隱含地從 轉換為 。 int
unsigned int
這會導致重新解譯位模式。
unsigned int u3 = 0 - 1;
cout << u3 << endl; // prints 4294967295
編譯器不會警告帶正負號與不帶正負號整數類型之間的隱含轉換。 因此,建議您避免完全簽署到未簽署的轉換。 如果您無法避免這些值,請新增執行時間檢查,以偵測要轉換的值是否大於或等於零,以及小於或等於帶正負號型別的最大值。 此範圍中的值會從帶正負號到未帶正負號,或從未帶正負號到已簽署的值傳輸,而不會重新解譯。
指標轉換
在許多運算式中,C 樣式陣列會隱含地轉換成陣列中第一個專案的指標,而常數轉換可能會以無訊息方式發生。 雖然這很方便,但也可能容易出錯。 例如,下列設計錯誤的程式碼範例似乎不明確,但它將會編譯並產生 'p' 的結果。 首先,「Help」 字串常數常值會轉換成 char*
指向陣列第一個專案的 ,然後該指標會遞增三個元素,讓它現在指向最後一個專案 'p'。
char* s = "Help" + 3;
明確轉換(轉換)
藉由使用轉換作業,您可以指示編譯器將某個類型的值轉換成另一個類型。 如果這兩種類型完全不相關,則編譯器在某些情況下會引發錯誤,但在其他情況下,即使作業不是型別安全,也不會引發錯誤。 請謹慎使用轉換,因為從某個類型轉換成另一種類型的任何轉換都是程式錯誤的潛在來源。 不過,有時需要轉型,並非所有的轉型都同樣危險。 轉換的有效用法是當您的程式碼執行縮小轉換,且您知道轉換不會造成程式產生不正確的結果時。 實際上,這會告訴編譯器您知道您正在做什麼,並停止對它發出警告的困擾。 另一個用法是從指標轉換成衍生類別到指標到基類。 另一個用法是將變數的常數轉換為需要非 const 引數的函式。 這些轉型作業大多涉及一些風險。
在 C 樣式程式設計中,相同的 C 樣式轉換運算子會用於各種轉換。
(int) x; // old-style cast, old-style syntax
int(x); // old-style cast, functional syntax
C 樣式轉換運算子與呼叫運算子 () 相同,因此在程式碼中並不顯眼,而且易於忽略。 兩者都很糟糕,因為它們很難一目了然或搜尋,而且它們足以叫用 、 const
和 reinterpret_cast
的任何組合 static
。 弄清楚舊式演員實際上所做的可能很困難和容易出錯。 基於上述所有原因,當需要轉換時,我們建議您使用下列其中一個 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
變數的 -ness,或將非const
變數const
轉換成 。 使用這個運算子來const
轉換 -ness 就像使用 C 樣式轉換一樣容易發生錯誤,不同之處在于您const_cast
不太可能不小心執行轉換。 有時候,您必須將變數的 -ness 轉型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
。注意
此轉換運算子不會像其他運算子一樣經常使用,而且不保證可移植到其他編譯器。
下列範例說明與 有何
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
運算子 。
另請參閱
意見反應
https://aka.ms/ContentUserFeedback。
即將登場:在 2024 年,我們將逐步淘汰 GitHub 問題作為內容的意見反應機制,並將它取代為新的意見反應系統。 如需詳細資訊,請參閱:提交並檢視相關的意見反應