C++11/14 literals:part 1


這篇基本上是 C++11C++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*

在上面的例子裡面,如果只有寫「1.0」的話,會被判斷成是 double,而如果是要把它作為 float 來處理的話,則就是要在後面加上一個「f」。

而在字串的部分,一般直接寫的話,會被認定是一般的字元(char)陣列;如果在前面加上「L」的話,編譯器則會把它當作寬字元(wchar_t)陣列來處理。

根據類型的不同,可以分為 integer literalfloating literalcharacter literalstring 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 = Rdelimiter( 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_tchar32_t 有什麼用、要怎麼用 Heresy 其實也還不知道,可能要等之後有碰到在研究了吧?

而 RAW literal 也算是實用的東西,他基本上可以用來把沒有使用脫逸字元(\)的文字、直接處理成字串;也就是不必再去處理字串中 \ 這類的特殊字元,相對起來算是比較方便。

下面就是一個例子:

const char* s1 = R"a(Hello ""
World \t
!)a";

這邊的寫法基本上就是用「a(」和「)a」這樣的「delimiter」、把所需要的文字包起來,作為 raw characters 來使用。

實際上他是等同於下面的寫法的:

const char* s2 = "Hello \"\"\nWorld \\t\n!";

如果是要直接把其他地方複製來的純文字拿來用(例如 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 就可以了!
在上面的例子裡,s1s2 都代表一秒,是完全相同的。

而其他,也還有很多其他的單位,包括了:hminmsusns
(C++20 還有加入 dy

所以如果要寫一個比較複雜的時間的話,就可寫成

auto mTime = 1h + 2min + 3s;

這樣相對簡單的形式了~

不過這邊也要注意,為了避免可能的衝突,這些預先定義好的 literal operator 是被定義在 std::literals::chrono 這個 namespace 下;所以要使用的話,必須要先透過 using namespace 來做設定。

而應該是為了方便起見,標準函式庫的設計是讓使用者也可以透過 std::literalsstd::chrono_literals 這兩個 namespace 來做存取;這邊的範例就是使用 std::literals


complex

而在虛數的部分,則是有 iifil 三種 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_literalsstd::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\0\0def";
  std::string s2 = "abc\0\0def"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、字串建立的時候不會因為 \0 而被切斷、所以長度還是 8;但是一般傳統的寫法,遇到 \0 字串就斷了,所以長度只剩下 3。

但是這到底有什麼用?恩,不清楚。

另外,C++17 也有提供對應 std::string_viewsv參考),不過這邊就先不提了。


這篇就先這樣了。

至於怎麼自己定義 literal opertaor,之後有空再找時間寫得更詳細一點吧。

對「C++11/14 literals:part 1」的想法

  1. string literal 代表你所給的字串值的型別是 std::string 而不是 c-string,所以 s1 會因為是 c-string 而被 null character 所終止; s2 則是完整的 string 的值。

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Google photo

您的留言將使用 Google 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.