C++用户定义字面量
400 Words | Read in about 2 Min | View times
Overview
C++11引入了用户自定义字面量(user-defined literal),也叫自定义后缀操作符。通过实现一个后缀操作符,可以将声明了后缀标识的字面量转换为所需的类型和数值。本节内容我们将探讨各种常见的字面量,以及C++11新加入的用户定义字面量。
本系列文章将包括以下领域:
本章其他内容请见 《现代C++》
字面量
维基百科对于字面量(literal)的定义如下:
字面常量(literal constant),是C程序设计语言与C++语言的词法上的概念(lexical conventions),是指源程序中表示固定值的符号。
我们一般把字面常量简称为字面量。现代C++支持的字面量类型包括:
整型字面量
支持的数值进制有10进制(如1234
)、8进制(如0373
)、16进制(如0x2f
)、2进制(如0b1001
)。
整型字面量按照以下顺序转化为整数类型表示:int
、unsigned int
、long
、unsigned long
、long long
、unsigned long long
。其中,10进制表示的字面量仅考虑有符号的整数类型;而8进制或16进制的字面量先考虑能否用有符号的整数类型表示,如不能再考虑能否用同样长度的无符号整数类型表示。
例如,字面量0x87654321
,这是一个正值,用4字节长的int
无法表示,编译器就会自动选用unsigned int
来表示该字面量。
整型字面量可以使用后缀u
、U
、ul
、UL
、ull
、ULL
明确表示各种无符号整型,如1234u
、123ul
。
浮点型字面量
支持的数值进制有10进制(如1.5e15
、2.3f
)、16进制(如0x1p10
、0x1.001p10
)。如果不是科学计数法的形式,就必须有小数点,小数点前或后的数字可以省略。
没有后缀的浮点型字面量是double
类型,使用后缀f
的是float
类型。
字符型字面量
字符型字面量是用单引号括起来的一个或多个字符。
没有前缀字符的叫普通字符型字面量或窄字符字面量,包含多个字符的普通字符字面量叫多字符字面量。
1//多字符字面量
2int a = 'abcd'; //值为0x61626364的整型数
在C++中,字符字面量a
是char
类型,所以sizeof('a')
为1
;而在C语言中,字符字面量a
是int
类型,所以sizeof('a')
为4
。
当字符字面量有前缀u
、U
、L
时,分别表示char16_t
、char32_t
和wchar_t
。
转义字符也是字符型字面量的一种,其表示范围可能不属于C++的字符范围,所以需要用反斜杠\
来转义所需的字符。
字符串字面量
字符串字面量是最常见的形式。C++11加入了原始字符串字面量,来避免使用大量反斜杠转义字符,增强可读性和计算性能。
枚举字面量
枚举类型本质上是取有限个值的整型,在传统C++中,枚举字面量的作用域属于定义了这个枚举类型的作用域,而不属于这个枚举类型的内部,这容易导致枚举字面量重定义冲突:
1enum FileAccess {
2 Read,
3 Write,
4};
5
6enum FileShare {
7 Read, //error: ‘Read’ conflicts with a previous declaration
8 Write, //error: ‘Write’ conflicts with a previous declaration
9};
C++11为了解决这个问题,引入了枚举类。定义枚举类时在原enum
关键字后面增加class
关键字,此时枚举字面量的作用域就归属于这个枚举类了,避免了重定义冲突;同时也避免了相同数值的枚举字面量进行隐式转换,保证枚举是强类型的。
1enum class FileAccess {
2 Read,
3 Write,
4};
5
6enum class FileShare {
7 Read, //ok
8 Write, //ok
9};
C++11还支持指定枚举类的存储类型,从而可以支持前置声明:
1//前置声明
2enum class FileAccess : char;
3
4//枚举类定义
5enum class FileAccess : char {
6 Read,
7 Write,
8};
布尔型字面量
有两个布尔型字面量,即true
和false
。
指针型字面量
C++11定义了一个指针型字面量,即nullptr
。
用户自定义的字面量
用户定义字面量,user-defined literal,是C++11新加入的特性。
用户定义字面量
C++11允许我们定义一个用户定义后缀(user-defined suffix)来把原生的整型、浮点型、字符、字符串等字面量转换为我们自己需要的形式,这个形式可以是原生数据类型,也可以是自定义类。
具体地,字面量会自动调用一个函数来转换它的类型。由用户定义的字面量调用的函数称为字面量运算符。如果它是模板,则称为字面量运算符模板。
- 字面量运算符
语法定义如下:
1return_type operator"" custom_suffix (parameters);
- 字面量运算符模板
语法定义如下:
1template <char...> return_type operator"" custom_suffix ();
注意这两种语法定义中的自定义后缀必须以下划线_
开头,并符合标识符命名规则。
另外,字面运算符的返回值类型无限制,但是参数只能是以下类型:
支持的参数类型 | 说明 |
---|---|
const char* | 原始字符串字面量,整型、浮点用户定义字面量的候选方式 |
unsigned long long int | 整型用户定义字面量的首选方式 |
long double | 浮点型用户定义字面量的首选方式 |
char | |
wchar_t | |
char8_t | |
char16_t | |
char32_t | |
const char*, size_t | |
const wchar_t*, size_t | |
const char8_t*, size_t | |
const char16_t*, size_t | |
const char32_t*, size_t |
另外字面量运算符和字面量运算符模板是普通函数和模板函数,它们可以声明为inline
或constexpr
。
1constexpr long double operator"" _mm(long double x) { return x; }
2constexpr long double operator"" _cm(long double x) { return x * 10; }
3constexpr long double operator"" _m(long double x) { return x * 1000; }
4
5auto length = 100_mm; //100.0
6auto height = 3.0_cm; //30.0
7auto width = 1.5_m; //1500.0
再看另一个例子,用户定义字面量转换为自定义类型:
1struct S {
2 int value;
3 void print() { std::cout << "value = " << value << std::endl; }
4};
5
6S operator""_zxl(unsigned long long v) {
7 S s;
8 s.value = (int)v;
9 return s;
10}
11
12int main() {
13 //2022_zxl.print(); //error: unable to find numeric literal operator ‘operator""_zxl.print’
14 2022_zxl .print(); //value = 2022
15 (2022_zxl).print(); //value = 2022
16 return 0;
17}
注意这里2022_zxl
转换为S
类型的对象后,要调用对象的成员函数,要么后面接空格,要么用圆括号把用户自定义字面量包裹起来。否则将被编译器当作_zxl.print()
操作符来看待,而编译器找不到这样的重载操作符,所以会报错。