字面常數 (C語言)

字面常數(literal constant),是C程式設計語言C++語言的詞法上的概念(lexical conventions),[1]是指源程式中表示固定值的符號(token)。[2]

下述內容遵從C11C++11語言標準。

整型字面常數

編輯

包括下述的數值進制:

  • 10進制: 如1234;
  • 8進制:如0373;
  • 16進制:如0x2a7;
  • 2進制(從C++14開始):如0b101; 或者0B0101;

整型字面量轉化為整數類型表示,依照下述順序:[3]

  • int
  • unsigned int
  • long int
  • unsigned long int
  • long long int
  • unsigned long long int

其中,10進制表示的字面量僅考慮有符號的整數類型;而8進制或16進制的字面量先考慮能否用有符號的整數類型表示,如不能再考慮能否用同樣長度的無符號整數類型表示。例如,字面量0x87654321,這是一個正值,用4位元組長的int無法表示,編譯器就會自動選用unsigned int來表示該字面量。

整型字面量可以使用字尾u U ul UL ull ULL明確表示各種無符號整型;使用字尾l L ll LL表示該字面量至少為long或為long long型。

C++14引入了千分位分隔符。例如:

    auto integer_literal = 1'000'000;

C++14還引入了二進制字面量,例如:

    auto perm = 0b100110111;

浮點型字面常數

編輯

包括下述的數值進制:

  • 10進制:如 2.3 2e-3 2.e-5 2.12e15等。如果不是科學計數法形式,就必須有小數點,小數點前或後的數字可省略;
  • 16進制(C語言特有,C99引入):如 0x1p10(值為102410) 0x1.0004p10(值為1024.062510)等。

沒有字尾的浮點型字面量具有double類型。使用字尾 f F l L表示float或long double類型。

C++14引入了千分位分隔符。例如:

    auto dd = 1'234.567'8;

字元型字面常數

編輯

字元型字面量(character literal)是用單引號括起來的一個或多個字元,可選字首字元有u、U、L。[4]

沒有字首字元的稱為普通字元字面量(ordinary character literal)或窄字元字面常數(narrow-character literal)

只包含執行字元集(execution character set)中一個可表示字元的普通字元字面量,如:'a',則其類型為char,其值等於該字元在執行字元集中的編碼值。實際上,編譯器在token分析階段,通常就會把字元與字串在原始檔中的編碼串轉換為指定或者執行字元集的編碼串。

包含多個字元的普通字元字面常數,被稱為多字元字面量(multicharacter literal)[4]。多字元字面量,以及包含執行字元集(execution character set)中一個不可表示字元的普通字元字面量,被有條件支援,具有類型int,其值為實現定義。[5]各常見C/C++編譯器一般都支援如下例子:

    // Multicharacter literals
    int m0 = 'abcd'; // int, value 0x61626364

需要注意的是,在C語言中字元常數'a'具有int類型,sizeof('a')等於sizeof(int);在C++語言中字元字面量'a'具有char類型,sizeof('a')為1。

使用字首L u U分別表示wchar_t char16_t char32_t等字元類型的字面量。例如u'y'。一個wchar_t類型的字面量的值,就是該字元在執行寬字元集(execution wide-character set)中的編碼值;單如果該字元在執行寬字元集中不可表示,則其值是實現定義的。一個char16_t類型的字面量的值,就是該字元在Unicode編碼中的16位元的碼位;顯然該字元必須屬於Unicode中的基本多文種平面才能用16位元來編碼,否則程式是病態的。一個char32_t類型的字面量的值,就是該字元在Unicode編碼中的32位元的碼位。這些字元類型在x86平台均為小端序表示。char16_t字面量或char32_t字面量,如果包含多個字元,是病態的。wchar_t 字面量,如果包含多個字元,其值是實現定義的。

由於字元型字面量可能不屬於C/C++的token的字元範圍,這就需要用反斜線\開始的跳脫序列來表示一個字元值:

  • 簡單跳脫序列:\' \" \? \\ \a \b \f \n \r \t \v 共計11個字元;
  • 八進制跳脫序列:如 \1 \12 \123等等,直至不是八進制數字為止,最多使用3位八進制數字;
  • 十六進制跳脫序列:如 \x1abf4 ,可以使用任意多的十六進制數字,直至不是十六進制數字為止;
  • 通用字元名(universe-character name):\u後面必須跟4個十六進制數字(不足四位前面用零補齊),表示Unicode中在0至0xFFFF之內的碼位(但不能表示0xD800到0xDFFF之內的碼點,Unicode標準規定這個範圍內的碼位保留,不表示字元);
  • 32位元的通用字元名:\U後面必須跟8個十六進制數字(不足八位前面用零補齊),表示Unicode中所有可能的碼位(除0xD800到0xDFFF之外)。

上述兩種Unicode跳脫序列的字元表示法,由編譯器自動轉換為相應的字元內部編碼格式。如為wchar_t字元。

如果字元字面量的值超過了char、char16_t、char32_t、wchar_t實現定義的範圍,那麼其值是實現定義的。例如,原始檔是Latin-1編碼,執行字元集為utf-8,則char c='ö';中的字元值將被編譯器從Latin-1編碼的單位元組的0xD6自動轉為utf-8編碼的雙位元組的0xC3B6,在目的檔可執行檔中字元c的值是'ö'的utf-8編碼值的最後一個位元組值即0xB6。

為支援老的代碼,C語言規定了三字元組替換,在掃描處理C語言原始檔時,替換下述的3字元出現為1個字元:

三字元組 替換為
??= #
??/ \
??' ^
??( [
??) ]
??! |
??< {
??> }
??- ~

如果希望在源程式中有兩個連續的問號,且不希望被預處理器替換,這種情況出現在字元字面常數、字串字面常數或者是程式注釋中,可選辦法是用字串的自動連接:"...?""?..."或者跳脫序列"...?\?..."

Microsoft Visual C++ 2010版開始,該編譯器預設不再自動替換三字元組。如果需要使用三字元組替換(如為了相容古老的軟體代碼),需要設定編譯器命令列選項/Zc:trigraphs

gcc預設不辨識三字元組,並會給出編譯警告。在指定了C/C++標準時,gcc才會辨識三字元組,但仍會給出編譯警告。

字串字面常數

編輯

C語言經典的ASCII-0字串,例如"Hello world!"。

R"<someChars>opt()<someChars>opt"括起來的字串字面量叫做raw string literal,這可以避免大量使用反斜線跳脫字元造成的令人眼花繚亂的傾斜牙籤症候群,特別適用於定義正規表示式的模式字串時。例如:

 std::string filePath = R"(C:\Foo\Bar.txt)";
 std::regex re{ R"abc(s/"\([^"]*\)"/'\1'/g)abc" }; //如果字符串包含了)"这两个字符的组合,可选别的分界符,如abc。但这个分界符序列的长度最多16。

字串字面量的值預設為工作字元集的編碼。[6]使用編碼字首(encoding-prefix): u8 u U L 指出字串字面量的值為UTF-8、char16_t、char32_t、wchar_t的編碼序列。例如,

 char ss[]=u8"Hi世界";// ss数组内保存的是'H'、'i'、'世'、'界'这四个字符的UTF-8的编码值

相鄰的兩個字串將被編譯器自動連接為一個字串。如:

 char ss[]="Hello" " world.";

列舉常數

編輯

列舉類型實質上是取有限個值的整型。根據C與C++98標準,列舉常數屬於定義了列舉類型的那個作用域,而不屬於這個列舉類型的內部作用域。C++發明人Bjarne Stroustrup稱之為列舉常數的作用域不受限(unscoped)現象,這會造成命名衝突,與列舉常數是「有類型的常數」的初衷不符,與物件導向程式設計的原則嚴重相悖。例如:

enum FileAccess {
    Read = 0x1,
    Write = 0x2,
};
FileAccess access = ::Read; // 正确
FileAccess access = FileAccess::Read; // 错误
enum FileShare {
   Read = 0x1, // 重定义错误
   Write = 0x2, // 重定义错误
};

C++11為解決上述問題,引入了「列舉類」(enum class)。在定義列舉類型時,enum關鍵字後面加上class關鍵字(或struct關鍵字);參照列舉常數時,必須加上列舉類型名使其為作用域限定 (scoped)。這避免了列舉常數的命名衝突;同時也避免了不同列舉類型的值的隱式類型轉換,從而保證了列舉類型是強型別(strongly typed)的。例如:

enum class Color { RED, BLACK }; 
Color c = Color::RED;

C++11還允許指定列舉類型的儲存類型,這使得列舉類型的前向聲明(forward declaration)成為可能。例如:

enum class Color : char ; // forward declaration
void foo (Color *p);// ...
// ...
enum class Color : char { RED, BLUE }; // definition

布林型字面常數

編輯

有兩個bool類型的字面常數:true false

指標型字面常數

編輯

C++11定義了一個字面常數nullptr,其類型是std::nullptr_t。但std::nullptr_t既不是指標類型也不是到成員的指標類型。

使用者定義的字面量

編輯

C++11新增加了使用者定義的字面常數(user defined literal)。由使用者給出字面常數的字尾,並給出字面量運算子函式(或模板)的定義。編譯器可在執行期編譯期把帶有這樣字尾的整型、浮點型、字元型、字串型的字面量通過呼叫使用者的字面量運算子函式(或模板),生成指定資料類型的對象。例如,

#include<iostream>
struct S{
    int value;
};
S operator ""_mysuffix(unsigned long long v)  //用户定义字面量运算符的实现
{
     S s_;
     s_.value=(int)v;
     return s_;
}

int main()
{
   S sv;
   sv=101_mysuffix;  //这里的101是类型S的字面量
   std::cout<<sv.value<<std::endl;
   return 0;
}

參考文獻

編輯
  1. ^ C++2011 ISO/IEC 14882 §2.14 Literals
  2. ^ C++11標準§2.14.1的註腳21:The term 「literal」 generally designates, in this International Standard, those tokens that are called 「constants」 in ISO C. 即C語言的constants與C++的literal是一碼事,屬於詞法分析時的token。
  3. ^ C99標準,第6.4.4.1節,條款5
  4. ^ 4.0 4.1 ISO 14882 -- 《C++14 specification》 section 2.14.3, entry 1.
  5. ^ C99語言規範 6.4.4.4p10有類似內容: "The value of an integer character constant containing more than one character (e.g., 'ab'), or containing a character or escape sequence that does not map to a single-byte execution character, is implementation-defined."
  6. ^ 具體是哪一種工作字元集編碼,由編譯器採用預設設定,如Visual C++預設使用當前作業系統的預設頁碼,簡體中文Windows就是gbk編碼,Linux上的gcc一般預設為utf8編碼;也可以作為命令列參數指定給編譯器,如gcc的"-fexec-charset= ".