C++ 中的型別轉換包含內建型別的轉換和使用者自定義型別的轉換,而這兩者都又可分為隱式轉換和顯示轉換,所以一共有如下四象限表格中的 A、B、C、D 四種情況
隱式轉換 | 顯示轉換 (casting) |
|
---|---|---|
內建型別轉換 (int, float ...) |
A | B |
使用者自定義型別轉換 (類 vs 類; 類 vs 內建型別) |
C | D |
本篇只討論隱式轉換,內建型別的隱式轉換舉例如下
char c = 'A';
int i = c; // 將 char 隱式轉換為 int,又稱 Integral promotion
char* pc = 0; // int 轉換為 Null 指標
dog* pd = new yellowdog(); // 指標型別轉換,子類 yellowdog 指標轉換為父類 dog 指標
複製程式碼
使用者自定義的隱式轉換是本篇的重頭戲,一般我們說自定義隱式轉換,指兩方面內容:
- 利用接受單個引數的建構函式,可以將其他型別的物件轉換為本物件
- 使用型別轉換函式, 將本類的物件轉換為其他物件
兩者合起來可以構成一個雙向轉換關係,下面我們看一個例子
class dog {
public:
dog(string name) {m_name = name;}
string getName() {return m_name;}
private:
string m_name;
};
int main() {
string dogname = "dog";
dog d = dogname;
cout << "my name is " << d.getName() << \n";
return 0;
};
複製程式碼
上面例子中,dog(string name) {m_name = name;}
有兩層含義,除了建構函式外,它還可以作為隱式轉換函式,將 string
物件轉換為 dog
物件,可以看到我們把 dogname
賦給了 dog d
,像這樣的賦值,通常是無意的行為,而且它觸犯了 is-a 原則。
***如果你不想該建構函式具備隱式轉換的特性,你應該使用 explicit
對該函式進行宣告:
explicit dog(string) {m_name = name;}
複製程式碼
反過來,我們還可以定義一個轉換函式,將 dog
物件轉換為 string
,如下
class dog {
// ...
operator string () const { return m_name; }
};
複製程式碼
這樣,下面的輸出可以簡化為:
cout << "my name is " << (string)d << \n";
複製程式碼
可以看到,自定義型別的隱式轉換很容易寫出和本意不符的程式碼,這些程式碼往往容易出錯,很明顯,這樣的設計算不上是好設計,相反,我們更應遵循這樣的設計原則:
當我們定義一個 api 介面,我們希望正確的使用這個 api 是一件很容易的事情,且它很難被錯誤使用(理想情況下,當你錯誤使用一個 api 時,它不能被編譯通過),而定義過多的轉換函式,卻很容易讓我們的 api 出錯
對於隱式型別轉換,應該記住 2 點
- 避免定義看上去不符合預期的轉換,例如將 string 轉換為一個 dog
- 避免定義雙向的型別轉換,A 可以向 B 轉換,但繼續實現 B 向 A 的轉換就過猶不及
當然,隱式轉換並不是一無是處,它仍然有存在的意義,下面我們來舉一個正面的隱式轉換的例子,當你的類是一個處理數字的型別時,例如:有理數類,就可以使用隱式轉換技術:
class Rational {
public:
Rational(int numerator = 0, int denominator = 1)
: num(numberator), den(denominator) {}
int num;
int den;
};
Rational operator*(const Rational& lhs, const Rational& rhs) {
return Rational(lhs.num*rhs.num, lhs.den*rhs.den);
}
int main() {
Rational r1 = 23;
Rational r2 = r1 * 2;
Rational r3 = 3 * r1;
}
複製程式碼
上面程式碼定義了一個有理數類 Rational
,它的建構函式接受 2 個預設引數,分別代表分子和分母,給該建構函式傳遞一個引數時,Rational
具有隱式轉換的特性,所以我們可以直接將數字賦值給 Rational
物件,如:Rational r1 = 23;
為了避免雙向轉換,這裡並沒有定義將 Rational
轉換為 int
的轉換函式,而當我們想實現 Rational
物件和 int
之間自由的算術運算時,我們需要定義全域性的操作符過載,如上面的 operator*
定義了有理數的乘法雲算符。