在C++中,一个字符串字面值(string literal)一般是指使用双引号(””)包围起来的一段字符串,例如:

const char *s = "string literal";

有时,人们习惯于写成 char *s = “string literal”,实际上这是不对的,因为C++中的字符串字面值是一个常量,将一个常量直接赋值给一个非const变量可能会造成不可预知的行为,例如:

char *s = "ttring literal";
s[0] = 's'; // undefined behavior

在较新的编译器中,编译器会警告甚至拒绝将字符串字面值赋值给一个非const的指针,因为这些字符串常量可能被保存在内存的只读区,或与其它字符串常量共享内存,任何对字符串常量修改的行为都应当被禁止。

有些字符无法直接表示,需要用到转义来表示这些字符,例如:’\n’表示换行,’\t’表示水平制表符tab,’\”‘表示双引号,’\\’表示反斜杠自身等等,或者使用1-3位八进制字符表示一个字符’\ooo’(每个o表示一个八进制字符),或者使用1-2位十六进制表示一个字符’\xhh’(每个h表示一个十六进制字符)。例如:

const char *s = "string\040litera\x6C"; // "string literal"

但编译器在编译的时候,会在转义字符’\x’的后面贪婪地取字符,例如:

const char *s = "\xffz"; // ok, ['\xff', 'z']
const char *p = "\xfff"; // error, hex escape sequence out of range

编译器试图将’\xfff’转换成一个字符,但这超过了char的最大值限制,因此转换会失败,但实际上我们的目的是第一种情况,这时应当按照下面的写法避免这种错误:

const char *p = "\xff" "f"; // ok

编译器对相邻的两个字符串常量会进行连接,这种使用方法还可以解决另外一个问题:如果要书写一个很长的字符串字面值,写在一行内会使得代码变得很丑,使用字符串连接的特性,可以这样写:

const char *s = "a very long string begin, ..., very long string end.";
const char *p = "a very long string begin, "
    "..., "
    "very long string end.";

有时候,我们需要将一段很长的文本赋值给一个const char*, 这段文本中往往有很多换行符,甚至包含双引号以及反斜杠等需要转义的字符,这时即使使用字符串连接的方法,也需要一定的工作量才能将字符串正确地转义以及换行,而且这种方法还容易犯错。自C++11开始,C++引入了一种“原生字符串字面值(raw string literal)”来解决这个问题。例如:

const char *s = "Hello \n"
                "raw \n"
                "string \n"
                "literal";
const char *p = R"delimiter(Hello 
raw 
string 
literal)delimiter";

一个原生字符串字面值表示为R”delimiter()delimiter”,其中”delimiter”用来做字符串的分界符,任何出现在括号内的字符都将原封不动地被解析为它本来的值,包括换行符。一个不超过16个字符的delimiter可以被用来做分界符,这个操作需要小心地进行,因为需要保证括号内不会出现 )delimiter” 这个子串。例如:

const char *s = R"f(abcd()f")"; // error

字符串字面值可以包含一个前缀来表示编码格式,其中包括:

L"string literal"; // 宽字符字面值
u8"string literal"; // UTF-8 编码的字符串
u"string literal"; // UTF-16编码的字符串
U"string literal"; // UTF-32编码的字符串

原生字符串字面值也可以包含一个前缀,例如:

u8R"xx(string literal)xx"; // UTF-8编码的原生字符串

关于字符串编码涉及很多其它的知识,本文不再介绍。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注