static_cast,dynamic_cast 与隐式强制转换

一、static cast 与dynamic cast区别

1. 基本类型之间(数值类型)

  • static_cast
    • 允许在各种算术类型(整型、浮点型、枚举型等)之间做显式转换。其规则与隐式转换(implicit conversion)大体相同:凡是隐式可转换的,static_cast 当然可以;反过来,隐式禁止的(如用 {} 列表初始化时的窄化转换),在普通上下文下 static_cast 也是允许的(但编译器可能给出警告)。
  • dynamic_cast
    • 只能用于多态类(有虚函数的类)的指针或引用之间,不适用于任何算术类型。

2. 基本类型指针或引用之间

2.1 指针之间(如 int*float*

  • static_cast< float* >( some_int_ptr )
    • 不允许。只有在类层次(base ⇄ derived)或与 void* 互转时,才允许用 static_cast;不同算术类型的指针之间,要用 reinterpret_cast
  • dynamic_cast
    • 不适用,永远不支持基本类型指针。

2.2 引用之间(如 int&double&

  • 虽然对“引用”的形式上可以写 static_cast( some_int ),但它的行为是先做一次临时的数值转换(生成一个新的 double 临时对象),然后把引用绑定到该临时对象上。
  • 不存在用 dynamic_cast 在基本类型引用之间做转换的一说。

3. 与 void* 之间

  • static_cast< void* >( some_T_ptr )static_cast< T* >( some_void_ptr )
    • 都是允许的——任何对象指针(包括基本类型指针)都可与 void* 互转。
  • dynamic_cast
    • 同样不适用——它只针对多态类指针/引用。

小结

  • static_cast

    • 允许:
      • 算术类型之间的显式转换(整型 ⇄ 浮点型 ⇄ 枚举)。
      • 任何对象指针 ⇄ void*
      • 基类指针/引用 ⇄ 派生类指针/引用(多态或非多态都可,但运行时不检查安全性)。
    • 不允许:
      • 两种不相关类型的指针之间(比如 int*float*,或 int*char*);这时要用 reinterpret_cast
  • dynamic_cast

    • 仅用于“运行时类型识别”场景:
      • 必须是指向多态类(至少一个虚函数)的指针或引用。
      • 只能在同一个继承体系(base/derived)内上下转换。
    • 绝不用于算术类型或 void*

另外,从本质上来说 static cast是编译期完成的 ,是一种通用转换,但在用于多态类时缺乏类型安全。dynamic则属于运行时转换,dynamic效率会更低 但是更安全,只能用于多态类指针引用,不适用于基本算术类型

二、隐式转换与显示转换

在 C++11 及以后的标准里,你可以给“转换函数”(conversion operator)加上 explicit,来禁止它在“隐式”场景下自动触发。标记为 explicit 之后,它只会在“显式转换”上下文中被考虑,而不会出现在普通的赋值、传参、返回值等隐式转换过程中。


1. 不可用的场景

这些常见场景 不会 调用 explicit conversion operator:

  • 拷贝初始化
    struct A { explicit operator int(); };
    A a;
    int x = a;        // ❌ 隐式初始化,不会调用 explicit operator int
    
  • 函数调用时的隐式参数转换
    void f(int);
    f(a);             // ❌ 不会调用
    
  • 返回值优化中隐式转换
    A makeA();
    int y = makeA();  // ❌ 不会调用
    
  • 列表初始化的拷贝形式
    int z = { a };    // ❌ copy‐list‐init,不调用 explicit
    

2. 可用的“显式转换”场景

  1. 直接初始化
    • 括号形式
      int x(a);      // ✅ direct‐init,调用 explicit operator int
      
    • 大括号形式(direct‐list‐init)
      int y{a};      // ✅ direct‐list‐init,也会调用
      
  2. static_cast
    int x = static_cast<int>(a);  // ✅
    
  3. 函数式(functional)转换
    int x = int(a);               // ✅ 等同于 direct‐init
    
  4. C 风格的强制转换
    int x = (int)a;               // ✅ 等价于 static_cast(a)
    
  5. 在需要布尔上下文中(仅针对 explicit operator bool()
    C++11 起,if(a)while(a)!a、逻辑运算符 &&/||?: 的条件表达式中, 考虑 explicit operator bool(),从而支持
    struct B { explicit operator bool() const; };
    B b;
    if (b) {}     // ✅ 可以用
    

小结

  • 一旦你给 operator T() 加上了 explicit它就只剩下“显式调用”这几条路,所有普通的隐式初始化、隐式参数匹配、拷贝初始化都 无法 找到它。
  • 在需要把对象“硬”转成目标类型 T 时,你可以用:
    • T obj(a);T{a};
    • static_cast(a)
    • T(a)(functional cast)
    • (T)a(C 风格 cast)
  • 对于布尔转换,还有条件语句/逻辑表达式这类“特殊显式”上下文也可以。

补充说明

有些面试官可能会问C中的括号强制转换等价于C++的哪种转换,一般的答案是static_cast。
但在 C++ 里,static_cast 和 C 风格的强制转换并 等价。C 风格的强制转换

T-target expr-to-convert;= (T-target)expr-to-convert;

实际上是对下面几种转换的“无限制组合”:

  1. const_cast(添加或移除 const/volatile
  2. static_cast(可执行:
    • 数值类型间互转
    • 指针/引用向上转型(基类←派生类)
    • void* ↔ 对象指针
    • 枚举值 ↔ 整数
    • 调用单参数/显式转换运算符
      )
  3. reinterpret_cast(在不同类型指针、指针与整数之间做位级重解释)
  4. 上面几种的任意组合(先 const_cast,再 reinterpret_cast,…)

也就是说,C 风格的 (T)x “能跑”就会把 x 转成 T,无论它是 const 修饰、基/派生、完全不相关的指针、甚至是函数指针与数据指针的转换——风险极大,容易引入未定义行为。

——而 static_cast(x) 只做 安全的、类型检查能通过的那一小部分 转换。它能:

  • 派生类指针 转成 兄弟类指针(除非两者在继承体系里可导航)
  • 去掉或添加 const/volatile
  • 在任意不相关指针类型之间互转(比如 int*double*
  • 在指针与整数之间做“任意”转
  • 处理函数指针和数据指针的互转

如果你写

double *pd = (double*)some_int_ptr;    // C 风格 —— 编译过得去,但运行肯定挂!
static_cast<double*>(some_int_ptr);    // ERROR:static_cast 无法这么做

或者

char *pc = const_cast<char*>(some_const_char_ptr);  // OK
char *pc = static_cast<char*>(some_const_char_ptr); // ERROR:static_cast 不移除 const

再次小结

  • C 风格 cast 在 C++ 里危险得可怕——它会在后台做 const_caststatic_castreinterpret_cast 的“无差别”组合,能把几乎任何东西互相转换。
  • static_cast 只允许一小撮、编译器能静态检查出安全的转换,不移除 const,不乱搞“位级重解释”。
  • 如果你想做更危险的转换(移除 const、不同指针间 bitwise 转换),请显式用 const_castreinterpret_cast,不要再去用 C 风格 cast——这样至少在代码审查时,一眼就能看出你是在玩“危险特技”。

你可能感兴趣的:(开发语言)