当前位置: 首页 > news >正文

C++11 User-Defined Literals:从入门到精通

文章目录

    • 一、引言
    • 二、基本概念
      • 2.1 字面量的定义
      • 2.2 用户定义的字面量的引入
      • 2.3 字面量运算符的定义语法
      • 2.4 字面量运算符的参数类型限制
    • 三、入门示例
      • 3.1 定义数值字面量运算符
        • 3.1.1 整数字面量运算符
        • 3.1.2 浮点数字面量运算符
      • 3.2 定义字符串字面量运算符
      • 3.3 定义自定义类型的字面量运算符
    • 四、高级应用和复杂示例
      • 4.1 定义复数
      • 4.2 处理二进制字面量
      • 4.3 结合模板和常量表达式
    • 五、在实际项目中的使用
      • 5.1 单位转换和物理计算
      • 5.2 日志和调试
      • 5.3 配置文件解析
    • 六、注意事项
      • 6.1 代码可读性
      • 6.2 操作符重载冲突
      • 6.3 最大吞噬规则
    • 七、总结

一、引言

在C++编程的世界里,C++11引入了许多令人瞩目的新特性,其中用户定义的字面量(User-Defined Literals,简称UDLs)无疑是一项强大且实用的功能。它为程序员提供了前所未有的灵活性和便利性,允许我们根据自己的需求定义字面量,从而使代码更加直观、易读且富有表现力。本文将带领你从入门开始,逐步深入了解C++11 User-Defined Literals,直至精通并能在实际项目中熟练运用。

二、基本概念

2.1 字面量的定义

在C++中,字面量是程序中直接使用的固定值,它们是源代码中用于表示数据的常量形式。常见的字面量包括整数(如42)、浮点数(如3.14)、字符串(如"hello")等。在C++11之前,这些字面量的类型和值都是预定义好的,程序员无法对其进行自定义。

2.2 用户定义的字面量的引入

C++11引入了用户定义的字面量,这一特性允许程序员定义自己的字面量运算符,从而创建具有特定含义和行为的字面量。例如,我们可以定义一个字面量运算符 _km,使得 10_km 不仅是一个数值,而是明确表示10公里的距离。这种自定义字面量的能力,为代码的可读性和可维护性带来了显著提升。

2.3 字面量运算符的定义语法

用户定义的字面量是通过定义字面量运算符来实现的。字面量运算符是一种特殊的函数,其名称以 operator "" 开头,后面紧跟着一个用户自定义的标识符。这个标识符用于区分不同的字面量运算符,同时也为字面量赋予了特定的语义。其基本语法如下:

返回值类型 operator "" 自定义后缀 (参数);

如果字面量运算符是一个模板,它必须有一个空的参数列表,并且只能有一个模板参数,这个模板参数必须是一个元素类型为 char 的非类型模板参数包。在这种情况下它被称为数字字面量运算符模板,语法如下:

template < char ...> 返回值类型 operator "" 自定义后缀 ();

需要注意的是,自定义后缀必须以下划线 _ 开头,并符合标识符命名规范。虽然“以下划线开头”并非是从语法上强制性的,但如果坚持不以下划线开头,编译器会给出警告。另外,由于字面量的特殊语法结构,自定义的后缀其实可以同时是C++关键字而不产生冲突,这是合法的。

2.4 字面量运算符的参数类型限制

对于字面量运算符的参数,C++有语法上严格的规定。参数列表仅允许以下几种类型:

  1. unsigned long long int:用于表示整数,是用户定义整型字面量运算符的首选方式。
  2. long double:用于表示浮点数,是用户定义浮点型字面量运算符的首选方式。
  3. char:用于表示单个字符。
  4. const char *:用于表示字符串。
  5. const char *, size_t size:也用于表示字符串,其中第二个参数会自动传入字符串长度。
    不允许使用默认参数,也不允许使用C语言链接。除了上述限制之外,字面量运算符(和字面量运算符模板)是普通函数(和函数模板),它们可以声明为 inlineconstexpr,可能具有内部或外部链接,可以显式调用,其地址也可以被获取。

三、入门示例

3.1 定义数值字面量运算符

3.1.1 整数字面量运算符

以下是一个定义整数字面量运算符的示例,我们定义一个 _dozen 后缀,用于将输入的整数乘以12:

// 定义整数字面量运算符
int operator "" _dozen(unsigned long long d) {return d * 12;
}

在这个例子中,当使用 10_dozen 时,它会将10乘以12,结果为120。这里的参数类型 unsigned long long 是C++11为整数字面量运算符提供的专用类型,它可以确保在编译时捕获整数字面量。

3.1.2 浮点数字面量运算符

对于浮点数字面量运算符,定义方式类似,但参数类型有所不同。例如,我们定义一个 _percent 后缀,用于将输入的浮点数转换为百分比形式:

// 定义浮点数字面量运算符
long double operator "" _percent(long double p) {return p / 100.0;
}

使用 50.0_percent 时,结果为0.5。

3.2 定义字符串字面量运算符

字符串字面量运算符用于处理字符串字面量,其定义方式与数值字面量运算符略有不同,主要体现在参数类型上。以下是一个将字符串字面量转换为 std::string 对象的示例:

std::string operator "" _path(const char* str, size_t len) {return std::string(str, len);
}

在这个例子中,_path 是一个字符串字面量运算符,它将字符串字面量转换为 std::string 对象。参数 const char* str 指向字符串字面量的首字符,size_t len 表示字符串的长度。通过这种方式,我们可以方便地创建具有特定路径格式的字符串对象。

3.3 定义自定义类型的字面量运算符

我们还可以为自定义类型定义字面量运算符。例如,定义一个表示二维坐标的类 Point2D,并为其定义字面量运算符:

class Point2D {
public:long double x, y;Point2D(long double x, long double y) : x(x), y(y) {}// 其他成员函数...
};Point2D operator "" _point(unsigned long long x, unsigned long long y) {return Point2D(x, y);
}

使用这个字面量运算符,我们可以方便地创建 Point2D 对象,如 Point2D p = 3_point_4;,它创建了一个坐标为(3, 4)的点。

四、高级应用和复杂示例

4.1 定义复数

在数学和工程领域,复数的使用非常广泛。用户定义的字面量可以方便地定义复数字面量。以下是一个定义复数字面量运算符的示例:

#include <complex>
std::complex<long double> operator "" _i(long double x) {return std::complex<long double>(0, x);
}

使用这个字面量运算符,我们可以轻松创建复数,如 std::complex<long double> z = 3.0_i;,这里的 z 是一个复数 0 + 3i

4.2 处理二进制字面量

我们可以定义一个字面量运算符来处理二进制字面量,将二进制字符串转换为 std::bitset 对象:

#include <bitset>
#include <iostream>
template < char ... Bits >
struct checkbits { static const bool valid = false ; 
} ;
template < char High , char ... Bits >
struct checkbits < High , Bits ... > 
{ static const bool valid = ( High == '0' || High == '1' ) && checkbits < Bits ... > :: valid ; 
} ;
template < char High >
struct checkbits < High > 
{ static const bool valid = ( High == '0' || High == '1' ) ; 
} ;
template < char ... Bits >
inline constexpr std::bitset < sizeof ... ( Bits ) >
operator "" _bits ( ) noexcept { static_assert ( checkbits < Bits ... > :: valid , "invalid digit in binary string" ) ; return std::bitset < sizeof ... ( Bits ) > ( ( char [ ] ) { Bits ... , '\0' } ) ; 
}

使用示例:

int main ( ) 
{    auto bits = 0101010101010101010101010101010101010101010101010101010101010101_bits ; std::cout << bits << std::endl ; std::cout << "size = " << bits.size() << std::endl ; std::cout << "count = " << bits.count() << std::endl ; std::cout << "value = " << bits.to_ullong() << std::endl ; // 这会在编译时触发静态断言// auto badbits = 2101010101010101010101010101010101010101010101010101010101010101_bits ; 
}

这个示例展示了如何使用用户定义的字面量来创建二进制字面量,并在编译时进行有效性检查。

4.3 结合模板和常量表达式

用户定义的字面量还可以结合模板和常量表达式,实现更强大的功能。例如,我们可以定义一个模板类来处理科学量,并为其定义字面量运算符:

template < int m, int l, int t >
class quantity {
public:explicit quantity(double val = 0.0) : value(val) {}quantity(const quantity & x) : value(x.value) {}quantity & operator += (const quantity & rhs) {value += rhs.value;return *this;}quantity & operator -= (const quantity & rhs) {value -= rhs.value;return *this;}double convert(const quantity & rhs) {return value / rhs.value;}double get_value() const {return value;}
private:double value;
};// 定义长度的字面量运算符
quantity<0, 1, 0> operator "" _m(long double x) {return quantity<0, 1, 0>(x);
}

这样,我们就可以使用 10.0_m 来表示10米的长度,并且可以进行相应的计算和操作。

五、在实际项目中的使用

5.1 单位转换和物理计算

在科学计算和工程领域,物理单位的正确使用至关重要。用户定义的字面量可以方便地定义各种物理单位,如长度、质量、时间等,从而使代码更具物理意义。以下是一个简单的示例,定义了长度、质量和时间的单位:

long double operator "" _km(long double x) {return x * 1000; // 将公里转换为米
}
long double operator "" _kg(long double x) {return x;  // 千克
}
long double operator "" _s(long double x) {return x;  // 秒
}

使用这些字面量运算符,我们可以编写更具物理意义的代码:

long double distance = 10.0_km;
long double mass = 5.0_kg;
long double time = 2.0_s;

5.2 日志和调试

在日志和调试过程中,用户定义的字面量可以用于生成带有额外信息的日志条目,例如时间戳、日志级别等。以下是一个简单的示例,定义一个日志级别字面量:

#include <iostream>
enum class LogLevel { DEBUG, INFO, WARNING, ERROR };std::ostream& operator<<(std::ostream& os, LogLevel level) {switch (level) {case LogLevel::DEBUG: os << "DEBUG"; break;case LogLevel::INFO: os << "INFO"; break;case LogLevel::WARNING: os << "WARNING"; break;case LogLevel::ERROR: os << "ERROR"; break;}return os;
}void log(const char* message, LogLevel level) {std::cout << "[" << level << "] " << message << std::endl;
}// 定义日志级别字面量运算符
void operator "" _log(const char* message, LogLevel level) {log(message, level);
}

使用示例:

int main() {"This is a debug message"_log(LogLevel::DEBUG);"This is an error message"_log(LogLevel::ERROR);return 0;
}

5.3 配置文件解析

在处理配置文件时,用户定义的字面量可以用于解析特定格式的字符串,例如从一个配置字面量中提取键值对。以下是一个简单的示例,定义一个配置解析字面量:

#include <iostream>
#include <string>
#include <unordered_map>std::unordered_map<std::string, std::string> parseConfig(const char* str) {std::unordered_map<std::string, std::string> config;std::string key, value;bool inKey = true;for (const char* p = str; *p; ++p) {if (*p == '=') {inKey = false;} else if (*p == ';') {config[key] = value;key.clear();value.clear();inKey = true;} else {if (inKey) {key += *p;} else {value += *p;}}}if (!key.empty()) {config[key] = value;}return config;
}// 定义配置解析字面量运算符
std::unordered_map<std::string, std::string> operator "" _cfg(const char* str) {return parseConfig(str);
}

使用示例:

int main() {auto config = "key1=value1;key2=value2"_cfg;for (const auto& pair : config) {std::cout << pair.first << " = " << pair.second << std::endl;}return 0;
}

六、注意事项

6.1 代码可读性

用户定义的字面量应该谨慎使用,因为它们可能会使代码的可读性降低,特别是对于不熟悉UDLs的开发者。因此,在定义自定义后缀时,应该选择具有明确语义的名称,并且尽量避免使用过于复杂的逻辑。

6.2 操作符重载冲突

在使用时,要注意UDLs可能会与现有的操作符重载冲突。在定义字面量运算符时,应该确保其不会与其他函数或操作符产生歧义。

6.3 最大吞噬规则

由于最大吞噬规则,以 eE(C++17起还有 pP)结束的用户定义整数和浮点字面量,在后随运算符 +- 时,必须在源码中以空白符或括号与运算符分隔。同样的规则适用于后随整数或浮点用户定义字面量的点运算符。否则会组成单个非法预处理数字记号,导致编译失败。例如:

long double operator "" _E( long double );
long double operator "" _a( long double );
int operator "" _p( unsigned long long );
auto x = 1.0 _E+ 2.0 ; // 错误
auto y = 1.0 _a+ 2.0 ; // OK
auto z = 1.0 _E + 2.0 ; // OK
auto q = ( 1.0 _E)+ 2.0 ; // OK
auto w = 1 _p+ 2 ; // 错误
auto u = 1 _p + 2 ; // OK

七、总结

C++11引入的用户定义字面量为程序员提供了强大的自定义能力,使代码更加直观、易读且富有表现力。通过合理使用用户定义的字面量,我们可以在单位转换、物理计算、日志调试、配置文件解析等多个方面提高代码的质量和可维护性。然而,在使用过程中,我们也需要注意代码的可读性、操作符重载冲突等问题,确保在不牺牲代码清晰度的前提下发挥用户定义字面量的优势。希望本文能帮助你从入门到精通C++11 User-Defined Literals,并在实际项目中灵活运用这一强大的特性。

相关文章:

  • java 设计模式_行为型_22模板模式
  • web项目部署配置HTTPS遇到的问题解决方法
  • 【友加畅捷】T1飞跃版账套隐藏设置
  • 数据赋能(280)——过程管理——反馈机制
  • Spring 框架核心功能全解
  • 计算机网络期末速成 网络层 判断及单选题
  • 深度学习实战111-基于神经网络的A股、美股、黄金对冲投资策略(PyTorch LSTM)
  • MyBatis 模糊查询终极教程:安全高效的搜索实现
  • NLP学习路线图(五十一):PyTorch/TensorFlow
  • 论文笔记:Repetition Improves Language Model Embeddings
  • 人工智能100问☞第48问:GPT是怎么生成文本的?
  • Attention Backend的认识
  • 【完整源码+数据集+部署教程】水位面图像分割系统源码和数据集:改进yolo11-EMSC
  • 【C++】unordered_map和unordered_set的使用
  • 物理学 | 本质 / 体系 / 应用 / 教育启示
  • Java 中 DataSource-数据源 的基础介绍
  • day33 MLP神经网络的训练
  • FPGA基础 -- Verilog HDL 结构风格的描述
  • 企业级 Vue3 项目 iframe 封装方案
  • Excel单元格数值统计 - 华为OD机试真题(Python题解)
  • 诸城网站建设公司排名/长沙网站seo公司
  • 做网站广告哪家好/app推广之家
  • 网站建设分几块/如何购买域名
  • 如何设计网站建设方案/谷歌外贸网站推广
  • 政府网站建设年度总结/中国十大互联网公司
  • 平面设计展示网站/灰色词网站seo