C++的类型转换
Posted 森明帮大于黑虎帮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++的类型转换相关的知识,希望对你有一定的参考价值。
文章目录
C中的类型转换
- 今天提到的这个话题,想必各位在实际使用的过程中定然会时常用到。不过,一般来说,当发生类型转换时,我们一般的处理手段,通常是C风格的这种方案。
void Func1()
//这种称之为隐式的类型转换,编译器默默地将int类型的变量进行了转换
int a=1;
double b=a;
//或者说,这样稍微显示一些的类型转换
int c=(int)(5.0);
int main()
Func1();
;
- ps:顺带一提,在进行类型转换时,建议进行由低精度向高精度的转换,如果方向相反的话,会造成精度丢失,数据误差。
在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型转换和显式类型转换。
- 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败。
- 显式类型转化:需要用户自己处理。
C++中的类型转换
-
虽然上面的方法很简单粗暴,便于上手,但是在理解方面,可能就不是足够的清晰了,毕竟当某些隐式转换发生时,身为程序员可能会不易差距这一过程的发生。为了避免这一问题,除却类型转换这边,在构造函数中有个很鲜明的例子,那就是
**explicit**
来避免构造函数的隐式转换。class A public: explicit A(int a) std::cout<<"A(int a)"<<std::endl; A(const A* a) std::cout<<"A(const A& a)"<<std::endl; private: int a_; ; int main(int argc,char* argv[]) A a1(1); //隐式转换 -> A tmp(1); A a2(tmp); A a2=1; return 0;
-
不过,这不是今天所要聊到的主角了,为了适用于更多的场景,以及避免歧义,C++额外引入了四种新的转换方式:
C风格的转换格式很简单,但是有不少缺点的:
- 隐式类型转化有些情况下可能会出问题:比如数据精度丢失。
- 显式类型转换将所有情况混合在一起,代码不够清晰。
因此C++提出了自己的类型转化风格,注意因为C++要兼容C语言,所以C++中还可以使用C语言的转化风格。
void Func7()
int i=1;
//隐式类型转换
double d=8.88;
i=d;
std::cout<<i<<" "<<d<<std::endl;
int* ptr=&i;
int address=(int)ptr;
std::cout<<ptr<<" "<<address<<std::endl;
static_cast<type_id>(expression)
static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用static_cast,但它不能用于两个不相关的类型进行转换。
void Func8()
double d=12.34;
int a=static_cast<int>(d);
std::cout<<a<<std::endl;
-
用途1:执行那些基本的内置数据类型转换。
- float data=static_cast(1) --int类型转换为float类型。
-
用途2:用于指针之间的类型转换。
void Func2() //C++中的类型转换 int type_int=10; float* float_ptr1=&type_int; //隐式类型转换无效 float* float_ptr2=static_cast<float*>(&type_int); //显示转换有效 char* char_ptr1=&type_int; //隐式类型转换无效 char& char_ptr2=static_cast<char*>(&type_int); //不合理的转换显示依然无效 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_prt4=static_cast<char*>(void_ptr); //void* ->char* 使用static_cast转换成功
- 针对那些看似不合理的转换,我们采用
void *
作为中间体,实现我们类型转换的目的。 - 任何指针都可以隐式转换为void*。
- static_cast
是直接不允许不同类型的引用进行转换的,因为没有void类型引用可以作为中间介质,这点和指针是有相当大区别的。
注意:指针之间无法实现
隐式转换`,目的在于安全性保障。
- 针对那些看似不合理的转换,我们采用
-
用途3:用于类之间的类型转换。
class A
public:
int a;
;
class B
public:
int b;
;
class C:public A,public B
public:
int c;
;
void Func3()
C c;
A a=static_cast<A>(c); //子类转换为父类正常,叫做下转上
B b=static_cast<B>(c); //子类c转换为父类B正常,叫做下转上
C c_a=static_cast<C>(a); //父类转换为子类不正常,叫做上转下
C c_b=static_cast<C>(b); //父类b转换为子类c不正常,叫做上转下
-
- 上行转换:子类向基类转换
- 下行转换:基类向子类转换
static_cast
会进行类型判断,对于上行转换来说,这是正常的,可以通过编译,而对于下行转换来说的话,编译器则会认为二者是毫无关联的类型。这点从逻辑角度也很容易想的通,从基类转子类,基类少了子类的一些部分,转起来自然会有问题。- 解决方案:在子类中重载一个相应类型的构造函数即可。
class A
public:
int a;
;
class B
public:
int b;
;
class C:public A,public B
public:
C()
C(const A& a)
c=a.a;
C(const B& b)
c=b.b;
private:
int c;
;
void Func3()
C c;
A a=static_cast<A>(c); //子类转换为父类正常,叫做下转上
B b=static_cast<B>(c); //子类c转换为父类B正常,叫做下转上
C c_a=static_cast<C>(a); //子类C中有对象的构造函数 可以转换
C c_b=static_cast<C>(b); //子类C中有对象的构造函数 可以转换
- 用途4:用于没有多态的类实例指针或者引用之间的转换。
void Func4()
//用途4:用于没有多态的类实例指针或者引用之间的转换。
//上行part
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); //子类的对象转换为父类的引用,下转上正常
//下行part
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_prt_fail=static_cast<A*>(b_ptr); //失败 B*转换为A* ,两个无关联的类型无效
-
- 看到这里,小伙伴们可能就会有疑问了,上行转换没问题,但……下行转换不是会初始,怎么转换在当对象为指针和引用的时候可以正常执行呢?
- 答案是,确实会有问题,但是由于没有执行类型检查,所以仍可正常编译通过,但要牢记,此种做法是不安全的,要尽量避免这种方式。此外,这种转换也只是在两个类之间用于继承关系时,才会发生,如若是毫无关系的两个类,编译器会显式的给出错误。
- 用途5:用于具有多态的类实例指针或引用之间的转换。
class D
public:
virtual void print()
std::cout<<"D"<<std::endl;
;
class E
public:
virtual void print()
std::cout<<"E"<<std::endl;
;
class F:public D,public E
public:
virtual void print() override
std::cout<<"F"<<std::endl;
;
void Func5()
F f;
D* d_ptr=static_cast<D*>(&f); //子类对象的地址转换成父类的指针,下转上正常
E* e_ptr=static_cast<E*>(&f); //子类对象的地址转换成父类的指针,下转上正常
d_ptr->print(); //输出F,符合多态的要求
e_ptr->print(); //输出F符合多态的要求
D& d_ref=static_cast<D&>(f); //子类的对象转换成父类的引用,下转上正常
E& e_ref=static_cast<E&>(f); //子类的对象转换成父类的引用,下转上正常
d_ref.print(); //输出F,符合多态的要求
e_ref.print(); //输出F,符合多态的要求
//下行part
F* f_ptrd=static_cast<F*>(d_ptr); //正常, 因为在这个函数里面是可逆的
F* f_ptre=static_cast<F*>(e_ptr); //基类对象的地址转换为子类对的指针
f_ptrd->print(); //输出F,符合多态的要求
f_ptre->print(); //输出F,符合多态的要求
F& f_refd=static_cast<F&>(d_ref); //基类的对象转换为子类的引用
F& f_refe=static_cast<F&>(d_ref);
f_refd.print(); //输出F,符合多态的要求
f_refe.print(); //输出F,符合多态的要求
-
- 这块上行和下行转换都正常了???虽然没进行检查,不过我要说这个例子其实有点极端了,虽然正常归正常了,但说白了只是将对应子类的指针或者引用上行转换完,又进行了下行转换……,着实感觉是啥都没做一样。
- 接下来,还是聊聊转换错误的例子吧:
void Func6()
D d;
E e;
F* f_ptrd=static_cast<F*>(&d);
F* f_ptre=static_cast<F*>(&e);
f_ptrd->print(); //正常输出D
//f_ptre->print(); //段错误
F& f_refd=static_cast<F&>(d);
F& f_refe=static_cast<F&>(e);
f_refd.print(); //正常输出D
f_refe.print(); //段错误
-
- 有小伙伴,可能难免会疑惑,为毛会出现一个正常输出,另一个没有正常输出呢?
- 原因在于,继承的一些内在机制导致的,其实这种也是不安全,不可靠的,只是转换完的内存布局,刚好使得其拥有了正常的输出调用。
-
- 说到这里了,我直接给一个定性的结论好了。
static_cast
用于下行转换时,是极度不推荐的。 毕竟没有安全检查,如果没有出问题,也只是凑巧没有发生的结果,采用此种方式的代码撰写,可能会埋下极大的安全隐患。
- 说到这里了,我直接给一个定性的结论好了。
const_cast<type_id>(expression)
const_cast最常用的用途就是删除变量的const属性,方便赋值。
void Func1()
const int a=10;
int *b=const_cast<int*>(b);
*p=20;
std::cout<<a<<" "<<*P<<std::endl;
- 用途:用于去除变量的
const
或者volitale
属性。 - 这个其实没啥可以聊的地方,只要记住,这玩意的功能只有这个就是了,不能用于除此之外的类型转换。顺带一提,虽然这玩意对于我们处理
const
变量时能方便好不少,毕竟你没法隐式的转换const
变量,但是……感觉好不安全,有丢让const
的安全性打折扣。
reinterpret_cast<type_id>(expression)
reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型。
- 用途:这个关键字就很简单粗暴,无论其是否相关,都直接重新解释
- 注意事项
- 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属性
错误例子:
class A
public:
int a;
;
class B
public:
int b;
;
void Func1()
float type_float=10.1;
int type_int=reinterpret_cast<int>(type_float); //出错,type_id和expression中必须要有一个指针或者引用类型
char type_char=reinterpret_cast<char>(&type_float); //出错,我的是64位的系统,这里的type_id必须是long类型
double* type_double_ptr=reinterpret_cast<double*>(type_float); //出错,这里的expression只能是整形
A a;
B b;
long type_long=reinterpret_cast<long>(a); //出错,type_id和expression必须有一个是指针或者引用
B b1=reinterpret_cast<B>(a); //出错,type_id和expression必须有一个是指针或者引用
A a1=reinterpret_cast<A>(&b); //出错,B* ->A 不允许,64位系统的话type_id只能是long类型
A* a_ptr=reinterpret_cast<A*>(b); //出错,expression只能是整形
正确例子:
void Func2()
float type_float=10.1;
long type_long=reinterpret_cast<long>(&type_float); //将引用类型转换为整形
float* type_float_ptr=reinterpret_cast<float*>(type_long); //将整形转换为指针或者引用类型
std::cout<<*type_float_ptr<<std::endl; //正确,仍然输出10.1
long* type_long_ptr=reinterpret_cast<long*>(&type_float); //正确,float* -> long*
char type_char='A';
double& type_double_ptr=reinterpret_cast<double&>(type_char); //正确,char->double
A a;
B b;
long type_long=reinterpret_cast<long>(&a); //将指针类型转换为整形
A* a_ptr1=reinterpret_cast<A*>(type_long); //将整形转换为指针类型
A* a_ptr2=reinterpret_cast<A*>(&b); //B*->A* 两个都是指针或者引用,或者混用
dynamic_cast<type_id>(expression)
dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)。
向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则) 向下转型:父类对象指针/引用>子类指针/引用(用dynamic_cast转型是安全的)。
注意: 1. dynamic_cast只能用于含有虚函数的类 2. dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0。
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class A
public:
virtual void f_cast()
int _a;
;
class B :public A
public:
int _b;
;
void f_cast(A* pa)
//如果想区分pa指向父类,还是子类对象?
//如果pa指向的是子类对象,则转换成功
//如果pa指向的是父类对象,则转换失败,返回nullptr
B* pb = dynamic_cast<B*>(pa);
//B* pb=(B*)pa; C语言中的强制类型转换不行
if (pb != nullptr)
cout << "转换成功:pa指向子类对象!" << endl;
pb->_a = 1;
pb->_b = 2;
else
cout << "转换失败:pa指向父类对象!" << endl;
int main()
A a;
A* pa = &a;
B b;
f_cast(pa);
pa = &b;
f_cast(pa);
return 0;
注意 强制类型转换关闭或挂起了正常的类型检查,每次使用强制类型转换前,程序员应该仔细考虑是否还有其他不同的方法达到同一目的,如果非强制类型转换不可,则应限制强制转换值的作用域,以减少发生错误的机会。强烈建议:避免使用强制类型转换。
- 用途:这个关键字的出现就是为了解决
static_cast
无法进行类型转换之间的类型检查的问题关键,且要注意的一点时,此关键字要求类的转换拥有多态的特性,说的更具体一些,需要类中需要虚函数的存在,因为唯有如此,该关键字才认为此次转换时拥有意义的。否则,毫无价值就。 - 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:
virtual void print()
std::cout<<"A"<<std::endl;
;
class B
public:
virtual void print()
std::cout<<"A"<<std::endl;
;
class C:public A,public B
public:
virtual void print() override
std::cout<<"C"<<std::endl;
;
void Func1()
C c;
//第一组
A* a_ptr=dynamic_cast<A*>(&c);
B* b_ptr=dynamic_cast<B*>(&c);
a_ptr->print();
b_ptr->print();
C* c_ptra=dynamic_cast<C*>(a_ptr); //成功,类C基类具有多态性,可以适用dynamic_cast进行转换
C* c_ptrb=dynamic_cast<C*>(b_ptr); //成功,类C具有多态性,可以适用dynamic_cast进行转换
c_ptra->print();
c_ptrb->print();
//以下内容输出一致
std::cout<<&c<<std::endl;
std::cout<<c_ptra<<std::endl;
std::cout<<c_ptrb<<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;
int main(int argc,char* argv[])
Func1();
return 0;
输出结果:
PS D:\\C++的类型转换\\bin\\Debug> .\\main.exe
C
C
C
C
000000C8D88FFC48
000000C8D88FFC48
000000C8D88FFC48
0000000000000000
0000000000000000
PS D:\\C++的类型转换\\bin\\Debug>
- 看一个成功的例子吧,通过该关键字实现了下行转换,但即使编译器的没有报错……这次的转换也没有得到有意义的结果,而第一组的测试……显然有些毫无意义……。
- 故此处再重申一下我的观点,非必要必要进行下行转换,如果非要进行就用
dynamic_cast
吧,至少会给你一显式的错误。 - 不过可能有小伙伴会质疑这个关键字的用途。在我现行的理解下,如果基类是个抽象的基类,那么
dynamic_cast
的下行转换就有了意义,并且能够为你提供可靠的类型检查,让你发现自身的错误。
以上是关于C++的类型转换的主要内容,如果未能解决你的问题,请参考以下文章