在Swift中,`let`和`var`有什么区别?

365
在苹果的Swift语言中,“let”和“var”的区别是什么?
据我所知,Swift是一种编译型语言,但它在编译时不检查类型。这使我感到困惑。编译器如何知道类型错误?如果编译器不检查类型,那么在生产环境中岂不是有问题?
当我尝试给一个“let”赋值时,会出现以下错误提示:
“Cannot assign to property: 'variableName' is a 'let' constant Change 'let' to 'var' to make it mutable”。

50
let 用于常量,var 用于变量。 - Collin Henderson
3
@Edward 你说的编译时没有类型检查是什么意思?据我所知,它是静态类型的,但如果编译器可以自行推断类型,则会进行类型推断。但我只看到了25页...... ;-) - Joachim Isaksson
5
这是一个主题相关但措辞不佳的问题。至少有两个问题:(i) letvar 之间的区别;(ii) 类型安全与类型推断。此外,当发布者提到“生产阶段”时,实际上他是指在运行时。 - Kevin Le - Khnle
1
此外,对于定义集合(数组和字典)的变量使用 var 关键字会创建一个可变集合(不仅是引用,而且可以修改集合的内容)。var 的另一种用法是能够修改传递给函数的参数:func foo(var bar:Int) 将允许您在函数作用域内本地修改参数 bar。 - John Difool
可能是在Swift编程语言中区分let和var的可能重复问题 - The Paramagnetic Croissant
显示剩余2条评论
32个回答

445

let 关键字定义一个常量:

let theAnswer = 42
theAnswer 不能在之后更改。这就是为什么任何使用 let 写的 weak 都不行。它们需要在运行时更改,因此必须使用 var

var 定义一个普通变量。

有趣的是:

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

另一个奇怪的特点是:

您可以使用几乎任何字符作为常量和变量名称,包括 Unicode 字符:

let  = "dogcow"

摘自:苹果公司《Swift编程语言》iBooks. https://itunes.apple.com/WebObjects/MZStore.woa/wa/viewBook?id=881256329


社区知识库

由于评论请求添加其他事实到答案中,因此将其转换为社区知识库答案。请随意编辑答案以使其更好。


1
C和C++也允许使用Unicode标识符,通过UCN实现。如果编译器支持任何Unicode源编码,则可以直接使用Unicode字符。例如,在clang的C++中,auto = "dogcow";是有效的。 - bames53
143
@bames53 好的,我只是不确定是否想要调试这样的程序:if === { = ♠︎ } :) ;) - clt60
1
首先,如上所述,这在REPL中完全不同。其次,如果它是一个常量,那么它并不是一个很好的常量。这在编译器中可以正常工作:let foo = [1,2,3]; foo[2] = 5; println("(foo)") // [1, 2, 5] - Kevin Frost
36
尽管这个回答获得了最多赞,但它没有提到在引用对象而不是值类型时let/var的行为。关键在于,在两种情况下,您仍然可以更改对象的属性,但不能修改指针以引用另一个对象。这一点从涉及整数和字符串的基本示例中并不立即明显。 - SaltyNuts
@SaltyNuts 是的,你说得对。这里缺少主要的东西。有些人说 let 是不可变的,一旦初始化就不能再改变,我看到每个人都在他们的例子中使用 integers。试着用 let 前缀取一个 NSMutableArray,你仍然可以从中 添加和删除 对象。正确的是 let 使一个 const 指针,所以我们不能再次初始化对象,但我们可以通过添加或追加来改变它的值。 - TheTiger
显示剩余12条评论

36
根据The Swift Programming Language Book,与C语言类似,Swift使用变量通过标识名称存储和引用值。Swift还广泛使用值无法更改的变量。这些称为常量,并且比C中的常量更强大。 varlet都是引用,因此let是一个常量引用。使用基本类型并不能真正显示出letconst的区别。区别在于将其与类实例(引用类型)一起使用时:
class CTest
{
    var str : String = ""
}

let letTest = CTest()
letTest.str = "test" // OK

letTest.str = "another test" // Still OK

//letTest = CTest() // Error

var varTest1 = CTest()
var varTest2 = CTest()
var varTest3 = CTest()

varTest1.str = "var 1"
varTest2.str = "var 2"
varTest3 = varTest1
varTest1.str = "var 3"

varTest3.str // "var 3"

1
这个术语完全是错误的...所有的引用都是常量引用。没有“const ref”的概念。一旦绑定,引用始终绑定到相同的内存地址,不能被更改或“解除绑定”。我认为你的意思是var是一个“指针”,而let是一个“常量指针”。 - AyBayBay
@AyBayBay,虽然你所写的在C++中是正确的,但我认为上面的代码片段证明了在Swift中并非如此。指针将允许指针算术和直接内存访问,而这在这里又不是这种情况。 --编辑:我没有找到任何证据表明“所有引用确实都是const引用”对于每种编程语言都是真实的。 - Krzak
顺便提一下,根据Wikipedia的说法,指针是引用。 - Krzak
3
我同意您的术语是错误的。 varlet与所绑定标识符是否引用无关。如果类型是“结构体”类型,则在概念上是一个值。如果类型是“类”,则在概念上是一个引用,如果使用let,则该引用是一个常量。如果CTest是一个结构体,则您的所有letTest分配都不起作用。 - JeremyP
1
如果类型是 struct,那么它就是值类型,没有任何概念上的问题。同样的,class 也是引用类型。 当修改值类型时,你会创建一个该类型的新实例。 https://developer.apple.com/swift/blog/?id=10 因此,显然你不能修改绑定到值类型的 let 字段/属性。 - Krzak

18

Swift let vs var

let - constant
var - variable

[Constant vs variable]
[Struct vs Class]

官方文档 docs.swift.org 表示:

let 定义的常量在设定之后不能被改变,而 var 定义的变量可以在将来被设置为不同的值。

这个术语实际上描述了一种重新分配机制。

Mutability(可变性)

Mutability - 可更改的 - 对象的状态可以在创建后被更改[相关]

值类型和引用类型[相关]

引用类型(Class)

Swift 的 class默认是可变

var+class
它可以被重新分配或更改

let+class=地址常量
它不能被重新分配,但可以更改

值类型(Struct, Enum)

Swift 的 struct 可以更改它们的可变性状态:

var+struct=可变
它可以被重新分配或更改

let+struct=*不可变或者更确切地说是不可修改的[相关] [示例] [示例]=数值常量
它不能被重新分配或更改

*不可变 - 请检查 testStructMutability 测试

实验:

class MyClass {
    var varClass: NSMutableString
    var varStruct: String
    
    let letClass: NSMutableString
    let letStruct: String
    
    init(_ c: NSMutableString, _ s: String) {
        varClass = c
        varStruct = s
        
        letClass = c
        letStruct = s
    }
}

struct MyStruct {
    var varClass: NSMutableString
    var varStruct: String
    
    let letClass: NSMutableString
    let letStruct: String
    
    init(_ c: NSMutableString, _ s: String) {
        varClass = c
        varStruct = s
        
        letClass = c
        letStruct = s
    }
    
    
    //mutating function block
    func function() {
//            varClass = "SECONDARY propertyClass" //Cannot assign to property: 'self' is immutable
//            varStruct = "SECONDARY propertyStruct" //Cannot assign to property: 'self' is immutable
    }

    mutating func mutatingFunction() {
        varClass = "SECONDARY propertyClass"
        varStruct = "SECONDARY propertyStruct"
    }
}

可能的使用案例

func functionVarLetClassStruct() {
    
    var varMyClass = MyClass("propertyClass", "propertyStruct")
    
    varMyClass.varClass = "SECONDARY propertyClass"
    varMyClass.varStruct = "SECONDARY propertyStruct"
    
//        varMyClass.letClass = "SECONDARY propertyClass" //Cannot assign to property: 'letClass' is a 'let' constant
//        varMyClass.letStruct = "SECONDARY propertyStruct" //Cannot assign to property: 'letStruct' is a 'let' constant
    
    let letMyClass = MyClass("propertyClass", "propertyStruct")
    
    letMyClass.varClass = "SECONDARY propertyClass"
    letMyClass.varStruct = "SECONDARY propertyStruct"
    
//        letMyClass.letClass = "SECONDARY propertyClass" //Cannot assign to property: 'letClass' is a 'let' constant
//        letMyClass.letStruct = "SECONDARY propertyStruct" //Cannot assign to property: 'letStruct' is a 'let' constant
    
    var varMyStruct = MyStruct("propertyClass", "propertyStruct")
    
    varMyStruct.varClass = "SECONDARY propertyClass"
    varMyStruct.varStruct = "SECONDARY propertyStruct"
    
//        varMyStruct.letClass = "SECONDARY propertyClass" //Cannot assign to property: 'letClass' is a 'let' constant
//        varMyStruct.letStruct = "SECONDARY propertyStruct" //Cannot assign to property: 'letStruct' is a 'let' constant
    
    let letMyStruct = MyStruct("propertyClass", "propertyStruct")
    
//        letMyStruct.varClass = "SECONDARY propertyClass" //Cannot assign to property: 'letMyStruct' is a 'let' constant
//        letMyStruct.varStruct = "SECONDARY propertyStruct" //Cannot assign to property: 'letMyStruct' is a 'let' constant
    
//        letMyStruct.letClass = "SECONDARY propertyClass" //Cannot assign to property: 'letClass' is a 'let' constant
//        letMyStruct.letStruct = "SECONDARY propertyStruct" //Cannot assign to property: 'letStruct' is a 'let' constant
    
}

mutating - 更改结构体函数

你可以将结构体中的方法标记为mutating

  1. 表示此函数更改内部属性值
  2. 仅能在var变量上调用可变函数
  3. 当可变函数结束时,结果是可见的
func testStructMutatingFunc() {
    //given
    var varMyStruct = MyStruct("propertyClass", "propertyStruct")
    
    //when
    varMyStruct.mutatingFunction()
    
    //than
    XCTAssert(varMyStruct.varClass == "SECONDARY propertyClass" && varMyStruct.varStruct == "SECONDARY propertyStruct")
    
    // It is not possible to call a mutating function on a let variable
    let letMyStruct = MyStruct("propertyClass", "propertyStruct")
//        letMyStruct.mutatingFunction() //Cannot use mutating member on immutable value: 'letMyStruct' is a 'let' constant
}

inout函数参数

  1. inout可以让你重新分配/修改传递(原始)值。
  2. 您只能在inout参数中传递var变量
  3. 结果在函数完成时可见

inout的下一个流程如下:

  1. 在调用函数之前,传递的值被复制到复制的值中
  2. 在函数完成后,将复制的值赋给传入的值
//InOut
func functionWithInOutParameter(a: inout MyClass, s: inout MyStruct) {
    
    a = MyClass("SECONDARY propertyClass", "SECONDARY propertyStruct") //<-- assign
    s = MyStruct("SECONDARY propertyClass", "SECONDARY propertyStruct") //<-- assign
}


func testInOutParameter() {

    //given
    var varMyClass = MyClass("PRIMARY propertyClass", "PRIMARY propertyStruct")
    var varMyStruct = MyStruct("PRIMARY propertyClass", "PRIMARY propertyStruct")

    //when
    functionWithInOutParameter(a: &varMyClass, s: &varMyStruct)

    //then
    XCTAssert(varMyClass.varClass == "SECONDARY propertyClass" && varMyClass.varStruct == "SECONDARY propertyStruct")
    XCTAssert(varMyStruct.varClass == "SECONDARY propertyClass" && varMyStruct.varStruct == "SECONDARY propertyStruct")
    
    
    // It is not possible to pass let into inout parameter
    let letMyClass = MyClass("PRIMARY propertyClass", "PRIMARY propertyStruct")
    let letMyStruct = MyStruct("PRIMARY propertyClass", "PRIMARY propertyStruct")
//        functionWithInOutParameter(a: &letMyClass, s: &letMyStruct) //Cannot pass immutable value as inout argument: 'letMyClass', 'letMyStruct' are 'let' constants
}     

你仍然可以改变let + struct

func testStructMutability()  {
    //given
    let str: NSMutableString = "propertyClass"
    let letMyStruct = MyStruct(str, "propertyStruct")
    
    //when
    str.append(" SECONDARY")
    
    //then
    XCTAssert(letMyStruct.letClass == "propertyClass SECONDARY")
}

尽可能使用let。当必须时使用var

【更改结构】


17

let用于定义常量,var用于定义变量。

与C语言类似,Swift使用变量来存储和引用通过标识名称识别的值。Swift还广泛使用变量,其值无法更改。这些被称为常量,并且比C中的常量更为强大。在Swift中,常量在处理不需要更改的值时,在整个代码中都是安全且意图更清晰的。

https://docs.swift.org/swift-book/LanguageGuide/TheBasics.html


9
也许更好的表达这种区别是通过可变性/不可变性的概念,这是对象空间中值和实例可变性的正确范式,比仅有的“常量/变量”通常概念更为广泛。此外,这更接近Objective C的方法。
2种数据类型:值类型和引用类型。
在值类型的上下文中:
'let'定义了一个常量值(不可变)。'var'定义了一个可变值(可变)。
let aInt = 1   //< aInt is not changeable

var aInt = 1   //< aInt can be changed

在引用类型的上下文中:
数据的标签不是值,而是对值的引用。
如果有一个人 aPerson = Person(name:Foo, first:Bar)
aPerson并不包含此人的数据,而是指向此人数据的引用。
let aPerson = Person(name:Foo, first:Bar)
               //< data of aPerson are changeable, not the reference

var aPerson = Person(name:Foo, first:Bar)
               //< both reference and data are changeable.

例如:

var aPersonA = Person(name:A, first: a)
var aPersonB = Person(name:B, first: b)

aPersonA = aPersonB

aPersonA now refers to Person(name:B, first: b)

并且

let aPersonA = Person(name:A, first: a)
let aPersonB = Person(name:B, first: b)

let aPersonA = aPersonB // won't compile

但是
let aPersonA = Person(name:A, first: a)

aPersonA.name = "B" // will compile

2
但是如果aPerson有setter,你可以修改它的属性,对吧?所以let并没有使Person成为不可变的。 - drewish
5
不可变性?那是不可能的!肯定不可取。我想你要找的词可能是“不变性” :-) - paxdiablo

7

非常简单:

  • let 是常量。
  • var 是动态的。

一些说明:

let 创建一个常量(类似于 NSString)。一旦设置了它的值,就不能再更改它。但是您仍然可以将其添加到其他内容并创建新变量。

var 创建一个变量(类似于 NSMutableString),因此您可以更改其值。但是这个问题已经被解决了很多次。


6

The Swift Programming Language的声明常量和变量部分指定了以下内容:

使用let关键字声明常量,使用var关键字声明变量。

请确保理解这对引用类型的工作原理。与值类型不同,即使将引用类型的实例声明为常量,对象的基础属性也可以更改。请查看文档中的类是引用类型部分,并查看更改frameRate属性的示例。


5

let 定义了一个“常量”。它的值只能被设置一次,尽管不一定要在声明时设置。例如,在类中使用 let 定义一个必须在初始化期间设置的属性:

class Person {

    let firstName: String
    let lastName: String

    init(first: String, last: String) {
         firstName = first
         lastName = last
         super.init()
    }
}

使用这个设置后,在调用(例如)Person(first:"Malcolm", last:"Reynolds")来创建一个Person实例之后,将无效地分配给firstNamelastName
必须在编译时为所有变量(letvar)定义类型,并且任何试图设置变量的代码只能使用该类型(或子类型)。您可以在运行时分配,但其类型必须在编译时知道。

4

let用于声明一个常量值-一旦给它赋予了初始值,就不能更改它。
var用于声明一个变量值-您随意可以更改它的值。


2

我遇到的其他语言中常量的另一个区别是:无法在稍后初始化常量(let)应该在声明常量时进行初始化。

例如:

let constantValue : Int // Compile error - let declarations require an initialiser expression

变量
var variableValue : Int // No issues 

这并不完全正确。在一个函数中,我可以有以下代码:let num: Int,然后是:if someCondition { num = 42 } else { num = 100 }。只要在声明和赋值的代码行之间没有其他代码,它就可以跨行分割。 - HangarRash

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