嵌入式现代C++教程:C++98——从C向C++的演化(3)

嵌入式现代C++教程:C++98------从C向C++的演化(3)

完整的仓库地址在Tutorial_AwesomeModernCPP中,您也可以光顾一下,喜欢的话给一个Star激励一下作者

10. 类型转换运算符

C++提供了四种专用的类型转换运算符,比C风格的强制转换更安全、更明确。

10.1 static_cast

static_cast用于编译时已知的类型转换:

cpp 复制代码
// 基本类型转换
int i = 10;
float f = static_cast<float>(i);  // int到float

// 指针类型转换(需要有继承关系或void*)
void* void_ptr = &i;
int* int_ptr = static_cast<int*>(void_ptr);

// 向上转换(派生类到基类,总是安全的)
class Base {};
class Derived : public Base {};

Derived d;
Base* base_ptr = static_cast<Base*>(&d);  // 向上转换

// 向下转换(基类到派生类,程序员需确保安全)
Base b;
// Derived* derived_ptr = static_cast<Derived*>(&b);  // 危险!可能不安全

在嵌入式开发中的典型用法:

cpp 复制代码
// 寄存器地址转换
uint32_t address = 0x40020000;
volatile uint32_t* reg = static_cast<volatile uint32_t*>(
    static_cast<void*>(address)
);

// 或使用reinterpret_cast(见下文)

10.2 dynamic_cast

dynamic_cast用于运行时类型检查,主要用于多态类型(包含虚函数的类):

cpp 复制代码
class Base {
public:
    virtual ~Base() {}  // 必须有虚函数
};

class Derived : public Base {
public:
    void derived_specific_method() {}
};

// 向下转换,带运行时检查
Base* base_ptr = new Derived();
Derived* derived_ptr = dynamic_cast<Derived*>(base_ptr);
if (derived_ptr != nullptr) {
    // 转换成功
    derived_ptr->derived_specific_method();
} else {
    // 转换失败,base_ptr不指向Derived对象
}

// 引用的dynamic_cast失败时抛出异常
Base& base_ref = *base_ptr;
try {
    Derived& derived_ref = dynamic_cast<Derived&>(base_ref);
    derived_ref.derived_specific_method();
} catch (std::bad_cast& e) {
    // 转换失败
}

注意 :在嵌入式系统中,dynamic_cast需要RTTI(运行时类型信息)支持,会增加代码大小和运行时开销。许多嵌入式编译器默认禁用RTTI以节省资源。

10.3 reinterpret_cast

reinterpret_cast执行低级别的重新解释转换,常用于指针类型之间的转换:

cpp 复制代码
// 整数到指针的转换(嵌入式中常见)
uint32_t address = 0x40020000;
volatile uint32_t* reg = reinterpret_cast<volatile uint32_t*>(address);

// 不同指针类型之间的转换
struct GPIO_TypeDef {
    volatile uint32_t MODER;
    volatile uint32_t OTYPER;
    // ...
};

uint32_t gpio_base = 0x40020000;
GPIO_TypeDef* gpio = reinterpret_cast<GPIO_TypeDef*>(gpio_base);

// 函数指针转换(例如中断向量表)
typedef void (*ISR_Handler)(void);

void timer_isr() {
    // 中断处理代码
}

uint32_t isr_address = reinterpret_cast<uint32_t>(timer_isr);

在嵌入式系统中,reinterpret_cast是访问硬件寄存器的标准方法:

cpp 复制代码
// 定义外设基地址
#define PERIPH_BASE     0x40000000UL
#define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000UL)
#define GPIOA_BASE      (AHB1PERIPH_BASE + 0x0000UL)

// 定义寄存器结构
typedef struct {
    volatile uint32_t MODER;    // 模式寄存器
    volatile uint32_t OTYPER;   // 输出类型寄存器
    volatile uint32_t OSPEEDR;  // 输出速度寄存器
    volatile uint32_t PUPDR;    // 上拉/下拉寄存器
    volatile uint32_t IDR;      // 输入数据寄存器
    volatile uint32_t ODR;      // 输出数据寄存器
    volatile uint32_t BSRR;     // 位设置/复位寄存器
} GPIO_TypeDef;

// 创建指向硬件的指针
#define GPIOA (reinterpret_cast<GPIO_TypeDef*>(GPIOA_BASE))

// 使用
GPIOA->MODER |= 0x01;  // 配置引脚模式

10.4 const_cast

const_cast用于添加或移除const属性:

cpp 复制代码
// 移除const属性(应谨慎使用)
const int const_value = 100;
int* modifiable = const_cast<int*>(&const_value);
*modifiable = 200;  // 危险!可能导致未定义行为

// 合法的用法:调用不接受const参数的遗留C API
void legacy_c_function(char* str);  // 老的C函数,不接受const

const char* message = "Hello";
// legacy_c_function(message);  // 错误:不能将const char*转换为char*

// 如果确定函数不会修改字符串,可以使用const_cast
legacy_c_function(const_cast<char*>(message));

在嵌入式开发中,const_cast偶尔用于与硬件或遗留代码接口:

cpp 复制代码
// 某些硬件驱动库可能没有正确使用const
void hal_uart_send(uint8_t* data, size_t length);  // 应该用const,但没有

class UARTWrapper {
public:
    void send(const uint8_t* data, size_t length) {
        // 我们知道hal_uart_send实际上不会修改数据
        // 但它的签名错误
        hal_uart_send(const_cast<uint8_t*>(data), length);
    }
};

警告 :移除真正const对象的const属性并修改它会导致未定义行为。const_cast应该只用于移除意外添加的const属性。

11. 异常处理 (Exception Handling)

异常处理提供了一种结构化的错误处理机制,可以将错误处理代码与正常逻辑分离。至少看起来,代码能干净一些,后面会讲为什么很多情况下,我们会禁止使用异常处理。

11.1 基本异常处理

基本的异常处理范式是try catch throw办法------尝试的执行代码,遇到错误抛出异常,然后捕获住这个异常。

cpp 复制代码
#include <exception>
#include <stdexcept>

// 抛出异常
void risky_function(int value) {
    if (value < 0) {
        throw std::invalid_argument("Value must be non-negative");
    }
    if (value > 100) {
        throw std::out_of_range("Value exceeds maximum");
    }
    // 正常处理
}

// 捕获异常
void caller() {
    try {
        risky_function(-5);
    } catch (const std::invalid_argument& e) {
        // 处理invalid_argument异常
        printf("Invalid argument: %s\n", e.what());
    } catch (const std::out_of_range& e) {
        // 处理out_of_range异常
        printf("Out of range: %s\n", e.what());
    } catch (const std::exception& e) {
        // 捕获所有标准异常
        printf("Exception: %s\n", e.what());
    } catch (...) {
        // 捕获所有其他异常
        printf("Unknown exception\n");
    }
}

11.2 自定义异常类

cpp 复制代码
// 自定义异常类
class HardwareException : public std::exception {
private:
    const char* message;
    int error_code;
    
public:
    HardwareException(const char* msg, int code) 
        : message(msg), error_code(code) {}
    
    const char* what() const throw() override {
        return message;
    }
    
    int get_error_code() const {
        return error_code;
    }
};

class SensorException : public HardwareException {
public:
    SensorException(const char* msg, int code) 
        : HardwareException(msg, code) {}
};

// 使用自定义异常
void read_sensor() {
    if (!sensor_initialized) {
        throw SensorException("Sensor not initialized", 0x01);
    }
    if (sensor_timeout) {
        throw SensorException("Sensor read timeout", 0x02);
    }
    // 读取传感器
}

// 捕获
try {
    read_sensor();
} catch (const SensorException& e) {
    printf("Sensor error: %s (code: 0x%02X)\n", 
           e.what(), e.get_error_code());
}

11.3 异常安全性

编写异常安全的代码需要考虑资源管理:

cpp 复制代码
// 不安全的代码
void unsafe_function() {
    int* data = new int[100];
    risky_operation();  // 如果抛出异常,data不会被释放
    delete[] data;
}

// 安全的代码(使用try-catch)
void safe_function_v1() {
    int* data = new int[100];
    try {
        risky_operation();
        delete[] data;
    } catch (...) {
        delete[] data;
        throw;  // 重新抛出异常
    }
}

// 更好的做法:使用RAII(Resource Acquisition Is Initialization)
class AutoArray {
private:
    int* data;
    
public:
    AutoArray(size_t size) : data(new int[size]) {}
    ~AutoArray() { delete[] data; }
    
    int& operator[](size_t index) { return data[index]; }
};

void safe_function_v2() {
    AutoArray data(100);
    risky_operation();  // 即使抛出异常,data也会自动释放
}

11.4 异常规格说明

C++98允许指定函数可能抛出的异常类型(C++11中已废弃):

cpp 复制代码
// 声明函数不会抛出异常
void no_throw_function() throw() {
    // 不应该抛出异常
}

// 声明函数可能抛出特定异常
void specific_throw(int value) throw(std::invalid_argument, std::out_of_range) {
    if (value < 0) throw std::invalid_argument("negative");
    if (value > 100) throw std::out_of_range("too large");
}

11.5 嵌入式系统中的异常处理

在嵌入式系统中使用异常需要谨慎考虑,第一点,在本来资源环境就很紧张的情况下,异常处理会增加显著的代码大小(异常表、展开代码等),致命的是------发生错误了,咱们处理异常的时间开销完全无法预测,在实时性看得极重的嵌入式实时系统里,这么玩就是玩火自焚。所以,许多嵌入式项目选择禁用异常(使用-fno-exceptions编译选项),而使用返回值或错误码进行错误处理。

现代C++中的optional和expected就是成熟得多的方案,笔者就在用这个。

cpp 复制代码
// 推荐的嵌入式错误处理方式
enum ErrorCode {
    ERROR_OK = 0,
    ERROR_INVALID_PARAM,
    ERROR_TIMEOUT,
    ERROR_HARDWARE_FAULT
};

ErrorCode initialize_hardware() {
    if (!check_hardware()) {
        return ERROR_HARDWARE_FAULT;
    }
    if (!configure_registers()) {
        return ERROR_TIMEOUT;
    }
    return ERROR_OK;
}

// 使用
ErrorCode result = initialize_hardware();
if (result != ERROR_OK) {
    // 处理错误
}

12. C++98的其他重要特性

12.1 explicit关键字

explicit关键字防止隐式类型转换:

cpp 复制代码
class Distance {
private:
    int meters;
    
public:
    // 没有explicit:允许隐式转换
    Distance(int m) : meters(m) {}
    
    int get_meters() const { return meters; }
};

void process_distance(Distance d) {
    // 处理距离
}

// 隐式转换
process_distance(100);  // OK:100被隐式转换为Distance(100)

// 使用explicit防止隐式转换
class SafeDistance {
private:
    int meters;
    
public:
    explicit SafeDistance(int m) : meters(m) {}
    
    int get_meters() const { return meters; }
};

void process_safe_distance(SafeDistance d) {
    // 处理距离
}

// process_safe_distance(100);  // 错误:不能隐式转换
process_safe_distance(SafeDistance(100));  // OK:显式构造

在嵌入式开发中,使用explicit可以避免意外的类型转换错误:

cpp 复制代码
class PWMChannel {
private:
    int channel;
    
public:
    explicit PWMChannel(int ch) : channel(ch) {
        if (ch < 0 || ch >= MAX_CHANNELS) {
            // 错误处理
        }
    }
    
    void set_duty_cycle(int duty) {
        // 设置占空比
    }
};

// 使用
// pwm.set_duty_cycle(PWMChannel(50));  // 错误!防止意外传递通道号而非占空比

12.2 mutable关键字

mutable允许在const成员函数中修改成员变量:

cpp 复制代码
class Cache {
private:
    mutable int access_count;  // 可以在const函数中修改
    int data;
    
public:
    Cache() : access_count(0), data(0) {}
    
    int get_data() const {
        access_count++;  // OK:access_count是mutable
        return data;
    }
    
    int get_access_count() const {
        return access_count;
    }
};

const Cache cache;
int value = cache.get_data();  // 增加access_count

在嵌入式系统中,mutable可用于实现缓存、统计信息等:

cpp 复制代码
class Sensor {
private:
    mutable bool cached_valid;
    mutable float cached_value;
    int pin;
    
public:
    explicit Sensor(int p) : cached_valid(false), pin(p) {}
    
    float read() const {
        if (!cached_valid) {
            cached_value = read_from_hardware();
            cached_valid = true;
        }
        return cached_value;
    }
    
private:
    float read_from_hardware() const {
        // 实际读取硬件
        return 25.0f;
    }
};

12.3 内联函数

内联函数在C++中得到了增强:

cpp 复制代码
// 在头文件中定义内联函数
inline int max(int a, int b) {
    return (a > b) ? a : b;
}

class Math {
public:
    // 类内定义的成员函数隐式为inline
    int add(int a, int b) {
        return a + b;
    }
    
    // 显式inline
    inline int multiply(int a, int b);
};

// 在类外定义时也需要inline
inline int Math::multiply(int a, int b) {
    return a * b;
}

在嵌入式开发中,内联函数可以提高性能而不牺牲类型安全:

cpp 复制代码
// 相比宏,内联函数更安全
inline void set_bit(volatile uint32_t& reg, int bit) {
    reg |= (1UL << bit);
}

inline void clear_bit(volatile uint32_t& reg, int bit) {
    reg &= ~(1UL << bit);
}

inline bool read_bit(volatile uint32_t& reg, int bit) {
    return (reg >> bit) & 1UL;
}

12.4 类型别名

除了C的typedef,C++还可以使用更灵活的语法:

cpp 复制代码
// 传统typedef
typedef unsigned int uint32;
typedef void (*ISR_Handler)(void);

// C++的typedef用于类型
typedef std::vector<int> IntVector;

// 为类模板创建别名,不过涉及到模板了,这里只是提一嘴
typedef std::map<std::string, int> StringIntMap;

12.5 作用域解析运算符

作用域解析运算符::用于访问全局作用域或命名空间:

cpp 复制代码
int value = 100;  // 全局变量

void function() {
    int value = 50;  // 局部变量
    
    printf("Local: %d\n", value);      // 50
    printf("Global: %d\n", ::value);   // 100:访问全局变量
}

// 访问命名空间成员
namespace math {
    const double PI = 3.14159;
}

double circumference = 2.0 * math::PI * radius;
相关推荐
moonquakeTT2 小时前
C++:深拷贝与浅拷贝
c++
TAEHENGV2 小时前
创建目标模块 Cordova 与 OpenHarmony 混合开发实战
android·java·开发语言
程序员zgh2 小时前
C语言 指针用法与区别(指针常量、常量指针、指针函数、函数指针、二级指针)
c语言·开发语言·jvm·c++
RanceGru2 小时前
LLM学习笔记8——多模态CLIP、ViLT、ALBEF、VLMo、BLIP
笔记·学习
中屹指纹浏览器2 小时前
动态IP场景下指纹浏览器的实时协同适配技术研究与实现
经验分享·笔记
是一个Bug2 小时前
如何阅读JDK源码?
java·开发语言
石头dhf2 小时前
大模型配置
开发语言·python
冉佳驹2 小时前
C++ ——— 仿函数的使用、继承方式、赋值转换规则、菱形继承与虚继承
c++·继承·virtual·仿函数·菱形继承·模板特化·虚继承
inferno2 小时前
JavaScript 基础
开发语言·前端·javascript