C++ 支持各种字符串和字符类型,并提供了表达每种类型字面值的方法。在源代码中,我们使用字符集来表示字符或字符串。同时我们还可以使用通用字符名和转义字符来通过基本的源字符集表示任何字符串。而原始字符串能够避免对转义字符进行转义,并可用于表示所有类型的字符串。

wchar_t 与 char16_t

wchar_t 是C89/C99中引入的,表示宽字符,但是该类型在不同的编译器/系统上所占的直接数不一样,Windows系统 sizeof(wchar_t) == 2,而Linux/macOS则sizeof(wchar_t) == 4。

为了更好的跨平台,C++11引入了 char16_t 类型,在各个编译器上始终 sizeof(char16_t) == 2。

两种类型分别对应的字符串类型为:

  • wchar_t 对应 std::wstring(std::basic_string
  • char16_t 对应 std::u16string(std::basic_string
1
2
3
4
5
wchar_t l = L'1';
std::wstring strL = L"1";

char16_t c = u'1';
std::u16string strC = u"11111";

字符串前缀

在C++中,字符串前缀(如u8、L、u、U)用于显式指定字符串的编码方式和字符类型。

前缀 标准来源 字符类型 编码 每个字符的大小 跨平台性
u8 C++11 char(C++11~17)
char8_t(C++20)
UTF-8 变长(1~4字节) 优秀
L C++98 wchar_t Windows平台UTF-16
其他UTF-32
固定2字节 差(编码歧义)
u C++11 char16_t UTF-16 固定2字节 优秀
U C++11 char32_t UTF-32 固定4字节 优秀
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main()
{
// 字符字面量
auto c0 = 'A'; // char
auto c1 = u8'A'; // char
auto c2 = L'A'; // wchar_t
auto c3 = u'A'; // char16_t
auto c4 = U'A'; // char32_t

// 字符串字面量
auto s0 = "hello"; // const char*
auto s1 = u8"hello"; // const char* before C++20, encoded as UTF-8,
// const char8_t* in C++20
auto s2 = L"hello"; // const wchar_t*
auto s3 = u"hello"; // const char16_t*, encoded as UTF-16
auto s4 = U"hello"; // const char32_t*, encoded as UTF-32
}

原始字符串

1
2
3
4
5
6
7
8
9
10
int main()
{
// 原始字符串可以包含 \ 和 "
auto R0 = R"("Hello \ world")"; // const char*
auto R1 = u8R"("Hello \ world")"; // const char* before C++20, encoded as UTF-8,
// const char8_t* in C++20
auto R2 = LR"("Hello \ world")"; // const wchar_t*
auto R3 = uR"("Hello \ world")"; // const char16_t*, encoded as UTF-16
auto R4 = UR"("Hello \ world")"; // const char32_t*, encoded as UTF-32
}

字符串后缀

在 C++ 14 引入了字符串后缀,常用的字符串后缀是在字符串后面加s,用于将C风格字符串字面量直接转换为std::string对象,旨在简化std::string的构造。如:

1
2
3
4
5
// 需要包含string头文件,并引用命名空间
#include <string>
using namespace std::string_literals;

auto S0 = "hello"s; // std::string

此时构造std::string时,使用的是其字面量运算符operator””s:

1
constexpr std::string operator""s(const char* str, std::size_t len) noexcept

也可以配合字符串前缀构造其他类型的字符串:

1
2
3
4
5
6
auto S0 =   "hello"s; // std::string
auto S1 = u8"hello"s; // std::string before C++20, std::u8string in C++20
auto S2 = L"hello"s; // std::wstring
auto S3 = u"hello"s; // std::u16string
auto S4 = U"hello"s; // std::u32string
std::string_view sv = "abc\0\0def"sv;

还可以与原始字符串一起使用:

1
2
3
4
5
6
7
// 和原始字符串一起使用
auto S5 = R"("Hello \ world")"s; // std::string from a raw const char*
auto S6 = u8R"("Hello \ world")"s; // std::string from a raw const char* before C++20, encoded as UTF-8,
// std::u8string in C++20
auto S7 = LR"("Hello \ world")"s; // std::wstring from a raw const wchar_t*
auto S8 = uR"("Hello \ world")"s; // std::u16string from a raw const char16_t*, encoded as UTF-16
auto S9 = UR"("Hello \ world")"s; // std::u32string from a raw const char32_t*, encoded as UTF-32

对于包含\0的字符串,如”abc\0\0def”,如果使用字符串后缀,则是使用的字面量运算符operator””s来初始化std::string:

1
constexpr std::string operator""s(const char* str, std::size_t len) noexcept

其len是字符串中字符的数量(不包括末尾的\0,但包含显式的\0)。

自定义字符串后缀

字符串后缀是通过操作符的重载实现的:

1
std::string operator""s( const char *str, std::size_t len );

我们也可以自定义操作符的重载来实现其他的字符串后缀,自定义的后缀建议以下划线开头,否则会产生编译警告:

1
warning C4455: “operator ""mm”: 已保留不以下划线开头的文本后缀标识符

下面自定义_mm_m_km后缀分别表示毫米、米、千米:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
long double operator"" _mm(long double x) {
return x / 1000;
}

long double operator"" _m(long double x) {
return x;
}

long double operator"" _km(long double x) {
return x * 1000;
}

int main()
{
std::cout << 1.0_mm << std::endl; // 0.001
std::cout << 1.0_m << std::endl; // 1
std::cout << 1.0_km << std::endl; // 1000

return 0;
}