标识符

来自cppreference.com
< cpp‎ | language

标识符是一个由数字,下划线,大小写拉丁字母和大多数 Unicode 字符组成的任意长度的序列。

有效的标识符的首个字符必须是以下之一:

  • 大写拉丁字母 A-Z
  • 小写拉丁字母 a-z
  • 下划线
  • 具有 Unicode 属性 XID_Start 的 Unicode 字符

有效的标识符的其他字符必须是以下之一:

  • 数字 0-9
  • 大写拉丁字母 A-Z
  • 小写拉丁字母 a-z
  • 下划线
  • 具有 Unicode 属性 XID_Continue 的 Unicode 字符

具有 XID_Start 或 XID_Continue 属性的字符在 DerivedCoreProperties.txt 列出。

标识符区分大小写(小写和大写字母是不同的),而且每一个字符都是起作用的。每个标识符都必须遵循正规化形式 C

注意:大多数实现中对 Unicode 标识符的支持是受限的,比如 gcc (10 之前)

在声明中

可以用标识符来命名对象、引用、函数、枚举项、类型、类成员、命名空间、模板、模板特化、形参包 (C++11 起)、goto 标号,以及其他实体,但有以下例外:

  • 关键词标识符不能用于其他目的;
  • 关键词只有在属性记号中可做他用。(例如 [[private]] 是一个合法属性
(C++11 起)
  • 作为特定运算符与标点符的代用表示不能用于其他目的;
  • 拥有特殊含义的标识符(finalimportmodule (C++20 起)override)在特定语境不作为常规标识符使用,而是表达它们的特定含义;
    • 在没有特别指明的情况下,这些标识符在既能作为常规标识符也能表达特定含义的场合均被视为常规标识符。
(C++11 起)
  • 有以下形式之一的,作为记号或预处理记号(即不在形如 operator ""id用户定义字符串字面量 中)出现的 (C++11 起)标识符被保留:
    • 任何位置带有双下划线的标识符;
    • 以单下划线后随一个大写字母开始的标识符;
    • 在全局命名空间中,以一个下划线开始的标识符。
  • 字面量运算符中:
    • 不以单个下划线开始的字面量后缀标识符为未来的标准化而被保留;
    • 含有双下划线的字面量后缀标识符为实现使用而被保留。
(C++11 起)

这里“被保留”的意思是,标准库的头文件可能 #define 或者声明这样的标识符以便它在内部使用,编译器可能会预先定义这种非标准的标识符,而且名字重整算法可能会假定某些这样的标识符是没有被使用的。如果程序员使用了这样的标识符的话,程序非良构,不要求诊断。

此外,在一个翻译单元 #define#undef 特定名字是未定义行为,详情请见保留宏名

僵尸标识符

C++14 起,某些标识符被从 C++ 标准库移除。它们列于僵尸名列表

然而,这些标识符仍然在某些语境中为以前的标准化保留。可移植的代码中,被移除的成员函数名不可用做函数式宏的名字,而其他被移除的成员名不可用作对象式宏的名字。

在表达式中

命名某个变量、函数概念的特化 (C++20 起)或枚举项的标识符可以作为表达式使用。仅由这个标识符组成的表达式的结果,是该标识符所命名的实体。如果该标识符命名的是某个函数、变量模板形参对象 (C++20 起)或数据成员,那么表达式的值类别左值,否则是右值 (C++11 前)纯右值 (C++11 起)(例如枚举项是纯右值表达式,概念的特化是 bool 纯右值 (C++20 起))。该表达式的类型以下列方式确定:

  • 如果该(无限定)标识符所命名的实体是局部实体,且在该标识符所出现的声明区之外命名它时将导致它被穿插其间的某个 lambda 表达式 按复制俘获,那么该表达式的类型是命名最内层的这种穿插其间的 lambda 表达式的闭包对象中为这种俘获所声明的非静态数据成员的类成员访问表达式的类型。
void f()
{
    float x, &r = x;
 
    [=]
    {
        decltype(x) y1;        // y1 拥有 float 类型
        decltype((x)) y2 = y1; // y2 拥有 float const& 类型
                               // 因为此 lambda 非 mutable 而 x 是左值
        decltype(r) r1 = y1;   // r1 拥有 float& 类型
        decltype((r)) r2 = y2; // r2 拥有 float const& 类型
    };
}
(C++11 起)
  • 如果所命名的实体是某个 T 类型模板形参的模板形参对象,那么表达式的类型是 const T
(C++20 起)
  • 否则,表达式的类型与被命名的实体的类型相同。

在非静态成员函数内,命名非静态成员的每个标识符 member 都隐式变换成一个类成员访问表达式 this->member

无限定的标识符

除了适当声明了的标识符之外,以下各项也可以以相同方式用在表达式中:

(C++11 起)
  • 模板的名字后随它的实参列表,比如 MyTemplate<int>
  • ~ 字符后随类名,比如 ~MyClass
  • ~ 字符后随 decltype 说明符,比如 ~decltype(str)
(C++11 起)

这些和标识符一起,被称作无限定的标识表达式

有限定的标识符

有限定的标识表达式是在无限定的标识表达式前面带上作用域解析运算符 ::,以及以作用域解析运算符分隔的包含以下任意内容的序列(可以为空):

  • 命名空间的名字
  • 类的名字
(C++11 起)

例如表达式 std::string::npos 是命名在命名空间 std 内的类 string 中的静态成员 npos 的表达式。表达式 ::tolower 命名的是全局命名空间内的函数 tolower。表达式 ::std::cout 指名 std 命名空间(顶层命名空间)中的全局变量 cout。表达式 boost::signals2::connection 指名的是在 signals2 命名空间中声明的类型 connection,前者则在命名空间 boost 声明。

有限定标识符中,可能会需要以关键词 template 来消除待决模板名的歧义。

关于为有限定的标识符进行的名字查找的细节,请参见有限定的名字查找

名字

名字是以下各项之一,用来代表某个实体或者某个标号:

  • 标识符;
  • 函数写法的重载运算符的名字(operator+operator new);
  • 用户定义的转换函数的名字(operator bool);
  • 用户定义的字面量运算符的名字(operator "" _km);
(C++11 起)
  • 模板的名字后随它的实参列表(MyTemplate<int>)。

每个代表实体的名字都是由声明引入到程序中来的。代表标号的名字则既可以通过 goto 语句,也可以通过带标号语句引入到程序中来。在多个翻译单元中使用的相同名字,可以根据它的链接代表相同或者不同的实体。

每当编译器在程序中遇到一个未知的名字时,它就会通过进行名字查找来将它与引入这个名字的声明联系起来,但对模板的声明和定义中的待决名不会这样做。(对于这些名字,编译器需要确定它们命名的是类型、模板还是某些其他实体,这可能需要显式消歧义)。

缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

缺陷报告 应用于 出版时的行为 正确行为
CWG 1440 C++11 :: 之前的 decltype 表达式可以表示任意类型 只能表示类和枚举类型
CWG 1963 C++11 标识符中可以使用数字,非数字字符和通用字符名以外的由实现定义的字符 不能使用
CWG 2521 C++11 字面量运算符的用户定义字符串字面量 中的标识符照常保留 规则不同

参阅