這篇基本上是 C++11 與 C++14 針對「literal」(不確定該怎麼翻譯,Cpp Reference 翻譯成「字面量」、MSDN 則是翻譯成「常值 」)的一些新特性做介紹。
所謂的 literal 在 Heresy 來看,主要的功能就是在透過透過數字、或是字串來建立新的變數時,強制指定型別的一種方式。
之前的 C++ 標準中就已經存在了,下面就是一些例子:
auto fVal = 1.0f; // float auto dVal = 1.0; // double auto sVal = "test"; // const char* auto wVal = L"test"; // const wchar_t*
<!–more–>
在上面的例子裡面,如果只有寫「1.0」的話,會被判斷成是 double,而如果是要把它作為 float 來處理的話,則就是要在後面加上一個「f」。
而在字串的部分,一般直接寫的話,會被認定是一般的字元(char)陣列;如果在前面加上「L」的話,編譯器則會把它當作寬字元(wchar_t)陣列來處理。
根據類型的不同,可以分為 integer literal、floating literal、character literal 和 string literal。
到了 C++11 以後,除了編譯器內建的 literal 類型又有增加之外,更重要的是也提供了「User-defined literals」(參考)、允許開發者定義自己的 literal。
新增的 literal
在 C++11 中,新增的 literal 有那些呢?Heesy 個人覺得比較可能用的到的有:
- binary-literal:
- C++14 新增的,對於 bitmask 的使用來說會很方便
- 範例:int b = 0b101010;
- RAW literal:
- C++11 新增的,允許文字有任何特殊字元
- 他的格式是 const char* s = R“delimiter( raw )delimiter“
- UTF literal:C++11 新增的,指定字串是 UTF 的形式
- UTF-8:const char* u8Val = u8“test”;
- UTF-16:const char16_t* u16Val = u“test”;
- UTF-32:const char32_t* u32Val = U“test”;
不過老實說,char16_t 和 char32_t 有什麼用、要怎麼用 Heresy 其實也還不知道,可能要等之後有碰到在研究了吧?
而 RAW literal 也算是實用的東西,他基本上可以用來把沒有使用脫逸字元()的文字、直接處理成字串;也就是不必再去處理字串中 、“ 這類的特殊字元,相對起來算是比較方便。
下面就是一個例子:
const char* s1 = R"a(Hello "" World t !)a";
這邊的寫法基本上就是用「a(」和「)a」這樣的「delimiter」、把所需要的文字包起來,作為 raw characters 來使用。
實際上他是等同於下面的寫法的:
const char* s2 = "Hello ""nWorld \tn!";
如果是要直接把其他地方複製來的純文字拿來用(例如 shader、HTML 等等的內容)的話,使用這樣的方法,算是相對方便的~
另外,針對整數和浮點樹的部分,以往在數字很長的時候,往往會難以計算位數;而在 C++14 的時候,為了解決這個問題,也允許在數字間任意加入單引號(‘)作為分隔、方便人類來處理。
下面就是一些例子:
float fPI2 = 3.141'592'653f; int iNum = 1'234'567'890; int bMask = 0b0000'1111'0000'1111;
這邊的結果,是和把 ‘ 拿掉的結果完全相同的。
User-defined literals
前面講的都是內建的 literal。而實際上,C++ 11 也提供了讓使用者可以擴充、定義自己的 literal、方便快速建立變數的機制,他就是 literal operator。
下面就是一個簡單的例子(參考):
#include <iostream> constexpr long double operator "" _deg(long double deg) { return deg * 3.14159265358979323846264L / 180; } int main(int argc, char** agrv) { double dVal = 180.0_deg; }
這邊的 operator “” _deg() 這個看來很特殊的函式,就是 literal operator。其中,「_deg」就是由開發者自定義的文字。
在使用時,就是在數字後面加上「_deg」,他就會去使用這個 operator 來做處理了~像這邊的 dVal 結果就會是 3.14… 了。
而在這邊雖然是回傳一個 long double,但是實際上比較實用的狀況,應該會是回傳一個自訂的型別。像是標準函式庫中的時間函式庫 chrono、或是虛數(complex),其實在 C++14 後、也有提供相關的定義。
標準函式庫提供的 literal
chrono
以 chrono 來說,他的時間的變數是採用不同單位就是不同的類別的設計,雖然可以互相轉換,但是在撰寫上,其實相對地也滿麻煩的;而透過 C++14 加入、預先定義好的 literal operator,則可以有效地簡化時間的值的定義。
下面就是一個簡單的例子:
#include <iostream> #include <chrono> using namespace std::literals; int main(int argc, char** agrv) { auto s1 = std::chrono::seconds(1); auto s2 = 1s; }
本來要宣告出一個代表一秒的變數,需要寫成 std::chrono::seconds(1),算是滿長的;而如果是透過預先定義好的 literal 的話,只需要寫 1s 就可以了!
在上面的例子裡,s1 和 s2 都代表一秒,是完全相同的。
而其他,也還有很多其他的單位,包括了:h、min、ms、us、ns。
(C++20 還有加入 d、y)
所以如果要寫一個比較複雜的時間的話,就可寫成
auto mTime = 1h + 2min + 3s;
這樣相對簡單的形式了~
不過這邊也要注意,為了避免可能的衝突,這些預先定義好的 literal operator 是被定義在 std::literals::chrono 這個 namespace 下;所以要使用的話,必須要先透過 using namespace 來做設定。
而應該是為了方便起見,標準函式庫的設計是讓使用者也可以透過 std::literals 或 std::chrono_literals 這兩個 namespace 來做存取;這邊的範例就是使用 std::literals。
complex
而在虛數的部分,則是有 i、if、il 三種 literal(參考),個別代表不同型別的虛數部分。
下面就是一個簡單的例子:
#include <iostream> #include <complex> using namespace std::literals; int main(int argc, char** agrv) { auto x = 10.0 + 5i; std::cout << x; }
以這個例子來說,x 的型別會是 std::complex<double>(不過在 g++ 下,使用 auto 似乎型別會被誤判?);而他的實部是 10、虛部是 5。
這樣寫起來,算是相對簡單不少了~
而這些 literal 則是被定義在 std::literals::complex_literals 這個命名空間之下,也可以透過 std::complex_literals 或 std::literals 來做存取。
string
另外,std::string 也有提供一個新的 string literal s。老實說,個人不太知道它的功能到底是什麼?
在 CppReference 這邊倒是提供了一個例子(網頁):
#include <iostream> #include <string> using namespace std::literals; int main(int argc, char** argv) { std::string s1 = "abc def"; std::string s2 = "abc def"s; std::cout << "s1: " << s1.size() << " "" << s1 << ""n"; std::cout << "s2: " << s2.size() << " "" << s2 << ""n"; }
他執行的結果,可能會是:
s1: 3 "abc" s2: 8 "abc^@^@def"
可以看到,在使用 string literal 的 s2、字串建立的時候不會因為 而被切斷、所以長度還是 8;但是一般傳統的寫法,遇到 字串就斷了,所以長度只剩下 3。
但是這到底有什麼用?恩,不清楚。
另外,C++17 也有提供對應 std::string_view 的 sv(參考),不過這邊就先不提了。
這篇就先這樣了。
至於怎麼自己定義 literal opertaor,之後有空再找時間寫得更詳細一點吧。