跳转至

嵌入式C++教程——字面量运算符与自定义单位

我相信,不少人会在在代码里写 delay(5000),但是实际上,实际单位是微秒(当然这个锅,写函数的人也有),于是系统冻结了五秒而不是五毫秒。或者你写 Timer timeout = 100 * 1000; 想表示100毫秒,但六个月后有人读到这段代码,完全猜不到那个 1000 是什么。字面量运算符(Literal Operator)就是 C++ 的"单位后缀"机制,让你写出 5000_ms2.5_kHz 这样自文档化的代码,编译器还能帮你检查单位是否匹配。


一句概念总结

字面量运算符(C++11)允许你为自定义类型定义后缀,让形如 123_suffix3.14_user 的字面量直接构造你的类型:

  • 通过 operator"" 后缀() 定义;
  • 支持整数、浮点、字符串、字符等多种字面量类型;
  • 编译期计算,零运行时开销;
  • 能实现类型安全的单位系统,防止把毫秒当成微秒用。

为什么嵌入式中需要自定义单位

  1. 避免单位混淆:把毫秒、微秒、秒做成不同类型,编译器会阻止你混用;
  2. 代码自文档化timeout_ms = 5000_mstimeout = 5000 清楚一万倍;
  3. 编译期计算:单位换算在编译期完成,没有运行时开销;
  4. 类型安全:不会不小心把"频率"传给"延时"函数;
  5. 可读性爆炸freq = 72_MHz; baud = 115200_baud; 一眼就懂。

最简单的例子:定义时间单位后缀

#include <cstdint>

// 毫秒类型
struct Milliseconds {
    std::uint64_t value;
    constexpr explicit Milliseconds(std::uint64_t v) : value(v) {}
};

// 字面量运算符:123_ms -> Milliseconds
constexpr Milliseconds operator""_ms(unsigned long long v) {
    return Milliseconds{v};
}

// 使用
void delay(Milliseconds ms);
void test() {
    delay(500_ms);  // 直观!
    delay(Milliseconds{500}); // 也可以,但没那味儿
}

注意:字面量运算符的参数必须是标准规定的几种类型之一。对于整数字面量,unsigned long long 是最常见的选择;对于浮点,用 long double


完整的单位系统:时间、频率、波特率

下面是一个实用的嵌入式单位系统示例,覆盖常见的时间相关单位:

#include <cstdint>
#include <type_traits>

// ===== 时间单位 =====

struct Milliseconds {
    std::uint64_t value;
    constexpr explicit Milliseconds(std::uint64_t v) : value(v) {}
};

struct Microseconds {
    std::uint64_t value;
    constexpr explicit Microseconds(std::uint64_t v) : value(v) {}

    // 转换到毫秒
    constexpr Milliseconds to_milliseconds() const {
        return Milliseconds{value / 1000};
    }
};

struct Seconds {
    std::uint64_t value;
    constexpr explicit Seconds(std::uint64_t v) : value(v) {}

    constexpr Milliseconds to_milliseconds() const {
        return Milliseconds{value * 1000};
    }

    constexpr Microseconds to_microseconds() const {
        return Microseconds{value * 1000000};
    }
};

// 字面量运算符
constexpr Milliseconds operator""_ms(unsigned long long v) { return Milliseconds{v}; }
constexpr Microseconds operator""_us(unsigned long long v) { return Microseconds{v}; }
constexpr Seconds        operator""_s (unsigned long long v) { return Seconds{v}; }

// ===== 频率单位 =====

struct Hertz {
    std::uint32_t value;
    constexpr explicit Hertz(std::uint32_t v) : value(v) {}
};

struct KiloHertz {
    std::uint32_t value;
    constexpr explicit KiloHertz(std::uint32_t v) : value(v) {}

    constexpr Hertz to_hertz() const {
        return Hertz{value * 1000};
    }
};

struct MegaHertz {
    std::uint32_t value;
    constexpr explicit MegaHertz(std::uint32_t v) : value(v) {}

    constexpr Hertz to_hertz() const {
        return Hertz{value * 1000000};
    }
};

constexpr Hertz     operator""_Hz (unsigned long long v) { return Hertz{static_cast<std::uint32_t>(v)}; }
constexpr KiloHertz operator""_kHz(unsigned long long v) { return KiloHertz{static_cast<std::uint32_t>(v)}; }
constexpr MegaHertz operator""_MHz(unsigned long long v) { return MegaHertz{static_cast<std::uint32_t>(v)}; }

// ===== 波特率单位 =====

struct BaudRate {
    std::uint32_t value;
    constexpr explicit BaudRate(std::uint32_t v) : value(v) {}
};

constexpr BaudRate operator""_baud(unsigned long long v) {
    return BaudRate{static_cast<std::uint32_t>(v)};
}

// ===== 使用示例 =====

void system_init() {
    // 配置系统时钟
    Hertz sysclk = 72_MHz.to_hertz();

    // 配置 UART 波特率
    BaudRate uart_baud = 115200_baud;

    // 配置延时
    auto startup_delay = 100_ms;
    auto debounce = 50_us;
}

void delay(Milliseconds ms);
void delay_us(Microseconds us);

void example() {
    delay(500_ms);           // 清楚:500 毫秒
    delay_us(1500_us);       // 清楚:1500 微秒

    // delay(500);           // 编译错误!必须明确单位
    // delay(500_s);         // 类型不匹配
}

这样写出来的代码几乎不需要注释——每个数字后面都带着它的单位。


类型安全的运算:单位之间的运算规则

我们可以为单位类型添加运算符,让单位参与数学运算时保持类型安全:

struct Milliseconds {
    std::uint64_t value;
    constexpr explicit Milliseconds(std::uint64_t v) : value(v) {}

    // 单位相同才能相加
    constexpr Milliseconds operator+(Milliseconds other) const {
        return Milliseconds{value + other.value};
    }

    constexpr Milliseconds operator-(Milliseconds other) const {
        return Milliseconds{value - other.value};
    }

    // 可以和标量相乘
    constexpr Milliseconds operator*(std::uint64_t factor) const {
        return Milliseconds{value * factor};
    }

    // 比较运算
    constexpr bool operator==(Milliseconds other) const {
        return value == other.value;
    }

    constexpr bool operator<(Milliseconds other) const {
        return value < other.value;
    }
};

// 标量 × 单位(反向乘法)
constexpr Milliseconds operator*(std::uint64_t factor, Milliseconds ms) {
    return ms * factor;
}

// 使用
void example() {
    Milliseconds total = 100_ms + 250_ms;    // 350_ms
    Milliseconds double_ = 2 * 100_ms;        // 200_ms
    Milliseconds triple = 100_ms * 3;         // 300_ms

    // Milliseconds bad = 100_ms + 200_us;    // 编译错误!单位不同
}

如果你确实需要跨单位运算,可以提供显式转换或重载运算符:

constexpr Microseconds operator+(Milliseconds ms, Microseconds us) {
    return Microseconds{ms.value * 1000 + us.value};
}

void example() {
    auto total = 100_ms + 500_us;  // 结果是 Microseconds: 100500_us
}

但这通常不推荐——隐式转换单位容易引入 bug。更好的方式是显式转换:

auto total = 100_ms.to_microseconds() + 500_us;  // 显式且清晰

浮点字面量运算符

有时候你需要浮点精度(例如 3.3_V、2.54_mm),这时用 long double 参数:

struct Voltage {
    float value;  // 存储为 float,节省空间
    constexpr explicit Voltage(float v) : value(v) {}
};

struct Length {
    double value;
    constexpr explicit Length(double v) : value(v) {}
};

// 浮点字面量运算符
constexpr Voltage operator""_V(long double v) {
    return Voltage{static_cast<float>(v)};
}

constexpr Length operator""_mm(long double v) {
    return Length{static_cast<double>(v)};
}

constexpr Length operator""_cm(long double v) {
    return Length{static_cast<double>(v) * 10.0};
}

// 使用
void set_voltage(Voltage v);
void measure(Length l);

void example() {
    set_voltage(3.3_V);       // 3.3 伏特
    set_voltage(Voltage{1.2}); // 也可以构造

    Length thickness = 1.5_mm + 0.2_cm;  // 显式转换更安全
    Length l2 = 1.5_mm + 2.0_mm;        // 直接相加
}

注意:浮点字面量运算符只接受 long double,整数版本只接受 unsigned long longcharwchar_tchar16_tchar32_t 等特定类型。


字符串字面量运算符

字符串字面量运算符可以用来创建编译期字符串哈希、日志标记等:

#include <cstdint>

// 简单的 FNV-1a 哈希(编译期)
constexpr std::uint32_t hash_string(const char* str, std::uint32_t value = 2166136261u) {
    return *str ? hash_string(str + 1, (value ^ static_cast<std::uint32_t>(*str)) * 16777619u) : value;
}

// 字符串字面量运算符
constexpr std::uint32_t operator""_hash(const char* str, std::size_t) {
    return hash_string(str);
}

// 使用
void example() {
    constexpr auto id1 = "temperature"_hash;
    constexpr auto id2 = "humidity"_hash;

    static_assert(id1 != id2, "different strings should have different hashes");
}

这在嵌入式里可以用于实现高效的事件 ID、消息类型标识符等。


常见误区与实战技巧

1) 下划线开头是保留给你的,但不能全是大写

  • _xxx__xxxxxx_(全大写)是实现保留的,别用
  • xxx_yyy(包含下划线且不全大写)是给你的
  • 推荐风格:_ms_Hz_V——一个小写前缀后跟单位
// 推荐
constexpr Milliseconds operator""_ms(unsigned long long v);

// 避免(可能冲突)
constexpr Milliseconds operator"" _MS(unsigned long long v);

2) 别把单位后缀搞得像宏

宏是文本替换,字面量运算符是编译期计算的。前者不类型安全,后者类型安全。别混用:

// 坏主意:宏
#define MS(x) Milliseconds{x}

// 好主意:字面量运算符
constexpr Milliseconds operator""_ms(unsigned long long v);

3) 注意整数溢出

如果你的单位转换涉及乘法,小心溢出:

struct Seconds {
    std::uint64_t value;
    constexpr explicit Seconds(std::uint64_t v) : value(v) {}

    constexpr Milliseconds to_milliseconds() const {
        return Milliseconds{value * 1000};  // 可能溢出!
    }
};

可以考虑用 __builtin_mul_overflow(GCC/Clang)或在文档中注明范围限制。

4) constexpr 让一切在编译期完成

务必把字面量运算符标记为 constexpr。这样 500_ms 就会被编译器优化成一个常量,没有运行时开销。

// 好:编译期计算
constexpr Milliseconds operator""_ms(unsigned long long v) {
    return Milliseconds{v};
}

// 坏:引入运行时开销
Milliseconds operator""_ms(unsigned long long v) {  // 没有 constexpr
    return Milliseconds{v};
}

5) 单位不是万能的

复杂物理量(力、能量、功率)的完整单位系统(如 SI 单位)做起来会很复杂。对于嵌入式,通常只需要时间、频率、电压、温度这几个常用单位就够了。别为了单位而单位——保持简单实用。

6) 和枚举类配合

可以把"单位类型"和"值"结合,实现更强类型的系统:

template<typename Unit>
struct Quantity {
    double value;
    constexpr explicit Quantity(double v) : value(v) {}
};

struct MillisecondUnit {};
using Milliseconds = Quantity<MillisecondUnit>;

constexpr Milliseconds operator""_ms(long double v) {
    return Milliseconds{static_cast<double>(v)};
}

这能让不同单位完全无法隐式转换,类型安全性拉满。


实战示例:延时函数的类型安全 API

#include <cstdint>

// 单位定义(简化版)
struct Milliseconds { std::uint32_t value; };
struct Microseconds { std::uint32_t value; };

constexpr Milliseconds operator""_ms(unsigned long long v) {
    return Milliseconds{static_cast<std::uint32_t>(v)};
}
constexpr Microseconds operator""_us(unsigned long long v) {
    return Microseconds{static_cast<std::uint32_t>(v)};
}

// 类型安全的延时函数
void delay(Milliseconds ms);
void delay_us(Microseconds us);

// 硬件相关的底层实现(假设 SysTick 以 1ms 为单位)
namespace detail {
    inline void delay_milliseconds(std::uint32_t ms) {
        // 实际的硬件延时实现
        volatile std::uint32_t count;
        for (std::uint32_t i = 0; i < ms; ++i) {
            count = 1000; while(count--);  // 简化的延时循环
        }
    }
}

inline void delay(Milliseconds ms) {
    detail::delay_milliseconds(ms.value);
}

inline void delay_us(Microseconds us) {
    detail::delay_milliseconds((us.value + 999) / 1000);  // 向上取整到毫秒
}

// 使用
void init_sequence() {
    delay(100_ms);    // 启动延时
    // ... 初始化代码 ...
    delay(50_us);     // 短延时等待稳定
    // ... 更多代码 ...

    // delay(100);    // 编译错误!必须明确单位
    // delay_us(100_ms); // 编译错误!类型不匹配
}

这样写出来的 API,调用者不可能搞错单位——编译器会替你把关。

查看完整可编译示例
// time_units.cpp
// 时间单位字面量运算符示例

#include <cstdint>
#include <iostream>

// ===== 时间单位 =====

struct Milliseconds {
    std::uint64_t value;
    constexpr explicit Milliseconds(std::uint64_t v) : value(v) {}

    // 运算符重载
    constexpr Milliseconds operator+(Milliseconds other) const {
        return Milliseconds{value + other.value};
    }
    constexpr Milliseconds operator-(Milliseconds other) const {
        return Milliseconds{value - other.value};
    }
    constexpr Milliseconds operator*(std::uint64_t factor) const {
        return Milliseconds{value * factor};
    }
    constexpr bool operator==(Milliseconds other) const {
        return value == other.value;
    }
    constexpr bool operator<(Milliseconds other) const {
        return value < other.value;
    }
};

// 标量 × 单位(反向乘法)
constexpr Milliseconds operator*(std::uint64_t factor, Milliseconds ms) {
    return ms * factor;
}

struct Microseconds {
    std::uint64_t value;
    constexpr explicit Microseconds(std::uint64_t v) : value(v) {}

    // 转换到毫秒
    constexpr Milliseconds to_milliseconds() const {
        return Milliseconds{value / 1000};
    }
};

struct Seconds {
    std::uint64_t value;
    constexpr explicit Seconds(std::uint64_t v) : value(v) {}

    constexpr Milliseconds to_milliseconds() const {
        return Milliseconds{value * 1000};
    }

    constexpr Microseconds to_microseconds() const {
        return Microseconds{value * 1000000};
    }
};

// 字面量运算符
constexpr Milliseconds operator""_ms(unsigned long long v) { return Milliseconds{v}; }
constexpr Microseconds operator""_us(unsigned long long v) { return Microseconds{v}; }
constexpr Seconds        operator""_s (unsigned long long v) { return Seconds{v}; }

// 延时函数(声明)
void delay(Milliseconds ms);
void delay_us(Microseconds us);

// 实现模拟
void delay(Milliseconds ms) {
    std::cout << "Delay: " << ms.value << " ms\n";
}
void delay_us(Microseconds us) {
    std::cout << "Delay: " << us.value << " us\n";
}

int main() {
    std::cout << "=== Time Units ===\n\n";

    // 基本使用
    delay(500_ms);
    delay_us(1500_us);

    // 单位运算
    std::cout << "\n=== Unit Operations ===\n";
    Milliseconds total = 100_ms + 250_ms;
    std::cout << "100_ms + 250_ms = " << total.value << " ms\n";

    Milliseconds double_time = 2 * 100_ms;
    std::cout << "2 * 100_ms = " << double_time.value << " ms\n";

    Milliseconds triple = 100_ms * 3;
    std::cout << "100_ms * 3 = " << triple.value << " ms\n";

    // 单位转换
    std::cout << "\n=== Unit Conversion ===\n";
    Seconds s = 2_s;
    Milliseconds ms = s.to_milliseconds();
    std::cout << "2_s = " << ms.value << " ms\n";

    Microseconds us = s.to_microseconds();
    std::cout << "2_s = " << us.value << " us\n";

    Microseconds us2 = 1500_us;
    Milliseconds ms2 = us2.to_milliseconds();
    std::cout << "1500_us = " << ms2.value << " ms\n";

    // 类型安全演示
    std::cout << "\n=== Type Safety ===\n";
    // delay(500);        // 编译错误!必须明确单位
    // delay(500_s);      // 编译错误!类型不匹配

    return 0;
}
查看频率单位完整示例
// frequency_units.cpp
// 频率和波特率单位字面量运算符示例

#include <cstdint>
#include <iostream>

// ===== 频率单位 =====

struct Hertz {
    std::uint32_t value;
    constexpr explicit Hertz(std::uint32_t v) : value(v) {}
};

struct KiloHertz {
    std::uint32_t value;
    constexpr explicit KiloHertz(std::uint32_t v) : value(v) {}

    constexpr Hertz to_hertz() const {
        return Hertz{value * 1000};
    }
};

struct MegaHertz {
    std::uint32_t value;
    constexpr explicit MegaHertz(std::uint32_t v) : value(v) {}

    constexpr Hertz to_hertz() const {
        return Hertz{value * 1000000};
    }
};

// 频率字面量运算符
constexpr Hertz     operator""_Hz (unsigned long long v) { return Hertz{static_cast<std::uint32_t>(v)}; }
constexpr KiloHertz operator""_kHz(unsigned long long v) { return KiloHertz{static_cast<std::uint32_t>(v)}; }
constexpr MegaHertz operator""_MHz(unsigned long long v) { return MegaHertz{static_cast<std::uint32_t>(v)}; }

// ===== 波特率单位 =====

struct BaudRate {
    std::uint32_t value;
    constexpr explicit BaudRate(std::uint32_t v) : value(v) {}
};

constexpr BaudRate operator""_baud(unsigned long long v) {
    return BaudRate{static_cast<std::uint32_t>(v)};
}

// 使用示例函数
void set_clock_frequency(Hertz freq);
void set_uart_baudrate(BaudRate baud);

void set_clock_frequency(Hertz freq) {
    std::cout << "Clock frequency: " << freq.value << " Hz\n";
}

void set_uart_baudrate(BaudRate baud) {
    std::cout << "UART baudrate: " << baud.value << " baud\n";
}

int main() {
    std::cout << "=== Frequency and Baud Rate Units ===\n\n";

    // 频率单位
    Hertz sysclk = 72_MHz.to_hertz();
    std::cout << "System clock: " << sysclk.value << " Hz\n";

    set_clock_frequency(48_MHz.to_hertz());
    set_clock_frequency(16_kHz.to_hertz());
    set_clock_frequency(1000_Hz);

    // 波特率
    std::cout << "\n=== UART Configuration ===\n";
    set_uart_baudrate(115200_baud);
    set_uart_baudrate(9600_baud);
    set_uart_baudrate(1000000_baud);

    // 演示自文档化代码
    std::cout << "\n=== Self-Documenting Code ===\n";
    std::cout << "Compare:\n";
    std::cout << "  delay(5000)      <- What unit?\n";
    std::cout << "  delay(5000_ms)   <- Clear: 5000 milliseconds\n";
    std::cout << "  set_clk(72000000)        <- What frequency?\n";
    std::cout << "  set_clk(72_MHz.to_hertz()) <- Clear: 72 MHz\n";

    return 0;
}
查看浮点单位完整示例
// voltage_units.cpp
// 浮点字面量运算符示例(电压、长度等)

#include <cstdint>
#include <iostream>
#include <cmath>

// ===== 浮点单位 =====

struct Voltage {
    float value;
    constexpr explicit Voltage(float v) : value(v) {}

    // 运算符
    constexpr Voltage operator+(Voltage other) const {
        return Voltage{value + other.value};
    }
    constexpr Voltage operator-(Voltage other) const {
        return Voltage{value - other.value};
    }
};

struct Length {
    double value;
    constexpr explicit Length(double v) : value(v) {}

    constexpr Length operator+(Length other) const {
        return Length{value + other.value};
    }
    constexpr bool operator<(Length other) const {
        return value < other.value;
    }
};

// 浮点字面量运算符(参数必须是 long double)
constexpr Voltage operator""_V(long double v) {
    return Voltage{static_cast<float>(v)};
}

constexpr Length operator""_mm(long double v) {
    return Length{static_cast<double>(v)};
}

constexpr Length operator""_cm(long double v) {
    return Length{static_cast<double>(v) * 10.0};
}

constexpr Length operator""_m(long double v) {
    return Length{static_cast<double>(v) * 1000.0};
}

// 使用函数
void set_voltage(Voltage v);
void measure(Length l);

void set_voltage(Voltage v) {
    std::cout << "Voltage set to: " << v.value << " V\n";
}

void measure(Length l) {
    std::cout << "Length: " << l.value << " mm\n";
}

int main() {
    std::cout << "=== Floating-Point Literal Operators ===\n\n";

    // 电压单位
    set_voltage(3.3_V);
    set_voltage(5.0_V);
    set_voltage(1.2_V);

    // 长度单位
    std::cout << "\n=== Length Units ===\n";
    measure(1.5_mm);
    measure(2.5_cm);
    measure(0.01_m);

    // 单位运算
    std::cout << "\n=== Unit Operations ===\n";
    Length total = 1.5_mm + 2.5_mm;
    std::cout << "1.5_mm + 2.5_mm = " << total.value << " mm\n";

    Length l1 = 5.0_cm;
    Length l2 = 50.0_mm;
    std::cout << "5.0_cm < 50.0_mm: " << (l1 < l2 ? "true" : "false") << "\n";

    // 自文档化代码演示
    std::cout << "\n=== Self-Documenting Code ===\n";
    std::cout << "Compare:\n";
    std::cout << "  voltage = 3.3           <- What unit?\n";
    std::cout << "  voltage = 3.3_V          <- Clear: 3.3 Volts\n";
    std::cout << "  thickness = 1.5          <- What unit?\n";
    std::cout << "  thickness = 1.5_mm       <- Clear: 1.5 millimeters\n";

    return 0;
}

小结:让数字说话

嵌入式代码里到处都是魔法数字:波特率、时钟频率、延时、阈值……用字面量运算符把这些数字变成带单位的"量",是提升代码可读性和安全性最简单也最有效的方法。

5000_ms5000 多了三个字符,但少了一整类 bug。下次你写延时函数、时钟配置、波特率设置时,花五分钟定义几个字面量运算符,未来的你会感谢现在的自己——而代码审查的人,也会给你竖起大拇指。