C++11 用戶自定義字面量


【1】為什么引入用戶自定義字面量?

在C/C++程序中,常常會使用結構體或者類來創造新的類型,以滿足實際的需求。

比如,在進行科學計算時,用戶可能需要用到復數(通常會包含實部和虛部兩部分)。

對於顏色,用戶通常會需要一個四元組(三原色及Alpha)。

對於奧運會組委會,他們則常常會需要七元組(標示來自七大洲的狀況)等等。

而當用戶想聲明一個自定義類型的“字面量”(literal)時,尤其顯得麻煩。如下示例:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 typedef unsigned char uint8;
 5 
 6 struct RGBA
 7 {
 8     uint8 r;
 9     uint8 g;
10     uint8 b;
11     uint8 a;
12     RGBA(uint8 R, uint8 G, uint8 B, uint8 A = 0)
13         : r(R), g(G), b(B), a(A)
14     {}
15 };
16 
17 ostream & operator<<(ostream& out, RGBA& col)
18 {
19     return out << "r: " << (int)col.r
20         << ", g: " << (int)col.g
21         << ", b: " << (int)col.b
22         << ", a: " << (int)col.a << endl;
23 }
24 
25 void blend(RGBA& col1, RGBA& col2)
26 {
27     cout << "blend " << endl << col1 << col2 << endl;
28 }
29 
30 int main()
31 {
32     RGBA col1(255, 240, 155);
33     RGBA col2({ 15, 255, 10, 7 });
34     blend(col1, col2);
35     system("pause");
36 }
37 
38 /*運行結果
39 blend
40 r: 255, g: 240, b: 155, a: 0
41 r: 15, g: 255, b: 10, a: 7
42 */

在程序中想通過blend函數對兩個確定的RGBA對象進行運算。采用了傳統的方式,即先聲明兩個RGBA的變量,並且賦予相應初值,再將其傳給函數blend。

在編寫測試用例的時候,常會遇到需要聲明較多值確定的RGBA變量。那么這樣的聲明變量–傳值運算的方式顯得非常麻煩。

如果自定義類型可以像內置類型一樣向函數傳遞字面常量,比如向函數func傳遞字面常量func(2, 5.0f),無疑這樣的測試代碼會方便很多。

以上即引入用戶自定義字面量的原因。

【2】自定義字面量的應用與注意事項

C++11實現了如上的願望,即可以通過定一個后綴標識的操作符,將聲明了該后綴標識的字面量轉換為需要的相應類型。

如上示例改造代碼:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 typedef unsigned char uint8;
 5 
 6 struct RGBA
 7 {
 8     uint8 r;
 9     uint8 g;
10     uint8 b;
11     uint8 a;
12     RGBA(uint8 R, uint8 G, uint8 B, uint8 A = 0)
13         : r(R), g(G), b(B), a(A)
14     {}
15 };
16 
17 RGBA operator "" _C(const char* col, size_t n)
18 {
19     const char* p = col;
20     const char* end = col + n;
21     const char* r, *g, *b, *a;
22     r = g = b = a = nullptr;
23     for (; p != end; ++p)
24     {
25         if (*p == 'r') r = p;
26         else if (*p == 'g') g = p;
27         else if (*p == 'b') b = p;
28         else if (*p == 'a') a = p;
29     }
30     if ((nullptr == r) || (nullptr == g) || (nullptr == b))
31     {
32         throw;
33     }
34     else if (nullptr == a)
35         return RGBA(atoi(r + 1), atoi(g + 1), atoi(b + 1));
36     else
37         return RGBA(atoi(r + 1), atoi(g + 1), atoi(b + 1), atoi(a + 1));
38 }
39 
40 ostream& operator<<(ostream& out, RGBA& col)
41 {
42     return out << "r: " << (int)col.r
43         << ", g: " << (int)col.g
44         << ", b: " << (int)col.b
45         << ", a: " << (int)col.a << endl;
46 }
47 
48 void blend(RGBA && col1, RGBA && col2)
49 {
50     cout << "blend " << endl << col1 << col2 << endl;
51 }
52 
53 int main()
54 {    
55     blend("r255 g240 b155"_C, "r15 g255 b10 a7"_C);
56     system("pause");
57 }
58 
59 /*運行結果
60 blend
61 r: 255, g: 240, b: 155, a: 0
62 r: 15, g: 255, b: 10, a: 7
63 */

聲明了一個字面量操作符(literal operator)函數:RGBA operator"" _C(const char*col, size_t n)函數。

這個函數會解析以_C為后綴的字符串,並返回一個RGBA的臨時變量。

有了這樣一個用戶字面常量的定義,程序中不再需要通過聲明RGBA類型的聲明變量–傳值運算的方式來傳遞實際意義上的常量。

通過聲明一個字符串以及一個_C后綴,operator""_C函數會產生臨時變量。blend函數就可以通過右值引用獲得這些臨時值並進行計算。

這樣一來,用戶就完成了定義自定義類型的字面常量,可見main函數中的代碼書寫顯得更加清晰。

注意事項:

(1)在字面量操作符函數的聲明中,operator""與用戶自定義后綴之間必須有空格。

(2)后綴建議以下划線開始。不宜使用非下划線后綴的用戶自定義字符串常量,否則會被編譯器警告。

當然,這也很好理解,因為形如201203L這樣的字面量,后綴“L”無疑會引起一些混亂的狀況。為了避免混亂,最好只使用下划線開始的后綴名。

(3)C++11標准中要求聲明字面量操作符有一定的規則,該規則跟字面量的“類型”密切相關。具體規則如下:

[1] 如果字面量為整型數,那么字面量操作符函數只可接受unsigned long long或者const char*為其參數。

當unsigned long long無法容納該字面量的時候,編譯器會自動將該字面量轉化為以\0為結束符的字符串,並調用以const char*為參數的版本進行處理。

[2] 如果字面量為浮點型數,則字面量操作符函數只可接受long double或者const char*為參數。const char*版本的調用規則同整型的一樣(過長則使用const char*版本)。

[3] 如果字面量為字符串,則字面量操作符函數函數只可接受const char*, size_t為參數(已知長度的字符串)。

[4] 如果字面量為字符,則字面量操作符函數只可接受一個char為參數。

如下應用示例:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 // 浮點數操作符
 5 long double operator"" _mm(long double x) { return x / 1000; }
 6 long double operator"" _m(long double x) { return x; }
 7 long double operator"" _km(long double x) { return x * 1000; }
 8 
 9 // 字符串操作符,第二個參數會自動推斷為字符串的長度
10 size_t operator"" _len(char const*, size_t n)
11 {
12     return n;
13 }
14 
15 // 原始字面量操作符1
16 char const* operator"" _r(char const* s)
17 {
18     return s;
19 }
20 
21 // 原始字面量操作符2
22 string operator"" _rs(char const* s)
23 {
24     return 'x' + string(s) + 'y';
25 }
26 
27 int main()
28 {
29     cout << 1.0_mm << '\n';  // 0.001
30     cout << 1.0_m << '\n';   // 1
31     cout << 1.0_km << '\n';  // 1000
32 
33     cout << "ABCDEFGH"_len << '\n'; // 8
34 
35     cout << 12_r << '\n'; // 12
36     cout << 5_rs << '\n'; // x5y
37 }

5~7行:很簡單不做解釋。

10~13行:對於字符串相當有用,因為第二個參數會自動推斷為字符串的長度。

16~19行:為原始字面量raw literal操作符。

22~25行:字面量的返回值沒有被嚴格限定,完全可以提供相容類型的返回值。

 

good good study, day day up.

順序 選擇 循環 總結


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM