C++ 的数据类型转换

C 与 C++ 的强制类型转换?

  • 将类型名作为强制类型转换运算符的做法是 c 的老式做法,c++ 为保持兼容而予以保留
  • C++ 引入新的强制类型转换机制,克服 C 语言的缺点:(1)转换太过随意,可以在任意类型之间转换;(2)没有统一的关键字和标示符。对于大型系统,做代码排查时容易遗漏和忽略

C++ 上的隐式类型转换

  • 定义―自动执行的,无需显式的操作符,比如函数实参到形参的类型转换、函数返回值类型的自动转换等等
  • 种类
    • 数值类型转换
      • 负数转化为无符号类型,通常会采用二进制补码表示
      • 数值类型转 bool 类型,false 等价于 0 (数值类型) 或者空指针 (指针类型); true 则等价于其它任何数值或者由 true 转化为 1
      • 浮点数转化为整数会采取截断操作,即移除小数部分。如果转换时发生了数值溢出,可能出现未定义的行为
    • 指针类型转换
      • 空指针可以转换到任意指针类型
      • 任意指针类型都可以转换到 void* 指针
      • 继承类指针可以转换到可访问的明确的基类指针, 同时不改变 const 或者 volatile 属性
      • 一个 C 风格的数组隐式把数组的第一个元素转换为一个指针。 虽然此方法很方便,但它也有潜在的错误

C++ 上的显式类型转换

  • C 风格的强制类型转换:
    1
    2
    3
    // 不推荐使用
    (int) x;
    int(x);
  • 声明为 explicit 的构造函数不能在隐式转换中使用
  • C++ 上的 4 种强制显式类型转换

C++ 上强制类型转换中的 explicit 关键字?

  • 可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生。即声明为 explicit 的构造函数不能在隐式转换中使用
  • 使用例子
    • 不使用 explicit
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      // 类的通过构造函数的隐式转换:
      #include
      using namespace std;

      class A {};

      class B {
      public:
      // conversion from A (constructor):
      B (const A& x) {}
      // conversion from A (assignment):
      B& operator= (const A& x) {return *this;}
      // conversion to A (type-cast operator)
      operator A() {return A();}
      };

      int main ()
      {
      A foo;
      B bar = foo; // 调用构造函数实现隐式类型转换
      bar = foo; // calls assignment
      foo = bar; // calls type-cast operator,相当于 foo = A(bar);
      return 0;
      }
    • 使用 explicit
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      #include 
      using namespace std;

      class A {};

      class B {
      public:
      explicit B (const A& x) {}
      B& operator= (const A& x) {return *this;}
      operator A() {return A();}
      };

      void fn (B x) {} // 当我们希望x只能是B类型时,我们就需要禁止隐式类型转换

      int main ()
      {
      A foo;
      B bar (foo); // 必须显式类型转换,不再允许B bar = foo;
      bar = foo;
      foo = bar;

      // fn (foo); // 不允许隐式类型转换
      fn (bar);

      return 0;
      }

C++ 上的 4 种强制显式类型转换?

  • static_cast (静态类型转换)
    • 进行比较 “自然” 和低风险的转换
    • 使用场景
      • 用于类层次结构中基类和派生类之间指针或引用的转换
      • 用于基本数据类型之间的转换,如把 int 转换为 char,安全性问题由开发者来保证
      • 把空指针转换成目标类型的空指针
      • 把任何类型的表达式转为 void 类型
    • 特点
      • 主要执行非多态的转换操作,用于代替 C 中通常的转换操作
      • 隐式转换都建议使用 static_cast 进行标明和替换
    • 使用例子
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      #include 
      using namespace std;

      class A
      {
      public:
      operator int() { return 1; }
      operator char*() { return NULL; }
      };

      int main()
      {
      A a;
      int n;
      double dpi = 3.1415926;
      char* p = "New Dragon Inn";

      n = (int)dpi; // C语言写法
      n = static_cast (dpi); // n 的值变为 3
      n = static_cast (a); // 调用 a.operator int,n 的值变为 1
      p = static_cast (a); // 调用 a.operator char*,p 的值变为 NULL
      n = static_cast (p); // 编译错误,static_cast不能将指针转换成整型
      p = static_cast (n); // 编译错误,static_cast 不能将整型转换成指针

      return 0;
      }
  • dynamic_cast (动态类型转换)
    • 使用场景
      • 只有在派生类之间转换时才使用 dynamic_cast
    • 使用特点
      • 基类必须要有虚函数,因为 dynamic_cast 是运行时类型检查,需要运行时类型信息,而这个信息是存储在类的虚函数表中,只有一个类定义了虚函数,才会有虚函数表(如果一个类没有虚函数,那么一般意义上,这个类的设计者也不想它成为一个基类)
      • 对于下行转换,dynamic_cast 是安全的(当类型不一致时,转换过来的是空指针),而 static_cast 是不安全的(当类型不一致时,转换过来的是错误意义的指针,可能造成踩内存,非法访问等各种问题)
      • dynamic_cast 还可以进行交叉转换
    • 使用例子
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      #include 
      #include
      using namespace std;

      class Base
      {
      public:
      virtual ~Base() {} //有虚函数,因此是多态基类
      };

      class Derived : public Base { };

      int main()
      {
      Base b;
      Derived d;
      Derived* pd;

      pd = reinterpret_cast (&b); // 此处pd不会为 NULL。reinterpret_cast不检查安全性,总是进行转换
      if (pd == NULL)
      cout << "unsafe reinterpret_cast" << endl; // 不会执行

      pd = dynamic_cast (&b); // 结果会是NULL,因为 &b 不指向派生类对象,此转换不安全
      if (pd == NULL)
      cout << "unsafe dynamic_cast1" << endl; // 会执行

      pd = dynamic_cast (&d); // 安全的转换,此处 pd 不会为 NULL
      if (pd == NULL)
      cout << "unsafe dynamic_cast2" << endl; // 不会执行

      return 0;
      }
  • const_cast (去只读属性转换)
    • 使用场景
      • 常量指针转换为非常量指针,并且仍然指向原来的对象
      • 常量引用被转换为非常量引用,并且仍然指向原来的对象
    • 使用特点
      • 四种类型转换符中唯一可以对常量进行操作的转换符
      • 去除常量性是一个危险的动作,尽量避免使用
    • 使用例子
      1
      2
      3
      const string s = "Inception";
      string& p = const_cast (s);
      string* ps = const_cast (&s); // &s 的类型是 const string*
  • reinterpret_cast (重解析类型转换)
    • 使用场景
      • 不到万不得已,不用使用这个转换符,高危操作
    • 使用特点
      • 从底层对数据进行重新解释,依赖具体的平台,可移植性差
      • 可以将整型转换为指针,也可以把指针转换为数组
      • 可以在指针和引用里进行肆无忌惮的转换
    • 使用例子
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      #include 
      using namespace std;

      class A
      {
      public:
      int i;
      int j;
      A(int n):i(n),j(n) { }
      };

      int main()
      {
      A a(100);
      int &r = reinterpret_cast(a); // 强行让 r 引用 a

      r = 200; // 把 a.i 变成了 200
      cout << a.i << "," << a.j << endl; // 输出 200,100

      int n = 300;
      A *pa = reinterpret_cast (&n); // 强行让 pa 指向 n
      pa->i = 400; // n 变成 400
      pa->j = 500; // 此条语句不安全,很可能导致程序崩溃
      cout << n << endl; // 输出 400

      long long la = 0x12345678abcdLL;
      pa = reinterpret_cast(la); // la太长,只取低32位0x5678abcd拷贝给pa
      unsigned int u = reinterpret_cast(pa); // pa逐个比特拷贝到u
      cout << hex << u << endl; // 输出 5678abcd

      typedef void (* PF1) (int);
      typedef int (* PF2) (int,char *);
      PF1 pf1;
      PF2 pf2;
      pf2 = reinterpret_cast(pf1); // 两个不同类型的函数指针之间可以互相转换
      }