Swift中的"let"关键字是如何工作的?

28

我在指南中读到了这个简单的解释:

常量的值不需要在编译时知道,但必须恰好分配一次值。

但我想要比这更详细的信息。如果常量引用了一个对象,我还能修改它的属性吗?如果它引用了一个集合,我能添加或删除其中的元素吗?我来自C#背景; 它是否类似于readonly的工作方式(除了能在方法体中使用),如果不同,那么它有什么不同之处?


似乎意味着您无法更改变量,但如果变量指向一个对象,则应该能够以与块作用域中访问对象相同的方式更改对象。换句话说,如果变量指向可变数组,则应该能够更改数组中的值。 - zaph
如果你来自C#,那么它的意思与“readonly var”相同,如果允许的话。或者在F#中使用“let”。 - Asik
可能是Swift中let和var的区别是什么?的重复问题。 - nhgrif
是的,您仍然可以修改属性,这取决于它们的配置方式。这类似于JavaScript中的“const”。通常情况下,字段仍然可以被修改,除非它被冻结/密封或标记为不可写。 - james_womack
6个回答

48

let类似于C语言中的常量指针。如果您使用let引用对象,可以更改对象的属性或调用其方法,但不能将不同的对象分配给该标识符。

let还涉及集合和非对象类型。如果您使用let引用一个struct,则无法更改其属性或调用任何mutating func方法。

使用let/var处理集合时,其工作方式类似于可变/不可变Foundation集合:如果将数组赋值给let,则无法更改其内容。如果您使用let引用字典,则无法添加/删除键/值对或为键分配新值-它是完全不可变的。如果您想要在数组或字典中分配下标、追加或以其他方式进行突变,则必须使用var声明它。

(在Xcode 6 beta 3之前,Swift数组具有奇怪的值和引用语义混合,并且在分配给let时部分可变 - 现在已经消失了。)


你知道这个行为是否与 NSDictionary/NSMutableDictionary 一致吗? - Jiaaro
以什么方式保持一致? - rickster
嗯...我想我应该问一下:有没有任何值得注意的不一致之处?(性能,方法等) - Jiaaro
2
很奇怪,你可以更改用let定义的数组,但你不能更改用let定义的字典。 - Maciej Jastrzebski
1
更新:从XCode 6 beta 3开始,您也不能替换使用let定义的数组中的值,因此现在似乎更加一致。 - garyc40
谢谢您对“let”和C语言中的“const指针”的类比,这很有帮助。 - Goffredo

5

最好将let的思想看作是静态单赋值(SSA)——每个SSA变量正好被赋值一次。在像Lisp这样的函数式语言中,您通常不使用赋值运算符——名称仅绑定到一个值一次。例如,下面的名称yz每次调用时都只绑定到一个值:

func pow(x: Float, n : Int) -> Float {
  if n == 0 {return 1}
  if n == 1 {return x}
  let y = pow(x, n/2)
  let z = y*y
  if n & 1 == 0 {
    return z
  }
  return z*x
}

这有助于编写更正确的代码,因为它强制实现不变性且没有副作用。
下面是一位命令式编程者计算前6个5的幂的方法:
var powersOfFive = Int[]()
for n in [1, 2, 3, 4, 5, 6] {
    var n2 = n*n
    powersOfFive += n2*n2*n
}

显然,n2 是一个循环不变量,因此我们可以使用 let 来代替:
var powersOfFive = Int[]()
for n in [1, 2, 3, 4, 5, 6] {
    let n2 = n*n
    powersOfFive += n2*n2*n
}

但是一个真正的功能程序员会避免所有的副作用和变异:

let powersOfFive = [1, 2, 3, 4, 5, 6].map(
    {(num: Int) -> Int in
        let num2 = num*num
        return num2*num2*num})

1
Lisp绝对有赋值功能,变量的值可以被多次赋值。 - newacct
是的,Lisp确实允许赋值(例如setf),但函数式程序员只在真正必要时使用它...这确实会发生。 - wcochran

4

Let


Swift使用两种基本技术来存储程序员通过名称访问的值:letvar。如果您永远不会更改与该名称相关联的值,请使用let。如果您希望该名称引用一组不断变化的值,请使用var

let a = 5  // This is now a constant. "a" can never be changed.
var b = 2  // This is now a variable. Change "b" when you like.

一个常量所指向的值永远不会改变,不过如果它指向的是一个类的实例,那么它所指向的东西可以改变。
let a = 5
let b = someClass()
a = 6  // Nope.
b = someOtherClass()  // Nope.
b.setCookies( newNumberOfCookies: 5 )  // Ok, sure.

let 和集合


当你将数组分配给常量时,该数组的元素将无法添加或删除。但是,仍然可以更改该数组任何元素的值。

let a = [1, 2, 3]
a.append(4)  // This is NOT OK. You may not add a new value.
a[0] = 0     // This is OK. You can change an existing value.

常量所分配的字典无法进行任何更改。

let a = [1: "Awesome", 2: "Not Awesome"]
a[3] = "Bogus"             // This is NOT OK. You may not add new key:value pairs.
a[1] = "Totally Awesome"   // This is NOT OK. You may not change a value.

这是我对该主题的理解。 如有需要,请纠正。 如果问题已经得到解答,请原谅,我这样做部分是为了帮助自己学习。

3
抱歉,我认为你在使用数组示例来说明“Let和Collections”的示例是不正确的,因为对于a[0] = 0,不允许进行可变操作,因此这是不被允许的。 - Vlad

3

首先,对于那些来自C#背景(比如我)的初学者来说,“let”关键字定义常量的说法很容易造成困惑。通过阅读许多Stack Overflow答案,我得出了以下结论:

实际上,在Swift中并没有常量的概念。

常量是在编译时解析的表达式。对于C#和Java来说,常量必须在声明时被赋值:

public const double pi = 3.1416;         // C#
public static final double pi = 3.1416   // Java

苹果文档(使用“let”定义常量):

常量的值在编译时无需知道,但必须准确赋值一次。

从C#的角度来看,“let”可以被视为“只读(readonly)”变量。

Swift中的“let”等同于C#中的“readonly”。


0

F#用户将会对Swift的let关键字感到非常熟悉。:)

从C#的角度来看,你可以把"let"看作是"readonly var",如果这个构造被允许的话,也就是说,一个只能在声明点绑定的标识符。


0

Swift 属性:

Swift 属性官方文档

在其最简单的形式中,存储属性是作为特定类或结构体实例的一部分存储的常量或变量。存储属性可以是可变存储属性(由 var 关键字引入)或常量存储属性(由 let 关键字引入)。

例如:

下面的示例定义了一个名为 FixedLengthRange 的结构体,该结构体描述了一个整数范围,其范围长度在创建后无法更改:

struct FixedLengthRange {
    var firstValue: Int
    let length: Int
}

FixedLengthRange 的实例有一个名为 firstValue 的可变存储属性和一个名为 length 的常量存储属性。在上面的示例中,length 在创建新范围时被初始化,因为它是一个常量属性,所以之后不能更改。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接