百度百科:
在計算機科學中,字面量(literal)是用於表達源代碼中一個固定值的表示法(notation)。
C++ 中,字面量分為以下幾種:
- 整型字面量 integer literal
- 浮點型字面量 floating-point literal
- 布爾型字面量 boolean literal
- 字符字面量 character literal
- 字符串字面量 string literal
- 用戶定義字面量 user-defined literal
本文暫只對用戶定義字面量進行討論。
用戶定義字面量 User-defined literal
(從 C++11 開始啟用)
C++ 允許我們定義一個用戶定義后綴(user-defined suffix)來把原生的整型、浮點型、字符、字符串 4 種字面量轉換為我們自己需要的形式,這個形式可以是原生數據類型,也可以是自定義類。
具體地,字面量會自動調用一個函數來轉換它的類型。由用戶定義的字面量調用的函數稱為字面量運算符(或者,如果它是模板,則稱為字面量運算符模板)。
定義語法
字面量運算符的函數名以 operator"" 開頭,后面緊跟用戶定義后綴。
返回值類型 operator"" 自定義后綴 (參數);
如果字面量運算符是一個模板,它必須有一個空的參數列表。並且只能有一個模板參數,這個模板參數必須是一個元素類型為 char 的非類型模板參數包(a non-type template parameter pack with element type char)。在這種情況下它被稱為數字字面量運算符模板(numeric literal operator template)。
template <char...> 返回值類型 operator"" 自定義后綴 ();
注意,其中的自定義后綴 必須以下划線 _ 開頭,並符合標識符命名規范。
一些事實
其實“以下划線開頭”並非是從語法上強制性的。
如果你堅持不以下划線開頭,那么編譯器會給你一個警告:
[Warning] literal operator suffixes not preceded by '_' are reserved for future standardization [-Wliteral-suffix]
但是畢竟只是一個“警告”。
另外,由於字面量的特殊語法結構,自己定義的這個后綴其實可以同時是 C++ 關鍵字而不產生沖突,這是合法的。
如 C++ 標准庫 <complex> 中就定義了 if 來把整形和浮點型轉換為 complex<float> :
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wliteral-suffix"
constexpr std::complex<float>
operator""if(long double __num)
{ return std::complex<float>{0.0F, static_cast<float>(__num)}; }
constexpr std::complex<float>
operator""if(unsigned long long __num)
{ return std::complex<float>{0.0F, static_cast<float>(__num)}; }
#pragma GCC diagnostic pop
(同時使用了預編譯指令來忽略剛才所說的警告)
關於參數列表:
對於字面量運算符的參數, C++ 有語法上嚴格的規定。參數列表僅允許以下幾種類型:
| Parameter Lists | Details | ||
|---|---|---|---|
| 1 | (const char *) |
原始字面量運算符(raw literal operators),用於整數和浮點用戶定義字面量的后備方式 | |
| 2 | (unsigned long long int) |
用戶定義整型字面量運算符的首選方式 | |
| 3 | (long double) |
用戶定義浮點型字面量運算符的首選方式 | |
| 4 | (char) |
通過用戶定義字符型字面量調用 (user-defined character literals) |
|
| 5 | (wchar_t) |
||
| 6 | (char8_t) |
||
| 7 | (char16_t) |
||
| 8 | (char32_t) |
||
| 9 | (const char *, std::size_t) |
通過用戶定義字符串字面量調用 (user-defined string literals) 其中第二個參數被自動傳入字符串長度 |
|
| 10 | (const wchar_t *, std::size_t) |
||
| 11 | (const char8_t *, std::size_t) |
||
| 12 | (const char16_t *, std::size_t) |
||
| 13 | (const char32_t *, std::size_t) |
||
| 備注 | 注意char8_t從 C++20 開始啟用。不允許使用默認參數。 不允許使用 C 語言鏈接。 |
||
除了上述限制之外,字面量運算符(和字面量運算符模板)是普通函數(和函數模板),它們可以聲明為 inline 或 constexpr ,它們可能具有內部或外部鏈接,它們可以顯式調用,它們的地址可以被獲取等。
注意
由於最大吞噬規則,以 e 和 E (C++17 起還有 p 和 P) 結束的用戶定義整數和浮點字面量,在后隨運算符 + 或 - 時,必須在源碼中以空白符或括號與運算符分隔:
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
同樣的規則適用於后隨整數或浮點用戶定義字面量的點運算符:
#include <chrono>
using namespace std::literals;
auto a = 4s.count(); // 錯誤
auto b = 4s .count(); // OK
auto c = (4s).count(); // OK
否則會組成單個非法預處理數字記號(例如 1.0_E+2.0 或 4s.count),這導致編譯失敗。
最大吞噬規則
最大吞噬規則指的是編譯器在處理源文件時,翻譯階段中解析預處理記號的一種特性。
如果一個給定字符前的輸入已被解析為預處理記號,下一個預處理記號通常會由能構成預處理記號的最長字符序列構成,即使這樣處理會導致后續分析失敗。這常被稱為最大吞噬。
比如經典的 i+++++i 會被解析為 i++ ++ +i 而報錯,而不是人們所想的 i++ + ++i 。
示例
#include <iostream>
using std::cout;
namespace test
{
struct My_type
{
double value;
void print() const { cout << "print value: " << value << '\n'; }
};
namespace literal
{
// 輸出 整型 和 浮點型
void operator"" _output(const char* num)
{
cout << "output: " << num << '\n';
}
// 類型轉換
inline My_type operator"" _to_My_type(unsigned long long num)
{
My_type tmp;
tmp.value = num;
return tmp;
}
// 四舍五入
constexpr int operator"" _round(long double num)
{
return int( num + 0.5 );
}
// 獲取字符的 ASCII 碼
constexpr int operator"" _get_ascii(char c)
{
return int(c);
}
// 獲取字符串長度
constexpr size_t operator"" _get_len(const char* s, size_t len)
{
return len;
}
}
}
// 一般來說,字面量容易重名,所以常常放在命名空間里
using namespace test::literal;
int main()
{
114514_output;
3.14_output;
2022_to_My_type .print(); // 注意這里的空格
cout << "round: " << 9.86_round;
cout << "\n\t" << 9.39_round << '\n';
cout << "ascii: " << 'A'_get_ascii << '\n';
cout << "strlen: " << "abcd"_get_len << '\n';
return 0;
}
輸出結果
C++ 標准庫對用戶定義字面量的應用
此處僅舉部分例子。
| 字面量運算符 | 類型 | 作用 | 起始版本 | 備注 |
|---|---|---|---|---|
operator""if |
std::complex字面量 |
表示純虛數 | C++14 | 定義於內聯命名空間std::literals::complex_literals |
operator""i |
||||
operator""il |
||||
operator""h |
std::chrono::duration字面量 |
表示小時 | C++14 | 定義於內聯命名空間std::literals::chrono_literals |
operator""min |
表示分鍾 | |||
operator""s |
表示秒 | |||
operator""ms |
表示毫秒 | |||
operator""us |
表示微秒 | |||
operator""ns |
表示納秒 | |||
operator""y |
std::chrono::year字面量 |
表示特定年 | C++20 | |
operator""d |
std::chrono::day字面量 |
表示月內日期 | ||
operator""s |
std::basic_string字面量 |
轉換字符數組字面量為std::basic_string |
C++14 | 定義於內聯命名空間std::literals::string_literals |
operator""sv |
std::basic_string_view字面量 |
創建一個字符數組字面量的字符串視圖 | C++17 | 定義於內聯命名空間std::literals::string_view_literals |
顯然 std 沒有遵守以下划線開頭的命名規范
參考:
1.https://en.cppreference.com/w/cpp/language/user_literal
2.https://en.cppreference.com/w/cpp/language/translation_phases#.E6.9C.80.E5.A4.A7.E5.90.9E.E5.99.AC
