类型转换
隐式类型转换
类型转换,即一个变量的类型向另一个类型转换。C++支持 C 风格的类型转换。
隐式类型转换的场景:
- 赋值
- 实参到形参
- 函数返回
严格来说,运算造成的类型转换属于运算符重载的范畴,但通常也可以看作是一种类型转换。
基础类型
// 变量运算
int a1 = 10;
double b1 = 3.14;
a1 + b1; // 右值为 double 类型
// 赋值
int a2 = 10;
double b2 = a2;
// 参数传递
void test(int a2) {}
char b2 = 10;
// 函数返回
double test2() {
int a = 10;
return a;
}
// 任意基础类型的指针向 void* 转换
int * i;
void* p = i;实际上任何指针类型(基础类型或自定义类型)都可以隐式转换为 void* 类型的指针。故 void* 又称为万能指针。
自定义类型
子类 --> 父类:隐式向上转换
向上转换指的是将派生类对象赋值给基类对象的转换。
继承类型(私有继承,保护继承,公有继承)直接影响了向上转换的合法性。
例如,私有继承规定了子类对象在类外不允许访问基类成员,因此私有继承基类的子类对象不可在类外隐式的向上转换,但是可以在类内隐式向上转换。
struct A {
A(x): a(x) {}
int a;
};
struct B: private A {
B(x = 0, y = 0): A(x), b(y) {}
A getA() {
return *this; // 合法的类内隐式向上转换
}
A* getA_ptr() {
return this; // 合法的类内隐式向上转换
}
int b;
}
struct C : public A {
C(x = 0, y = 0): A(x), c(y) {}
int c;
}
int main() {
C c0;
A a0 = c0; // 合法的类外隐式向上转换
B b0;
A a1 = b0; // 报错
A a2 = b.getA(); // 合法
C* cptr = new C();
A* aptr = cptr; // 合法
B* bptr = new B();
A* aptr = bptr; // 报错
A* aptr1 = b.getA_ptr(); // 合法
return 0;
}这里的隐式向上转换还包括了子类指针向父类指针的转换。
如果父类存在虚函数,实现了多态。
重载隐式类型转换
类内可以使用重载的方式来定义隐式类型转换。
struct A {
operator int() {
}
int a;
};
struct B {
B(int x): b(x) {}
operaotr std::string() {
return std::to_string(b);
}
int b;
}
int main() {
A a;
B b(a); // 合法,A 实现了向 int 的隐式类型转换
string s = b; //合法,B 实现了向 string 的隐式类型转换
return 0;
}显式类型转换
隐式类型转换并不总是非常明确,当隐式类型转换有多条转换路径时,编译器会选择一个最合适的转换路径。但这样给开发者带来了很多不确定性。
void show(int x) {
cout << "show1" << endl;
}
void show(double x) {
cout << "show2" << endl;
}
int main() {
char a = 'a';
show(a);
// 可以向 int 转换,也可以向 double 转换
// 当代码量巨大时,关注隐式类型转换就成了开发负担
return 0;
}C++ 支持 C 风格的类型转换,但不推荐使用。因为 C 风格的类型转换没有类型安全检查,并且语义不明确。
// C 风格显式类型转换
int a = 10;
double b = (int)a;
double c = int(a); // 有时也可以这么写C++11标准引入了显式类型转换,明确了不同类型转换的用途。
static_cast
主要用于非多态的类型转换,编译期进行类型检查
void show(int x) {cout << "show1" << endl;}
void show(double x) {cout << "show2" << endl;}
int main() {
char a = 'a';
show(static_cast<double>(a));
return 0;
}子类和父类之间的类型转换有安全风险,这种场景需要谨慎使用,或者使用 dynamic_cast。
dynamic_cast
用于多态的类型转换,运行时进行类型检查。
如果转换失败,则返回nullptr,或者抛出std::bad_cast异常。
dynamic_cast 依赖于RTTI(Run-Time Type Information)即运行时类型信息,这是通过虚表来实现的。所以 dynamic_cast 只能应用在包含虚表指针的对象上。
Base* b = new Derived(); // 向上转换
/* ... */
Derived* p = dynamic_cast<Derived*>(b); // 向下转换
if (p == nullptr) {
// 转换失败
}
else {
// 转换成功
}const_cast
const_cast 用于去掉 const 或 volatile 的修饰。虽然const_cast可以去除 const 修饰,但是修改const 变量本身是一个未定义行为,必须谨慎使用。
const_cast 只能用于指针和引用。
struct A {
void show() const { cout << a << endl; }
void print() { cout << a << endl; }
int a;
};
int main() {
const A a = {10};
a.show(); // 合法
a.print(); // 报错,const对象只能调用const成员函数
// 合法,使用 const_cast 去掉const属性
const_cast<A*>(&a)->print();
return 0;
}reinterpret_cast
直接进行类型转换,重新解释底层二进制到目标类型,无任何类型检查。
只能应用在指针或者引用类型上。
非常强大也非常危险的类型转换工具,具体解释往往依赖于编译器和平台,需要谨慎使用。
int x = 10;
char* p = reinterpret_cast<char*>(&x); // 将 int* 转换为 char*
int* q = reinterpret_cast<int*>(p); // 将 char* 转换回 int*