词法约定 - 现代 C++(心得-贰)

简介

关于词法的约定,其实没有很多心得可以书写,既然作为约定,那么它就是制定的一些规则语义,需要大家共同来遵守,就好像一门语音大家共同的发音和语法。但是还是要简略介绍一下这些约定。同时作为记录以防某一天网站无法访问等。

本节介绍 C++ 程序的基本元素。 你将使用这些名为“词法元素”或“标记”的元素构造用于构造完整程序的语句、定义和声明等。 本节将讨论以下词法元素:

有关如何分析 C++ 源文件的详细信息,请参阅转换阶段

标记和字符集

C++ 程序的文本由标记和空格组成。 标记是对编译器有用的 C++ 程序的最小元素。 C++ 分析器识别这些类型的标记:

标记通常由一个或多个空格分隔:

  • 空白
  • 水平或垂直制表符
  • 新行
  • 表单源
  • 注释

基本源字符集

C++ 标准指定可用于源文件的基本源字符集。 若要表示这组字符之外的字符,可以通过使用 通用字符名称指定其他字符。 MSVC 实现允许使用附加字符。 基本源字符集由可用于源文件的 96 个字符组成。 这组字符包括空白字符、水平选项卡、垂直选项卡、换页符和换行控制字符以及这一组图形字符:

a b c d e f g h i j k l m n o p q r s t u v w x y z

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

0 1 2 3 4 5 6 7 8 9

_ { } [ ] # ( ) < > % : ; . ? * + - / ^ & | ~ ! = , \ " '

通用字符名称

由于 C++ 程序可使用的字符要比在基本源字符集中指定的字符要多得多,所以可以通过使用 通用字符名称以可移植的方式指定这些字符。 通用字符名称由表示 Unicode 码位的字符序列组成。 采用两种形式。 使用 \UNNNNNNNN 表示形式为 U+NNNNNNNN 的 Unicode 码位,其中 NNNNNNNN 是八位的十六进制码位数字。 使用四位的 \uNNNN 表示形式为 U+0000NNNN 的 Unicode 码位。

通用字符名称可用于标识符、字符串和字符文本中。 通用字符名称不能用于表示范围 0xD800-0xDFFF 之内的代理项码位。 而应使用所需的码位;编译器会自动生成任何必需的代理项。 其他限制适用于可在标识符中使用的通用字符名称。 有关详细信息,请参阅 Identifiers 和 String and Character Literals

执行字符集

执行字符集表示编译程序中可显示的字符和字符串。 这些字符集包含源文件中允许的所有字符,以及表示警告、退格、回车和空字符的控制字符。 执行字符集具有特定于区域设置的表示形式。

注释 (C++)

虽然不被执行但是被程序员用来记录和解释代码的作用,方便自己回溯以及其他程序员阅读。

注释是编译器忽略的文本,但它对程序员很有用。 注释通常用于批注代码以供将来参考。 编译器将它们视为空白。 可以在测试中使用注释来使某些代码行处于非活动状态;但是,#if/#endif 预处理器指令在这方面表现更佳,因为你可以环绕包含注释的代码,但不能嵌套注释。

C++ 注释的编写方法如下:

  • /*(斜线、星号)字符,后跟任意字符序列(包括新行),然后是 */ 字符。 此语法与 ANSI C 相同。

  • //(两个斜杠)字符,后跟任意字符序列。 没有紧跟反斜杠的新行将终止这种形式的注释。 因此,它通常称为“单行注释”。

注释字符(/**/ 和 //)在字符常量、字符串字面量或注释中没有特殊含义。 因此,不能嵌套使用第一种语法的注释。

这里与c语言保持一致,也是大部分编程语言都支持的注释字符。

标识符 (C++)

标识符可以理解为程序中的名字,你要定义一个东西的时候肯定要给他取个名字,就像我们的大千世界中各种物体都有自己的名字。

标识符是用于表示以下内容之一的字符序列:

  • 对象或变量名称

  • 类、结构或联合名称

  • 枚举类型名称

  • 类、结构、联合或枚举的成员

  • 函数或类成员函数

  • typedef 名称

  • 标签名称

  • 宏名称

  • 宏参数

允许将以下字符用作标识符的任意字符:

_ a b c d e f g h i j k l m
n o p q r s t u v w x y z
A B C D E F G H I J K L M
N O P Q R S T U V W X Y Z

还允许在标识符中使用通用字符名称的某些范围。 标识符中的通用字符名称不能指定控制字符或基本源字符集中的字符。 有关详细信息,请参阅 Character Sets。 允许将以下 Unicode 码位数字范围用作标识符中任意字符的通用字符名称:

  • 00A8、00AA、00AD、00AF、00B2-00B5、00B7-00BA、00BC-00BE、00C0-00D6、00D8-00F6、00F8-00FF、0100-02FF、0370-167F、1681-180D、180F-1DBF、1E00-1FFF、200B-200D、202A-202E、203F-2040、2054、2060-206F、2070-20CF、2100-218F、2460-24FF、2776-2793、2C00-2DFF、2E80-2FFF、3004-3007、3021-302F、3031-303F、3040-D7FF、F900-FD3D、FD40-FDCF、FDF0-FE1F、FE30-FE44、FE47-FFFD、10000-1FFFD、20000-2FFFD、30000-3FFFD、40000-4FFFD、50000-5FFFD、60000-6FFFD、70000-7FFFD、80000-8FFFD、90000-9FFFD、A0000-AFFFD、B0000-BFFFD、C0000-CFFFD、D0000-DFFFD、E0000-EFFFD

允许将以下字符用作标识符中除第一个字符以外的任意字符:

0 1 2 3 4 5 6 7 8 9

还允许将以下 Unicode 码位数字范围用作标识符中除第一个字符以外任意字符的通用字符名称:

  • 0300-036F、1DC0-1DFF、20D0-20FF、FE20-FE2F

Microsoft 专用

只有 Microsoft C++ 标识符的前 2048 个字符是有意义的。 用户定义类型的名称由编译器“修饰”以保留类型信息。 结果名称(包括类型信息)不能超过 2048 个字符。 (有关详细信息,请参阅修饰名。)可能影响修饰标识符长度的因素包括:

  • 标识符是表示用户定义类型的对象还是表示派生自用户定义类型的类型。

  • 标识符是否表示派生自函数的函数或类型。

  • 函数的参数的数量。

美元符号 $ 在 Microsoft C++ 编译器 (MSVC) 中是有效标识符。 MSVC 还允许在标识符中使用通用字符名称允许的范围所表示的实际字符。 若要使用这些字符,必须使用包含它们的文件编码代码页保存文件。 此示例演示如何在代码中互换使用扩展字符和通用字符名称。

// extended_identifier.cpp
// In Visual Studio, use File, Advanced Save Options to set
// the file encoding to Unicode codepage 1200
struct テスト         // Japanese 'test'
{
    void トスト() {}  // Japanese 'toast'
};

int main() {
    テスト \u30D1\u30F3;  // Japanese パン 'bread' in UCN form
    パン.トスト();        // compiler recognizes UCN or literal form
}

编译 C++/CLI 代码时,标识符中允许的字符范围限制更少。 使用 /clr 编译的代码中的标识符应遵循标准 ECMA-335:公共语言基础结构 (CLI)

结束 Microsoft 专用

标识符的第一个字符必须是字母字符(大写、小写或带下划线 ( _ ) 的字母)。 由于 C++ 标识符区分大小写,因此 fileName 与 FileName不同。

标识符不能与关键字有完全相同的拼写和大小写。 包含关键字的标识符是合法的。 例如, Pint 是一个合法标识符,即使它包含 int关键字。

在标识符中使用两个顺序下划线字符 ( __ ) 或在单个前导下划线后跟一个大写字母的用法是专为所有范围的 C++ 实现保留的。 由于当前或将来的保留标识符可能发生冲突,因此应避免对文件范围的名称使用一个前导下划线后跟小写字母。

关键字 (C++)

关键字可以理解为现实生活中的代号,比如警察李二狗,警察张三李四,他们有个共同的标签那就是警察,他们的职业被限定。关键字的作用也是如此,将程序中的标识符限定修饰,赋予他特别的意义,同时也加上了限定。

关键字是具有特殊意义的预定义保留标识符。 它们不能用作程序中的标识符。 Microsoft C++ 保留了下列关键字。 带有前导下划线的名称,以及为 C++/CX 和 C++/CLI 指定的名称都是 Microsoft 扩展。

标准 C++ 关键字

alignas
alignof
andb
and_eqb
asma
auto
bitandb
bitorb
bool
break
case
catch
char
char8_tc
char16_t
char32_t
class
complb
conceptc
const
const_cast
constevalc
constexpr

constinitc
continue
co_awaitc
co_returnc
co_yieldc
decltype
default
delete
do
double
dynamic_cast
else
enum
explicit
exportc
extern
false
float
for
friend
goto
if
inline

int
long
mutable
namespace
new
noexcept
notb
not_eqb
nullptr
operator
orb
or_eqb
private
protected
public
register reinterpret_cast
requiresc
return
short
signed
sizeof
static
static_assert

static_cast
struct
switch
template
this
thread_local
throw
true
try
typedef
typeid
typename
union
unsigned
using 声明
using 指令
virtual
void
volatile
wchar_t
while
xorb
xor_eqb

a Microsoft 专用 __asm 关键字替换了 C++ asm 语法。 保留了 asm 以便与其他 C++ 实现兼容,但未成功。 将 __asm 用于 x86 目标上的内联程序集。 Microsoft C++ 不支持其他目标的内联程序集。

b 当指定 /permissive- 或 /Za(禁用语言扩展)时,扩展运算符同义词是关键字。 当启用 Microsoft 扩展时,它们不是关键字。

c 指定 /std:c++20 或更高版本(例如 /std:c++latest)时受支持。

Microsoft 专用 C++ 关键字

在 C++ 中,包含两个连续下划线的标识符会保留用于编译器实现。 Microsoft 约定位于带双下划线的 Microsoft 专用关键字前面。 这些单词不能用作标识符名称。

默认情况下将启用 Microsoft 扩展。 若要确保你的程序是完全可移植的,可通过在编译期间指定 /permissive- 或 /Za(禁用语言扩展)选项来禁用 Microsoft 扩展。 这些选项禁用某些 Microsoft 专用关键字。

启用 Microsoft 扩展后,你可以在程序中使用 Microsoft 特定关键字。 为了符合 ANSI,这些关键字的前面有一条双下划线。 出于后向兼容性考虑,支持许多双下划线关键字的单下划线版本。 提供的 __cdecl 关键字没有前导下划线。

__asm 关键字替代了 C++ asm 语法。 保留了 asm 以便与其他 C++ 实现兼容,但未成功。 请使用 __asm

__based 关键字对 32 位和 64 位目标编译的用途有限。

__alignofe
__asme
__assumee
__basede
__cdecle
__declspece
__event
__excepte
__fastcalle
__finallye
__forceinlinee

__hookd
__if_exists
__if_not_exists
__inlinee
__int16e
__int32e
__int64e
__int8e
__interface
__leavee
__m128

__m128d
__m128i
__m64
__multiple_inheritancee
__ptr32e
__ptr64e
__raise
__restricte
__single_inheritancee
__sptre
__stdcalle

__super
__thiscall
__unalignede
__unhookd
__uptre
__uuidofe
__vectorcalle
__virtual_inheritancee
__w64e
__wchar_t

d 事件处理中使用的内部函数。

e 为了与以前的版本向后兼容,当启用 Microsoft 扩展时(默认),这些关键字既可以使用两个前导下划线,也可以使用一个前导下划线。

__declspec 修饰符中的 Microsoft 关键字

这些标识符是 __declspec 修饰符的扩展属性。 它们被视为该上下文中的关键字。

align
allocate
allocator
appdomain
code_seg
deprecated

dllexport
dllimport
jitintrinsic
naked
noalias
noinline

noreturn
no_sanitize_address
nothrow
novtable
process
property

restrict
safebuffers
selectany
spectre
thread
uuid

C++/CLI 和 C++/CX 关键字

__abstractf
__boxf
__delegatef
__gcf
__identifier
__nogcf
__noop
__pinf
__propertyf
__sealedf

__try_castf
__valuef
abstractg
arrayg
as_friend
delegateg
enum class
enum struct
eventg

finally
for each in
gcnewg
genericg
initonly
interface classg
interface structg
interior_ptrg
literalg

newg
propertyg
ref class
ref struct
safecast
sealedg
typeid
value classg
value structg

f 仅适用于 C++ 托管扩展。 此语法现已弃用。 有关更多信息,请参见 Component Extensions for Runtime Platforms

g 适用于 C++/CLI。

在此列出所有关键字

标点符号 (C++)

在 C++ 中,标点符号相对于编译器来说具有语法意义和语义含义,但是它们本身不会指定一个产生数值的操作。 某些标点符号(单独或组合)也可以是 C++ 运算符或对预处理器很重要。

以下任意字符都被视为标点符号:

! % ^ & * ( ) - + = { } | ~
[ ] \ ; ' : " < > ? , . / #

标点符号 [ ]、( ) 和 { } 必须成对出现在转换阶段 4 后。

这些标点符号配合对应的字符有自己特殊的意义。

数值、布尔和指针文本

这里的文本需要理解为数据类型。

文本是一种直接表示值的程序元素。 本文介绍整数、浮点、布尔和指针类型的文本。 有关字符串文本和字符文本的信息,请参阅字符串文本和字符文本 (C++)。 你也可以基于任何这些类别定义自己的文本。 有关详细信息,请参阅用户定义的文本 (C++)

你可以在许多上下文中使用文本,但文本的最常用法是初始化命名变量以及将自变量传递给函数:

const int answer = 42;      // integer literal
double d = sin(108.87);     // floating point literal passed to sin function
bool b = true;              // boolean literal
MyClass* mc = nullptr;      // pointer literal

有时需要指示编译器如何解释某个文本或者为其赋予哪种特定类型。 可以通过为文本追加前缀或后缀来实现此目的。 例如,前缀 0x 指示编译器将其后面的数字解释为十六进制值,例如 0x35。 后缀 ULL 指示编译器将值视为 unsigned long long 类型,就像 5894345ULL 中那样。 有关每个文本类型的前缀和后缀的完整列表,请参阅以下各节。

整数文本

整数文本以数字开头,没有小数部分或指数。 你可以指定十进制、二进制、八进制或十六进制形式的整数文本。 可以选择使用后缀将整数文本指定为无符号类型以及 long 类型或 long long 类型。

如果没有前缀或后缀,编译器将为整型文本值赋予 int 类型(32 位),前提是该值符合该类型,否则将赋予 long long 类型(64 位)。

要指定十进制整型文本,请以非零数字作为规范的开头。 例如:

int i = 157;        // Decimal literal
int j = 0198;       // Not a decimal number; erroneous octal literal
int k = 0365;       // Leading zero specifies octal literal, not decimal
int m = 36'000'000  // digit separators make large values more readable

要指定八进制整型文本,请以 0 作为规范的开头,后跟 0 到 7 之间的一系列数字。 在指定八进制文本时,使用数字 8 和 9 是错误做法。 例如:

int i = 0377;   // Octal literal
int j = 0397;   // Error: 9 is not an octal digit

要指定十六进制整型文本,请以 0x 或 0X 作为规范的开头(“x”的大小写形式并不重要),后跟 0 到 9 以及 a(或 A)到 f(或 F)之间的一系列数字。 十六进制数字 a(或 A)到 f(或 F)表示介于 10 和 15 之间的值。 例如:

int i = 0x3fff;   // Hexadecimal literal
int j = 0X3FFF;   // Equal to i

若要指定无符号类型,请使用 u 或 U 后缀。 若要指定 long 类型,请使用 l 或 L 后缀。 要指定 64 位整型类型,请使用 LL 或 ll 后缀。 i64 后缀仍受支持,但不建议使用。 它是 Microsoft 专用的,不可移植。 例如:

unsigned val_1 = 328u;                  // Unsigned value
long val_2 = 0x7FFFFFL;                 // Long value specified
                                        //  as hex literal
unsigned long val_3 = 0776745ul;        // Unsigned long value
auto val_4 = 108LL;                           // signed long long
auto val_4 = 0x8000000000000000ULL << 16;     // unsigned long long

数字分隔符:可以使用单引号字符(撇号)分隔较大数字中的位值,使它们更易于人类阅读。 分隔符不会对编译产生任何影响。

long long i = 24'847'458'121;

浮点文本

浮点文本指定必须具有小数部分的值。 这些值包含小数点 (.) 并可能包含指数。

浮点文本有一个有效数字(有时称为尾数),它指定数字的值。 它们有一个指数,用于指定数字的度量值。 而且,它们有一个可选的后缀,用于指定文本的类型。 指定的有效数字的格式是一系列位数后跟一个句点,再后跟表示数字的小数部分的可选的一系列位数。 例如:

18.46
38.

指数(如果有)指定数字的量级为 10 次幂,如以下示例所示:

18.46e0      // 18.46
18.46e1      // 184.6

指数可以使用 e 或 E(意义相同)后跟可选的符号(+ 或 -)和一系列数字来指定。 如果指数存在,则整数(如 18E0)中不需要尾随的小数点。

浮点文本默认为 double 类型。 通过使用后缀 f 或 l 或 F 或 L(后缀不区分大小写),可以将文本指定为 float 或 long double

虽然 long double 和 double 具有相同的表示形式,但它们不属于同一类型。 例如,你可能有类似于下面的重载函数

void func( double );

void func( long double );

布尔值文字

布尔文本为 true 和 false

指针文本 (C++11)

C++ 引入了 nullptr 文本来指定初始化为零的指针。 在可移植代码中,应使用 nullptr,而不是整型类型零或宏(如 NULL)。

二进制文本 (C++14)

可以通过使用 0B 或 0b 前缀,后跟一系列 1 和 0,来指定二进制文本:

auto x = 0B001101 ; // int
auto y = 0b000001 ; // int

字符串和字符文本 (C++)

C++ 支持各种字符串和字符类型,并提供表示每种类型的文本值的方法。 在源代码中,使用字符集表示字符和字符串文本的内容。 通用字符名称和转义字符允许你仅使用基本源字符集表示任何字符串。 原始字符串使你可以避免使用转义字符,可以用于表示所有类型的字符串。 还可以创建 std::string 文本,而无需执行额外的构造或转换步骤。

#include <string>
using namespace std::string_literals; // enables s-suffix for std::string literals

int main()
{
    // Character literals
    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

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

    // String literals
    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

    // Raw string literals containing unescaped \ and "
    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

    // Combining string literals with standard s-suffix
    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

    // Combining raw string literals with standard s-suffix
    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
}

字符串文本可以没有前缀,也可以具有 u8L、 u和 U 前缀以分别指示窄字符(单字节或多字节)、UTF-8、宽字符(UCS-2 或 UTF-16)、UTF-16 和 UTF-32 编码。 原始字符串文本可以具有 Ru8RLRuR 和 UR 前缀来表示这些编码的原始版本等效项。 若要创建临时或静态 std::string 值,可以使用带 s 后缀的字符串文本或原始字符串文本。 有关详细信息,请参阅下面的字符串文本部分。 有关基本源字符集、通用字符名称以及在源代码中使用扩展代码页中的字符的详细信息,请参阅字符集

字符文本

字符文本 由一个字符常量构成。 它由用单引号引起来的字符表示。 有五种类型的字符文本:

  • char 类型的普通字符文本,例如 'a'

  • char 类型的 UTF-8 字符文本(C++20 中的 char8_t),例如 u8'a'

  • 类型 wchar_t的宽字符文本,例如 L'a'

  • char16_t 类型的 UTF-16 字符文本,例如 u'a'

  • char32_t 类型的 UTF-32 字符文本,例如 U'a'

用于字符文本的字符可以是除保留字符反斜杠 (\)、单引号 (') 和换行符以外的任何字符。 可以使用转义序列指定保留字符。 可以通过使用通用字符名称指定字符,只要类型的大小足以保留字符。

编码

字符文本根据其前缀以不同的方式进行编码。

  • 没有前缀的字符文本是普通字符文本。 包含可在执行字符集中表示的单个字符、转义序列或通用字符名称的普通字符文本的值等于它在执行字符集中的编码数值。 包含多个字符、转义序列或通用字符名称的普通字符文本是多字符文本。 无法在执行字符集中表示的多字符文本或普通字符文本的类型为 int,其值由实现定义。 有关 MSVC,请参阅下面的 特定于 Microsoft 部分。

  • 以 L 前缀开头的字符文本是宽字符文本。 包含单个字符、转义序列或通用字符名称的宽字符文本的值等于它在执行宽字符集中的编码数值,除非该字符文本在执行宽字符集中没有表示形式,在这种情况下,值由实现定义。 包含多个字符、转义序列或通用字符名称的宽字符文本的值由实现定义。 有关 MSVC,请参阅下面的 特定于 Microsoft 部分。

  • 以 u8 前缀开头的字符文本是 UTF-8 字符文本。 如果包含单个字符、转义序列或通用字符名称的 UTF-8 字符文本的值可以由单个 UTF-8 代码单元(对应于 C0 控件和基本拉丁语 Unicode 块)表示,该值等于其 ISO 10646 码位值。 如果该值不能由单个 UTF-8 代码单元表示,则程序的格式不当。 包含多个字符、转义序列或通用字符名称的 UTF-8 字符文本是格式不当的。

  • 以 u 前缀开头的字符文本是 UTF-16 字符文本。 如果包含单个字符、转义序列或通用字符名称的 UTF-16 字符文本的值可以由单个 UTF-16 代码单元(对应于基本多语言平面)表示,该值等于其 ISO 10646 码位值。 如果该值不能由单个 UTF-16 代码单元表示,则程序的格式不当。 包含多个字符、转义序列或通用字符名称的 UTF-16 字符文本是格式不当的。

  • 以 U 前缀开头的字符文本是 UTF-32 字符文本。 包含单个字符、转义序列或通用字符名称的 UTF-32 字符文本的值等于其 ISO 10646 码位值。 包含多个字符、转义序列或通用字符名称的 UTF-32 字符文本是格式不当的。

转义字符

有三种类型的转义字符:简单、八进制和十六进制。 转义序列可为下列任一值:

Expand table

转义序列
换行符\n
反斜杠\\
水平制表符\t
问号? 或 \?
垂直制表符\v
单引号\'
退格符\b
双引号\"
回车符\r
null 字符\0
换页符\f
八进制\ooo
警报(响铃)\a
十六进制\xhhh

八进制转义序列包含一个反斜杠,后跟 1 到 3 个八进制数字的序列。 如果在第三位数之前遇到八进制转义序列,该转义序列将在第一个不是八进制数字的字符处终止。 可能的最高八进制值为 \377

十六进制转义序列包含一个反斜杠,后接 x 字符,再后接由一个或多个十六进制数字组成的序列。 将忽略前导零。 在普通或以 u8 为前缀的字符文本中,最高十六进制值为 0xFF。 在使用 L 或 u 前缀的宽字符文本中,最大的十六进制值为 0xFFFF。 在使用 U 前缀的宽字符文本中,最大的十六进制值为 0xFFFFFFFF。

此示例代码演示了一些使用普通字符文本的转义字符示例。 相同的转义序列语法对其他字符文本类型有效。

#include <iostream>
using namespace std;

int main() {
    char newline = '\n';
    char tab = '\t';
    char backspace = '\b';
    char backslash = '\\';
    char nullChar = '\0';

    cout << "Newline character: " << newline << "ending" << endl;
    cout << "Tab character: " << tab << "ending" << endl;
    cout << "Backspace character: " << backspace << "ending" << endl;
    cout << "Backslash character: " << backslash << "ending" << endl;
    cout << "Null character: " << nullChar << "ending" << endl;
}
/* Output:
Newline character:
ending
Tab character:  ending
Backspace character:ending
Backslash character: \ending
Null character:  ending
*/

反斜杠字符 (\) 在位于行末尾时将作为行继续符。 如果你希望反斜杠字符显示为字符文本,则必须在一行中键入两个反斜杠 (\\)。 有关行继续符的详细信息,请参阅 Phases of Translation

通用字符名称

在字符文本和本机(非原始)字符串文本中,任何字符都可由通用字符名称表示。 通用字符名称由前缀 \U 后跟八位数 Unicode 码位组成,或者由前缀 \u 后跟四位数 Unicode 码位组成。 必须分别显示所有八个或四个数字,以组成一个格式正确的通用字符名称。

char u1 = 'A';          // 'A'
char u2 = '\101';       // octal, 'A'
char u3 = '\x41';       // hexadecimal, 'A'
char u4 = '\u0041';     // \u UCN 'A'
char u5 = '\U00000041'; // \U UCN 'A'

字符串文本

字符串文本表示字符序列,这些字符合起来可组成以 null 结尾的字符串。 字符必须放在双引号之间。 字符串文本有以下类型:

窄字符串文本

窄字符串文本是一个没有前缀且以双引号分隔、以 null 结尾的 const char[n] 类型的数组,其中 n 是数组的长度(以字节为单位)。 窄字符串文本可包含除双引号 (")、反斜杠 (\) 或换行符以外的所有图形字符。 窄字符串文本还可包含上面列出的转义序列和装入一个字节中的通用字符名称。

const char *narrow = "abcd";

// represents the string: yes\no
const char *escaped = "yes\\no";
UTF-8 编码的字符串

UTF-8 编码的字符串是一个前缀为 u8 且以双引号分隔、以 null 结尾的 const char[n] 类型的数组,其中 n 是编码的数组的长度(以字节为单位)。 以 u8 为前缀的字符串文本可包含除双引号 (")、反斜杠 (\) 或换行符以外的所有图形字符。 以 u8 为前缀的字符串文本还可包含上面列出的转义序列和任何通用字符名称。

C++20 引入了可移植的 char8_t(UTF-8 编码的 8 位 Unicode)字符类型。 在 C++20 中,u8 文本前缀指定 char8_t 而不是 char 的字符或字符串。

// Before C++20
const char* str1 = u8"Hello World";
const char* str2 = u8"\U0001F607 is O:-)";
// C++20 and later
const char8_t* u8str1 = u8"Hello World";
const char8_t* u8str2 = u8"\U0001F607 is O:-)";

宽字符串文本

宽字符串是一个以 null 结尾且具有前缀“L”的常数 wchar_t 数组,其中包含除双引号 (")、反斜杠 (\) 或换行符以外的所有图形字符。 宽字符串文本可包含上面列出的转义序列和任何通用字符名称。

const wchar_t* wide = L"zyxw";
const wchar_t* newline = L"hello\ngoodbye";
char16_t 和 char32_t (C++11)

C++11 引入了可移植的 char16_t (16 位 Unicode)和 char32_t (32 位 Unicode)字符类型:

auto s3 = u"hello"; // const char16_t*
auto s4 = U"hello"; // const char32_t*

原始字符串文本 (C++11)

原始字符串是一个以 null 结尾的数组(属于任何字符类型),其中包括含双引号 (")、反斜杠 (\) 或换行符在内的所有图形字符。 原始字符串通常用于使用字符类的正则表达式,还用于 HTML 字符串和 XML 字符串。 有关示例,请参阅以下文章: 关于 C++11 的 Bjarne Stroustrup 常见问题

// represents the string: An unescaped \ character
const char* raw_narrow = R"(An unescaped \ character)";
const wchar_t*  raw_wide  = LR"(An unescaped \ character)";
const char*     raw_utf8a = u8R"(An unescaped \ character)"; // Before C++20
const char8_t*  raw_utf8b = u8R"(An unescaped \ character)"; // C++20
const char16_t* raw_utf16 = uR"(An unescaped \ character)";
const char32_t* raw_utf32 = UR"(An unescaped \ character)";

分隔符是用户定义的最多包含 16 个字符的序列,它紧贴在原始字符串文本的左括号之前,紧跟在右括号之后。 例如,在 R"abc(Hello"\()abc" 中,分隔符序列为 abc ,字符串内容为 Hello"\(。 你可使用分隔符来消除同时含有双引号和括号的原始字符串。 此字符串文本会导致编译器错误:

// meant to represent the string: )"
const char* bad_parens = R"()")";  // error C2059

但分隔符能够解决这样的错误:

const char* good_parens = R"xyz()")xyz";

可以构造在源中包含换行的原始字符串文本(非转义字符):

// represents the string: hello
//goodbye
const wchar_t* newline = LR"(hello
goodbye)";

std::string 文本 (C++14)

std::string 文本是用户定义的文本(请参阅下文)的标准库实现,表示为 "xyz"s(具有 s 后缀)。 这种字符串文本根据指定的前缀生成 std::stringstd::wstringstd::u32string 或 std::u16string 类型的临时对象。 如上所示不使用任何前缀时,会生成 std::string。 L"xyz"s 生成 std::wstring。 u"xyz"s 生成 std::u16stringU"xyz"s 生成 std::u32string

//#include <string>
//using namespace std::string_literals;
string str{ "hello"s };
string str2{ u8"Hello World" };     // Before C++20
u8string u8str2{ u8"Hello World" }; // C++20
wstring str3{ L"hello"s };
u16string str4{ u"hello"s };
u32string str5{ U"hello"s };

s 后缀也可以用于原始字符串:

u32string str6{ UR"(She said "hello.")"s };

std::string 文本在 <string> 头文件的命名空间 std::literals::string_literals 中定义。 因为 std::literals::string_literals和 std::literals 都声明为 内联命名空间,所以会自动将 std::literals::string_literals 视为如同它直接属于命名空间 std

字符串文本大小

对于 ANSI char* 字符串和其他单字节编码(但不是 UTF-8),字符串的大小(以字节为单位)是字符数加 1(用于 null 终止字符)。 对于所有其他字符串类型,大小不与字符数严格相关。 UTF-8 使用最多四个 char 元素对某些代码单位进行编码,编码为 UTF-16 的 char16_t 或 wchar_t 可以使用两个元素(针对总共四个字节)对单个 “代码单位”进行编码。 本示例演示了宽字符串文本的大小(以字节为单位):

const wchar_t* str = L"Hello!";
const size_t byteSize = (wcslen(str) + 1) * sizeof(wchar_t);

请注意,strlen() 和 wcslen() 不包括 null 终止字符的大小,该字符的大小等于字符串类型的元素大小:char* 或 char8_t* 字符串中是一个字节,wchar_t* 或 char16_t* 字符串中是两个字节,char32_t* 字符串中是四个字节。

在 Visual Studio 2022 版本 17.0 之前的 Visual Studio 版本中,字符串字面量的最大长度为 65,535 个字节。 此限制适用于窄字符串文本和宽字符串文本。 在 Visual Studio 2022 版本 17.0 及更高版本中,此限制被取消,字符串长度受可用资源的限制。

修改字符串文本

因为字符串(不包括 std::string 文本)是常量,所以尝试修改它们(例如,str[2] = 'A')会导致编译器错误。

Microsoft 专用

在 Microsoft C++ 中,可以使用字符串文本将指针初始化,使指针成为非常数 char 或 wchar_t。 此非常数初始化可以在 C99 代码中使用,但在 C++98 中已弃用,在 C++11 中已删除。 尝试修改该字符串将导致访问冲突,例如:

wchar_t* str = L"hello";
str[2] = L'a'; // run-time error: access violation

当你设置 /Zc:strictStrings(禁用字符串文本类型转换)编译器选项且字符串文本转化为非常数字符指针时,可导致编译器发生错误。 我们建议将其用于符合标准的可移植代码。 使用 auto 关键字声明经过字符串文本初始化的指针也是一个很好的做法,因为它可以解析为正确(常数)的类型。 例如,此代码示例捕捉到一次在编译时写入字符串文本的尝试:

auto str = L"hello";
str[2] = L'a'; // C3892: you cannot assign to a variable that is const.

在某些情况下,可以合并相同的字符串文本,节省可执行文件的空间。 字符串文本合并过程中,编译器将导致对特定字符串文本的所有引用都指向内存中的同一位置,而不是每次引用都指向一个单独的字符串文本实例。 若要启用字符串合并,请使用 /GF 编译器选项。

“特定于 Microsoft”部分到此结束。

串联相邻字符串文本

相邻宽或窄字符串文本是串联的。 声明如下:

char str[] = "12" "34";

与此声明相同:

char atr[] = "1234";

也和此声明相同:

char atr[] =  "12\
34";

使用嵌入式十六进制转义代码来指定字符串会导致意外的结果。 以下示例旨在创建包含 ASCII 5 字符、后跟字符 f、i、v 和 e 的字符串文本:

"\x05five"

实际结果是十六进制 5F,它是一个下划线 ASCII 代码,后跟字符 i、v 和 e。 若要获得正确的结果,可以使用以下转义序列之一:

"\005five"     // Use octal literal.
"\x05" "five"  // Use string splicing.

std::string 文本(以及相关的 std::u8stringstd::u16string 和 std::u32string)可与为 basic_string 类型定义的 + 运算符连接。 它们还可以通过与相邻字符串相同的方式进行串联。 在两种情况下,字符串编码和后缀都必须匹配:

auto x1 = "hello" " " " world"; // OK
auto x2 = U"hello" " " L"world"; // C2308: disagree on prefix
auto x3 = u8"hello" " "s u8"world"z; // C3688, disagree on suffixes

具有通用字符名称的字符串文本

本机(非原始)字符串文本可能使用通用字符名称来表示任何字符,只要通用字符名称可被编码为字符串类型中的一个或多个字符。 例如,表示扩展字符的通用字符名称不能以使用 ANSI 代码页的窄字符串进行编码,但可以使用一些多字节代码页中的窄字符串、UTF-8 字符串或宽字符串进行编码。 在 C++11 中,Unicode 支持由 char16_t* 和 char32_t* 字符串类型扩展,C++20 将其扩展为 char8_t 类型:

// ASCII smiling face
const char*     s1 = ":-)";

// UTF-16 (on Windows) encoded WINKING FACE (U+1F609)
const wchar_t*  s2 = L"😉 = \U0001F609 is ;-)";

// UTF-8  encoded SMILING FACE WITH HALO (U+1F607)
const char*     s3a = u8"😇 = \U0001F607 is O:-)"; // Before C++20
const char8_t*  s3b = u8"😇 = \U0001F607 is O:-)"; // C++20

// UTF-16 encoded SMILING FACE WITH OPEN MOUTH (U+1F603)
const char16_t* s4 = u"😃 = \U0001F603 is :-D";

// UTF-32 encoded SMILING FACE WITH SUNGLASSES (U+1F60E)
const char32_t* s5 = U"😎 = \U0001F60E is B-)";

用户定义的文本

在 C++ 中,文本有六个主要类别:整数、字符、浮点、字符串、布尔和指针。 从 C++ 11 开始,可以基于这些类别定义你自己的文本,以便为常见惯用语提供快捷语法,并提高类型安全性。 例如,假设有一个 Distance 类。 你可以将一个文本定义为表示公里,将另一个文本定义为表示英里,并通过编写以下内容帮助用户明确度量单位:auto d = 42.0_km 或 auto d = 42.0_mi。 用户定义的文本没有任何性能优势或劣势;它们的主要作用在于方便或实现编译时类型推断。 标准库具有 std::stringstd::complex 以及 <chrono> 标头中的时间和持续时间操作单位的用户定义文本:

Distance d = 36.0_mi + 42.0_km;         // Custom UDL (see below)
std::string str = "hello"s + "World"s;  // Standard Library <string> UDL
complex<double> num =
   (2.0 + 3.01i) * (5.0 + 4.3i);        // Standard Library <complex> UDL
auto duration = 15ms + 42h;             // Standard Library <chrono> UDLs

用户定义的文本运算符签名

通过以下形式之一在命名空间范围定义 operator"" 来实现用户定义的文本:

ReturnType operator "" _a(unsigned long long int);   // Literal operator for user-defined INTEGRAL literal
ReturnType operator "" _b(long double);              // Literal operator for user-defined FLOATING literal
ReturnType operator "" _c(char);                     // Literal operator for user-defined CHARACTER literal
ReturnType operator "" _d(wchar_t);                  // Literal operator for user-defined CHARACTER literal
ReturnType operator "" _e(char16_t);                 // Literal operator for user-defined CHARACTER literal
ReturnType operator "" _f(char32_t);                 // Literal operator for user-defined CHARACTER literal
ReturnType operator "" _g(const char*, size_t);      // Literal operator for user-defined STRING literal
ReturnType operator "" _h(const wchar_t*, size_t);   // Literal operator for user-defined STRING literal
ReturnType operator "" _i(const char16_t*, size_t);  // Literal operator for user-defined STRING literal
ReturnType operator "" _g(const char32_t*, size_t);  // Literal operator for user-defined STRING literal
ReturnType operator "" _r(const char*);              // Raw literal operator
template<char...> ReturnType operator "" _t();       // Literal operator template

上例中的运算符名是你提供的任意占位符,但需要前导下划线。 (仅标准库才允许定义不带下划线的文本。)在返回类型中,你可以自定义文本执行的转换或其他操作。 此外,这些运算符中的任何一个都可定义为 constexpr

加工的文本

在源代码中,任何文本(无论是否为用户定义的)实质上都是字母数字字符序列,例如 10154.7"hello" 或 true。 编译器将序列解释为整数、浮点、常量字符*字符串等。 接受编译器分配给文本值的任何类型作为输入的用户定义的文本非正式地称为“加工的文本”。 以上所有运算符(_r 和 _t 除外)均为加工的文本。 例如,文本 42.0_km 将绑定到签名与 _b 类似的运算符 _km;而文本 42_km 将绑定到签名与 _a 类似的运算符。

下面的示例演示用户定义的文本如何帮助用户明确其输入。 若要构造 Distance,用户必须通过使用相应的用户定义文本显式指定公里或英里。 也可以通过其他方式实现相同的结果,但用户定义的文本比其他方案简便。

// UDL_Distance.cpp

#include <iostream>
#include <string>

struct Distance
{
private:
    explicit Distance(long double val) : kilometers(val)
    {}

    friend Distance operator"" _km(long double val);
    friend Distance operator"" _mi(long double val);

    long double kilometers{ 0 };
public:
    const static long double km_per_mile;
    long double get_kilometers() { return kilometers; }

    Distance operator+(Distance other)
    {
        return Distance(get_kilometers() + other.get_kilometers());
    }
};

const long double Distance::km_per_mile = 1.609344L;

Distance operator"" _km(long double val)
{
    return Distance(val);
}

Distance operator"" _mi(long double val)
{
    return Distance(val * Distance::km_per_mile);
}

int main()
{
    // Must have a decimal point to bind to the operator we defined!
    Distance d{ 402.0_km }; // construct using kilometers
    std::cout << "Kilometers in d: " << d.get_kilometers() << std::endl; // 402

    Distance d2{ 402.0_mi }; // construct using miles
    std::cout << "Kilometers in d2: " << d2.get_kilometers() << std::endl;  //646.956

    // add distances constructed with different units
    Distance d3 = 36.0_mi + 42.0_km;
    std::cout << "d3 value = " << d3.get_kilometers() << std::endl; // 99.9364

    // Distance d4(90.0); // error constructor not accessible

    std::string s;
    std::getline(std::cin, s);
    return 0;
}

文本数字必须使用十进制数。 否则数字将被解释为整数,而该类型与运算符不兼容。 对于浮点数输入,类型必须是 long double;而对于整数类型则必须是 long long

原始文本

在原始的用户定义文本中,你定义的运算符将文本作为字符值的序列接受。 由你决定将该序列解释为数字、字符串还是其他类型。 在此页上方显示的运算符列表中,_r 和 _t 可用于定义原始文本:

ReturnType operator "" _r(const char*);              // Raw literal operator
template<char...> ReturnType operator "" _t();       // Literal operator template

可使用原始文本来提供不同于编译器正常行为的输入序列的自定义解释。 例如,可以定义一段文本,用于将序列 4.75987 转换为自定义的十进制类型,而不是 IEEE 754 浮点类型。 原始文本(如加工的文本)还可用于输入序列的编译时验证。

示例:原始文本的限制

原始文本运算符和文本运算符模板仅适用于整型和浮点型用户定义文本,如下面的示例所示:

#include <cstddef>
#include <cstdio>

// Literal operator for user-defined INTEGRAL literal
void operator "" _dump(unsigned long long int lit)
{
    printf("operator \"\" _dump(unsigned long long int) : ===>%llu<===\n", lit);
};

// Literal operator for user-defined FLOATING literal
void operator "" _dump(long double lit)
{
    printf("operator \"\" _dump(long double)            : ===>%Lf<===\n",  lit);
};

// Literal operator for user-defined CHARACTER literal
void operator "" _dump(char lit)
{
    printf("operator \"\" _dump(char)                   : ===>%c<===\n",   lit);
};

void operator "" _dump(wchar_t lit)
{
    printf("operator \"\" _dump(wchar_t)                : ===>%d<===\n",   lit);
};

void operator "" _dump(char16_t lit)
{
    printf("operator \"\" _dump(char16_t)               : ===>%d<===\n",   lit);
};

void operator "" _dump(char32_t lit)
{
    printf("operator \"\" _dump(char32_t)               : ===>%d<===\n",   lit);
};

// Literal operator for user-defined STRING literal
void operator "" _dump(const     char* lit, size_t)
{
    printf("operator \"\" _dump(const     char*, size_t): ===>%s<===\n",   lit);
};

void operator "" _dump(const  wchar_t* lit, size_t)
{
    printf("operator \"\" _dump(const  wchar_t*, size_t): ===>%ls<===\n",  lit);
};

void operator "" _dump(const char16_t* lit, size_t)
{
    printf("operator \"\" _dump(const char16_t*, size_t):\n"                  );
};

void operator "" _dump(const char32_t* lit, size_t)
{
    printf("operator \"\" _dump(const char32_t*, size_t):\n"                  );
};

// Raw literal operator
void operator "" _dump_raw(const char* lit)
{
    printf("operator \"\" _dump_raw(const char*)        : ===>%s<===\n",   lit);
};

template<char...> void operator "" _dump_template();       // Literal operator template

int main(int argc, const char* argv[])
{
    42_dump;
    3.1415926_dump;
    3.14e+25_dump;
     'A'_dump;
    L'B'_dump;
    u'C'_dump;
    U'D'_dump;
      "Hello World"_dump;
     L"Wide String"_dump;
    u8"UTF-8 String"_dump;
     u"UTF-16 String"_dump;
     U"UTF-32 String"_dump;
    42_dump_raw;
    3.1415926_dump_raw;
    3.14e+25_dump_raw;

    // There is no raw literal operator or literal operator template support on these types:
    //  'A'_dump_raw;
    // L'B'_dump_raw;
    // u'C'_dump_raw;
    // U'D'_dump_raw;
    //   "Hello World"_dump_raw;
    //  L"Wide String"_dump_raw;
    // u8"UTF-8 String"_dump_raw;
    //  u"UTF-16 String"_dump_raw;
    //  U"UTF-32 String"_dump_raw;
}

Output

operator "" _dump(unsigned long long int) : ===>42<===
operator "" _dump(long double)            : ===>3.141593<===
operator "" _dump(long double)            : ===>31399999999999998506827776.000000<===
operator "" _dump(char)                   : ===>A<===
operator "" _dump(wchar_t)                : ===>66<===
operator "" _dump(char16_t)               : ===>67<===
operator "" _dump(char32_t)               : ===>68<===
operator "" _dump(const     char*, size_t): ===>Hello World<===
operator "" _dump(const  wchar_t*, size_t): ===>Wide String<===
operator "" _dump(const     char*, size_t): ===>UTF-8 String<===
operator "" _dump(const char16_t*, size_t):
operator "" _dump(const char32_t*, size_t):
operator "" _dump_raw(const char*)        : ===>42<===
operator "" _dump_raw(const char*)        : ===>3.1415926<===
operator "" _dump_raw(const char*)        : ===>3.14e+25<===

// UDL_Distance.cpp

#include <iostream>
#include <string>

struct Distance{
private:
    explicit Distance(long double val) : kilometers(val)
    {} 

    // 用户定义的字面量后缀函数,用于将数值转换为 Distance 对象
    friend Distance operator"" _km(long double val);
    friend Distance operator"" _mi(long double val);

    long double kilometers{ 0 };

public:
    const static long double km_per_mile;

    long double get_kilometers() { return kilometers; }

    // 重载加法运算符,用于计算两个 Distance 对象的相加结果
    Distance operator+(Distance other)
    {
        return Distance(get_kilometers() + other.get_kilometers());
    }
};

const long double Distance::km_per_mile = 1.609344L;

// 用户定义的字面量后缀函数实现,用于将数值转换为 Distance 对象
Distance operator"" _km(long double val){
    return Distance(val);
}

Distance operator"" _mi(long double val){
    return Distance(val * Distance::km_per_mile);
}

int main(){
    // Must have a decimal point to bind to the operator we defined!
    Distance d{ 402.0_km }; // construct using kilometers
    std::cout << "Kilometers in d: " << d.get_kilometers() << std::endl; // 402

    Distance d2{ 402.0_mi }; // construct using miles
    std::cout << "Kilometers in d2: " << d2.get_kilometers() << std::endl;  // 646.956

    // add distances constructed with different units
    Distance d3 = 36.0_mi + 42.0_km;
    std::cout << "d3 value = " << d3.get_kilometers() << std::endl; // 99.9364

    // Distance d4(90.0); // error constructor not accessible

    std::string s;
    std::getline(std::cin, s);

    return 0;
}
 

  • 18
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值