C++<第三十三篇>:四种类型转换

在 C++ 中,不同数据类型之间可以相互转换。无需用户指明如何转换的称为自动类型转换(隐式类型转换),需要用户显式的指明如何转换的称为强制类型转换。
隐式类型转换是安全的,显式类型转换是有风险的,C语言之所以增加强制类型转换的语法,就是为了强调风险,让程序员意识到自己在做什么。
但是,这种强调风险的方式还是比较粗放,粒度比较大,它并没有表明存在什么风险,风险程度如何。再者,C风格的强制类型转换统一使用 ( )
为了使潜在风险更加细化,使问题追溯更加方便,使书写格式更加规范,C++ 对类型转换进行了分类,并新增了四个关键字来予以支持,它们分别是:static_cast、const_cast、reinterpret_cast、dynamic_cast。

(1)自动类型转换 和 自动类型转换

自动类型转换示例:

int a = 6;
a = 7.5 + a;

编译器对 7.5 是作为 double 类型处理的,在求解表达式时,先将 a 转换为 double 类型,然后与 7.5 相加,得到和为 13.5。在向整型变量 a 赋值时,将 13.5 转换为整数 13,然后赋给 a。整个过程中,我们并没有告诉编译器如何去做,编译器使用内置的规则完成数据类型的转换。

强制类型转换示例:

int n = 100;
int *p1 = &n;
float *p2 = (float*)p1;

p1 是int *类型,它指向的内存里面保存的是整数,p2 是float *类型,将 p1 赋值给 p2 后,p2 也指向了这块内存,并把这块内存中的数据作为小数处理。我们知道,整数和小数的存储格式大相径庭,将整数作为小数处理非常荒诞,可能会引发莫名其妙的错误,所以编译器默认不允许将 p1 赋值给 p2。但是,使用强制类型转换后,编译器就认为我们知道这种风险的存在,并进行了适当的权衡,所以最终还是允许了这种行为。

不管是自动类型转换还是强制类型转换,前提必须是编译器知道如何转换,例如,将小数转换为整数会抹掉小数点后面的数字,将int *转换为float *只是简单地复制指针的值,这些规则都是编译器内置的,我们并没有告诉编译器。

(2)4种类型转换简单说明

为了使潜在风险更加细化,使问题追溯更加方便,使书写格式更加规范,C++ 对类型转换进行了分类,并新增了四个关键字来予以支持,它们分别是:

关键字 说明
static_cast 用于良性转换,一般不会导致意外发生,风险很低。
const_cast 用于 const 与非 const、volatile 与非 volatile 之间的转换。
reinterpret_cast 高度危险的转换,这种转换仅仅是对二进制位的重新解释,不会借助已有的转换规则对数据进行调整,但是可以实现最灵活的 C++ 类型转换。
dynamic_cast 借助 RTTI,用于类型安全的向下转型(Downcasting)。

这四个关键字的语法格式都是一样的,具体为:

xxx_cast<newType>(data)

newType 是要转换成的新类型,data 是被转换的数据。
例如,老式的C风格的 double 转 int 的写法为:

double scores = 95.5;
int n = (int)scores;

C++ 新风格的写法为:

double scores = 95.5;
int n = static_cast<int>(scores);
(3)static_cast

static_cast 是 静态转换 的意思,也就是在编译期间转换,转换失败的话会抛出一个编译错误。

static_cast 只能用于良性转换,这样的转换风险较低,一般不会发生什么意外,例如:

(1)基本数据类型之间的转换,例如 short 转 int、int 转 double;

    int m = 10;
    long n1 = m; // int 转 long(范围小的转成范围大的,无风险)
    long n2 = static_cast<long>(m); // 基本数据类型的转换可以使用static_cast转换

    char c1 = m; // int 转 char(范围大的转成范围小的,精度丢失,肯能存在风险)
    char c2 = static_cast<char>(m); // 基本数据类型的转换可以使用static_cast转换

    cout << n1 << endl;
    cout << n2 << endl;
    cout << c1 << endl;
    cout << c2 << endl;


(2)基类和子类之间的转换

定义两个类:Personal 和 Student类,它们之间是继承关系,Personal  是基类,Student 是派生类,代码如下:

class Personal
{
public:
    void setIndex(int index)
    {
        mIndex = index;
    }
    int getIndex()
    {
        return mIndex;
    }
private:
    int mIndex;
};

class Student :public Personal
{
public:
    void setAge(int age)
    {
        mAge = age;
    }
    int getAge()
    {
        return mAge;
    }
private:
    int mAge;
};

基类转派生类是有风险的,而派生类转基类是无风险的;
派生类转基类可以直接向上转型,代码如下:

Personal* personal = new Student(); // 向上转型,无风险

上转型也可以使用 static_cast 来实现:

Student* student = new Student();
Personal*  personal = static_cast<Personal*>(student);

而基类转派生类属于向下转型,向下转型存在风险,不能使用 static_cast 来转换。

(3)void 指针和具体类型指针相互转换

C风格的转换是这样的:

int* a = new int;
*a = 10;
void* p = a; // int指针转void指针
int* b = (int*)p; // void指针转int指针

如果使用 static_cast 来转换的话,代码如下:

int* a = new int;
*a = 10;
void* p = static_cast<void*>(a); // int指针转void指针
int* b = static_cast<int*>(p); // void指针转int指针

(4)有转换构造函数的类与其它类型之间的转换

class Book 
{
public:
    Book() {}
    Book(int a):mA(a) {}
    int getA() 
    {
        return mA;
    }
private:
    int mA;
};

class Student
{
public:
    Student(const Book& book)
    {
        mBook = book;
    }

    Book getBook()
    {
        return mBook;
    }
private:
    Book mBook;
};

int main() {

    Book book(10);
    Student student = book;
    cout << student.getBook().getA() << endl;

    return 0;
}

其中,类型转换代码:

    Student student = book;

可以写成:

Student student = static_cast<Student>(book);



(5)有类型转换函数的类与其它类型之间的转换


class Student
{
public:
    Student():mA(10) {}

    operator double() // 强制类型转换运算符的重载
    {
        return mA;
    }

    double getA()
    {
        return mA;
    }
private:
    double mA;
};

int main() {

    Student student;
    double n = student;
    cout << n << endl;

    return 0;
}

其中,类型转换代码:

double n = student;

可以写成:

double n = static_cast<Student>(student);



需要注意的是,static_cast 不能用于无关类型之间的转换,因为这些转换都是有风险的,例如:

(1)两个具体类型指针之间的转换;
(2)int 和指针之间的转换;
(3)static_cast 也不能用来去掉表达式的 const 修饰和 volatile 修饰。换句话说,不能将 const/volatile 类型转换为非 const/volatile 类型。
(4)const_cast

const_cast 比较好理解,它用来去掉表达式的 const 修饰或 volatile 修饰。换句话说,const_cast 就是用来将 const/volatile 类型转换为非 const/volatile 类型。

const 和 volatile 都是用于修饰变量的,所以下面就以 const 为例:

C风格的类型转换是:

const int constA = 10;
int* p = (int*) & constA;

使用 const_cast 关键字转换代码是:

const int constA = 10;
int* p = const_cast<int*>(&constA);
(5)dynamic_cast

dynamic_cast 用于在类的继承层次之间进行类型转换,它既允许向上转型(Upcasting),也允许向下转型(Downcasting)。
向上转型是无条件的,不会进行任何检测,所以都能成功;
向下转型的前提必须是安全的,要借助 RTTI 进行检测,所有只有一部分能成功。

dynamic_cast 与 static_cast 是相对的,dynamic_cast 是 动态转换 的意思,static_cast 是 静态转换 的意思。
dynamic_cast 会在程序运行期间借助 RTTI 进行类型转换,这就要求基类必须包含虚函数
static_cast 在编译期间完成类型转换,能够更加及时地发现错误。

dynamic_cast 的语法格式为:

dynamic_cast <newType> (expression)

newType 和 expression 必须同时是指针类型或者引用类型。
换句话说,dynamic_cast 只能转换指针类型和引用类型,其它类型(int、double、数组、类、结构体等)都不行。

对于指针,如果转换失败将返回 NULL;
对于引用,如果转换失败将抛出 std::bad_cast 异常。

下面开始代码演示,首先准备好基类和派生类:

class Personal
{
public:
    virtual void setIndex(int index)
    {
        mIndex = index;
    }
    virtual int getIndex()
    {
        return mIndex;
    }
private:
    int mIndex;
};

class Student :public Personal
{
public:
    void setAge(int age)
    {
        mAge = age;
    }
    int getAge()
    {
        return mAge;
    }
private:
    int mAge;
};

Personal 是基类,Student 是派生类。

【向上转型】 向上转型没有风险型,可以使用 static_cast 转换,也可以使用 dynamic_cast 转换。

static_cast 向上转型的类型转换已经演示过了,下面直接演示使用 dynamic_cast 实现向上转型。

Student* student = new Student();
Personal* personal = dynamic_cast<Personal*>(student);

【向下转型】 向下转型是一个比较危险的动作,只能使用 dynamic_cast 实现。

Personal* personal = new Personal();
Student* personal = dynamic_cast<Student*>(personal);
(6)reinterpret_cast

reinterpret 是 重新解释 的意思,顾名思义,reinterpret_cast 这种转换仅仅是对二进制位的重新解释,不会借助已有的转换规则对数据进行调整,非常简单粗暴,所以风险很高。

reinterpret_cast 可以认为是 static_cast 的一种补充,一些 static_cast 不能完成的转换,就可以用 reinterpret_cast 来完成,例如两个具体类型指针之间的转换、int 和指针之间的转换(有些编译器只允许 int 转指针,不允许反过来)。

【举例一:将 char* 转换为 float*】

char str[] = "zhangsan";
float* p = reinterpret_cast<float*>(str); // 将 char* 转换为 float*
cout << *p << endl;

【举例二:将 int 转换为 int*】

// 将 int 转换为 int*
int a = 100;
int* p = reinterpret_cast<int*>(a);

【举例三:类指针转int指针】

class A {
public:
    A(int a = 0, int b = 0) : m_a(a), m_b(b) {}
private:
    int m_a;
    int m_b;
};
int main() {

    A* a = new A(10, 20);
    int* p = reinterpret_cast<int*>(a);
    cout << *p << endl;

    return 0;
}

[本章完...]

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容