使用自定义字面量简化常量的定义

在涉及文件大小的代码中,经常要写出判断大小的代码。例如,在上传文件之前,要判断文件的大小是否超出了可支持的范围:

1
2
3
if (file_size > 4 * 1024 * 1024 * 1024) { 
//超出可支持范围
}

这里出现了好几个1024,阅读这段代码的人首先要心算一遍之后才能理解“哦,原来这里的文件大小上限是4GB”。对程序员来说这种计算都是小菜一碟,即便如此,这段代码的可读性也是不高的,因为它没有直截了当地表达出它的意图。

有几种方法可以优化这段代码,例如抽取出一个GB()函数。这里要介绍的是另一种表达更自然的方法,C++11引入的新特性:自定义字面量。

字面量是指在代码中写下的数字、字符串等值,例如:

  • 整数,如1024
  • 浮点数,如3.14
  • 字符,如'a'
  • 字符串,如"abc"

如果在字面量后面加上特定后缀,可以调用对应的转换函数,将这个字面量转成特定类型。例如,在<string>中定义了s后缀,可以将一个字符串字面量转换成std::string类型:

1
2
3
4
5
6
#include <string>

//将std::literals名称空间引入当前作用域,才能使用C++内置的自定义字面量
using namespace std::literals;

std::string str = "This is a std::string."s;

我们也可以定义自己的字面量后缀和转换函数,方法是定义一个字面量操作符函数。该函数的语法如下:

1
ReturnType operator"" _Suffix(Parameters);

ReturnType是这个转换函数的返回值。_Suffix是字面量的后缀,要注意的是,开发者自定义字面量的后缀必须以_字符开头,否则会编译失败,因为不带这个字符开头的后缀是预留给C++的。Parameters是转换函数的参数列表,可以从以下几种参数列表中选择:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//整数字面量
(unsigned long long)

//浮点数字面量
(long double)

//字符字面量
(char)
(wchar_t)
(char16_t)
(char32_t)

//字符串字面量
(const char*, size_t)
(const wchar_t*, size_t)
(const char16_t*, size_t)
(const char32_t*, size_t)

//原始字面量
(const char*)

具体选择哪种参数列表,根据你的需求而定。如果你希望只支持整数,那么只需要定义一个参数列表是(unsigned long long)的操作符函数即可;如果你希望同时支持整数和浮点数,那么就要定义两个操作符函数,参数列表分别是(unsigned long long)(long double)

你只能从上面的参数列表中选择,不能使用别的参数列表,否则编译会失败。例如,即使你只需要一个int类型的整数字面量,也必须把参数定义成unsigned long long。编译器总是把字面量的值以可表示范围最大的类型传给你,具体怎么使用这个值由你自己决定。

由于C++中存在四种字符类型,所以字符字面量和字符串字面量都分别有四种参数类型。在字符串字面量的参数列表中,第二个size_t类型的参数表示字符串的长度。

如果字面量操作符函数的参数列表定义成const char*,那么它是一个原始字面量操作符函数。原始字面量操作符只能用在整数或浮点数字面量上,它的参数指向整数或浮点数的字符串。它提供了一种方法,让我们可以以自定义的规则来解析整数或浮点数字面量。

回到本文开头的问题,我们可以定义下面的字面量操作符函数来简化文件大小常量的定义:

1
2
3
constexpr std::int64_t operator"" _GB(unsigned long long value) {
return static_cast<std::int64_t>(value * 1024 * 1024 * 1024);
}

替换使用这个自定义字面量之后,代码含义一目了然,大大提高了可读性:

1
2
3
if (file_size > 4_GB) { 
//超出可支持范围
}