C++类型转换

本文深入探讨了C++中类型转换的概念,包括隐式转换和显式转换的区别、使用场景和注意事项。通过实例展示了不同转换方式的应用,以及转型的本质和同类型转换的重要性。最后提供了关于引用与转型交集的代码演示,帮助读者理解和实践C++类型转换。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C++类型转换

隐式转换

当编译认为转换是不存在数据丢失的情况隐匿类型转换就会发生,比如:

int i = 1;
double j = i;

这里发生了隐式类型转换就是在 j=i 这里,i的值被转换成双精度保存到变量 j。这里的转换是编译器自行转换的,并不用提示用户。

隐式类型转换还会发生在函数参数的传递与返回等情况下。又如,字面常量浮点数转换为整数,字符变量转换为整形,这会将字符的ASSII编码值当作整数:

int i = 3.14
int j = 'a'

又如,数组转换为指针的情况,数组a将被转换成第一个数的地址并赋值给指针,指针p将指向数组的第一个数:

int a[10];
int *p = a;
int (*pa)[10] = &a;

显式转换

当编译器不能自行处理时,也就是编译器认为转换会产生数据丢失的情况下,就需要指定显式类型转换,否则编译将提示程序错误。

比如上面的例子中,如果转换过程相反,即这样:

double i = 1.0;
int j = i;

这里,本来 i 是双精度的数据,尽管小数点后的值是 0,但是它用精度意义,当赋值给 j 时,这个精度信息就丢失了,这就是数据的丢失,编译不允许这种情况的发生,因些需要指定显式类型转换。C++中可以使用的类型转换方式有五种,虽然C++有多种显式转换方法,但是优质程序开发应该尽量不用或少用类型转换。其中一种是兼容C言语的语法方式,上面的例子就可以这样处理,两种方式者是可行的:

int j = (int)i;
int j = int(i);

这样,通过显式类型转换,编译就可以理解这是一种认可的数据过程,编译可以通过。

另外四种C++原生的类型转换有 const_caststatic_cast, dynamic_castreinterpret_cast,它是是有名字可叫的,因此叫做命名类型转换 Named cast,基本使用语法和C风格类型转换有差异:

x_cast<type-id>( expression );

const_cast转换正如其名字一样是针对const关键字一类的转换,通过 const_cast可以常量转变量,也可以将volatile转换为non-volatile等等。volatile这个关键字在嵌入式开发是最常用的,使用它可以用来避免因编译优化过程产生意想不到的问题。volatile这个词翻译为易变的,可能会误导新手,让人以为它定义的变量会比通常的变量更易变,事实上它是用来解决易变情况下的变量问题。

大多数情况下,C风格的转换方式和const_caststatic_cast, reinterpret_cast三种命名转换方式是功能一致的,而使用命名转换更能表达代码意图,例子:

int i; double d;
i += int (d); // static_cast: double => int
const char* p;
string_copy((char*)p); // const_cast: const => non-const
int *ip;
char *pc = (char*)ip; // reinterpret_cast: int* => char*

上面的例子中,reinterpret_cast 这种方式可以认为是“武断” low-level 的转换,因为它不会考虑转换的正确性,它就是做转换,它就是简单武断地认为例子中的ip指向的内存内容就是字符,它就是字符指针,所以要清楚被转换的内容是不是所期待的。

所有编译器进行的隐式转换都可以使用static_cast进行等价的转换,因此它主要用来消除编译器的隐式转换警告信息。除此外,static_cast还可以应用在其它场合,如 void 指针的转换,通常用来将 void 指针转换回原来的指针类型。综合来看,static_cast可以看作是最传统的一个命名转换方式,和它相关的转换基本上都是C语言中的转换形式。static_cast 用于类对象引用的向下转型,其背后隐含了拷贝构造函数的调用。

最后是dynamic_cast转换,如名字所示,它是动态的即运行时的转换,基于Run-time Type Identification (RTTI)。它增加了运行时的类型检测功能,所以它会要求被转换的类型和转换目标类型要为相同类型或相同类型的指针及引用。通常应用在类继承体系中,进行父类到子类的转换,或子类到父类的转换,这两种方式称为下行转换 down-casting 和上行转换 up-casting。这种称谓是因为在制作类关系图时,基类通常是在上子类在下的,因此就有上行下行之区别。当dynamic-cast转换失败时,它会引发 bad_cast 异常,例子:

class A { };
class B : public A { };
class C : public B { };
class D : public B, public A { };

A *pa = new C;
B *pb = dynamic_cast< B* >(pa); // OK down-casting 

B *pb = new B;
C *pc = dynamic_cast< C* >(pb); // bad_cast

A *pa = new D;
B *pb = dynamic_cast< B* >(pa); // OK down-casting

C++引入的运算符重载机制,使得定义类时可以定义它自身的类型转换功能,这可以使得类可以表现出预置类型一样的集成类型转换功能,这部分功能称为类内转换 Class-Type Covertion。

除此以外,那些不适当地使用类型转换的行为都是不理性的,比如使用 static_cast或reinterpret_cast 进行继承体系的类型转换,尽管static_cast可以做上行转换,但这并不是明智的。

转型的本质

关于转型的本质的问题,教材都采取了当作看不见的态度。然而要理解上面提到5种转型,只有透过本质才是理解。在《那年声明不理解定义与初始化》的第三部分内容中,解释了引用的本质就是指针。转型就是将目标转换成期待的类型,以新的类型行为来解析原有的数据。如整数向浮点数转换,将在原有的数据基础上增添浮点数的行为,并以浮点数的存储方式出现在内存中。在类继承机制中,向上转型将使得子类实例以基类的行为来解析,这是可行的;向下转型时,如果目标定义时根本就不是子类的实例,那么即使转换成功,其指向的内存空间也会因为不足以保存子类实列而出现异常。

下面以一组代码来演示引用与转型的交集,使用 GCC 4.7.1 编译器,类定义如下:

#include <string>

using namespace std;

class Food
{
    public:
        Food(){ weight = 1; nutrition = string("raw"); }
    protected:
        string nutrition;
    private:
        double weight;
};
class Pancake : public Food
{
    public:
        Pancake(){ nutrition = string("sugar & powder");}
};

测试代码如下,定义了两种引用形式:

Food f;
Pancake p;
Food &fr = f;
Pancake &pr = p;
Food * fp;
Pancake *pp;

fp = dynamic_cast<Food *>(pp);
pp = dynamic_cast<Pancake*>(fp);

fp = static_cast<Food *>(pp);
pp = static_cast<Pancake*>(fp);

fp = reinterpret_cast<Food *>(pp);
pp = reinterpret_cast<Pancake*>(fp);

fp = dynamic_cast<Food *>(pp);
pp = dynamic_cast<Pancake*>(fp);

fr = dynamic_cast<Food&>(pr);
pr = dynamic_cast<Pancake&>(fr);

fr = static_cast<Food&>(pr);
pr = static_cast<Pancake&>(fr);

fr = reinterpret_cast<Food&>(pr);
pr = reinterpret_cast<Pancake&>(fr);

f = dynamic_cast<Food>(p);    //error: dynamic_cast 'p' to type 'class Food'
p = dynamic_cast<Pancake>(f); //error: dynamic_cast 'f' to type 'class Pancake'

f = static_cast<Food>(p);
p = static_cast<Pancake>(f);  //error: no matching function 'Pancake::Pancake(Food&)'

f = reinterpret_cast<Food>(p);    //error: invalid cast from type 'Pancake' to type 'Food'
p = reinterpret_cast<Pancake>(f); //error: invalid cast from type 'Food' to type 'Pancake'

测试中发现,通过引用(用&号定义)指针的形式进行的转换,无论是上行转换还是下行转换都通过了。但是通过无&号定义引用的转换方式只有 static_cast 勉强通过,只是在向下转型时提示缺少拷贝构造函数。不通过&号定义的引用方式是一种高度集成的定义方式,灵活性更差。而&号定义的这种引用定义则松散些,编译器不会绑定一些额外的自动化功能。

转型中需要注意同类型之间进行转换,所谓同类型,即相同的类型定义,比如指针与指针之间的转换,引用与引用之间的转换。如果是引用与指针之间的转换,就不是所谓的同类型了。当需要在指针与引用之间转换时,就需要变通使用运算符来得到一致的类型:

fp = static_cast<Food *>(&p);
pp = static_cast<Pancake*>(&f);
fp = static_cast<Food *>(&pr);
pp = static_cast<Pancake*>(&fr);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值