现在位置: 首页 > Swift 教程 > 正文

Swift 常量

常量用来保存一旦确定就不再改变的值,Swift 用 let 声明常量。

letvar 的声明语法几乎一样,区别只在能否再次赋值:let 只能赋值一次。

本篇讲 let 的不可变语义、它对引用类型的真实含义、延迟初始化、命名惯例,以及和 var 之间的选择准则;var 的用法见《Swift 变量》。


用 let 声明常量

let 声明一个只能赋值一次的常量,初始化之后它的值就固定下来。

声明写法和 var 完全一致,把关键字换掉即可。

实例

import Foundation

// let 声明常量:赋值之后不能再改
let site = "www.runoob.com"
let visits = 100
let price = 9.9

print("站点 \(site) 访问量:\(visits),单价:\(price)")

运行结果:

站点 www.runoob.com 访问量:100,单价:9.9

如果把 let 声明的常量再赋值一次,编译器会直接报错,代码根本编译不过去。

实例

let site = "www.runoob.com"
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 声明的实例仍然可以修改自己的属性,只是不能让它改指向另一个实例。

实例

import Foundation

// 引用类型:let 限制的是绑定,不是对象本身
class Counter {
    var value = 0
}

let counter = Counter()
counter.value = 10
print("counter.value =", counter.value)

// counter = Counter()  // 非法:不能再指向别的实例

运行结果:

counter.value = 10

值类型的行为不一样。结构体、数组、字典这类值类型,let 声明的实例整体不可变,连内部的属性也不能改。

实例

struct Site {
    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 只能被赋值一次,而且所有分支都必须保证它最终被赋值。

实例

// 先声明,后赋值: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 里虽然能编译,但不符合社区规范,代码审查时通常会被要求改掉。

规则推荐不推荐
小驼峰,首字母小写maxRetryCountMaxRetryCountMAX_RETRY_COUNT
见名知义,不用单字母defaultTimeouttn
布尔常量用 is、has 开头isDebugEnableddebug
不使用关键字,必要时才加反引号className`class`

类型名用大驼峰、变量和常量用小驼峰,是 Swift 里最基础也最统一的一条约定。

如果常量属于某个类型而不是全局共享,更推荐写成 static let 放在类型内部,避免污染全局命名空间,具体见《Swift 属性》。


let 与 var 的选择准则

社区习惯是优先用 let,只有确实需要修改时才改成 var,这样代码的可变性范围最小,也更容易推理。

选择的关键是可读性与安全性,而不是性能,所以没有理由为了「灵活」而放宽可变性。

实例

// 不变的东西用 let
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

实例

var names = ["Runoob"]
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 社区最普遍的建议。

常量能不能延迟赋值

能,只声明类型、之后一次性赋值即可,但所有分支都必须保证它被赋值,否则编译报错。