2018-03-30

2018-03-30  本文已影响0人  lwj_ow

让我们继续咯, 今天要总结的是C++中的四种强制类型转换符, 这四种转换符算不上难点, 不过也还是需要我们注意总结一下, 毕竟知识都是在比较和总结之后才能得到更深的理解.

  1. static_cast
    C++ primer上这样写static_cast的使用方式:任何有明确定义的类型转换, 只要不包含底层const, 都可以使用static_cast. 比如通过将一个运算对象通过强制类型转换成double类型就能使表达式执行浮点数除法.下面是个例子:
int i = 5, j = 2;
double c = i / j;//这个时候c实际上是1, 并不是我们预期的2.5
c = static_cast<double>(i) / j;//c = 2.5

当需要把一个较大的算数类型赋值给较小的类型时, static_cast非常有用, 这时, 强制类型转换告诉程序的读者和编译器, 我们不在乎潜在的精度损失.
static_cast对于编译器无法自动执行的类型转换也很有用.例如, 我们可以用static_cast找出存在与void*指针中的值.
> double d = 9.9;
> void *p = &d;//正确, 任何非常量对象的地址都能存入void *
//正确, 将void* 转换为初始的指针类型
> double *dp = static_cast<double*> (p);

当我们把指针存放在void *中, 并且使用static_cast将其强制类型转换为原来的类型时, 应该确保指针的值保持不变. 也就是说, 强制类型转换的结果和原始的地址值相等. 因此我们必须转换后所得的类型就是指针所指的类型(在上面的例子中指向一个double型的数据). 类型如果不符合的话, 那么将会产生未定义的行为.

如果涉及到类的话, static_cast只能在有相互继承习惯的类型中相互转换, 类中不一定要包含虚函数, 为什么要强调这一点呢, 因为后面说到的dynamic_cast必须要求转换的类中有虚函数.

  1. const_cast
    const_cast只能改变运算对象的底层const,

const char* pc;
char *p = const_cast<char*>(pc);//正确, 但是要注意通过p写值是未定义的行为

对于将常量对象转换为非常量对象的行为, 我们一般称其为"去掉const性质". 一旦我们去掉了某个对象的const性质, 编译器就不再组织我们对该对象进行写操作了, 如果对象本身不是一个常量, 那么使用强制类型转换获得写权限是合法的行为, 然而如果对象是一个常量, 再使用const_cast执行写操作就会产生未定义的后果.
针对这段话, 我写了一个例子, 在Ubuntu16.04, g++ 5.4.0环境下测试:

#include <iostream></iostream>
using namespace std;
int main()
{
    const int a = 10;
    int b = const_cast<int&>(a);
    cout<<&a<<endl;
    cout<<&b<<endl;

    const int i = 10;
    const int& j = i;
    int& k = const_cast<int&>(j);
    cout<<&i<<endl;
    cout<<&j<<endl;
    cout<<&k<<endl;
    k = 9;
    cout<<i<<endl;
    cout<<j<<endl;
    cout<<k<<endl;

    int x = 10;
    const int* cp = &x;
    int* p = const_cast<int*>(cp);
    cout<<&x<<endl;
    cout<<cp<<endl;
    cout<<p<<endl;

    const int x1 = 10;
    const int* cp1 = &x1;
    int* p1 = const_cast<int*>(cp1);
    cout<<&x1<<endl;
    cout<<cp1<<endl;
    cout<<p1<<endl;
    *p1 = 9;
    cout<<x1<<endl;
    cout<<&x1<<endl;
    cout<<*cp1<<endl;
    cout<<*p1<<endl;
    
    return 0;
}

结果图如下:


image.png

这个图看起来并不好理解, 有兴趣的同学可以在自己电脑上测试下, 我测试了大部分情况下的结果, 我直接来上个人总结的一点小结论, 不一定正确, 欢迎大家指出错误:

  1. reinterpret_cast
    reinterpret_cast通常是为运算对象的位模式提供较低层次上的重新解释, 假设有如下的转换:

int *p;
char *pc = reinterpret_cast<char*>(p);

我们必须时刻牢记pc指向的真实对象是一个int而不是一个字符, 如果把pc当成普通的字符指针就可能会导致运行时错误.
> string str(pc);

这话基本上肯定会导致我们的运行错误.
从上面这个简单的例子我们可以看出使用reinterpret_cast是十分危险的, 因为我们相当于在欺骗编译器, 所以这种问题往往带来的后果是很可怕的, 更糟糕的是, 这种问题很难被查找, 如果强制类型转换的语句和使用强制类型转换结果的语句不在一个文件的时候就更是了.

  1. dynamic_cast
    dynamic_cast运算符的使用形式如下所示:

dynamic_cast<type*>(e)
dynamic_cast<type&>(e)
dynamic_cast<type&&>(e)

其中type必须是一个类类型, 并且通常情况下应该含有虚函数. 在第一种形式中, e必须是个有效的指针, 在第二种形式中, e必须是一个左值, 在第三种形式中, e不能是左值.

在上面的所有形式中, e的类型必须符合以下三个条件中的任意一个: e的类型是目标type的公有派生类, e的类型是目标type的公有基类或者e的类型就是目标type的类型. 如果符合, 则转型可以成功, 否则转换失败. 如果一套dynamic_cast语句的转换目标是指针类型并且失败了, 则结果为0, 就是空指针. 如果转换目标为引用类型并且失败了,则dynamic_cast运算符将抛出一个bad_cast异常.
下面是个例子, 比较了static_cast和dynamic_cast:

#include <iostream>
#include <string>
using namespace std;
class Base
{
    int a;
public:
    virtual void do_something()
    {
        cout << "Base" << endl;
    }
};
class Derived : public Base
{
    int b;
public:
    void do_something()
    {
        cout << "Derived" << endl;
    }
};
int main()
{
    Base b;
    Derived d;
    Base *bp = &d;
    Derived* dp = dynamic_cast<Derived*>(bp);
    if(dp == nullptr)
        cout << "dp is null" << endl;
    else
        dp->do_something();

    bp = &b;
    dp = dynamic_cast<Derived*>(bp);
    if(dp == nullptr)
        cout << "dp is null" << endl;
    else
        dp->do_something();

    bp = &d;
    dp = static_cast<Derived*>(bp);
    if(dp == nullptr)
        cout << "dp is null" << endl;
    else
        dp->do_something();
    bp = &b;
    dp = static_cast<Derived*>(bp);
    if(dp == nullptr)
        cout << "dp is null" << endl;
    else
        dp->do_something();
}

我们可以看出来对类类型进行转换的时候, dynamic_cast会更安全一些, 不过我们需要检查一下dynamic_cast的返回值, 如果是NULL的话说明就转型失败了.而static_cast就不会了, 它执行的类似于强制类型转换, 是不安全的.从上面的例子就可以看出来.
下面总结一下dynamic_cast的几个特点:
* 其他三种强制类型转换都是编译时完成的, dynamic_cast是运行时处理的, 运行要进行进程类型检查.
* dynamic_cast转换成功的话返回指向类的指针或者引用, 转换失败的话会返回NULL或者抛出异常.
* 使用dynamic_cast进行转换的话, 基类中一定要有虚函数, 因为在上面的第一条总结中我们写了dynamic_cast需要运行时类型检查, 而进行运行时类型检查需要运行时类型信息, 而这个信息储存在类的虚函数表中, 而只有定义了虚函数的类才有虚函数表.
* 在类的转换时, 在类层次之间进行向上转换时, dynamic_cast和static_cast的效果是一样的, 但是在向下类型转换时, dynamic_cast会进行类型检查, 更安全一些.

总结: 实际上在c++ primer中, 作者提到如果我们的代码中使用了过多的强制类型转换的话, 那么大概率我们的代码是写的有问题的, 除了几种常见情况下使用强制类型转换, 其他情况下我们应该尽量避免使用强制类型转换, 另外在C++中很不建议使用C语言风格的强制类型转换, 我们如果必须要使用强制类型转换, 也请选择上面4种.

上一篇 下一篇

猜你喜欢

热点阅读