Swift 常量
常量用来保存一旦确定就不再改变的值,Swift 用 let 声明常量。
let 和 var 的声明语法几乎一样,区别只在能否再次赋值:let 只能赋值一次。
本篇讲 let 的不可变语义、它对引用类型的真实含义、延迟初始化、命名惯例,以及和 var 之间的选择准则;var 的用法见《Swift 变量》。
用 let 声明常量
let 声明一个只能赋值一次的常量,初始化之后它的值就固定下来。
声明写法和 var 完全一致,把关键字换掉即可。
实例
// let 声明常量:赋值之后不能再改
let site = "www.runoob.com"
let visits = 100
let price = 9.9
print("站点 \(site) 访问量:\(visits),单价:\(price)")
运行结果:
站点 www.runoob.com 访问量:100,单价:9.9
如果把 let 声明的常量再赋值一次,编译器会直接报错,代码根本编译不过去。
实例
site = "RUNOOB"
编译错误(只摘录关键两行,实际还会打印出错位置):
error: cannot assign to value: 'site' is a 'let' constant note: change 'let' to 'var' to make it mutable
这个错误在编译期就出现,而不是等到运行时才崩溃,这正是 Swift 强调的「安全」。
let 限制的是绑定,不是对象
这是 let 最容易被误解的一点:它限制的是「这个绑定不能再指向别的值」,而不是「它指向的对象完全不能变」。
对引用类型来说,let 声明的实例仍然可以修改自己的属性,只是不能让它改指向另一个实例。
实例
// 引用类型:let 限制的是绑定,不是对象本身
class Counter {
var value = 0
}
let counter = Counter()
counter.value = 10
print("counter.value =", counter.value)
// counter = Counter() // 非法:不能再指向别的实例
运行结果:
counter.value = 10
值类型的行为不一样。结构体、数组、字典这类值类型,let 声明的实例整体不可变,连内部的属性也不能改。
实例
var name = "RUNOOB"
}
let site = Site()
site.name = "Runoob"
编译错误(只摘录关键两行):
error: cannot assign to property: 'site' is a 'let' constant note: change 'let' to 'var' to make it mutable
把 let 改成 var 之后,site.name = "Runoob" 就是合法的,所以「结构体的值不可修改」这个说法只在 let 声明时成立。
| 场景 | let 声明后能否修改 | 说明 |
|---|---|---|
| 值类型实例的内部属性 | 不能 | 结构体、数组、字典整体不可变 |
| 引用类型实例的属性 | 能 | 对象本身的状态可以变 |
| 引用类型实例的重新赋值 | 不能 | 绑定不可变,不能指向新实例 |
注意:值类型与引用类型的完整区别见《Swift 值语义与写时复制》,类的定义与属性会在《Swift 类》展开。
常量的延迟初始化
常量不一定要在声明的那一行就赋值,可以只声明类型,稍后再赋值。
这种写法叫延迟初始化,但要注意:let 只能被赋值一次,而且所有分支都必须保证它最终被赋值。
实例
var input = 85
let score: Int
if input >= 60 {
score = input
} else {
score = 0
}
print("score =", score)
运行结果:
score = 85
编译器会检查所有分支,如果有一条分支漏掉赋值,就会报「常量未初始化」的错误。
延迟初始化最常见的场景是:值依赖某个条件才能算出来,又希望声明之后不再改动。
注意:延迟初始化不是 lazy。这里的 let 在赋值前不能读取,而 lazy 是属性首次访问时才计算,属于属性的一种修饰,会在《Swift 属性》介绍。
常量命名惯例
Swift 的常量命名和变量一样用小驼峰,这一点和 C、Java 用全大写加下划线的习惯不同。
全大写写法在 Swift 里虽然能编译,但不符合社区规范,代码审查时通常会被要求改掉。
| 规则 | 推荐 | 不推荐 |
|---|---|---|
| 小驼峰,首字母小写 | maxRetryCount | MaxRetryCount、MAX_RETRY_COUNT |
| 见名知义,不用单字母 | defaultTimeout | t、n |
| 布尔常量用 is、has 开头 | isDebugEnabled | debug |
| 不使用关键字,必要时才加反引号 | className | `class` |
类型名用大驼峰、变量和常量用小驼峰,是 Swift 里最基础也最统一的一条约定。
如果常量属于某个类型而不是全局共享,更推荐写成 static let 放在类型内部,避免污染全局命名空间,具体见《Swift 属性》。
let 与 var 的选择准则
社区习惯是优先用 let,只有确实需要修改时才改成 var,这样代码的可变性范围最小,也更容易推理。
选择的关键是可读性与安全性,而不是性能,所以没有理由为了「灵活」而放宽可变性。
实例
let siteName = "RUNOOB"
let maxRetryCount = 3
// 会变的东西用 var
var retryCount = 0
retryCount += 1
print("\(siteName):已重试 \(retryCount) 次,上限 \(maxRetryCount) 次")
运行结果:
RUNOOB:已重试 1 次,上限 3 次
如果拿不准,先写 let,等编译器提示 cannot assign to value 时再改成 var,这样能保证每一个 var 都是有理由的。
| 情况 | 选择 | 说明 |
|---|---|---|
| 值确定后不再改变 | let | 默认选择,配置项、固定文案都属此类 |
| 需要反复赋值 | var | 计数器、累加器、状态标志 |
| 需要延迟赋值 | let | 先声明类型,之后一次性赋值 |
| 引用类型的对象状态要变 | let | 对象内部可变,绑定本身不需要 var |
常见问题
下面是常量部分最常被问到的几个问题。
let 声明的数组能添加元素吗
不能。Array 是值类型,let 声明的数组是不可变的,调用 append 会报错,必须用 var。
实例
names.append("RUNOOB")
print(names)
let fixed = ["Runoob"]
fixed.append("RUNOOB")
编译错误(只摘录关键两行):
error: cannot use mutating member on immutable value: 'fixed' is a 'let' constant note: change 'let' to 'var' to make it mutable
什么时候该用 let
默认就用 let,只有编译器提示你需要修改时才改成 var,这是 Swift 社区最普遍的建议。
常量能不能延迟赋值
能,只声明类型、之后一次性赋值即可,但所有分支都必须保证它被赋值,否则编译报错。
