当前位置: C语言 -- 基础 -- 词法元素

词法元素(七)

五、字符串字面量

C语言中字符串字面量(string literals)可以使用以下格式表示:

encoding-prefixopt"s-char-sequenceopt"

其中encoding-prefix是编码前缀,例如:u8LuUs-char-sequence是字符序列,两者都是可选的。

s-char-sequence中字符为源字符集的任何成员(双引号、反斜杠、换行符除外)或者转义序列。双引号、反斜杠、换行符可以使用转义序列表示,例如:\"\\\n。在字符串字面量中,单引号既可以使用字符'表示,也可以使用转义序列\'表示;但双引号只能使用转义序列\"表示。


字符字符串字面量(character string literal)无编码前缀;UTF-8字符串字面量存在编码前缀u8wchar_t字符串字面量、UTF-16字符串字面量、UTF-32字符串字面量统称为宽字符串字面量(wide string literals)。宽字符串字面量(wide string literal)存在编码前缀Lu或者U。不同编码前缀对应的字符串字面量如下表所示:

字符串字面量前缀及其对应类型
类别 前缀 对应类型 示例
  字符字符串字面量   char *   "中国"
  UTF-8字符串字面量 u8   char8_t *   u8"中国"
  宽字符串字面量 L   wchar_t *   L"中国"
u   char16_t *   u"中国"
U   char32_t *   U"中国"

:如果实现定义了宏__STDC_UTF_16__char16_t类型字符将使用UTF-16编码;如果实现定义了宏__STDC_UTF_32__char32_t类型字符将使用UTF-32编码。有些实现中wchar_t类型和char16_t类型是兼容类型。)


字符字符串字面量是使用双引号括起来的0个或者至少一个多字节字符的序列,例如:"""A""中秋节"UTF-8字符串字面量和宽字符串字面量的构成方式与字符字符串字面量基本相同,唯一不同的是存在编码前缀u8Lu或者U,例如:u8"\u4e2d\u56fd"L"家"u"\u4e2d\u79cb\u8282"U"Hello"

对于字符字符串字面量,序列中每个元素视为是一个整型字符常量;对于UTF-8字符串字面量,序列中每个元素视为是一个UTF-8字符常量;对于宽字符串字面量,序列中每个元素视为是一个宽字符常量。


编译的第6个步骤,相邻字符字符串字面量和具有相同前缀的字符串字面量会串联成单个多字节字符序列,例如:"a" "b"会串联成"ab"L"a" L"b"会串联成L"ab"

字符字符串字面量和UTF-8字符串字面量或者宽字符串字面量串联时,得到的多字节字符序列与UTF-8字符串字面量或者宽字符串字面量具有相同的编码前缀,例如:"a" L"b"会串联成L"ab"

具有不同编码前缀的字符串字面量能否串联,ISO/IEC 9899:2018标准未作明确规定;但ISO/IEC 9899:2024标准明确规定:相邻字符串字面量如果存在编码前缀,编码前缀应相同。


编译的第7个步骤,一个值为0的字节或者代码会添加到源字符串字面量的多字节字符序列的末尾(因此字符串字面量可能不是字符串,因为空字符可以通过转义序列\0嵌入其中。);然后这个多字节字符序列会用于初始化一个具有静态存储期限的数组,数组长度刚好包含该多字节字符序列。

对于字符字符串字面量,数组元素的类型是char类型,数组元素使用与文字编码对应的多字节字符序列的单个字节初始化。对于UTF-8字符串字面量,数组元素的类型是char8_t类型,数组元素使用UTF-8编码的多字节字符序列的字符初始化。

对于宽字符串字面量,如果编码前缀是L,数组元素的类型是wchar_t类型,数组元素使用与宽文字编码对应的宽字符序列初始化;如果编码前缀是u,数组元素的类型是char16_t类型,数组元素使用对应UTF-16编码文本的宽字符序列初始化;如果编码前缀是U,数组元素的类型是char32_t类型,数组元素使用对应UTF-32编码文本的宽字符序列初始化。

如果字符串字面量包含非执行字符集的多字节字符或者转义序列,其值将由实现定义。存在u8uU编码前缀的字符串中任何十六进制转义序列或者八进制转义序列都指定了单个char8_tchar16_t或者char32_t类型值,并可能导致整个字符序列不是有效的UTF-8UTF-16或者UTF-32字符序列。


1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
53 
54 
55 
56 
57 
58 
59 
60 
61 
62 
63 
64 
65 
66 
67 
68 
69 
70 
71 
72 
73 
74 
75 
76 
77 
78 
79 
80 
81 
82 
83 
84 
85 
86 
87 
88 
89 
90 
91 
92 
93 
94 
95 
96 
97 
98 
99 
100 
101 
102 
103 
104 
105 
106 
107 
108 
109 
110 
111 
112 
113 
114 
115 
116 
/*
** 不同编码前缀的字符串字面量的比较。
*/

#include <inttypes.h>
#include <locale.h>
#include <stdint.h>
#include <stdio.h>
#include <wchar.h>
#include <uchar.h>

/*声明数组元素输出函数。*/
void charFunc(char *ptr);
void wchar_tFunc(wchar_t *ptr);void char8_tFunc(char8_t *ptr);void char16_tFunc(char16_t *ptr);
void char32_tFunc(char32_t *ptr);

/*定义泛型宏。*/
#define FUNC(x) _Generic((x),                   \		    wchar_t *: wchar_tFunc,     \		    char8_t *: char8_tFunc,     \		    char16_t *: char16_tFunc,   \
                    char32_t *: char32_tFunc,   \
                    default: charFunc           \
                    )(x)

int main(void)
{
    setlocale(LC_ALL, "zh_CN.UTF-8");
    
    char *ptr = "中国";    char8_t *u8Ptr = u8"中国";    wchar_t *LPtr = L"中国";
    char16_t *uPtr = u"中国";
    char32_t *UPtr = U"中国";
    
    printf("  \"中国\": ");
    FUNC(ptr);
    
    printf("u8\"中国\": ");
    FUNC(u8Ptr);
    printf(" L\"中国\": ");
    FUNC(LPtr);
    
    printf(" u\"中国\": ");
    FUNC(uPtr);
    
    printf(" U\"中国\": ");
    FUNC(UPtr);
    
    return 0;
}

/*定义数组元素输出函数。*/
void charFunc(char *ptr)
{
    int i = 0;
 
    while(ptr[i] != 0)
    {
        printf("%X ", (unsigned char)ptr[i++]);
    }
    
    puts("");
}

void char8_tFunc(char8_t *u8ptr)
{
    int i = 0;
     
    while(u8ptr[i] != 0)
    {
        printf("%"PRIX8" ", (uint8_t)u8ptr[i++]);
    }
    
    puts("");
}

void wchar_tFunc(wchar_t *Lptr)
{
    int i = 0;
     
    while(Lptr[i] != 0)
    {
        printf("%X ", (unsigned int)Lptr[i++]);
    }
    
    puts("");
}

void char16_tFunc(char16_t *uptr)
{
    int i = 0;
    
    while(uptr[i] != 0)
    {
        printf("%"PRIX16" ", (uint16_t)uptr[i++]);
    }
    
    puts("");
}

void char32_tFunc(char32_t *Uptr)
{
    int i = 0;
    
    while(Uptr[i] != 0)
    {
        printf("%"PRIX32" ", (uint32_t)Uptr[i++]);
    }
    
    puts("");
}

将输出:

  "中国": E4 B8 AD E5 9B BD

 L"中国": 4E2D 56FD

 u"中国": 4E2D 56FD

 U"中国": 4E2D 56FD

:使用ideone编译。char8_t类型是ISO/IEC 9899:2024标准新增类型,实现可能还未支持该类型;上述输出是删除黄色背景代码后的输出结果。如果编译环境中wchar_t类型和char16_t类型是兼容类型,例如:Windows 11,应删除绿色背景代码。)


在这个例子中定义了一个泛型宏FUNC,泛型宏FUNC会根据参数类型调用对应函数输出数组元素。

CP936中,汉字中的编码为D6D0汉字国的编码为B9FA。在UTF-8中,汉字中的编码为E4 B8 AD汉字国的编码为E5 9B BD汉字中的Unicode值为\0x4E2D汉字国的Unicode值为\0x56FD


值相同的字符串字面量是否具有相同的存储地址,ISO/IEC 9899:2024标准未作明确说明,例如:表达式"China" == "China"值可能是0,也可能是1,具体取决于实现。

如果试图修改字符串字面量,其行为是未定义的。

"China"[0] = 'c';       //未定义行为。