自定义字面量

1. 自定义字面量

1.1 基本语法

自定义字面量是C++11引入的特性,允许程序员为字面量定义自己的后缀,从而创建具有特定类型和值的对象。它的基本语法如下:

1
2
3
4
// 定义字面量运算符
返回值类型 operator"" 后缀名(参数类型 参数) {
// 转换逻辑
}

如果详细进行划分,定义字面量的时候有四种重载形式:

  1. 整数字面量

    1
    2
    // 整数字面量
    ReturnType operator"" _suffix(unsigned long long);
  2. 浮点数字面量

    1
    2
    // 浮点字面量
    ReturnType operator"" _suffix(long double);
  3. 字符字面量

    1
    2
    // 字符字面量
    ReturnType operator"" _suffix(char);
  4. 字符串字面量

    1
    2
    // 字符串字面量
    ReturnType operator"" _suffix(const char*, size_t);

关于自定义字面量后缀的命名必须以下划线(_)开头标准库提供的字面量不带下划线后缀

为了便于大家理解,下面的代码中展示了四种参数形式的详细使用方法:

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <iostream>
#include <string>
#include <cstring>

// 1. 整数参数 - 处理整数字面量
constexpr unsigned long long operator"" _k(unsigned long long value)
{
return value * 1000; // 千位转换
}

// 2. 浮点数参数 - 处理浮点字面量
constexpr long double operator"" _km(long double km)
{
return km * 1000.0L; // 千米转米
}

// 3. 字符参数 - 处理单个字符
char operator"" _upper(char c)
{
if (c >= 'a' && c <= 'z')
{
return c - 'a' + 'A';
}
return c;
}

// 4. 字符串参数 - 处理字符串字面量
std::string operator"" _w(const char* str, size_t len)
{
return std::string("(") + str + ")"; // 添加括号
}

// 字符串参数 - 另一种处理方式
std::string operator"" _repeat(const char* str, size_t len)
{
std::string result;
for (size_t i = 0; i < 3; ++i)
{
result.append(str, len);
}
return result;
}

int main()
{
// 整数参数示例
unsigned long long distance = 5_k; // 5000
std::cout << "5_k = " << distance << std::endl;

// 浮点数参数示例
long double meters = 3.5_km; // 3500.0
std::cout << "3.5_km = " << meters << "米" << std::endl;

// 字符参数示例
char upperA = 'a'_upper; // 'A'
std::cout << "'a'_upper = " << upperA << std::endl;

// 字符串参数示例
std::string wrapped = "hello"_w; // "(hello)"
std::cout << "\"hello\"_w = " << wrapped << std::endl;

std::string repeated = "hi"_repeat; // "hihihi"
std::cout << "\"hi\"_repeat = " << repeated << std::endl;

return 0;
}

2.2 复合类型自定义字面量

在 C++ 中,自定义字面量允许我们为自定义类型(包括复合类型)创建直观的字面量表示法。这也是通过重载 operator"" 运算符来实现的。

下面代码中实现了一个复数类 Complex 并利用 C++ 的自定义字面量功能,为复数提供了自然的数学表达式语法。

这个类存储了两个 double 类型的浮点数:

  • real:实部,表示复数的实数部分
  • imag:虚部系数,表示复数虚数部分的系数(真正的虚数部分是 imag × i
1
2
3
4
5
// 数学数据和代码之间的对应关系
数学表示: 3 + 4i → 代码: Complex(3.0, 4.0)
数学表示: -2 - 1.5i → 代码: Complex(-2.0, -1.5)
数学表示: 3.14i → 代码: Complex(0.0, 3.14)
数学表示: 5(纯实数) → 代码: Complex(5.0, 0.0)
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
#include <iostream>

class Complex
{
public:
// 构造函数
Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}

// 打印函数
void print() const
{
std::cout << real << " + " << imag << "i" << std::endl;
}
private:
double real;
double imag;
};

// 纯虚数字面量
Complex operator"" _i(long double imag)
{
return Complex(0.0, static_cast<double>(imag));
}

// 也可以支持整数的虚数
Complex operator"" _i(unsigned long long imag) {
return Complex(0.0, static_cast<double>(imag));
}

int main()
{
// 直接创建纯虚数
Complex z = 3.14_i; // Complex(0.0, 3.14)
z.print(); // 输出: 0 + 3.14i
}

2. C++14 中的自定义字面量

在 C++11 中,只能为以下类型定义自定义字面量:

  1. 整数unsigned long long
  2. 浮点数long double
  3. 字符char
  4. 字符串const char*, size_t(字符串长度)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 示例
// 1. 整数版本
unsigned long long operator"" _KB(unsigned long long n)
{
return n * 1024;
}
// 2. 浮点数版本
double operator"" _km(long double n)
{
return n * 1000.0; // 公里转米
}
// 3. 字符版本
char operator"" _char(char c)
{
return std::toupper(c);
}
// 4. 字符串版本
std::string operator"" _s(const char* str, size_t len)
{
return std::string(str, len);
}

C++14 扩展了自定义字面量的功能,表现在一下两个方面。

2.1 原始字面量

原始字面量还是用来处理原始字符序列,和字符串字面量一样,只是不需要指定字符串长度。

1
2
3
4
5
6
7
8
// C++14 新增:原始字符序列
ReturnType operator"" _suffix(const char* str);

// 示例:
std::chrono::seconds operator"" _s(const char* str)
{
return std::chrono::seconds(std::stoi(str));
}

2.2 标准库内置字面量

C++14 标准库主要在以下几个头文件中引入了内置字面量:

  • 时间字面量 (在 <chrono> 中)

    在 C++ 的 std::chrono 中,这些后缀是时间间隔(duration)的用户定义字面量,用于方便地创建时间单位。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include <chrono>
    using namespace std::chrono_literals; // 或 std::literals::chrono_literals

    // 时、分、秒、毫秒、微秒、纳秒
    auto sec = 10s; // 10秒 (std::chrono::seconds)
    auto ms = 100ms; // 100毫秒 (std::chrono::milliseconds)
    auto us = 500us; // 500微秒 (std::chrono::microseconds)
    auto ns = 1000ns; // 1000纳秒 (std::chrono::nanoseconds)
    auto min = 5min; // 5分钟 (std::chrono::minutes)
    auto hour = 2h; // 2小时 (std::chrono::hours)

    // 类型安全的运算
    auto total = 1h + 30min + 45s + 200ms;
    auto half_hour = 0.5h; // 浮点时间字面量
    后缀 全称 含义 等价于
    h hours 小时 std::chrono::hours
    min minutes 分钟 std::chrono::minutes
    s seconds std::chrono::seconds
    ms milliseconds 毫秒 std::chrono::milliseconds
    us microseconds 微秒 std::chrono::microseconds
    ns nanoseconds 纳秒 std::chrono::nanoseconds
  • 字符串字面量 (在 <string> 中)

    使用内置字面量s可以直接将一个char*字符串转换成std::string类型的对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #include <string>
    using namespace std::string_literals; // 或 std::literals::string_literals

    auto str1 = "hello"s; // std::string 类型
    auto str2 = u"hello"s; // std::u16string
    auto str3 = U"hello"s; // std::u32string
    auto str4 = L"hello"s; // std::wstring

    // 与原始字符串字面量组合使用
    auto raw = R"(Line1\nLine2)"s; // 原始字符串 + std::string

    // 实际用途
    std::string name = "Alice"s + " Smith"s; // 编译时连接
  • 复数字面量 (在 <complex> 中)

    在 C++ 中,std::complex 定义了三个方便的虚数字面量后缀::iifil

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include <complex>
    using namespace std::complex_literals; // 或 std::literals::complex_literals

    auto c1 = 1.0 + 2i; // std::complex<double>(1.0, 2.0)
    auto c2 = 3i; // std::complex<double>(0.0, 3.0)
    auto c3 = 4.5f + 5.6if; // std::complex<float>(4.5f, 5.6f)

    // 不同类型
    auto cf = 1.0f + 2.0if; // std::complex<float>
    auto cd = 1.0 + 2.0i; // std::complex<double>
    auto cld = 1.0L + 2.0il; // std::complex<long double>

    // 数学运算
    auto z = (1.0 + 2i) * (3.0 - 4i); // 复数乘法
    后缀 类型 对应的 std::complex 类型
    i double std::complex<double>
    if float std::complex<float>
    il long double std::complex<long double>

关于以上三种预定义字面量的命名空间的使用一般有三种方式:

1
2
3
4
5
6
7
8
9
10
11
// 方式1:导入特定字面量
using std::chrono_literals::operator""s;
using std::string_literals::operator""s; // 注意冲突!

// 方式2:使用限定名
auto s1 = std::chrono_literals::operator""s(5); // 5秒
auto s2 = std::string_literals::operator""s("hi"); // "hi"字符串

// 方式3:最佳实践(推荐)
using namespace std::chrono_literals; // 只导入时间
using namespace std::string_literals; // 只导入字符串
字面量类型 头文件 命名空间 主要后缀 返回类型
时间字面量 <chrono> std::chrono_literals h, min, s, ms, us, ns 对应的 duration 类型
字符串字面量 <string> std::string_literals s std::basic_string 相应类型
复数字面量 <complex> std::complex_literals i, if, il std::complex 相应类型