参考:C++类型转换:隐式转换和显式转换
隐式转换当一个值拷贝给另一个兼容类型的值时,隐式转换会自动进行。所谓隐式转换,是指不需要用户干预,编译器私下进行的类型转换行为。
隐式转换类型-
基本数据类型:基本数据类型的转换以取值范围的作为转换基础(保证精度不丢失)。隐式转换发生在从小->大的转换中。比如从char转换为int。从int->long。【当然大到小的转换也可以,但编译器会 waring 窄转换】
// 小到大 int a = 1; float b = a; // 大到小 float a = 1.1; int b = a;// 同样可以,但是编译器会发出narrow conversion窄转换的warning
-
类/对象:
-
构造函数:在自己定义的构造函数中,会出现隐式转换,通常,在只有一个参数的构造函数会默认定义了一个隐式转换,将该构造函数对应数据类型的数据转换为该类对象。在多个参数的构造函数中不会出现隐式转换
但这是在 C++98/03 时的情况,在 C++11 引入了列表初始化,因此多参数也可以被隐式转换。
class A{ A(int i){} A(int i, int ){} } int main(){ // 正常写法 A a(1); A a = A(1); // 隐式转换,会看起来很奇怪,一个对象为什么会等于一个int类型的值呢?让人难以理解 A a = 1;// 这就是隐式转换,把1会转换成A(1) A a = 'a';// 也是隐式转换,这里把char当作数字,且发生了基本类型的隐式转换char->int,又发生了对象的隐式转换A('a') -------------------- // C++11引入列表初始化 // 正常写法 A a(1,2); A a = A(1,2); // 多参数隐式转换 A a = {1,2};// 等价于A a = A(1,2) } -
对象赋值:可以将子类对象赋值给父类对象(可能会发送对象切片,但这是可行的,直接隐式转换),但反过来,父类对象不能赋值给子类对象,因为子类对象多的成员,父类不能提供
class A{}; class B : public A{}; int main(){ // 子类赋值给父类√ B b; A a(b); A a = b; ------------- // 错误 A a; B b(a); }
-
-
指针:在下面的显示转换中也可以看到,子类指针->父类指针是可以隐式转换的;同理有类型类指针转换成 void* 也是隐式可以的
C++中提供了explicit关键字,在构造函数声明的时候加上explicit关键字,能够禁止隐式转换。
class A{
explicit A(int i){}
explicit A(int i, int ){}
}
int main(){
// 正常写法
A a(1);
A a = A(1);
// 隐式转换
A a = 1;// error
A a = 'a';// error
--------------------
// C++11引入列表初始化
// 正常写法
A a(1,2);
A a = A(1,2);
// 多参数隐式转换
A a = {1,2};// error
}
显式转换
C++ 中引入 4 种强制类型转换:static_cast、dynamic_cast、const_cast、reinterpret_cast。使用格式 xxx_cast
C语言的转换方式很简单,可以在任意类型之间转换,但这也恰恰是缺点,因为极其不安全,这种转换很容易出bug,需要严格审查代码才能消除这种隐患,但是C这种转换方式不利于我们审查代码,且程序运行时也可能会出bug。
而C++引入的这几种类型转换可以解决上述问题,不同场景下不同需求使用不同的类型转换方式,有利于编译器帮助我们代码审查。
但真正在使用中,有时候图方便会直接使用 C 风格的强制类型转换,毕竟 C++ 兼容 C,正如使用 printf 还是 cout 一样,前者规定格式,后者只是输出流
参考:C++为什么非要引入那几种类型转换?
1. static_castclass A{};
class B : A{};
int main(){
// 1
double a = 1.1;
int c = static_cast(a);// C++中引入的强制转换
int d = (int)a;// c语言风格的强制转换
// 2
void *empty;
A *has_type = static_cast(empty);// void* -> A*
empty = static_cast(has_type);// A* -> void*【事实上可以隐式转换】
// 3
A father;
B *son_p = static_cast(&father);// 父类指针->子类指针【没有限制,可以成功,但运行时可能会出错,而下面的dynamic_cast向下转换时有两个要求】
B son;
A *father_p = static_cast(&son);// 子类指针->父类指针【事实上可以隐式转换,用父类声明子类】
}
适用场景
- 基本数据类型之间的转换使用
- 在有类型指针和void*之间转换使用
- 在继承关系的父类和子类指针中转换使用,如果不是继承关系,会报 which are not related by inheritance, is not allowed
非多态类型转换一般都使用static_cast【注意子类转成父类不一定有多态,继承不代表多态,只有父类中有虚函数时才是多态】
2. dynamic_castclass A{};
class B : A{};
int main(){
// 1
void *empty = nullptr;
A *has_type = dynamic_cast(empty);// void* -> A* 报错'void' is not a class type
empty = dynamic_cast(has_type);// A* -> void* 【事实上就是隐式转换】
// 2
A father;
B *son_p = dynamic_cast(&father);// 父类指针->子类指针【有限制:①dynamic_cast向下转换时要求有多态,即父类中必须有虚函数。②如果父类指针本来就指向父类,那么可以转换,但会转换成nullptr,地址为0】
cout << son_p;// 为0
//正确的转换:满足两个要求
A *father_p = new B;// 父类指针指向子类对象
B *son_p = dynamic_cast(father_p);
cout << son_p// 不为0,成功转换
B son;
A *father_p = dynamic_cast(&son);// 子类指针->父类指针【事实上可以隐式转换,用父类声明子类】
}
适用场景
- 只能用于指向类的指针和引用(或void*),但只能类转 void* 不能 void* 转类指针
- 用于将父类的指针或引用转换为子类的指针或引用【但有两个条件】
int main(){
const int a = 1;// 原数据可以是const,也可以不是,结果都一样,但是如果cout << a,如果是const int a = 1,则会输出1,好像a没有被修改一样,事实上是编译器会将使用常量的地方直接替换成对应的值
const int *p1 = &a;
int *p2 = const_cast(&a);// const int* -> int*
*p2 = 2;// 修改值
cout << *p2;// 2
const int *p3 = const_cast(p2);// int * ->const int*
}
- 用于常量指针或引用与非常量指针或引用之间的转换【不能是基本类型或者是直接对象 ,否则报Const_cast to 'int', which is not a reference, pointer-to-object, or pointer-to-data-member】,去除常量性是危险操作,还是要谨慎操作。
和 C 中的强制转换一样,什么都可以转,一般在前面三种解决不了时才使用,允许所有的指针转换: **既不检查指针所指向的内容,也不检查指针类型本身。**正如名字一样,reinterpret 重新定义
5. 与 C 中强制转换的区别static_cast
// 显式转换风格 y = int (x); // functional notation y = (int) x; // c-like cast notation,C语言中只有这种方式 // 两种转换对比 auto *p1 = new int(1); auto *p2 = new float (1.1); p2 = static_cast(p1);// error,在编译时就制止这种行为,避免在运行时出错 p2 = (float*)p1;// 编译通过,但如果使用*p2,那么会把p1指向的整数1的32位2进制表示当成浮点数来解释 // 不同对象指针的转换问题 class A{ int _a; }; class B{ int _b; int _c; public: int get_b(){ return b; } }; int main(){ A a; B *b = (B*)&a; b->get_b();// 不报错,甚至确实能得到值,事实上A对象所占的内存为32bit,B对象所占内存为64bit,因此当把A的指针转换成B*时,会把_a所在内存当作_b,_a后面一块32bit没有被分配的内存当成_c【可能发生segmentation fault】,而类的方法都在静态区中,因此可以调用,通过调用B的方法,将_a返回 }



