值类型 与 引用类型
class SomeClass {
var number: Int = 0
}
struct SomeStruct {
var number: Int = 0
}
var reference = SomeClass()
reference.number = 42
var reference2 = reference
reference.number = 43
print("The number in reference2 is \(reference2.number)")
var value = SomeStruct()
value.number = 42
var value2 = value
value.number = 43
print("The number in value2 is \(value2.number)")
打印结果为
The number in reference2 is 43
The number in value2 is 42
如何选择
从根本上讲这两者的区别就是当你在他们身上使用等号的时候发生了什么。值类型被拷贝,而引用类型只是获得另外一个引用。
因此当决定使用哪种数据类型时根本上要问的问题就是:拷贝这个类型有意义吗?你想方便地使用拷贝操作并且会频繁使用吗?
集合是个有趣的例子。他们包含了数组,字典还有字符串。他们是可拷贝的吗?很明显是。你想让拷贝成为一项便捷又频繁的操作吗?那就不是很明确了。
大多数语言对这个问题说“no”并把他们的集合设定为引用类型。这在 Objective-C、Java、Python、JavaScript 以及任何我能想到的语言中都是这样的(一个主要的例外就是 C++ 中的 STL 集合类型,但是 C++ 是语言界的奇葩,它总是表现得跟大家不一样)。
Swift 对此说“yes”,那也就意味着Array,Dictionary和String都是结构体而不是类。当他们被赋值以及作为参数被传递的时候都会被拷贝。如果拷贝的代价很小的话这绝对是明智的决定,而这也正是 Swift 很努力要做到的。
嵌套类型
当嵌套值和引用类型的时候有四种不同的组合。只这其中的一种就会让你的生活变的很有趣。
如果你有一个引用类型嵌套了另外一个引用类型,没有什么特别的事会发生。像通常那样,任何一个指向内部或者外部值的指针都能操纵他指向的对象。只要其中一个引用操纵值使其改变,其他引用指向的值也就跟着变了。
如果你有一个值类型嵌套了另外一个值类型,这就会有效地使值所占的内存区域变大。内部值是外部值的一部分。如果你把外部值放到一块新的存储空间里,所有的值包括内部值都会被拷贝。如果你把内部值放进一块新的存储空间中,只有内部值会被拷贝。
一个引用类型嵌套了一个值类型会有效扩大这个引用类型所占内存区域。任何指向外部值的指针都可以操纵一切,包括嵌套的内部值。内部值的任何改变对于引用外部值的指针来说都是可见的。如果你把内部值放进一块新的存储区,就会在那块存储区拷贝一份新的值。
一个值类型嵌套一个引用类型就没有那么简单了。你可以有效地打破值语义而不被察觉。这可能是好的也可能是坏的,取决于你怎么做。当你把一个引用类型嵌套进一个值类型中,外部值被放进一块新的内存区域时就会被拷贝,但是拷贝的对象仍然指向原始的那个嵌套对象。下面是一个举例:
class Inner {
var value = 42
}
struct Outer {
var value = 42
var inner = Inner()
}
struct gg_ClassStruct {
func example() {
var outer = Outer()
let outer2 = outer
outer.value = 43
outer.inner.value = 43
print("outer2.value = \(outer2.value) outer2.inner.value=\(outer2.inner.value)")
//outer2.value = 42 outer2.inner.value=43
/**
尽管outer2获取了value的一份拷贝,它只拷贝了inner的引用,因此两个结构体就共用了同一个inner对象。
这样一来当改变outer.inner.value的值也会影响outer2.inner.value的值。哎呀!
*/
}
}
结论
无论什么时候只要你移动一个值类型,它都会被拷贝,而引用类型只是产生了对同样的底层对象的一个新的引用。那也就意味着引用类型的改变对所有其他的引用都是可见的,而改变值类型只影响你改变的那块内存区域。当选择使用哪种类型时,考虑你的类型是否适合被拷贝,当类型从本质上来说是可拷贝时倾向使用值类型。最后,记住如果你在值类型中嵌入引用类型,不小心的话就会出错!
网友评论