在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编码的原生字符串
关于字符串编码涉及很多其它的知识,本文不再介绍。
本文由kedixa发表于个人博客,
转载请注明作者及出处。
本文链接:https://blog.kedixa.top/2017/cpp-string-literal/