词法结构

词法结构

使用语法的最低层级组件。

Swift 的 词法结构 描述了哪些字符序列构成了语言中的合法标记(tokens)。这些合法标记构成了语言的最低层级构建块,并在后续章节中用于描述语言的其他部分。一个标记可以由标识符、关键字、标点符号、字面量或运算符组成。

在大多数情况下,这些标记是从 Swift 源文件的字符中生成的,生成过程考虑了输入文本中最长的可能子字符串,并受以下语法规则的约束。这种行为策略被称为 最长匹配策略(longest match)最大吞噬策略(maximal munch)

空白和注释

空白字符有两个用途:在源文件中分隔标记,并区分前缀、后缀和中缀运算符(参见 ),除此之外,空白字符会被忽略。以下字符被视为空白字符:空格 (U+0020)、换行符 (U+000A)、回车符 (U+000D)、水平制表符 (U+0009)、垂直制表符 (U+000B)、换页符 (U+000C) 和空字符 (U+0000)。

编译器将注释视为空白字符。单行注释以 // 开头,并持续到换行符 (U+000A) 或回车符 (U+000D)。多行注释以 / 开头,以 / 结束。多行注释可以嵌套,但注释符号必须头尾匹配。

注释中可以包含额外的格式和标记,如 标记格式参考 中所述。

标识符

标识符 以大写或小写字母 A 到 Z、下划线 (_)、基本多语言平面(Basic Multilingual Plane)的非组合字母数字 Unicode 字符(Noncombining Alphanumeric Unicode Character),或基本多语言平面之外但不在私用区(Private Use Area)的字符开头。在第一个字符之后,还允许使用数字和组合 Unicode 字符(Combining Unicode Character)。

即使声明具有 public 访问级别修饰符,也应将以下内容视为仅内部使用:以下划线开头的标识符、第一个参数标签以下划线开头的下标操作,以及第一个参数标签以下划线开头的构造函数。这个约定允许框架作者以此方式标记某个 API 的一部分内容,以防止客户端与之交互或依赖,尽管某些限制要求这些声明是公开可访问的。此外,以两个下划线开头的标识符需保留给 Swift 编译器和标准库使用。

要将保留字用作标识符,可以在其前后加上反引号(`)。例如,class 不是一个合法的标识符,但 ` class 是合法的。反引号不被视为标识符的一部分; x x` 具有相同的指代含义。

在没有显式参数名称的闭包中,参数会被隐式命名为 $0`、`$1$2 等。这些名称在闭包的范围内是合法的标识符。

编译器会为具有属性包装器投射(Property Wrapper Projection)的属性合成以美元符号 ($) 开头的标识符。你的代码可以与这些标识符交互,但你不能声明带有该前缀的标识符。有关更多信息,请参阅 章节的 部分。

关键字和标点符号

以下关键字是保留字,不能用作标识符,除非用反引号将它们转义,如上文 中所述。除了 inoutvarlet 之外,其他关键字可以作为函数声明或函数调用中的参数名称,而无需使用反引号进行转义。当成员名称与关键字相同时,引用该成员时不需要使用反引号进行转义,除非在引用成员与使用关键字之间存在歧义——例如,selfTypeProtocol 在显式成员表达式中具有特殊含义,因此在这种情况下必须用反引号将它们转义。

以下符号被保留为标点符号,不能用作自定义运算符:(){}[].,:;=@#&(作为前缀运算符)、->、` `?!`(作为后缀运算符)。

字面量

字面量 是某种类型值的源代码表示形式,例如数字或字符串。

以下是一些字面量的示例:

42               // 整数字面量
3.14159          // 浮点数字面量
"Hello, world!"  // 字符串字面量
/Hello, .*/      // 正则表达式字面量
true             // 布尔字面量

字面量本身没有类型。相反,字面量被解析为具有无限精度,Swift 的类型推断机制会尝试为字面量推断出一个类型。例如,在声明 let x: Int8 = 42 中,Swift 使用显式的类型注解(: Int8)来推断整数字面量 42 的类型为 Int8。如果没有适当的类型信息可用,Swift 会推断该字面量的类型为 Swift 标准库中定义的默认字面量类型之一,如下表所示。在为字面量值指定类型注解时,注解的类型必须是可以从该字面量值实例化的类型。也就是说,该类型必须遵循下表中列出的 Swift 标准库协议。

例如,在声明 let str = "Hello, world" 中,字符串字面量 "Hello, world" 的默认推断类型是 String。同样,Int8 遵循 ExpressibleByIntegerLiteral 协议,因此可以在声明 let x: Int8 = 42 中用于整数字面量 42 的类型注解。

整数字面量

整数字面量 表示具有未指定精度的整数值。默认情况下,整数字面量以十进制表示;你可以使用前缀指定其他进制。二进制字面量以 0b 开头,八进制字面量以 0o 开头,十六进制字面量以 0x 开头。

十进制字面量包含数字 09。二进制字面量包含 01,八进制字面量包含 07,而十六进制字面量则包含 09 以及大写或小写的 AF

负整数字面量通过在整数字面量前加上负号 (-) 来表示,如 -42

为了提高可读性,数字之间允许使用下划线 (_),但它们会被忽略,因此不会影响字面量的值。整数字面量可以以前导零 (0) 开头,但这些零同样会被忽略,不会影响字面量的进制或值。

除非另有说明,否则整数字面量的默认推断类型是 Swift 标准库类型 Int。Swift 标准库还定义了用于表示各种大小的有符号和无符号整数的类型,详细内容请参阅

浮点数字面量

浮点数字面量 表示具有未指定精度的浮点值。

默认情况下,浮点数字面量以十进制形式表示(无前缀),但也可以以十六进制形式表示(带有 0x 前缀)。

十进制浮点数字面量由一串十进制数字序列组成,后面可以跟一个十进制小数部分、十进制指数部分或两者兼有。十进制小数部分由一个小数点 (.) 和紧随其后的一串十进制数字序列组成。指数部分以大写或小写的 e 为前缀,后面跟一串十进制数字,表示在 e 前面的值要乘以的 10 的幂。例如,1.25e2 表示 1.25 x 10²,结果为 125.0。类似地,1.25e-2 表示 1.25 x 10⁻²,结果为 0.0125

十六进制浮点数字面量由一个 0x 前缀、一个可选的十六进制小数部分和一个十六进制指数部分组成。十六进制小数部分由一个小数点和紧随其后的一串十六进制数字序列组成。指数部分以大写或小写的 p 为前缀,后面跟一串十进制数字,表示在 p 前面的值要乘以的 2 的幂。例如,0xFp2 表示 15 x 2²,结果为 60。类似地,0xFp-2 表示 15 x 2⁻²,结果为 3.75

负浮点数字面量通过在浮点数字面量前加上负号 (-) 来表示,如 -42.5

为了提高可读性,数字之间允许使用下划线 (_),但它们会被忽略,因此不会影响字面量的值。浮点数字面量可以以前导零 (0) 开头,但这些零同样会被忽略,不会影响字面量的进制或值。

除非另有说明,否则浮点数字面量的默认推断类型是 Swift 标准库类型 Double,它表示 64 位浮点数。Swift 标准库还定义了 Float 类型,它表示 32 位浮点数。

字符串字面量

字符串字面量是由引号包围的一串字符序列。单行字符串字面量由双引号包围,其形式如下:

"<#characters#>"

字符串字面量不能包含未转义的双引号(")、未转义的反斜杠(``)、回车符或换行符。

多行字符串字面量由三个双引号包围,其形式如下:

"""
<#characters#>
"""

与单行字符串字面量不同,多行字符串字面量可以包含未转义的双引号(")、回车符和换行符。但它不能包含连续三个未转义的双引号。

开启多行字符串字面量的 """ 之后的换行符不属于字符串的一部分。结束字面量的 """ 之前的换行符也不属于字符串的一部分。要创建一个以换行符开始或结束的多行字符串字面量,请在其第一行或最后一行写一个空行。

多行字符串字面量可以使用任意组合的空格和制表符进行缩进,这些缩进不会包含在字符串中。结束字面量的 """ 确定了缩进的长度:字面量中的每一个非空行开头的缩进必须与结束 """ 之前的缩进完全相同。制表符和空格之间不会有相互转换。在该缩进之后可以包含额外的空格和制表符,这些空格和制表符会出现在字符串中。

多行字符串字面量中的换行符会被标准化为使用行分隔符。即使源文件中包含混合的回车符和换行符,字符串中的所有换行符也会变为一致。

在多行字符串字面量中,在行末尾写一个反斜杠 (``) 会将其后的换行符从字符串中忽略。任何在反斜杠和换行符之间的空白也会被忽略。你可以使用这种语法在源代码中硬折叠一个多行字符串字面量,而不会改变结果字符串的值。

特殊字符可以通过以下转义序列包含在单行和多行字符串字面量中:

表达式的值可以通过在反斜杠 (``) 后面加上用括号括起来的表达式插入到字符串字面量中。插值表达式可以包含字符串字面量,但不能包含未转义的反斜杠、回车符或换行符。

例如,以下所有字符串字面量具有相同的值:

"1 2 3"
"1 2 \("3")"
"1 2 \(3)"
"1 2 \(1 + 2)"
let x = 3; "1 2 \(x)"

由扩展定界符包围的字符串是由引号和一组或多组配对的井号(#)包围的一串字符序列。由扩展定界符包围的字符串具有以下形式:

#"<#characters#>"#

#"""
<#characters#>
"""#

由扩展定界符包围的字符串中的特殊字符在结果字符串中显示为普通字符而不是特殊字符。你可以使用扩展定界符来创建包含通常会产生特殊效果字符的字符串,这些特殊效果比如有生成字符串插值、开启转义序列或终止字符串。

以下示例展示了一个字符串字面量和一个由扩展定界符包围的字符串,它们创建了等价的字符串值:

let string = #"\(x) \ " \u{2603}"#
let escaped = "\\(x) \\ \" \\u{2603}"
print(string)
// 打印 "\(x) \ " \u{2603}"
print(string == escaped)
// 打印 "true"

如果你使用多个井号来形成由扩展定界符包围的字符串,不要在井号之间放置空格:

print(###"Line 1\###nLine 2"###) // 正确
print(# # #"Line 1\# # #nLine 2"# # #) // 错误

使用扩展定界符创建的多行字符串字面量具有与常规多行字符串字面量相同的缩进要求。

默认情况下,字符串字面量的推断类型为 String

有关 String 类型的更多信息,请参见 String

字符串字面量通过 + 运算符连接时,连接操作在编译时完成。 例如,以下示例中 textAtextB 的值是相同的——没有发生运行时的连接操作。

let textA = "Hello " + "world"
let textB = "Hello world"

正则表达式字面量

正则表达式字面量是一串被斜杠(/)包围的一串字符序列,形式如下:

/<#regular expression#>/

正则表达式字面量不能以未转义的制表符或空格开头,也不能包含未转义的斜杠(/)、回车符或换行符。

在正则表达式字面量中,反斜杠被视为正则表达式的一部分,而不仅仅是像在字符串字面量中那样作为转义字符。它表示后续的特殊字符应按字面理解,或者后续的非特殊字符应按特殊方式处理。例如,/\(/ 匹配一个左括号,而 /\d/ 匹配一个数字。

由扩展定界符包围的正则表达式字面量是一串被斜杠(/)和一组或多组配对的井号(#)包围的字符序列。使用扩展定界符包围的正则表达式字面量有以下形式:

#/<#regular expression#>/#

#/
<#regular expression#>
/#

使用扩展定界符的正则表达式字面量可以以未转义的空格或制表符开头,可以包含未转义的斜杠(/),并且可以跨多行。在多行正则表达式字面量中,开始定界符必须在一行的末尾,结束定界符必须独立占一行。在多行正则表达式字面量内部,扩展正则表达式语法默认启用——具体来说,空白字符将被忽略,并允许使用注释。

如果使用多个井号来形成由扩展定界符包围的正则表达式字面量,不要在井号之间留有空格:

let regex1 = ##/abc/##       // 正确
let regex2 = # #/abc/# #     // 错误

如果你需要创建一个空的正则表达式字面量,则必须使用扩展定界符语法。

运算符

Swift 标准库定义了许多运算符供你使用,其中许多运算符在 中进行了讨论。本节描述了哪些字符可以用于定义自定义运算符。

自定义运算符可以用这些 ASCII 字符之一开头:/=-+!%<>&|^?~,或者是定义在下面语法中的 Unicode 字符(其中包括来自 数学运算符(Mathematical Operators)杂项符号(Miscellaneous Symbols)装饰符号(Dingbats)* Unicode 块的字符等)之一。在第一个字符之后,还允许使用组合 Unicode 字符(Combining Unicode Character)。

你还可以定义以点(.)开头的自定义运算符。这些运算符可以包含额外的点。例如,.+. 被视为一个单一的运算符。如果一个运算符不是以点开头的,则它不能在其他地方包含点。例如,+.+ 被视为 + 运算符后跟 .+ 运算符。

虽然你可以定义包含问号 (?) 的自定义运算符,但它们不能仅由单个问号字符组成。此外,尽管运算符可以包含感叹号 (!),但后缀运算符不能以问号或感叹号开头。

运算符周围的空白用来确定运算符是作为前缀运算符、后缀运算符还是中缀运算符使用。这种行为遵循以下规则:

在这些规则中,运算符前的字符 ([{,运算符后的字符 )]},以及字符 ,;: 也被视为空白。

如果预定义的 !? 运算符左边没有空白,无论右边是否有空白,它都会被视为后缀运算符。要使用 ? 作为可选链运算符(Optional-Chaining Operator),它左边必须没有空白。要在三元条件运算符 (? :) 中使用它,则必须在左右两边都有空白。

如果中缀运算符的其中一个参数是正则表达式字面量,则该运算符的左右两边都必须有空白。

在某些构造中,以 <> 开头的运算符可能会被拆分为两个或多个标记符号。拆分后剩余的部分会以同样的规则处理,并可能再次被拆分。这意味着在 Dictionary> 这样的构造中,你不需要添加空白来消除闭合 > 符号之间的歧义。在这个例子中,闭合的 > 符号不会被视为单个标记符号,也不会被错误解释为位移 >> 运算符。

要了解如何定义新的自定义操作符,请参阅 。要了解如何重载现有的操作符,请参阅

← 返回目录