Swift语言中的结构体与类的区别

208

来自苹果书的内容:

结构体和类之间最重要的区别之一是,在代码中传递结构体时,它们总是被复制,但是在传递类时则是按引用传递。

有人可以帮我理解这是什么意思吗?对我来说,类和结构体似乎是一样的。


3
请参阅在 .NET 中结构体和类之间的区别:https://dev59.com/UHVD5IYBdhLWcg3wVKEb#13275,我猜想 Swift 使用相同的语义。 - dalle
23
如果你知道这个,@jonrsharpe对你来说可能很容易。你能告诉我答案吗? - Manish Agrawal
1
值传递和引用传递不仅仅是面向对象编程的概念。在C语言中也有这种区别,例如void my_func(int a)void my_func(int &a)。这是编程中非常基础的问题。了解更多信息,请访问:https://dev59.com/s3RC5IYBdhLWcg3wOOP1 - superarts.org
13个回答

494

这是一个带有 class 的示例。请注意,当名称更改时,两个变量引用的实例都会更新。Bob现在已经变成了Sue,无论何时引用Bob,都将变为Sue

class SomeClass {
    var name: String
    init(name: String) {
        self.name = name
    }
}

var aClass = SomeClass(name: "Bob")
var bClass = aClass // aClass and bClass now reference the same instance!
bClass.name = "Sue"

println(aClass.name) // "Sue"
println(bClass.name) // "Sue"

现在,通过 struct 我们可以看到值被复制了,每个变量都保留了自己的一组值。 当我们将名称设置为 Sue 时,aStruct 中的 Bob 结构不会发生更改。

struct SomeStruct {
    var name: String
    init(name: String) {
        self.name = name
    }
}

var aStruct = SomeStruct(name: "Bob")
var bStruct = aStruct // aStruct and bStruct are two structs with the same value!
bStruct.name = "Sue"

println(aStruct.name) // "Bob"
println(bStruct.name) // "Sue"

所以,对于表示有状态的复杂实体,class非常棒。但是对于仅仅是度量或相关数据位的值,则更适合使用struct,这样您可以轻松地复制它们并进行计算或修改值,而不必担心副作用。


但对于仅仅是数字的值来说,它们并不复杂...谢谢你,Alex。 - Mike Rapadas
7
实际上在Swift中,数字确实是结构体(structs)。 - Nikolai Ruhe
你能澄清一下这句话“aStruct和bStruct是两个具有相同值的结构体!”吗?因为结构体内部变量的值是不同的,这让我感到困惑。 - Julian
@JulianKról 在那行代码中,aStructbStruct具有相同的值。它们都有一个名为“Bob”的单一字段。但它们是两个不同的结构体。这在下一行得到证明,当您更改其中一个结构体的名称时,另一个结构体保持不变。 - Alex Wayne
刚错过了任务。明白了,谢谢。也许外面太热了 :-) - Julian
显示剩余3条评论

66

类和结构都可以:

  • 定义属性以存储值
  • 定义方法以提供功能
  • 被扩展
  • 符合协议
  • 定义初始化器
  • 定义下标以访问其变量

只有类能够:

  • 继承
  • 类型转换
  • 定义析构函数
  • 允许引用计数多个引用。

36

struct是值类型,这意味着如果您将结构体的实例复制到另一个变量中,它只是被复制到该变量中。

值类型示例

struct Resolution {
    var width = 2
    var height = 3
}

let hd = Resolution(width: 1920, height: 1080)
var cinema = hd //assigning struct instance  to variable
println("Width of cinema instance is \(cinema.width)")//result is 1920
println("Width of hd instance is \(hd.width)")//result is 1920

cinema.width = 2048

println("Width of cinema instance is \(cinema.width)")//result is 2048
println("Width of hd instance is \(hd.width)")//result is 1920

类是引用类型。这意味着,如果您将类的实例分配给变量,则它将仅保存实例的引用而不是副本。


5
+1 表示,“如果你将一个类的实例分配给另一个变量,它将仅保留该实例的引用,而不是副本。” - Saif

14
  • 命名类型名义类型带名称的类型
  • 复合类型非名义类型无名称的类型
  • 值类型是指其值在分配给变量或常量、传递给函数或从函数返回时被复制的类型。(同时,asis 检查会复制结构体)

    引用类型在分配给变量或常量或传递给函数时不会被复制。

    值类型:

    结构体枚举[关于]元组
    struct Stringstruct Array(SetDictionary)

    (Objective-C 中的 int...)

    字符串和内置集合类型是值类型,它们包含对 heap 的内部引用以管理其大小

    • 当你给一个值类型赋值或传递时,会创建数据的一个新副本。对于一些特定的类(如Collections(Array, Dictionary, Set)),会使用“写时复制”- “COW”机制[关于]进行优化,例如在修改对象时创建副本。对于自定义类型,您需要自己支持COW。
    • 当您修改实例时,它只有局部影响。
    • 如果值是一个本地变量,则使用堆栈内存[关于]

    引用类型:
    Class, Function

    (Objective-C除外)

    使用ARC

    • 当您分配或传递引用类型时,会创建对原始实例的新引用(实例的地址被复制)。
    • 当您修改实例时,它具有全局影响,因为该实例是共享的,并且可以通过指向它的任何引用访问。
    • 通常使用堆内存[关于]

    在这里输入图片描述

    值类型默认推荐使用的值类型最大的优点通常是它们是线程安全的

    引用类型优点:

    • 它们可以被继承,
    • 可以使用deinit()方法,
    • 可以通过引用来比较实例===
    • 因为值类型是在Swift中引入的,所以可以与Objective-C互操作。

    [栈 vs 堆]
    [let vs var,class vs struct]
    [Class vs Structure]

    选择结构体还是类
    类型
    类和结构体


    12

    以上的答案是正确的,我希望我的回答能够帮助那些不理解上面的答案的人。

    在Swift中,有两种类型的对象:

    1. 结构体

    它们之间的主要区别是:

    • 结构体是值类型
    • 类是引用类型

    例如,以下代码可以更好地理解这个概念。

    struct SomeStruct {
    var a : Int;
    
    init(_ a : Int) {
        self.a = a
    }
    }
    
    class SomeClass {
    var a: Int;
    
    init(_ a: Int) {
        self.a = a
    }
    
    }
    var x = 11
    
    var someStruct1 = SomeStruct(x)
    var someClass1 = SomeClass(x)
    
    var someStruct2 = someStruct1
    var someClass2 = someClass1
    
    someClass1.a = 12
    someClass2.a // answer is 12 because it is referencing to class 1     property a
    
    someStruct1.a = 14
    someStruct2.a // answer is 11 because it is just copying it not referencing it
    

    这是它们的主要区别,但我们还有一些次要区别。

    1. 必须声明初始化器(构造函数)
    2. 有析构函数
    3. 可以继承其他类

    结构体

    1. 它自带一个免费的初始化器,如果您声明了初始化器,则免费的初始化器将被覆盖
    2. 没有析构函数
    3. 不能从其他结构体继承

    11

    这个问题似乎是重复的,但以下内容可以回答大部分用例:

    1. 结构体(Structures)和类(Classes)之间最重要的区别之一是:结构体是值类型(Value types),在代码中传递时总是复制它们自己,而类是引用类型(Reference type),在传递时会通过引用传递。

    2. 此外,类具有继承(Inheritance),允许一个类继承另一个类的特征。

    3. 结构体属性存储在栈(Stack)上,而类实例存储在堆(Heap)上,因此,有时栈比类快得多。

    4. 结构体自动获得默认初始化程序,而在类中,我们必须进行初始化。

    5. 结构体在线程安全或单例的任何时候都是安全的。

    并且也需要了解值类型和引用类型的区别,才能总结结构体和类之间的差异。

    1. 当你复制值类型时,它会将所有数据从你要复制的东西复制到新变量中。它们是两个独立的对象,改变其中一个不会影响另一个。
    2. 当你复制引用类型时,新变量引用与你正在复制的东西相同的内存位置。这意味着改变一个将会影响另一个,因为它们都引用了相同的内存位置。下面的示例代码可以作为参考。

    // sampleplayground.playground

      class MyClass {
            var myName: String
            init(myName: String){
                self.myName = myName;
            }
        }
    
        var myClassExistingName = MyClass(myName: "DILIP")
        var myClassNewName = myClassExistingName
        myClassNewName.myName = "John"
    
    
        print("Current Name: ",myClassExistingName.myName)
        print("Modified Name", myClassNewName.myName)
    
        print("*************************")
    
        struct myStruct {
            var programmeType: String
            init(programmeType: String){
                self.programmeType = programmeType
            }
        }
    
        var myStructExistingValue = myStruct(programmeType: "Animation")
        var myStructNewValue = myStructExistingValue
        myStructNewValue.programmeType = "Thriller"
    
        print("myStructExistingValue: ", myStructExistingValue.programmeType)
        print("myStructNewValue: ", myStructNewValue.programmeType)
    

    输出:

    Current Name:  John
    Modified Name John
    *************************
    myStructExistingValue:  Animation
    myStructNewValue:  Thriller
    

    嗨Dilip,你能举个例子说明“Struct在任何时候都是线程安全或单例”的吗?提前致谢。 - Narasimha Nallamsetty
    结构体在任何时候都不是线程安全或单例。 - john07

    3
    如果您在苹果手册中继续查看,您将看到这个部分:“结构体和枚举是值类型”。
    在这个部分中,您将看到以下内容:
    “let hd = Resolution(width: 1920, height: 1080) var cinema = hd” 这个例子声明了一个名为hd的常量,并将其设置为Resolution实例,该实例使用全高清视频的宽度和高度进行初始化(1920像素宽,1080像素高)。 然后,它声明了一个名为cinema的变量,并将其设置为当前hd的值。由于Resolution是一个结构体,因此会创建现有实例的副本,并将此新副本分配给cinema。即使hd和cinema现在具有相同的宽度和高度,它们在幕后也是两个完全不同的实例。 接下来,更改了cinema的宽度属性,以使其成为数字电影投影所使用的略宽的2K标准的宽度(2048像素宽,1080像素高): "cinema.width = 2048" 检查cinema的宽度属性表明它确实已更改为2048: "println("cinema is now (cinema.width) pixels wide") // prints "cinema is now 2048 pixels wide" 然而,原始hd实例的宽度属性仍具有旧值1920: "println("hd is still (hd.width) pixels wide") // prints "hd is still 1920 pixels wide" 当cinema获得hd的当前值时,hd中存储的值被复制到新的cinema实例中。最终结果是两个完全独立的实例,它们恰好包含相同的数字值。因为它们是单独的实例,所以将cinema的宽度设置为2048不会影响hd中存储的宽度。”
    这就是结构体和类的最大区别。结构体会被复制,而类则会被引用。

    2
    通常情况下(在大多数编程语言中),对象是存储在堆上的数据块,然后一个引用(通常是指针)指向这些块,包含一个名为name的标识符来访问这些数据块。这种机制允许通过复制它们的引用(指针)的值来共享堆中的对象。这不适用于基本数据类型,如整数,因为创建引用所需的内存几乎与对象(在这种情况下是整数值)相同。因此,在大型对象的情况下,它们将作为值而不是引用传递。
    Swift使用结构体以提高性能,即使是对于String和Array对象。 在这里有一篇非常好的阅读材料

    1
    1.structure is value type.
       = > when we assign structure variable to other variable or pass as parameter to function, it creates separate/new copy => so that changes made on one variable does not  reflect on another.[We can say like **call by value** concept] 
    Example :
    
        struct DemoStruct 
        { 
            var value: String 
            init(inValue: String) 
            { 
                self.value = inValue 
            } 
        } 
    
    
    var aStruct = DemoStruct(inValue: "original") 
    var bStruct = aStruct // aStruct and bStruct are two structs with the same value! but references to diff location`enter code here`
    bStruct.value = "modified" 
    
    print(aStruct.value) // "original" 
    print(bStruct.value) // "modified"
    
    
    2.class is reference type.
     = > when we assign structure variable to other variable or pass as parameter to function, it **does not** creates separate/new copy => so that changes made on one variable does not  reflect on another.[We can say like **call by reference** concept] 
    Example:
    class DemoClass 
    {   
        var value: String 
        init(inValue: String) 
        {
            self.value = inValue 
        } 
    } 
    
    var aClass = DemoClass(inName: "original") 
    var bClass = aClass // aClass and bClass now reference the same instance! 
    bClass.value = "modified" 
    
    print(aClass.value) // "modified" 
    print(bClass.value) // "modified"
    

    1
    正如许多人已经指出的那样,复制结构体和类之间的区别可以从它们在C语言中的来源中理解。例如一个结构体:
    struct A {
        let a: Int
        let c: Bool
    }
    

    在函数父对象或结构中本地存储的内存中,它会是类似这样的:

    64bit for int
    8 bytes for bool
    

    now for

    class A {
        let a: Int
        let c: Bool
    }
    

    与数据内容存储在本地内存、结构体或类不同,它将是一个单一指针。

    64bit address of class A instance
    

    当你复制这两个内容时,很容易看出它们之间的区别。如果复制第一个内容,你会复制int类型的64位和bool类型的8位;如果复制第二个内容,你会复制类A实例的64位地址,你可以有多个相同内存地址的副本,都指向同一个实例,但每个结构体的副本都是独立的。

    现在情况可能变得复杂,因为你可以将两者混合使用,例如:

    struct A {
        let a: ClassA
        let c: Bool
    }
    

    你的内存会类似于这样:
    64bit address of class A instance
    8 bytes for bool
    

    这是一个问题,因为即使您在程序中有多个结构的副本,它们都有一个指向同一对象ClassA的副本。这意味着,就像传递的每个实例ClassA的多个引用必须保持对对象存在的引用计数,以便知道何时删除它们一样,您的程序可能会有多个引用需要保留对其ClassA实例的引用计数。如果您的结构中有很多类,或者它包含的结构中有很多类,这可能会耗费时间。现在,当您复制结构时,编译器必须生成代码,遍历结构中引用的每个类实例和子结构,并增加它们的引用计数,以跟踪有多少引用。

    对于一些苹果结构类型,情况变得更加复杂,因为它们实际上包含对象类型。关于引用数据的好处是,它可以存储在内存中,并且可以根据需要加长和缩短,它们可以非常大,不像存储在本地堆栈上的数据。因此,像String、Array、Set、Dictionary这样的类型虽然表现得像结构体,甚至在尝试修改它们时会制作内部数据的副本,以便您不改变所有出现的情况,但它们的数据仍然必须进行引用计数,因此包含大量这些类型的结构体仍然可能很慢,因为每个类型的内部数据都必须保留。

    当然,传递结构类型可以减少许多错误的可能性,但根据它们包含的类型,它们也可能会减慢程序速度。


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