乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • C++ 学习路径
  • 第1章 C++概述与开发环境

    • C++ 语言概述与编译模型
    • 第一个 C++ 程序与规范
    • 开发环境配置
    • 预处理指令详解
  • 第2章 基本语法与类型系统

    • 变量与基本类型
    • 类型转换
    • 枚举类型
    • 引用与指针
    • 数组与 stdarray
    • 字符串与原始字符串字面量
    • const 与 constexpr
    • nullptr 与空指针
    • 类型推导 auto 与 decltype
    • 基于范围的 for 循环
    • 值类别全面解析
    • char16_t 与 char32_t
    • static_assert 编译期断言
  • 第3章 函数与重载

    • 函数声明与定义
    • 函数重载
    • 默认参数与内联函数
    • Lambda 表达式
    • 函数对象与 stdfunction
    • 后置返回类型与 noexcept
  • 第4章 类与对象

    • 类的基本定义
    • 构造函数与析构函数
    • 拷贝控制
    • 移动构造函数与移动赋值
    • 列表初始化与类内初始化器
    • 静态成员与嵌套类
    • 友元
    • =default 与 =delete
  • 第5章 继承与多态

    • 继承基础
    • 虚函数与多态
    • 虚函数表与动态绑定原理
    • 虚析构函数
    • 抽象类与纯虚函数
    • 多重继承与虚继承
    • 继承构造函数
  • 第6章 运算符重载

    • 运算符重载基础
    • 算术与关系运算符重载
    • 赋值与移动运算符重载
    • 特殊运算符重载
  • 第7章 模板与泛型编程

    • 函数模板
    • 类模板
    • 模板特化与偏特化
    • 可变参数模板
    • 别名模板与模板模板参数
    • SFINAE 与类型萃取
    • 依赖名与 typename/template 关键字
  • 第8章 异常处理

    • 异常处理机制
    • noexcept 与异常安全
    • RAII 与异常安全实践
  • 第9章 内存管理与智能指针

    • 动态内存与内存分区
    • RAII 惯用法
    • unique_ptr
    • shared_ptr 与 weak_ptr
    • 内存管理最佳实践
  • 第10章 右值引用与移动语义

    • 右值引用与值类别深度解析
    • std::move 原理与使用
    • 完美转发与 std::forward
    • 移动语义性能对比与最佳实践
  • 第11章 STL容器

    • vector 深度剖析
    • deque 内部机制
    • list 与 forward_list
    • map 与 set 深度解析
    • unordered 容器与哈希原理
    • array 与 tuple
    • 容器适配器
    • 容器选择全景指南
  • 第12章 STL算法与迭代器

    • 迭代器体系全解
    • 非变异算法
    • 变异算法
    • 排序与二分算法
    • Lambda 与算法组合
    • std::random 随机数库
    • 自定义迭代器开发
  • 第13章 IO流与文件

    • 标准 IO 流
    • 格式化输出控制
    • 文件流操作
    • 字符串流
    • std::regex 正则表达式
  • 第14章 并发与多线程

    • thread 基础与线程管理
    • mutex 与 lock_guard
    • unique_lock 与 condition_variable
    • thread_local 线程局部存储
    • atomic 与内存序
    • future 与 async 异步编程
    • std::chrono 时间库
  • 第15章 现代C++新特性

    • 从 C++11 到 C++20 演进路线
    • C++14 关键新特性
    • C++17 关键新特性
    • C++20 核心特性速览
  • 第16章 面试考点与最佳实践

    • C++ 综合最佳实践清单
    • 高频面试题精讲
    • 多线程面试题与实战
    • 内存管理常见陷阱与排查
  • 附录

    • C++ 核心知识点
    • C++ 专业术语

类型转换

定义与作用

C++ 的类型转换分两层次:

  1. 隐式转换:编译器自动执行的类型变化(整型提升、派生类指针转基类指针等)
  2. 显式转换:程序员明确指定的类型变化

C++ 提供了四种命名的显式转换运算符来替代 C 风格的 (T)expr:

转换用途安全性
static_cast相关类型间的编译期转换较安全,编译期检查
dynamic_cast多态类型的运行期安全向下转换安全,运行期检查
const_cast移除或添加 const/volatile危险,仅用于特定场景
reinterpret_cast无关类型间的位模式重解释最危险,极少使用

核心原理

转换选择决策树

隐式转换层级

C 风格转换 vs C++ 转换

// C 风格:(T)expr —— 什么都做,不安全
double d = 3.14;
int x = (int)d;           // 可能是 static_cast
const int* p = &x;
int* q = (int*)p;         // 可能是 const_cast(危险!)

// C++ 风格:明确意图
int x2 = static_cast<int>(d);          // 明确:数值转换
int* q2 = const_cast<int*>(p);         // 明确:去除 const(我知道这是危险的)

完整示例

示例一:static_cast 的典型场景

场景说明:展示 static_cast 在数值转换、指针转换(非多态)和显式触发隐式转换中的运用。

#include <iostream>
#include <iomanip>

class Base {
public:
    void base_func() { std::cout << "Base::base_func()" << std::endl; }
};

class Derived : public Base {
public:
    void derived_func() { std::cout << "Derived::derived_func()" << std::endl; }
};

void process_data(double* data, size_t size) {
    std::cout << "处理 " << size << " 个数据" << std::endl;
}

int main() {
    std::cout << "===== 数值转换 =====" << std::endl;

    // 明确精度损失意图
    double pi = 3.1415926;
    int int_pi = static_cast<int>(pi);
    std::cout << "pi = " << pi << ", static_cast<int>(pi) = " << int_pi << std::endl;

    // 强制浮点除法(而非整型除法)
    int a = 5, b = 2;
    double ratio = static_cast<double>(a) / b;
    std::cout << "5/2 = " << ratio << " (不会截断)" << std::endl;

    // 枚举与整数互转
    enum class Color : int { Red = 1, Green = 2, Blue = 3 };
    Color c = Color::Blue;
    int color_val = static_cast<int>(c);
    std::cout << "Color::Blue = " << color_val << std::endl;

    std::cout << "\n===== 指针转换(非多态) =====" << std::endl;

    // 派生类指针 → 基类指针(向上转换,安全)
    Derived d;
    Base* bp = static_cast<Base*>(&d);
    bp->base_func();

    // void* ↔ T*(从 C API 取回类型)
    void* vp = static_cast<void*>(&d);
    Derived* dp_back = static_cast<Derived*>(vp);
    dp_back->derived_func();

    // 阻止危险的向下转换(非多态)
    Base b;
    // Derived* bad = static_cast<Derived*>(&b);  // 编译通过,但运行时是 UB
    // bad->derived_func();  // 访问不存在的成员 → 未定义行为

    std::cout << "\n===== 与 C 风格转换对比 =====" << std::endl;

    // C 风格:可能做多种转换,难以审查
    // double* → int* 的 C 风格转换在编译期无法发现问题
    // static_cast 则会拒绝:double* 和 int* 不是相关类型
    // int* ip = static_cast<int*>(&pi);  // 编译错误

    return 0;
}

预期输出:

===== 数值转换 =====
pi = 3.14159, static_cast<int>(pi) = 3
5/2 = 2.5 (不会截断)
Color::Blue = 3

===== 指针转换(非多态) =====
Base::base_func()
Derived::derived_func()

逐段分析:

  • static_cast<int>(pi) 明确表达了"我知道会截断小数"的意图
  • static_cast<double>(a) / b 将整数转为浮点以触发浮点除法,而非整数除法
  • 向上转换(Derived → Base)即使不写 cast 也安全,显式写出增加可读性
  • void* 与 T* 之间用 static_cast 是合法的
  • static_cast 不允许无关类型间的转换(如 double* 转 int*),C 风格转换则不会阻止

示例二:dynamic_cast 的安全向下转换

场景说明:形状类层次中的安全向下转换,演示 dynamic_cast 的运行期类型检查。

#include <iostream>
#include <vector>
#include <memory>
#include <cmath>

class Shape {
public:
    virtual ~Shape() = default;
    virtual double area() const = 0;
    virtual void describe() const {
        std::cout << "形状" << std::endl;
    }
};

class Circle : public Shape {
public:
    explicit Circle(double r) : radius_(r) {}

    double area() const override {
        return M_PI * radius_ * radius_;
    }

    void describe() const override {
        std::cout << "圆形 (半径=" << radius_
                  << ", 面积=" << area() << ")" << std::endl;
    }

    double radius() const { return radius_; }

private:
    double radius_;
};

class Rectangle : public Shape {
public:
    Rectangle(double w, double h) : width_(w), height_(h) {}

    double area() const override {
        return width_ * height_;
    }

    void describe() const override {
        std::cout << "矩形 (宽=" << width_
                  << ", 高=" << height_
                  << ", 面积=" << area() << ")" << std::endl;
    }

    bool is_square() const {
        return std::abs(width_ - height_) < 1e-9;
    }

private:
    double width_, height_;
};

// 仅对圆形进行特殊处理(如放大半径)
void enlarge_if_circle(Shape* shape, double factor) {
    // dynamic_cast 在运行期检查实际类型
    Circle* circle = dynamic_cast<Circle*>(shape);
    if (circle) {
        std::cout << "  放大圆形(原半径=" << circle->radius() << ")"
                  << std::endl;
    } else {
        std::cout << "  不是圆形,跳过" << std::endl;
    }
}

// 统计正方形数量
size_t count_squares(const std::vector<std::unique_ptr<Shape>>& shapes) {
    size_t count = 0;
    for (const auto& s : shapes) {
        // 先转换为 Rectangle*,再判断是否为正方形
        auto* rect = dynamic_cast<Rectangle*>(s.get());
        if (rect && rect->is_square()) {
            ++count;
        }
    }
    return count;
}

int main() {
    std::vector<std::unique_ptr<Shape>> shapes;

    shapes.push_back(std::make_unique<Circle>(3.0));
    shapes.push_back(std::make_unique<Rectangle>(4.0, 4.0));  // 正方形
    shapes.push_back(std::make_unique<Circle>(2.0));
    shapes.push_back(std::make_unique<Rectangle>(5.0, 3.0));

    std::cout << "===== 所有形状 =====" << std::endl;
    for (const auto& s : shapes) {
        s->describe();
    }

    std::cout << "\n===== 尝试放大圆形 =====" << std::endl;
    for (auto& s : shapes) {
        enlarge_if_circle(s.get(), 2.0);
    }

    std::cout << "\n正方形数量: " << count_squares(shapes) << std::endl;

    // dynamic_cast 对引用:失败抛出 std::bad_cast
    Shape& ref = *shapes[0];
    try {
        auto& circle_ref = dynamic_cast<Circle&>(ref);
        std::cout << "\n第一项是圆形,半径=" << circle_ref.radius() << std::endl;
    } catch (const std::bad_cast& e) {
        std::cout << "转换失败: " << e.what() << std::endl;
    }

    // 错误演示:对非多态类型使用 dynamic_cast
    // class NonPoly {}; NonPoly np;
    // dynamic_cast<int*>(&np);  // 编译错误:非多态类型

    return 0;
}

预期输出:

===== 所有形状 =====
圆形 (半径=3, 面积=28.2743)
矩形 (宽=4, 高=4, 面积=16)
圆形 (半径=2, 面积=12.5664)
矩形 (宽=5, 高=3, 面积=15)

===== 尝试放大圆形 =====
  放大圆形(原半径=3)
  不是圆形,跳过
  放大圆形(原半径=2)
  不是圆形,跳过

正方形数量: 1

第一项是圆形,半径=3

逐段分析:

  • dynamic_cast<Circle*>(shape) 在运行期通过虚函数表(vtable)查询对象的实际类型
  • 指针版本失败返回 nullptr,引用版本失败抛出 std::bad_cast
  • dynamic_cast 只能用于多态类型(至少有一个虚函数的类),这是因为它依赖 vtable 中的 RTTI 信息
  • count_squares 先用 dynamic_cast<Rectangle*> 过滤出矩形,再调用 is_square()——这是典型的"先转换后使用"模式

示例三:const_cast 与 reinterpret_cast 的正确用法

#include <iostream>
#include <cstring>

// const_cast 的正当用途:兼容遗留 C API
void legacy_c_function(char* str) {
    // 遗留代码不保证不修改,但实际上这里只读
    std::cout << "C 函数收到: " << str << std::endl;
}

// 安全的包装函数
void safe_wrapper(const char* str) {
    // 我们知道 legacy_c_function 实际上不修改 str
    legacy_c_function(const_cast<char*>(str));
}

int main() {
    // ---- const_cast ----
    const char* msg = "Hello, const world";
    safe_wrapper(msg);

    // 危险示例:试图修改真正的 const 对象
    const int ci = 42;
    // int* p = const_cast<int*>(&ci);
    // *p = 100;            // 未定义行为!ci 存储在只读区域

    // ---- reinterpret_cast ----
    // 正当用途:网络编程中解析字节流
    uint32_t ip_addr = 0xC0A80001;  // 192.168.0.1
    uint8_t* bytes = reinterpret_cast<uint8_t*>(&ip_addr);
    std::cout << "\nIP 地址字节: ";
    for (int i = 0; i < 4; ++i) {
        std::cout << static_cast<int>(bytes[i]);
        if (i < 3) std::cout << ".";
    }
    std::cout << std::endl;

    // 注意:reinterpret_cast 不改变位模式,只改变解释方式
    float f = 3.14f;
    uint32_t bits = reinterpret_cast<uint32_t&>(f);
    std::cout << "3.14f 的 IEEE 754 位模式: 0x"
              << std::hex << bits << std::dec << std::endl;

    return 0;
}

易错场景与面试考点

易错场景

场景错误表现正确做法
对非多态类型用 dynamic_cast编译错误改用 static_cast 或添加虚函数
static_cast 做不安全的向下转换未定义行为多态层次用 dynamic_cast
const_cast 修改真正的 const 对象未定义行为仅用于兼容不修改参数的遗留 API
reinterpret_cast 跨类型访问严格别名(strict aliasing)违规用 memcpy 或 std::bit_cast(C++20)
C 风格 (T)expr 在代码审查中被忽略多种转换混杂,难以识别风险始终使用 C++ 命名转换

常见面试问题

  1. C++ 四种命名的强制转换及其使用场景?——static_cast(相关类型编译期转换)、dynamic_cast(多态运行期安全向下转换)、const_cast(去除/添加 cv 限定符)、reinterpret_cast(位模式重解释,极少使用)。

  2. dynamic_cast 的实现原理?——依赖虚函数表中的 RTTI(Run-Time Type Information)信息,在运行期比较类型信息以确定转换是否安全。这也是为什么它只能用于多态类型。

  3. 为什么推荐 C++ 命名转换而非 C 风格转换?——C 风格转换含义模糊(可能是 static/const/reinterpret 的任意组合),编译器不检查意图;命名转换明确表达目的,方便代码审查和工具搜索。

  4. reinterpret_cast 的严格别名(strict aliasing)问题?——C++ 标准规定,通过不兼容类型的指针访问同一块内存是未定义行为。reinterpret_cast 不改变位模式,但后续通过该指针访问可能违反严格别名规则。安全替代方案是 memcpy 或 C++20 的 std::bit_cast。

  5. explicit 关键字在类型转换中的作用?——阻止构造函数或转换运算符被用于隐式类型转换,要求调用者显式写出转换意图。C++11 扩展了 explicit 到转换运算符。

小结

  • 抛弃 C 风格 (T)expr,始终使用 C++ 四种命名转换
  • static_cast 是最常用的转换:数值、非多态指针、显式触发隐式转换
  • dynamic_cast 用于多态层次的安全向下转换:指针版返回 nullptr,引用版抛异常
  • const_cast 应极少使用,仅用于兼容不修改参数的遗留 API
  • reinterpret_cast 几乎不应出现在应用层代码中
上一页
变量与基本类型
下一页
枚举类型