C++11 用户自定义字面量,在C/C++程序中,程序员常常会使用结构体或者类来创造新的类型,以满足实际的需求。比如在进行科学计算时,用户可能需要用到复数(通常会包含实部和虚部两部分)。而对于颜色,用户通常会需要一个四元组(三原色及Alpha)。而对于奥运会组委会,他们则常常会需要七元组(标示来自七大洲的状况)。不过,有的时候自定义类型也会有些书写的麻烦,尤其是用户想声明一个自定义类型的“字面量”(literal)的时候。我们可以看看代码清单3-44所示的例子。
代码清单3-44
#include <iostream>
using namespace std;
typedef unsigned char uint8;
struct RGBA{
uint8 r;
uint8 g;
uint8 b;
uint8 a;
RGBA(uint8 R, uint8 G, uint8 B, uint8 A = 0):
r(R), g(G), b(B), a(A){}
};
std::ostream & operator << (std::ostream & out, RGBA & col) {
return out << "r: " << (int)col.r
<< ", g: " << (int)col.g
<< ", b: " << (int)col.b
<< ", a: " << (int)col.a << endl;
}
void blend(RGBA & col1, RGBA & col2){
cout << "blend " << endl << col1 << col2 << endl;
}
int main() {
RGBA col1(255, 240, 155);
RGBA col2({15, 255, 10, 7}); // C++11风格的列表初始化
blend(col1, col2);
}
我们在main函数中想对两个确定的RGBA变量进行运算。这里我们采用了传统的方式,即先声明两个RGBA的变量,并且赋予相应初值,再将其传给函数blend。程序员在编写测试用例的时候,常会遇到需要声明较多值确定的RGBA变量。那么这样的声明变量-传值运算的方式是件非常麻烦的。如果自定义类型可以像内置类型一样向函数传递字面常量,比如向函数func传递字面常量func(2, 5.0f)
,无疑这样的测试代码会简化很多。
C++11标准允许了这种想象,即我们可以通过定一个后缀标识的操作符,将声明了该后缀标识的字面量转化为需要的类型。我们可以看一看代码清单3-45所示的代码。
代码清单3-45
#include <cstdlib>
#include <iostream>
using namespace std;
typedef unsigned char uint8;
struct RGBA{
uint8 r;
uint8 g;
uint8 b;
uint8 a;
RGBA(uint8 R, uint8 G, uint8 B, uint8 A = 0):
r(R), g(G), b(B), a(A){}
};
RGBA operator "" _C(const char* col, size_t n) { // 一个长度为n的字符串col
const char* p = col;
const char* end = col + n;
const char* r, *g, *b, *a;
r = g = b = a = nullptr;
for(; p != end; ++p) {
if (*p == 'r') r = p;
else if (*p == 'g') g = p;
else if (*p == 'b') b = p;
else if (*p == 'a') a = p;
}
if ((r == nullptr) || (g == nullptr) || (b == nullptr))
throw;
else if (a == nullptr)
return RGBA(atoi(r+1), atoi(g+1), atoi(b+1));
else
return RGBA(atoi(r+1), atoi(g+1), atoi(b+1), atoi(b+1));
}
std::ostream & operator << (std::ostream & out, RGBA & col) {
return out << "r: " << (int)col.r
<< ", g: " << (int)col.g
<< ", b: " << (int)col.b
<< ", a: " << (int)col.a << endl;
}
void blend(RGBA && col1, RGBA && col2) {
// Some color blending action
cout << "blend " << endl << col1 << col2 << endl;
}
int main() {
blend("r255 g240 b155"_C, "r15 g255 b10 a7"_C);
}
这里,我们声明了一个字面量操作符(literal operator)函数: RGBA operator "" _C(const char* col, size_t n)
函数。这个函数会解析以_C为后缀的字符串,并返回一个RGBA的临时变量。有了这样一个用户字面常量的定义,main函数中我们不再需要通过声明RGBA类型的声明变量-传值运算的方式来传递实际意义上的常量。通过声明一个字符串以及一个_C后缀,operator "" _C
函数会产生临时变量。blend函数就可以通过右值引用获得这些临时值并进行计算了。这样一来,用户就完成了定义自定义类型的字面常量,main函数中的代码书写显得更加清晰。
除去字符串外,后缀声明也可以作用于数值,比如,用户可能使用60W、120W的表示方式来标识功率,用50kg来表示质量,用1200N来表示力等。
代码清单3-46
struct Watt{ unsigned int v; };
Watt operator "" _w(unsigned long long v) {
return {(unsigned int)v};
}
int main() {
Watt capacity = 1024_w;
}
// 编译选项:g++ -std=c++11 3-8-3.cpp
这里我们用_w后缀来标识瓦特。除了整型数,用户自定义字面量还可以用于浮点型数等的字面量。不过必须注意的是,在C++11中,标准要求声明字面量操作符有一定的规则,该规则跟字面量的“类型”密切相关。C++11中具体规则如下:
- 如果字面量为整型数,那么字面量操作符函数只可接受
unsigned long long
或者const char*
为其参数。当unsigned long long
无法容纳该字面量的时候,编译器会自动将该字面量转化为以\0
为结束符的字符串,并调用以const char*
为参数的版本进行处理。 - 如果字面量为浮点型数,则字面量操作符函数只可接受
long double
或者const char*
为参数。const char*
版本的调用规则同整型的一样(过长则使用const char*
版本)。 - 如果字面量为字符串,则字面量操作符函数函数只可接受
const char*, size_t
为参数(已知长度的字符串)。 - 如果字面量为字符,则字面量操作符函数只可接受一个char为参数。
总体上来说,用户自定义字面量为代码书写带来了极大的便利。此外,在使用这个特性的时候,应该注意以下几点:
- 在字面量操作符函数的声明中,
operator ""
与用户自定义后缀之间必须有空格。 - 后缀建议以下划线开始。不宜使用非下划线后缀的用户自定义字符串常量,否则会被编译器警告。当然,这也很好理解,因为如果重用形如201203L这样的字面量,后缀“L”无疑会引起一些混乱的状况。为了避免混乱,程序员最好只使用下划线开始的后缀名。
酷客教程相关文章:
评论前必须登录!
注册