隱式轉換:
基本類型的隱式轉換:
當函數參數類型非精確匹配,但是可以轉換的時候發生
如:
void func1(double x){cout << x << endl;
}void func2(char c){cout << c << endl;
}int main(){func1(2);//int隱式轉換為doublefunc2(3);//int隱式轉換為charreturn 0;
}
自定義類型的隱式轉換:
函數參數列表接受類類型變量,傳入的單個參數不是類對象,但可以通過這個參數調用類對應的構造函數,那么編譯器就會將其隱式轉換為類對象
如:
class A{
private:int a;
public:A(int x):a{x}{}
};
void func(A a){cout << "func" << endl;
}int main(){func(1);//隱式將int轉換為A對象A obj = 1;//同上return 0;
}
多參數構造函數:
調用多參數構造函數時,將所有參數用用{}括起來,表示是一個初始化列表,但這并不屬于自定義類型隱式轉換,而是隱式構造
class A{
private:int a;
public:A(int x):a{x}{}
};class B{
private:A obj;int b;
public:B(A a,int x):obj{a},b{x}{}
};void func(B b){cout << "func" << endl;
}
int main(){func({A{1}, 2});//使用{A,int}隱式構造B對象func({1,2});//仍能運行,隱式將1轉換為A對象,然后用{A,int}隱式構造B對象return 0;
}
值得注意:
自定義的類型轉換不能連續發生兩次以上,否則會報錯
例如:
class A{
private:string str;
public:A(const string& s):str{s}{}
};void func(A a){cout << "func"<<endl;
}int main(){func("Hello");//報錯,因為需要將const char*隱式轉換為string,string轉換為const string,//再將string隱式轉換為A//需要發生兩次自定義類型隱式轉換func(string{"Hello"});//編譯通過,只需要string轉換為const string,再將string隱式轉換為A,//只發生一次自定義類型隱式轉換return 0;
}
禁止任何隱式調用的關鍵字:explicit
在類構造函數前加上explicit關鍵字,則該構造函數無法再被隱式轉換或隱式構造
從而防止某些錯誤的函數調用,比如想傳入int卻被隱式轉換為了類對象
class A{
public:explicit A(int x){cout << x << endl;}explicit A(int x,int y){cout << x << y << endl;}
};A func(int a,int b){return {a, b};//禁止{int,int}到A的隱式構造
}A func(int x){return A{x};//合法return A{x, x};//合法
}int main(){A a = 1;//禁止int到A的隱式轉換return 0;
}
好習慣:
將所有接受單個參數(包括多個參數,但只有一個參數沒有缺省值的)的構造函數都限定為explicit
拷貝構造和移動構造不應該限定為explicit
顯式轉換:
初始化列表式轉換:
使用type{var}的方式進行轉換,如int{'c'}
這種轉換只允許小范圍變量向大范圍變量轉換,不允許反向,如
int x{3.5};//不被允許截斷
long long y{3};
cout<<double{y}<<endl;//不允許被截斷
C風格轉換:
使用類似(int),或者int()的方式來轉換類型,底層是組合調用了C++的各種cast函數來進行轉換,因此不推薦使用這種轉換
在這種轉換中,嘗試的cast調用順序:
const_cast
static_cast
static_cast+const_cast
reinterpret_cast
reinterpret_cast+const_cast
const_cast轉換:
實際極少使用
使用方式:
const_cast<to_type>(var)
作用:
給變量添加/去除const屬性
如果該變量本身就是const變量,則無法真正地去除const屬性
例如:
int main(){const int x = 2;const int *p = &x;int *p2 = const_cast<int *>(p);*p2 = 3;cout << x << endl;cout << *p2 << endl;return 0;
}
輸出:
2
3
這里我們試圖去除p所指向的的變量的const屬性,并通過p2來修改其值,但在打印x時輸出的值仍為2,這是因為編譯器在編譯的時候知道x為const變量,因此將cout<<x直接優化成了cout<<2,從而保證了其不會被修改。
而我們嘗試使用p2去修改x的值,這屬于未定義行為,需避免。因此,對于本身為const屬性的變量,我們不應該使用const_cast來去除其const屬性,否則可能會導致未定義行為
const_cast用于去除本來不是const變量的const屬性
如:
void func(const int*p){int *p2 = const_cast<int *>(p);//用來去除被函數傳參過程中隱式添加的const屬性*p2 = 6;
}int main(){int x = 3;const int *p = const_cast<const int *>(&x);//人為為其添加const屬性,使其無法被修改//在經過了一段時間的應用后,現在又想修改x的值了int *p2 = const_cast<int *>(p);*p2 = 5;cout << *p << endl;func(&x);cout << *p << endl;return 0;
}
static_cast轉換:
使用方式:
static_cast<to_type>(var)
作用:
1.進行基本類型之間的轉換(允許截斷,告訴編譯器是有意為之的截斷)
相比之下,列表初始化轉化不允許截斷,因此更推薦使用static_cast進行轉換,當然程序員要負起截斷的責任,告訴大家這是有意為之的截斷,而不是自己大意導致的截斷
int x=static_cast<int>(3.14);
2.進行安全性檢查,不允許無關類型指針互轉
int main(){double x = 3.2;int *p2 = static_cast<int *>(&x);//不合法return 0;
}
3.可以將void*指針轉為任意類型指針
無安全性檢查,需要保證指向的類型正確(下例為不正確用法)
int main(){double x = 3.2;int *p2 = static_cast<int *>(static_cast<void*>(&x));//通過double*轉void*,再從void*轉到int*,跳過了安全性檢查return 0;
}
4.將派生類指針/引用/對象轉換為基類指針/引用/對象(向上轉換)
class Base{
};
class Derived:public Base{
};
int main(){Base *p_b = static_cast<Base *>(new Derived);Derived d;Base &ref_b = static_cast<Base &>(d);Base obj_b = static_cast<Base>(d);//導致對象切片
}
5.將基類指針/引用轉換為派生類指針/引用(向下轉換,不推薦)
無安全性檢查,需要保證基類指針指向要轉換的派生類或者其派生類
若基類派生類擁有虛函數,應優先使用dynamic_cast(下文講解)
Base* p_b2=new Derived;
Derived* p_d=static_cast<Derived*>(p_b2);//合法
6.顯式調用自定義構造函數/類型轉換函數進行轉換
class MyInt {
public:explicit MyInt(int x) : value(x) {}operator int() const { return value; } // 自定義的轉換運算符
private:int value;
};MyInt mi = static_cast<MyInt>(42); // 調用構造函數
int x = static_cast<int>(mi); // 調用 operator int()
dynamic_cast轉換:
使用方式:
dynamic_cast<Derived_ptr/Derived_ref>(Base_ptr/Base_ref);
作用:
僅能對擁有虛函數的基類/派生類使用,且不能是protected/private繼承關系
基類為虛基類時,在某些情況會導致dynamic_cast失敗
對基類的指針/引用進行安全檢查的向下轉換(轉換為派生類指針/引用)
也可進行向上轉換(不推薦,應使用static_cast)
返回值:
若轉換成功,即Base_ptr/Base_ref實際指向的對象是Derived對象或者是其派生類對象,則返回指向該對象的Derived_ptr/Derived_ref
若轉換失敗,即Base_ptr/Base_ref實際指向的是別的東西,則返回nullptr
值得注意的是:
dynamic_cast依賴于RTTI(run-time type information)來確定指針指向對象的實際類型從而進行轉換,因此,沒有虛函數,或者關閉了RTTI優化,都會導致對象的RTTI信息丟失,從而導致dynamic_cast失敗
reinterpret_cast轉換:
該轉換無安全性檢查,直接重新解釋對象的二進制比特模式
高安全風險,極少使用,不推薦
使用方式:
reinterpret_cast<to_type>(var);
作用:
1.任意類型之間的指針互轉
2.指針與整數互轉
3.函數指針與數據指針互轉
4.任意數據類型互轉
路徑不確定導致的轉換二義性:
1.非虛繼承的菱形繼承的向上轉換:
此時將指向D對象的D指針向上轉換為A指針時,會出現二義性錯誤,因為編譯器不知道指向哪個A對象,這種情況下,只能一層一層地向上轉換,直到產生二義性的路徑消失,方可一次性轉換到A
2.虛繼承導致的菱形繼承的向下轉換:
此時將指向E對象的A指針向下轉換為B指針時,會出現二義性錯誤,因為編譯器不知道指向哪個B對象,而且虛繼承導致指針偏移無法計算(虛繼承機制以后的文章再講解),這種情況下,只能先轉成E指針,再向上轉換,直到二義性路徑消失
3.同層交叉cast轉換:
可以直接將指向E對象的D指針用dynamic_cast轉換為B指針
只需要滿足B和D沒有共同的虛基類即可(有的話,會導致指針偏移無法計算,從而dynamic_cast失敗),有的話,使用第二點的方法