-
2022-01-17 08:53:58
1、隐式类型转换:系统会自动进行,无需介入
//会造成一定的精度损失 int a=3+34.9 double n=3+35.9
2、强制类型转换
2.1、c语言风格强制类型转换int a=(int)2.3;//强制类型转换
//命名的强制类型转换通用形式
强制类型转换名<type>(express); type:目标类型 express:要转换的对象
2.2、static_cast:静态转换
编译的时候会进行类型转换,这个静态类型转换和c强制类型转换差不多
可用于:
(1)相关类型转换double f=90.0f; int a=static_cast<int>(f);
(2)子类转换成父类 切记:父类不可以用static_cast转换成子类
A a; B b; A a=static_cast<A>(b);
(3)void与其他任何类型的转换
但是不可用于其他基本类型之间的转换 如int转换成double*…int *p=&i; void*q=ststic_cast<void*>(p);
2.3、dynamic_cast
运行时类别识别和检查,主要用于父类型和子类型对象之间的转换,可以将夫类型强制转换成子类型2.4、const_cast
去除指针或者引用的const去除,能够将const性质转换掉。编译时进行类型转换int a=10; const int*p=&a; int *a2=const_cast<int*>(p)
2.5、reinterpret_cast (比较危险的类型转换)
重新解释(可以在无关的类型之间进行转换)
常用于
整形转换成另一种指针按照转换的类型重新解释内存中的内容
从一个指针类型转换成一个整形int i=10; int*pi=&i; void *pvoid=reinterpret_cast<void*>(pi); int*pi2=reterpret_cast<int*>(pvoid);
总结:
所有强制类型转换都不建议使用,强制类型转换会抑制编译器报错。尽量少用,千万不要再用c风格的类型转换更多相关内容 -
强制类型转换说明_LabVIEW中关于强制类型转换的使用_labview中tcp_
2021-10-01 15:44:54LabVIEW中TCP通讯需要用到的强制类型转换的使用方法 -
JavaScript强制类型转换和隐式类型转换操作示例
2020-11-28 10:42:30本文实例讲述了JavaScript强制类型转换和隐式类型转换。分享给大家供大家参考,具体如下: 在JavaScript中声明变量不需指定类型, 对变量赋值也没有类型检查,同时JavaScript允许隐式类型转换。这些特征说明... -
C++中4种强制类型转换的区别总结
2020-12-25 23:18:09使用标准C++的类型转换符:static_cast、dynamic_cast、reinterpret_cast和const_cast。 const_cast,字面上理解就是去const属性。 static_cast,命名上理解是静态类型转换。如int转换成char。 dynamic_cast,... -
关于C++的强制类型转换浅析
2020-12-26 08:43:09一说起强制类型转换大家都很熟悉,相信很多学习完C++的朋友还在使用C语言的强制类型的方式 (类型)变量. C++其实也具有自己的一套强制类型转换它们分明是:static_cast reinterpret_cast const_cast dynamic_cast... -
深入理解C++中五种强制类型转换的使用场景
2021-07-02 22:15:48C++中五种强制类型转换解析1、C风格的强制类型转换2、C++风格的强制类型转换2.1、static_cast2.1.1、类实例转换场景使用static_cast2.1.2、没有多态的场景下使用static_cast2.1.3、具有多态的场景下使用static_cast...深入理解C++中五种强制类型转换的使用场景
1、C风格的强制类型转换
C风格的强制类型转换很容易理解,不管什么类型都可以直接进行转换,使用格式如下:
Type b = (Type)a;
当然,C++也是支持C风格的强制类型转换的,但是C风格的强制类型转换可能会带来一些隐患,出现一些难以察觉的问题,所以C++又推出了四种新的强制类型转换来替代C风格的强制类型转换,降低使用风险。2、C++风格的强制类型转换
在C++中新增了四个关键字
static_cast
、const_cast
、reinterpret_cast
和dynamic_cast
,用于支持C++风格的强制类型转换。这几个看起来有点像类模板,但事实上他们是正儿八经的关键字。C++风格的强制类型转换的好处是它们能更清晰的表明它们要干什么,程序员只要扫一眼这样的代码,就能立即知道一个强制转换的目的,并且在多态场景也只能使用C++风格的强制类型转换。看本节之前建议看一下一下两篇文章,因为往下的内容会涉及一些关于多态的知识:
2.1、static_cast
static_cast
是最常用的C++风格的强制类型转换,主要是为了执行那些较为合理的强制类型转换,使用格式如下:
static_cast<type_id>(expression);
因为
static_cast
的使用范围比较广,而且限制也比较多,所以下面分小节对各个场景进行分析2.1.1、用于基本内置数据类型之间的转换
static_cast
可以用于基本内置数据类型之间的转换,比如把char
转成float
、double
转换成long
等,这种内置类型之间的转换往往可以由隐式转换自动执行,而不需要人工特地去执行强制类型转换。由于转换结果可能存在截断性,这种转换的安全性要由开发人员来保证(一般不会出现严重程序运行错误,但是可能会出现逻辑错误),编译器可能会适当打印警告信息。示例如下:#include <iostream> int main(int argc, char* argv[]) { char type_char = 'A'; float type_float = type_char; // 隐式转换也可以 float type_float_cast = static_cast<float>(type_char); // 显式地使用static_cast进行强制类型转换 double type_double = 1.23; long type_long = type_double; // 隐式转换也可以 long type_long_cast = static_cast<long>(type_double); // 显式地使用static_cast进行强制类型转换 }
2.1.2、用于指针之间的转换
static_cast
可以用于指针之间的转换,这种转换类型检查非常严格,不同类型的指针是直接不给转的,除非使用void*
作为中间参数,我们知道隐式转换下void*
类型是无法直接转换为其它类型指针的,这时候就需要借助static_cast来转换了。示例如下:#include <iostream> int main(int argc, char* argv[]) { int type_int = 10; float* float_ptr1 = &type_int; // int* -> float* 隐式转换无效 float* float_ptr2 = static_cast<float*>(&type_int); // int* -> float* 使用static_cast转换无效 char* char_ptr1 = &type_int; // int* -> char* 隐式转换无效 char* char_ptr2 = static_cast<char*>(&type_int); // int* -> char* 使用static_cast转换无效 void* void_ptr = &type_int; // 任何指针都可以隐式转换为void* float* float_ptr3 = void_ptr; // void* -> float* 隐式转换无效 float* float_ptr4 = static_cast<float*>(void_ptr); // void* -> float* 使用static_cast转换成功 char* char_ptr3 = void_ptr; // void* -> char* 隐式转换无效 char* char_ptr4 = static_cast<char*>(void_ptr); // void* -> char* 使用static_cast转换成功 }
补充说明:
static_cast
是直接不允许不同类型的引用进行转换的,因为没有void类型引用可以作为中间介质,这点和指针是有相当大区别的2.1.3、不能转换掉expression的const或volitale属性
static_cast
不能转换掉expression
的const
或volitale
属性。示例如下:#include <iostream> int main(int argc, char* argv[]) { int temp = 10; const int* a_const_ptr = &temp; int* b_const_ptr = static_cast<int*>(a_const_ptr); // const int* -> int* 无效 const int a_const_ref = 10; int& b_const_ref = static_cast<int&>(a_const_ref); // const int& -> int& 无效 volatile int* a_vol_ptr = &temp; int* b_vol_ptr = static_cast<int*>(a_vol_ptr); // volatile int* -> int* 无效 volatile int a_vol_ref = 10; int& b_vol_ref = static_cast<int&>(a_vol_ref); // volatile int& -> int& 无效 }
2.1.4、用于类实例的之间转换
测试程序如下:
#include <iostream> class A { public: int a; }; class B { public: int b; }; class C : public A, public B { public: int c; }; int main(int argc, char* argv[]) { C c; A a = static_cast<A>(c); // 上行转换正常 B b = static_cast<B>(c); // 上行转换正常 C c_a = static_cast<C>(a); // 下行转换无效 C c_b = static_cast<C>(b); // 下行转换无效 }
从测试程序中可以看到如果对类实例使用
static_cast
进行转换,static_cast
是会进行类型判断的,对于上行转换来说这个过程就是正常的(其实任何合法的上行转换都可以直接由隐式转换来完成,而不需要手工去强制类型转换),但是下行转换则不行,static_cast
认为下行转换等同于两个无关联的类进行转换,会报错。但是这个错误是有解决方法的,我们从报错信息中可以看到当static_cast
转换失败时,会使用expression
作为传入参数来调用type_id
的构造函数,所以我们可以把类C改成以下形式,上面的示例即可编译通过class C : public A, public B { public: C() { } C(const A& v) { a = v.a; } C(const B& v) { b = v.b; } int c; };
综上,我们可以得出使用
static_cast
对类实例进行强制类型转换时有以下特点:- 进行上行转换是完全安全合法的,当然这个过程由隐式转换来完成也是合法的
- 进行下行转换时,
static_cast
会认为两个类无关联,这种转换不合法。如果此时硬要转换的话,比如类A->类B(这两个类可以无任何关系,因为实例下行转换static_cast
就是认为他们没关联),可以在B中添加一个使用类A进行构造的构造函数,比如B(const A&),这样就可以正常使用static_cast
来进行类A->类B的操作了
2.1.5、用于没有多态的类实例指针或引用之间的转换
进行上行转换的示例如下:
#include <iostream> class A { public: int a; }; class B { public: int b; }; class C : public A, public B { public: int c; }; int main(int argc, char* argv[]) { C c; A* a_ptr = static_cast<A*>(&c); // 上行指针转换正常 B* b_ptr = static_cast<B*>(&c); // 上行指针转换正常 A& a_ref = static_cast<A&>(c); // 上行引用转换正常 B& b_ref = static_cast<B&>(c); // 上行引用转换正常 }
可以看到上行转换都是正常的,转换过程中不会出现任何显性和隐性错误,下面来看一下下行转换的示例:
int main(int argc, char* argv[]) { C c; A* a_ptr = static_cast<A*>(&c); B* b_ptr = static_cast<B*>(&c); A& a_ref = static_cast<A&>(c); B& b_ref = static_cast<B&>(c); C* c_ptra = static_cast<C*>(a_ptr); // 下行指针转换正常 C* c_ptrb = static_cast<C*>(b_ptr); // 下行指针转换正常 C& c_refa = static_cast<C&>(a_ref); // 下行引用转换正常 C& c_refb = static_cast<C&>(b_ref); // 下行引用转换正常 A* a_ptr_fail = static_cast<A*>(b_ptr); // B* -> A*,无关联的两个类型,无效 }
从上面的例子可以看到,下行转换也是正常的,并且
static_cast
也会拒绝掉两个无关联类之间的转换???这和书中说的不一样啊,不是说static_cast
下行转换不安全吗?别急,上面的例子是片面的,各位看一下下面的代码就知道了int main(int argc, char* argv[]) { A a; B b; // 以下都能转换成功,说明static_cast根本就没有安全检查,只看到有继承关系就给转换了 C* c_ptra = static_cast<C*>(&a); C* c_ptrb = static_cast<C*>(&b); C& c_refa = static_cast<C&>(a); C& c_refb = static_cast<C&>(b); }
综上,我们可以得出使用
static_cast
对没有多态的类实例指针或引用进行强制类型转换时有以下特点:- 进行上行转换(派生类指针->基类指针、派生类引用->基类引用)是完全安全的,没有任何问题,当然这个过程由隐式转换来完成也是合法的
- 进行下行转换(基类指针->派生类指针、基类引用->派生类引用)由于缺乏安全检查,所以是有问题的,要尽量避免这种用法
- 如果两个类无继承关系,则使用
static_cast
进行转换时会失败,但是这种情况下static_cast
会显性地展示出错误信息,是安全的
2.1.6、用于具有多态的类实例指针或引用之间的转换
进行上行转换的示例如下:
#include <iostream> class A { public: virtual void print() { std::cout << "A" << std::endl; } }; class B { public: virtual void print() { std::cout << "B" << std::endl; } }; class C : public A, public B { public: virtual void print() override { std::cout << "C" << std::endl; } }; int main(int argc, char* argv[]) { C c; A* a_ptr = static_cast<A*>(&c); // 上行指针转换正常 B* b_ptr = static_cast<B*>(&c); // 上行指针转换正常 a_ptr->print(); // 输出C,符合多态的要求 b_ptr->print(); // 输出C,符合多态的要求 A& a_ref = static_cast<A&>(c); // 上行引用转换正常 B& b_ref = static_cast<B&>(c); // 上行引用转换正常 a_ref.print(); // 输出C,符合多态的要求 b_ref.print(); // 输出C,符合多态的要求 }
可以看到上行转换都是正常的,转换过程中不会出现任何显性和隐性错误,下面来看一下正常的下行转换的示例:
int main(int argc, char* argv[]) { C c; A* a_ptr = static_cast<A*>(&c); B* b_ptr = static_cast<B*>(&c); A& a_ref = static_cast<A&>(c); B& b_ref = static_cast<B&>(c); C* c_ptra = static_cast<C*>(a_ptr); // 下行指针转换正常 C* c_ptrb = static_cast<C*>(b_ptr); // 下行指针转换正常 c_ptra->print(); // 输出C,符合多态的要求 c_ptrb->print(); // 输出C,符合多态的要求 C& c_refa = static_cast<C&>(a_ref); // 下行引用转换正常 C& c_refb = static_cast<C&>(b_ref); // 下行引用转换正常 c_refa.print(); // 输出C,符合多态的要求 c_refb.print(); // 输出C,符合多态的要求 }
可以看到这个也是正常的,和前面那个没有多态的差不多,接下来看一下不正常的下行转换例子:
int main(int argc, char* argv[]) { A a; B b; C* c_ptra = static_cast<C*>(&a); C* c_ptrb = static_cast<C*>(&b); c_ptra->print(); // 正常输出A c_ptrb->print(); // 段错误 C& c_refa = static_cast<C&>(a); C& c_refb = static_cast<C&>(b); c_refa.print(); // 正常输出A c_refb.print(); // 段错误 }
上面这个例子中的下行转换是错误的,但是通过
c_ptra
可以正常调用类A的print()
方法打印出字母A来,使用c_ptrb
就直接段错误了,原因是类A是第一个被继承的,类B是第二个被继承的,也就是在类C中,第一个虚表指针指向的就是类A的虚表,第二个虚表指针指向的就是类B的虚表。在上面的例子那样进行错误地转换时,由于类A被继承之后它位置的特殊性导致可以使用c_ptra
正确地调用类A的print()
方法,而类B则不行,可能这有点难理解,下面给大家看张图就明白了,如图2-1所示:图2-1
从图2-1中可以看出,对于类C来说,它始终调用着_vptr.A
指向的print()
方法,当我们使用纯类B类型进行下行转换时,根本就没有这一块的数据(这个转换是不完整的、不安全的),所以就会出现段错误了。当然,使用纯类A类型进行下行转换也是不完整、不安全的,只不过位置刚好才不会出现段错误而已。综合分析上面的代码是完全错误的,一定要杜绝写出这种垃圾代码。综上,我们可以得出使用
static_cast
对具有多态的类实例指针或引用进行强制类型转换时有以下特点:- 进行上行转换(派生类指针->基类指针、派生类引用->基类引用)是完全安全的,没有任何问题,当然这个过程由隐式转换来完成也是合法的
- 进行下行转换(基类指针->派生类指针、基类引用->派生类引用)由于缺乏安全检查,所以是有问题的,并且因为具有多态的类往往具有特殊的用法,所以在这种情况下产生的后果比前面没有多态情况下的要更严重,要尽量避免这种用法
小结:通过上面的介绍,我们可以很直观地看到
static_cast
相比C风格的强制类型转换要安全很多,有很大程度上的类型安全检查。本节我们所有的例子都可以使用C风格的强制类型转换去做,但是转出来的结果有可能会错到天际去,并且编译器不会给你任何报错信息。。。同时我们也要认识到static_cast
也是有明显缺点的,那就是无法消除const
和volatile
属性、无法直接对两个不同类型的指针或引用进行转换和下行转换无类型安全检查等,不过没关系,其它三个强制类型转换的关键字刚好能弥补static_cast
的这些缺点。2.2、const_cast
const_cast
的作用是去除掉const
或volitale
属性,前面介绍static_cast
的时候我们知道static_cast
是不具备这种功能的。使用格式如下:
const_cast<type_id>(expression);
注意事项:const_cast不是用于去除变量的常量性,而是去除指向常量对象的指针或引用的常量性,其去除常量性的对象必须为指针或引用,并且const_cast不支持不同类型指针或引用之间的转换,比如说float*转换成int*是不允许的,直白一点说就是type_id和expression要基本类型保持一致,相差的话只能差const或volatile属性。
先来看一个错误的使用示例:
#include <iostream> int main(int argc, char* argv[]) { int type_int = 100; float type_float = const_cast<float>(type_int); // 错误,const_cast只能转换引用或者指针 float* type_float_ptr = const_cast<float*>(&type_int); // 错误,从int* -> float* 无效 float& type_float_ref = const_cast<float&>(type_int); // 错误,从int& -> float& 无效 }
再来看一个不太正确的使用示例:
#include <iostream> int main(int argc, char* argv[]) { const int type_const_int = 100; int* type_const_int_ptr = const_cast<int*>(&type_const_int); // 转换正确 int& type_const_int_ref = const_cast<int&>(type_const_int); // 转换正确 *type_const_int_ptr = 10; std::cout << *type_const_int_ptr << std::endl; // 输出10 std::cout << type_const_int << std::endl; // 输出100,没有改变 type_const_int_ref = 20; std::cout << type_const_int_ref << std::endl; // 输出20 std::cout << type_const_int << std::endl; // 输出100,没有改变 // 以下三个输出结果一致,说明const_cast确实只是去除了一些属性,并没有重新搞快内存把需要转换的变量给复制过去 std::cout << "&type_const_int:\t" << &type_const_int << std::endl; std::cout << "type_const_int_ptr:\t" << type_const_int_ptr << std::endl; std::cout << "&type_const_int_ref:\t" << &type_const_int_ref << std::endl; }
在上面这个例子中,转换是成功了,但是
type_const_int
的常量性并没有被改变,这是因为const_cast
并没有办法把变量的常量性去除,而且比较有意思的是我们可以看到type_const_int
对应地址的内容确实被改变了,但是type_const_int
的值却并没有被改变,这是好事,因为从一开始我们把它定义为常量类型时这个值就不应该再被改变了。至于后面使用type_const_int_ptr
和type_const_int_ref
试图去改变type_const_int
的值,这是很危险的做法,不同编译器可能会有不同的处理,是有可能出现严重错误的,要杜绝这种用法。
从这里看起来const_cast
好像有点鸡肋。。。但是事实上不是这样的,在某些场景下const_cast
还是挺好用的,比如下面这个例子:#include <iostream> void fun(const int& v) { int& type_int_ref = const_cast<int&>(v); type_int_ref = 10; } int main(int argc, char* argv[]) { int type_int = 100; fun(type_int); std::cout << type_int << std::endl; // 输出10,改变了 }
上面的例子比较粗糙,但是大致上也就是这么个用法了。如果一个变量本来就不具备
const
属性,但是在传递过程中被附加了const
属性,这时候使用const_cast
就能完美清除掉后面附加的那个const
属性了。
当然,因为const_cast
的这种特殊性,它的应用范围是远不如static_cast
广泛的,但是在标准库中还是能找到一些使用const_cast
的例子的,比如“著名的”std::addressof
在源码实现中就有借助const_cast
作为中间层来消除参数的const
和volatile
属性,具体大家可以看一下这篇文章《C++11的std::addressof源码解析》。2.3、reinterpret_cast
reinterpret_cast
意为“重新解释”,它是C++中最接近于C风格强制类型转换的一个关键字。它让程序员能够将一种对象类型转换为另一种,不管它们是否相关。使用格式如下:
reinterpret_cast<type_id>(expression);
注意事项如下:
- type-id和expression中必须有一个是指针或引用类型(可以两个都是指针或引用,指针引用在一定场景下可以混用,但是建议不要这样做,编译器也会给出相应的警告)。
- reinterpret_cast的第一种用途是改变指针或引用的类型
- reinterpret_cast的第二种用途是将指针或引用转换为一个整型,这个整型必须与当前系统指针占的字节数一致
- reinterpret_cast的第三种用途是将一个整型转换为指针或引用类型
- 可以先使用reinterpret_cast把一个指针转换成一个整数,再把该整数转换成原类型的指针,还可以得到原先的指针值(由于这个过程中type-id和expression始终有一个参数是整形,所以另一个必须是指针或引用,并且整型所占字节数必须与当前系统环境下指针占的字节数一致)
- 使用reinterpret_cast强制转换过程仅仅只是比特位的拷贝,和C风格极其相似(但是reinterpret_cast不是全能转换,详见第1点),实际上reinterpret_cast的出现就是为了让编译器强制接受static_cast不允许的类型转换,因此使用的时候要谨而慎之
- reinterpret_cast同样也不能转换掉expression的const或volitale属性。
先来看一个错误的使用示例:
#include <iostream> class A { public: int a; }; class B { public: int b; }; int main(int argc, char* argv[]) { float type_float = 10.1; int type_int = reinterpret_cast<int>(type_float); // 出错,type-id和expression中必须有一个是指针或者引用(注意事项第1点) char type_char = reinterpret_cast<char>(&type_float); // 出错,我的是64位系统,这里type-id只能是long类型(注意事项第3点) double* type_double_ptr = reinterpret_cast<double*>(type_float); // 出错,这里expression只能是整型(注意事项第4点) A a; B b; long type_long = reinterpret_cast<long>(a); // 出错,type-id和expression中必须有一个是指针或者引用(注意事项第1点) B b1 = reinterpret_cast<B>(a); // 出错,type-id和expression中必须有一个是指针或者引用(注意事项第1点) A a1 = reinterpret_cast<A>(&b); // 出错,B* -> A不允许,我的是64位系统,type-id只能是long(注意事项第3点) A* a_ptr = reinterpret_cast<A*>(b); // 出错,这里expression只能是整型(注意事项第4点) }
下面再来看正确的使用示例:
#include <iostream> class A { public: int a; }; class B { public: int b; }; int main(int argc, char* argv[]) { float type_float = 10.1; long type_long = reinterpret_cast<long>(&type_float); // 正确,float* -> long(注意事项第3点) float* type_float_ptr = reinterpret_cast<float*>(type_long); // 正确,long -> float*(注意事项第4点) std::cout << *type_float_ptr << std::endl; // 正确,仍然输出10.1(注意事项第5点) long* type_long_ptr = reinterpret_cast<long*>(&type_float); // 正确,float* -> long*(注意事项第1点) char type_char = 'A'; double& type_double_ptr = reinterpret_cast<double&>(type_char); // 正确,char -> double&(注意事项第4点) A a; B b; long a_long = reinterpret_cast<long>(&a); // 正确,A* -> long(注意事项第3点) A* a_ptr1 = reinterpret_cast<A*>(type_long); // 正确,long -> A*(注意事项第4点) A* a_ptr2 = reinterpret_cast<A*>(&b); // 正确,B* -> A*(注意事项第1点) }
程序中写的比较清楚了,大家配合前面的注意事项去看就行了,很容易就可以看懂了。
reinterpret_cast
的一个典型使用也是标准库的std::addressof
,具体大家可以看一下这篇文章《C++11的std::addressof源码解析》。2.4、dynamic_cast
dynamic_cast
是本文讲的最后一个C++风格强制类型转换了,也是最特殊的一个,前面三种都是编译时完成的,而dynamic_cast
是运行时处理的,使用格式如下:
dynamic_cast<type_id>(expression);
注意事项如下:
- dynamic_cast是运行时处理的,运行时会进行类型检查(这点和static_cast差异较大)
- dynamic_cast不能用于内置基本数据类型的强制转换,并且dynamic_cast只能对指针或引用进行强制转换
- dynamic_cast如果转换成功的话返回的是指向类的指针或引用,转换失败的话则会返回nullptr
- 使用dynamic_cast进行上行转换时,与static_cast的效果是完全一样的
- 使用dynamic_cast进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。并且这种情况下dynamic_cast会要求进行转换的类必须具有多态性(即具有虚表,直白来说就是有虚函数或虚继承的类),否则编译不通过
需要有虚表的原因:类中存在虚表,就说明它有想要让基类指针或引用指向派生类对象的情况,dynamic_cast认为此时转换才有意义(事实也确实如此)。而且dynamic_cast运行时的类型检查需要有运行时类型信息,这个信息是存储在类的虚表中的 - 在C++中,编译期的类型转换有可能会在运行时出现错误,特别是涉及到类对象的指针或引用操作时,更容易产生错误。dynamic_cast则可以在运行期对可能产生问题的类型转换进行测试
先来看一个错误的使用示例:
#include <iostream> class A { public: void print() { std::cout << "A" << std::endl; } }; class B { public: void print() { std::cout << "B" << std::endl; } }; class C : public A, public B { public: void print() { std::cout << "C" << std::endl; } }; int main(int argc, char* argv[]) { C c; A* a_ptr = dynamic_cast<A*>(&c); // 成功,上行转换没有任何要求(注意事项第4点) B* b_ptr = dynamic_cast<B*>(&c); // 成功,上行转换没有任何要求(注意事项第4点) A a = dynamic_cast<A>(c); // 错误,dynamic_cast无法用于转换类实例(注意事项第2点) C* c_ptra = dynamic_cast<C*>(a_ptr); // 错误,类C不具备多态,无法进行下行转换(注意事项第5点) C* c_ptrb = dynamic_cast<C*>(b_ptr); // 错误,类C不具备多态,无法进行下行转换(注意事项第5点) }
从上面的例子中可以看到,
dynamic_cast
是无法对实例进行转换的,并且dynamic_cast
进行下行转换时是需要对应的类拥有多态性的,这两点和static_cast
是有区别,当然这些不是最重要的区别,下面我们再来看一个例子:#include <iostream> class A { public: virtual void print() { std::cout << "A" << std::endl; } }; class B { public: virtual void print() { std::cout << "B" << std::endl; } }; class C : public A, public B { public: virtual void print() { std::cout << "C" << std::endl; } }; int main(int argc, char* argv[]) { C c; // 第一组 A* a_ptr = dynamic_cast<A*>(&c); B* b_ptr = dynamic_cast<B*>(&c); C* c_ptra = dynamic_cast<C*>(a_ptr); // 成功,类C具备多态性,可以使用dynamic_cast进行下行转换 C* c_ptrb = dynamic_cast<C*>(b_ptr); // 成功,类C具备多态性,可以使用dynamic_cast进行下行转换 // 以下输出内容一致 std::cout << &c << std::endl; std::cout << c_ptra << std::endl; std::cout << c_ptra << std::endl; // 第二组 A a; B b; C* c_ptra1 = dynamic_cast<C*>(&a); // 编译正常(好的编译器会给你个警告),转换结果为nullptr,说明转换失败 C* c_ptrb1 = dynamic_cast<C*>(&b); // 编译正常(好的编译器会给你个警告),转换结果为nullptr,说明转换失败 // 以下输出内容一致,都是0,说明c_ptra1和c_ptrb1都是nullptr std::cout << c_ptra1 << std::endl; std::cout << c_ptrb1 << std::endl; }
从上面的例子中可以看出以下几点信息:
- 对于第一组来说,因为这一组的下行转换是安全的(因为a_ptr和b_ptr本来就是类C的指针类型转换过去的),所以对于这种情况
static_cast
和dynamic_cast
是一样的,都可以正常使用。 - 对于第二组来说,这个是一个错误使用的示例,我们之前也有用
static_cast
做过同样的实验(忘了的话可以翻回去2.1.3小节看一下),当时使用static_cast
进行这种错误的下行转换时是一点问题没有的,可以转换成功,甚至可以使用一下(当然这是极其糟糕的行为),而现在使用dynamic_cast
来转换则不同了,虽然编译也可以通过,但是它返回的结果是一个空指针,这就可以在运行时提示我们转换不合法了。
3、总结
本文讲解了C++中存在的五种强制类型转换,其中C风格的只是一笔带过而已,重点讲的是C++中新增的四种强制类型转换,文中也有对它们各自的优点和缺点进行解析。整体来说C++增加的这几种强制类型转换相比C风格的更加安全,所以在编写C++代码时还是尽量使用它们替换C风格的强制类型转换吧。
最后,如果大家觉得本文写得好的话麻烦点赞收藏关注一下谢谢,也可以关注该专栏,以后会有更多优质文章输出的。
-
golang语言强制类型转换
2021-01-07 19:30:43文章目录前言golang强制类型转换类型断言类型转换指针类型转换总结引用 前言 golang是强类型语言,有强制类型转换,但是不同于Java语言所使用的强制类型转换。 golang强制类型转换 golang语言中分为类型转换(type ... -
C#强制类型转换小结
2020-08-30 02:24:14任何一门编程语言均有相关数据类型。C#也不例外,不过转换过程要注意小类型能转换成大类型,但大类型一般不能转换成小类型,下面小编给大家详解C#强制类型转换小结,需要的朋友参考下吧 -
JS在if中的强制类型转换方式
2020-12-08 18:17:22众所周知,JS在很多情况下会进行强制类型转换,其中,最常见两种是: 1.使用非严格相等进行比较,对==左边的值进行类型转换 2.在if判断时,括号内的值进行类型转换,转化为布尔值 今天,我就来聊一聊JS在if中的强制... -
JS中的强制类型转换
2021-12-11 14:08:21类型转换发生在静态语言的编译阶段,强制类型转换则发生在动态语言的运行阶段,在js中通常把他们统称为强制类型转换,而强制类型转换可以分为两种,方便我们后面的理解,下面举出一些列子 显示强制类型转换 var a = ...概念
这里我们首先需要知道什么是值类型转换
值类型转换:将值从一种类型转换为另一种类型,就是类型转换,分显示转换和隐式转换
js类型转换出的值都是基本类型(number、boolean、string、null、undefined、string),并不会转换为引用类型的值。
强制类型转换
类型转换发生在静态语言的编译阶段,强制类型转换则发生在动态语言的运行阶段,在js中通常把他们统称为强制类型转换,而强制类型转换可以分为两种,方便我们后面的理解,下面举出一些列子
显示强制类型转换
var a = 42 var c =String(a)
隐式强制类型转换
var a = 42 var c =a + ''
这里注意下一元操作符 + ,+ 只要左侧或右侧出现字符串类型的都会是字符串拼接,后续会细说
在了解细致的显示与隐式的强制类型转换之前,我们得先知道值类型转换的方式’
显示强制类型转换:数字、字符串、布尔之间的类型转换
为了将值转换为对于的基本类型的值,我们这里得先了解下ToPrimitive,该方法会通过内部操作DefaultValue,检查该值是否带有valueOf()方法,有就返回基本类型,就用该值作为强制类型转换,没有就用toString()得返回值(若存在)来进行强制类型转换
字符串
负责非字符串到字符串得强制类型转换,这里书上P44页只介绍了toString,为了更好的对非字符串转换为字符串的理解,举出下面的列子
额外介绍:在谷歌浏览器中,console.log打印出的数字是蓝色的字体,字符串是黑色的字体
1、String()
String()是js的全局函数可以把null、undefined转换为字符,但是没办法转换为进制字符串,其他的都和toString()一样,如下列子:
var a = null; var b = undefined; console.log(String(a)); console.log(String(b));
2、toString()
toString()是object原型的一个方法,它与String的区别就是不能转换null、undefined,会报错,其他的没什么区别,如下列:
var a = null; var b = undefined; console.log(a.toString()); console.log(b.toString());
3、JSON.stringify()
JSON.stringify()对象序列化转换为字符串的时候也调用了toString,的方法,这里值得注意的是,JSON.stringify(),并非严格意义上的强制类型转换用法与toString基本一致,但是也有区别,如下:
1、JSON.stringify()在对象中遇到undefined、function、symbol都会自动忽略掉,而在数组中就会返回null
<script> var a = null; var b = undefined; var d = function foo() {}; var c = [1, undefined, d, null]; console.log("c: ", JSON.stringify(c)); console.log("a: ", JSON.stringify(a)); console.log("b: ", JSON.stringify(b)); </script>
2、JSON.stringify()对数组的返回与toString()返回的结果不一样,如下
<script> var a = [1, 2, 3]; console.log("toString()", a.toString(a)); // '1','2','3' console.log("JSON.stringify()", JSON.stringify(a)); // '[1,2,3]' </script>
JSON.stringify()中还可以接一个参数,这里不做过多扩展,只是简单展示与其他方法的区别,注意,JSON.stringify()不是强制类型转换
额外扩展:在JSON.stringify()中还会存在toJSON()方法,JSON字符串首先会调用该方法,然后用其返回值去进行序列化
toJSON()返回的是一个能够被字符串化的安全的JSON值,而不是一个JSON字符串
日期显示转数字
这部分知识在工作中比较重要,我们很多时候需要获得当前时间的时间戳,或者v-for、map中都需要key值,偶尔我们会没有key值,我们就可以用写一个js文件,封装获得时间戳+随机字符数字的集合来作为Key值,或者利用第三方库也行。这里做相关介绍
日期函数:是js中的全局函数
Date()
<script> var a = new Date(); var b = new Date().getTime(); var c = Date.now(); console.log("先强制类型转换", +a); console.log("Date中的内置函数getTime()", b); console.log("ES5的now()", c); </script>
数字
非数字转换为数字类型
首先举出一些列子,也可能是常见的面试资源
true 转换为 1
false 转换为 0
null 转换为 0
undefined转换为 NAN
1、Number()
这是js中的全局函数
<script> var a = "22"; var b = null; var c = undefined; var d = true; var e = false; var f = {}; var g = []; var h = function foo() {}; console.log(Number(a)); console.log(Number(b)); console.log(Number(c)); console.log(Number(d)); console.log(Number(e)); console.log(Number(f)); console.log(Number(g)); console.log(Number(h)); </script>
可以看到 函数与对象转换为了NAN
2、parseInt()
字符串转数字取整形,还可以接一个参数,确定返回数为几进制,这里不展开
parseInt()与Number()的区别:
1、前者在转换字符串的过程中若是遇到非字符数字的会终止,但是转换的数会被输出,后者就会转换失败,报出NAN
2、前者转换中会先进行一次强制类型转换,转换为字符串类型,再去把字符串转换为数字,所以使用parseInt()前的数值最好是字符串型的
具体的如下列:
<script> var a = "33"; var b = [1, 2, 3]; var c = {}; var d = function () {}; console.log(parseInt(a)); console.log(parseInt(b)); console.log(parseInt(c)); console.log(parseInt(d)); console.log(''); console.log(Number(a)); console.log(Number(b)); console.log(Number(c)); console.log(Number(d)); </script>
3、parseFloat()
字符串类型的转换为数字型的浮点数,具体用法和parseInt()类似,详细的区别不过多介绍
4、隐式转换
隐式转换有两种方式
方式一
这是显示强制类型转换
使用一元运算符+,上面只是有简单提起,注意的是若+左右侧任意一方是字符串类型,则为字符串拼接
<script> var a = "1"; var b = null; var c = undefined; var e = {}; var f = function () {}; console.log("隐式转换方式一", +a, typeof +a); console.log("隐式转换方式一", +b, typeof +b); console.log("隐式转换方式一", +c, typeof +c); console.log("隐式转换方式一", +e, typeof +e); console.log("隐式转换方式一", +f, typeof +f); </script>
方式二
在不改变原来的值情况下,使用一元运算符-,*,%,如下列:
<script> var a = "1"; var b = null; var c = undefined; var e = {}; var f = function () {}; console.log("隐式转换方式二", a - 0, typeof +a); console.log("隐式转换方式二", b - 0, typeof +b); console.log("隐式转换方式二", c % 1, typeof +c); console.log("隐式转换方式二", e * 1, typeof +e); console.log("隐式转换方式二", f - 0, typeof +f); </script>
布尔
非布尔类型转换为布尔类型
1、假值
我们先来认识js中的假值
undefined、null、false、+0、-0、NAN、""
这七个值被称为js中的假值,除了假值外其他的都是真值
2、假值对象
假值对象并不是假值,只是封装了价值的对象,如下:
<script> var a = new Boolean(false); var b = new Number(0); var c = new String(""); console.log(Boolean(a)); console.log(Boolean(b)); console.log(Boolean(c)); </script>
这里的价值对象需要和假值做出区分,一个是对象,一个是js中的基本类型
3、真值
介绍假值得时候说到,真值就是除假值外的值,如下列:
<script> var a = "false"; var b = '"'; var c = "0"; var d = []; var e = {}; var f = function () {}; console.log(Boolean(a)); console.log(Boolean(b)); console.log(Boolean(c)); console.log(Boolean(d)); console.log(Boolean(e)); console.log(Boolean(f)); </script>
真值有无限多,所有我们只要记住有几种假值就好了
Boolean()是js中的全局函数,和!!操作符一样都是显示强制类型转换,下面介绍下
!!
搜先说下!,一元运算符!显示的将值强制类型转换为布尔型,但是它同时还会将真值转换为假值,假值转化为值。所以想要原来值不变,类型转换为布尔型还需要再使用一次!,这样就取反了。
上面说这么多是想表达!!不是简单的取反再取反,是进行了显示的强制类型转换。
隐式强制类型转换:数字、字符串、布尔之间的类型转换
字符串和数字之间的隐式转换
前面简单的介绍了一元操作符+的作用,这里做点补充
通过重载,js中的+运算符既能用于数字加法,又能用于字符串拼接,在js中是怎么判断的呢?
1、+会去判断左右两侧的操作数,如果某个操作数是字符串型或者能够转换为字符串型,+就会对左右两侧进行拼接
2、若是没有字符串型的,就执行加法操作
<script> var a = 33; var b = 1; var c = ""; console.log(a + b); console.log(a + c); console.log("//"); var d = [1, 2]; var e = [3, 4]; console.log(d + e); </script>
上面是数字到 字符串的转换,同样的若是需要字符串到数字的隐式转换可以用一元操作符-、%、*,但是是在不改变原有的值的情况下,如下:
<script> var a = "3.14"; var b = a - 0; console.log("b: ", b); // '3.14' </script>
通过+ 和其他的一元运算符就可以进行字符串与数字之间的隐式强制类型转换
隐式强制类型转换为布尔值
在写代码的过程中我们经常会用到下面的场景,其实这里已经做了值类型的强制转换,如下 所示:
1、if()中的条件表达式
2、for() 语句中的条件判断表达式(第二个)
3、while() 和 do…while()循环中的条件判断表达式
4、? : 中的判断表达式
5、 && 和 || 左边的操作数会作为条件判断的表达式
上面列举出的这些情况,都会隐式强制类型转换把非布尔型转换为布尔型,我们重点理解下第五条
|| 和 &&
a||b a&&b
逻辑或和逻辑与,在工作中非常实用的知识点!在JS中这里其实应该叫做”选择器运算符“ ,和其他语言不同, 他们返回的并不是一个布尔值,而是两个操作数中的一个(仅且一个)
选择器运算符会先去判断左边的值不是不一个布尔值,若非布尔值的话,就会先进行值强制类转换转换为布尔值
<script> var a = 42; var b = null; var c = "abc"; console.log(a && c); console.log(a && b); console.log("/"); console.log(a || c); console.log(a || b); console.log(b || a); </script>
如上所示,这里打印出的并不是true和false,而是选择器运算符左侧或者右侧的一个操作数,他们遵循下面的这些规则:
a||b、a&&b
1、当为||选择器运算符的时候,如果a为true,则选择操作数a,否则选则b
2、当为&&选择器运算符的时候,如果a为true,则选择操作数b,否则选择a
这里的第一个操作数的真假值判断可以参考上面的假值得内容
这里是不是很好奇,既然||与&&返回的不是true和false,为什么if()判断条件中可以使用&&和||,如下列:
<script> var a = 42; var b = null; var c = "foo"; if (a && (b || c)) { console.log("yep"); // 输出的是yep } </script>
我们在这一小节的开始就说到,在举列子中得条件判断中,会自动将非布尔型得数值转换为布尔值的类型,所意这个列子如下解析:
1、b||c,b是null为假值,||第一个操作数为假的情况下,取第二个操作数,就是c
2、a&&c,a是42,为真值,&&第一个操作数为真的情况下,取第二个操作数,就是’foo‘
3、就得到 if(“foo”),前面说过,if()中会进行值的类型转换,”foo“的值为真,所以if()条件判断为真,打印log
上面都是进行了隐式的强制类型转换,如果需要避免进行隐式强制类型转换,可以这样写:
<script> var a = 42; var b = null; var c = "foo"; if (!!a && (!!b || !!c)) { console.log("yep"); } </script>
符号的强制类型转换
符号 Symbol是ES6新增的一个常用基本类型,这里简单的介绍下关于Symbol的强制类型转换。
关于他的值类型转换有一个坑:ES6允许从符号到字符串的显示强制类型转换,而隐式的强制类型转换就会报错,原因可自行百度,列子如下:
<script> var a = Symbol("cool"); console.log(String(a)); console.log(a + ""); </script>
对于符号的值强制类型转换,我们可记住下面这几点:
1、符号转字符串形式,只能显示强制类型转换,隐式强制类型转换会报错
2、符号不能被强值转换为数字,不管是显示还是隐式都会报错
3、符号转布尔型,隐式和显示的结果都是true
这里简单的记一下就好,具体的知识点需要单独的看看
-
C++中强制类型转换的应用
2020-12-22 18:57:051.1 强制类型转换 大家都知道在C语言里强制类型转换的方法非常简单,只要在要转换的变量前用括号确定要转换的类型即可,如要将一个double型的变量强制转换为int类型,代码如下: double x = 3.14; int y = ... -
【Java】强制类型转换
2022-02-19 22:18:20强制类型转换,可以理解为自动类型转换的逆过程。是将大容量的数据类型转换为小容量的数据类型。 使用时,前面要加上强制类型转换符号()。 2. 应用场景 需要将数据从高精度数据类型转换为低精度数据类型。(即高精度...1. 基本概念
强制类型转换,可以理解为自动类型转换的逆过程。是将大容量的数据类型转换为小容量的数据类型。
使用时,前面要加上强制类型转换符号
()
。2. 应用场景
需要将数据从高精度数据类型转换为低精度数据类型。(即高精度类型的数据不能直接赋值给低精度数据)
【注】Java中数据类型的精度从低到高的顺序为:
- byte
- short
- int
- long
- float
- double
3. 使用风险
程序中使用强制类型转换,可能面临以下2种风险:
- 精度损失。
- 数据溢出。
程序示例:
public class ForceConvert{ public static void main(String[] args){ //风险1:精度降低 double a = 1.9; int b = (int)a; System.out.println(b);//造成精度降低,输出1 //风险2:数据溢出 int c = 1000;//对应二进制为11 1110 1000 byte d = (byte)c;//强制转换为byte类型,直接截去11 1110 1000超出低8位的二进制,变为1110 1000。 //因为java中整型都是有符号,所以1110 1000的最高位是符号位,为1则表示是一个负数。 //计算机中,正数的原码等于补码,而负数的原码不等于补码。 //计算机中,用二进制的补码来存储数据。 //所以,1110 1000是补码,转换成原码就是1001 1000,就是-24。 //补码转换为原码的方法:符号位不变,从右边往左边数,第一个不是0的后面依次取反。 System.out.println(d);//造成数据溢出,输出-24 } }
运行结果:
4. 使用规则
- 强制类型转换符号(),只对最近的操作数有效。可以使用小括号来提升优先级。
程序示例:
public class ForceConvert1{ public static void main(String[] args){ //强制类型转换符号(),只对最近的操作数有效。可以使用小括号来提升优先级。 int a = (int)(10 * 3.5 + 1.5 * 6); int b = (int)10 * 3.5 + 1.5 * 6;//编译会报错。因为等号右边的运算结果是double类型,不能直接赋值给int型的变量a。 System.out.println(a); System.out.println(b); } }
编译结果:
- char类型的变量可以保存默认为int类型的常量(前提是常量的值在char类型的范围内),但是不能直接保存为int类型的变量值,需要进行强制类型转换。
char c1 = 100; int a = 100; char c2 = a;//程序编译会报错。 char c3 = (char)a;
-
char、byte、short三种类型在进行混合运算时,会自动提升数据类型,先转换成int型。
-
Java中,用常量(默认数据类型为int)来给变量(数据类型为byte、short、char)赋值时,只要常量的值在该变量所述数据类型的有效范围内,则就可以正常赋值。
5. 训练
判断以下哪些语句会报错。
short s = 12;//对,因为12在short类型的范围内。 s = s - 9;//错,等号右边为int类型,int类型数据不能直接赋值给short类型。 byte b = 10;//对,因为10在byte类型的范围内。 b = b + 11;//错,等号右边为int类型,int类型数据不能直接赋值给byte类型。 b = (byte)(b + 11);//对,等号右边为int类型,int类型数据通过强转,赋值给byte类型。 char c = 'a';//对 int i = 16;//对 float d = .314F;//对 double result = c + i + d;//对,等号右边的运算结果是float类型,float类型可以直接赋值给double类型 byte b = 16;//对 short s =14;//对 short t = s + b;//错,等号右边的运算结果是int类型,int类型不能直接赋值给short类型。
-
C++强制类型转换
2021-04-20 18:41:34C++ 类型转换(C风格的强制转换): 在C++基本的数据类型中,可以分为四类:整型,浮点型,字符型,布尔型。其中数值型包括 整型与浮点型;字符型即为char。 (1)将浮点型数据赋值给整型变量时,舍弃其小数部分。... -
解析C++中四种强制类型转换的区别详解
2021-01-20 07:03:04C++的四种强制类型转换,所以C++不是类型安全的。分别为:static_cast , dynamic_cast , const_cast , reinterpret_cast为什么使用C风格的强制转换可以把想要的任何东西转换成合乎心意的类型。那为什么还需要一个新... -
自动类型转换与强制类型转换
2022-01-11 11:25:35自动类型转换与强制类型转换的详细解释 -
c语言怎么进行强制类型转换
2021-05-24 01:58:26在c语言中,可以通过“(新类型名称) 数据或表达式”语句来进行强制类型转换。强制类型转换是程序员明确提出的、需要通过特定格式的代码来指明的一种类型转换。本教程操作环境:windows7系统、c99版本、Dell G3电脑。... -
C++四种强制类型转换
2022-01-13 11:37:16文章目录1 C++类型转换本质1.1 自动类型转换(隐式)1.2 强制类型转换(显式)1.3 类型转换的本质1.4 类型转换的安全性2 四种类型转换运算符2.1 C语言的强制类型转换与C++的区别3 static_cast4 reinterpret_cast5 ... -
Java的强制类型转换
2022-03-10 13:26:40Java的强制类型转换 -
C语言强制类型转换
2021-05-21 05:25:14不同的数据类型占有的空间大小不一,但是他们都必须有个地址,而这个地址就是硬件访问的依据,而名字只是提供给程序员的一种记住这个地址的方便一点的方法。但是,不同的变量在机器中都是0-1代码,所以,我们不能... -
C语言:强制类型转换、自动类型转换
2022-04-06 16:06:46C语言中类型转换有两种方式:即强制类型转换和自动类型转换。 一、强制转换比较简单,为:(类型)操作对象。 int n1 = 5, n2 = 2; float f1 = (float) 5 / 2; //结果为2.5000,若不强制转换结果为2.000 其它类型... -
c语言强制类型转换详解
2022-02-11 16:34:27在强制类型转换中,我们常用的整形强制转换有无符号和有符号类型的强制转换。所以首先我们得介绍一下计算机中存储数字的方式,计算机中通常以补码的形式来存储数据,以8位数据为例,二进制与有符号数的对应关系为: ... -
C++: 四种强制类型转换
2022-03-06 09:03:172、常量引用被转换成非常量的引用,并且仍然指向原来的对象; 3、const_cast一般用于修改指针。如const char *p形式。 #include<iostream> int main() { // 原始数组 int ary[4] = { 1,2,3,4 }; // ... -
TypeScript学习之强制类型的转换
2020-11-30 03:42:24下面来详细看看TypeScript的强制类型转换。 TypeScript强制类型转换 在 TypeScript 中将一个 number 转换成 string ,这样做会报错: var a:number = 12345; var b:string = <string> a; // laygroundSingle.ts -
C++ 强制类型转换
2020-01-07 21:00:07强制类型转换 C++语言中提供了static_cast、const_cast、reinterpret_cast和dynamic_cast。这四个关键字都是用于强制类型转换的。 二.static_cast 1.基本数据之间的转换,如int转化为double int a = 1; ... -
C语言中关于强制类型转换问题
2021-05-23 12:49:53因为朋友问到,因此整理了一份,若关于一些认识有错误还望指出.........spadouble←──float高code↑对象longblog↑unsigned编译↑int←──char,short低class●简单的说强制类型转换有显式转换、隐式转换变量好比... -
C#自动类型转换与强制类型转换的讲解
2020-08-26 10:07:07今天小编就为大家分享一篇关于C#自动类型转换与强制类型转换的讲解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧 -
QT-强制类型转换
2022-04-28 21:55:12刚开始学习的时候,直接对基本数据类型强制类型转换,如float fnum = 3.14; int num = (int)fnum; 随着C++标准的发展,又提供了dynamic_cast、const_cast 、static_cast、reinterpret_cast等高级安全的强制转换方法...