Swift语言中的感叹号是什么意思?

Swift编程语言指南有以下例子:

class Person { let name: String init(name: String) { self.name = name } var apartment: Apartment? deinit { println("\(name) is being deinitialized") } } class Apartment { let number: Int init(number: Int) { self.number = number } var tenant: Person? deinit { println("Apartment #\(number) is being deinitialized") } } var john: Person? var number73: Apartment? john = Person(name: "John Appleseed") number73 = Apartment(number: 73) //From Apple's “The Swift Programming Language” guide (https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html) 

然后,当将公寓分配给该人时,他们使用感叹号来“解开实例”:

 john!.apartment = number73 

这是什么意思“解开实例”? 为什么有必要? 与以下做法有什么不同?

 john.apartment = number73 

我对Swift语言很陌生。 只是试图让基础知识。

更新:
我错过的一大难题(不是在答案中直接说明 – 至less在写这篇文章的时候没有)是当你做下面的事时:

 var john: Person? 

这并不意味着“ john是人的types,可能是零”,就像我原先想的那样。 我只是误解了Person? 是完全独立的types。 一旦我掌握了那个,其他的?! 疯狂,以及下面的伟大答案,更有意义。

这是什么意思“解开实例”? 为什么有必要?

据我所知(这对我来说也是新鲜事)…

“包装”一词意味着我们应该把一个可选variables当作一个用闪闪发亮的纸包裹的礼物,可能(可惜)是空的

当“包装”时,可选variables的值是一个具有两个可能值的枚举(有点像布尔值)。 这个枚举描述variables是否保存一个值( Some(T) ),或者不保存( None )。

如果有值,可以通过“展开”variables(从Some(T)获得T来获得。

john!.apartment = number73john.apartment = number73什么不同? (意译)

如果你写了一个可选variables的名字(比如说文本john ,没有! ),那么它就是指“包装的”枚举(Some / None),而不是值本身(T)。 所以john不是人的一个实例,也没有apartment成员:

 john.apartment // 'Person?' does not have a member named 'apartment' 

实际的Person值可以用各种方式解开:

  • “强行解开”: john! (如果存在,则给出Person值,如果为零,则给出运行时错误)
  • “可选绑定”: if let p = john { println(p) } (如果值存在,则执行println
  • “可选链接”: john?.learnAboutSwift() (如果值存在,则执行此john?.learnAboutSwift()方法)

我想你可以select其中一种解开方式,这取决于在这种情况下会发生什么,以及这种情况的可能性。 这种语言devise迫使零案件被明确处理,我认为这提高了对Obj-C的安全性(在那里很容易忘记处理零案件)。

更新

感叹号也用于声明“隐含解包选项”的语法。

在目前的例子中, johnvariables被声明为var john:Person? ,它是一个可选的。 如果您想要该variables的实际值,则必须使用以上三种方法之一来展开它。

如果它被声明为var john:Person! 相反,variables将是一个隐式解包可选(请参阅苹果书中的标题部分)。 访问该值时不需要展开这种types的variables,并且可以使用john而无需额外的语法。 但是苹果的书说:

在稍后可能variables为零的情况下,不应该使用隐式解包的option。 如果您需要在variables的生命周期中检查nil值,请始终使用正常的可选types。

更新2

Mike Ash的文章“ 有趣的Swift特性 ”为可选types提供了一些动机。 我认为这是伟大的,明确的写作。

更新3

另一个有用的文章,关于感叹号的隐式解包可选用法:由Chris Adamson“ Swift and the Last Mile ”。 文章解释说,这是苹果用来声明Objective-C框架所使用的可能包含零的types的一个实际措施。 将types声明为可选(使用? )或隐式解包(使用! )是“安全和便利之间的折衷”。 在文章中给出的例子中,苹果已经select将这些types声明为隐式解包,使得调用代码更加方便,但是不太安全。

也许苹果可能会在未来梳理他们的框架,消除隐含解包(“可能永不为零”)参数的不确定性,并用可选的(“当然可能为零(特别是希望logging的情况下)”或者标准非 – 基于其Objective-C代码的确切行为,可选(“从不为零”)声明。

以下是我认为的区别:

 var john: Person? 

手段约翰可以是零

 john?.apartment = number73 

编译器会将此行解释为:

 if john != nil { john.apartment = number73 } 

 john!.apartment = number73 

编译器将会简单地解释这一行:

 john.apartment = number73 

因此,使用! 将打开if语句,并使其运行得更快,但如果john为零,则会发生运行时错误。

所以在这里包装并不意味着它是内存包装,但它意味着它是代码包装,在这种情况下,它是用一个if语句包装,因为苹果在运行时密切关注性能,他们想给你的方式让您的应用程序以最佳性能运行。

TL; DR

Swift语言中的感叹号是什么意思?

感叹号有效地说:“我知道这个可选肯定有价值, 请使用它“。这被称为强制展开可选的值:

 let possibleString: String? = "An optional string." print(possibleString!) // requires an exclamation mark to access its value // prints "An optional string." let assumedString: String! = "An implicitly unwrapped optional string." print(assumedString) // no exclamation mark is needed to access its value // prints "An implicitly unwrapped optional string." 

来源: https : //developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-XID_399

如果john是一个可选的var(这样声明)

 var john: Person? 

那么约翰就没有价值(按照ObjC的说法,零值)

感叹号基本上告诉编译器“我知道这有一个价值,你不需要testing它”。 如果你不想使用它,你可以有条件地testing它:

 if let otherPerson = john { otherPerson.apartment = number73 } 

这个内部只会评估约翰是否有价值。

john是一个可选的var 。 所以可以包含一个nil值。 确保价值不是零利用!var名称的末尾。

从文档

一旦确定可选项包含一个值,可以通过在可选名称的末尾添加一个感叹号(!)来访问其基础值。 感叹号有效地说:“我知道这个可选肯定有价值, 请使用它“。

另一种检查非零值的方法是

  if let j = json { // do something with j } 

一些大的图片视angular添加到其他有用的,但更详细为中心的答案:

在Swift中,感叹号出现在以下几个方面:

  • 强制解包: let name = nameLabel!.text
  • 隐式解开的optionals: var logo: UIImageView!
  • 强制铸造: logo.image = thing as! UIImage logo.image = thing as! UIImage
  • 未处理的exception: try! NSJSONSerialization.JSONObjectWithData(data, []) try! NSJSONSerialization.JSONObjectWithData(data, [])

它们中的每一个都是具有不同含义的不同语言结构,但它们都有三个重要的共同点:

感叹号绕过了Swift的编译时安全检查。

当你使用! 在Swift中,你基本上是在说:“嘿,编译器,我知道你认为在这里发生一个错误,但是我完全确定它永远不会知道

并非所有有效的代码都适合Swift的编译时types系统 – 或者任何语言的静态types检查。 在某些情况下,你可以从逻辑上certificate一个错误永远不会发生,但是你不能向编译器certificate它。 这就是为什么Swift的devise师首先添加了这些function。

但是,只要你使用! ,你排除了一个错误的恢复path,这意味着…

感叹号是潜在的崩溃。

一个惊叹号还说,“嘿斯威夫特,我很确定,这个错误不会发生,这是更好的让你崩溃我的整个应用程序,而不是我编写一个恢复path。”

这是一个危险的说法。 它可以是正确的:在关键代码中,你认真考虑了代码的不variables,可能是假输出比崩溃更糟糕。

但是,当我看到! 在野外,很less使用它。 相反,它通常意味着,“这个价值是可选的,我真的没有想太多,为什么它可能是零或什么约,但增加! 使它编译…所以我的代码是好的,对不对?

谨防感叹号的傲慢。 代替…

感叹号最好使用,几乎从不使用。

每一个! 构造有一个? 对方强迫你处理错误/无情况:

  • 有条件解包: if let name = nameLabel?.text { ... }
  • 可选项: var logo: UIImageView?
  • 有条件的转换: logo.image = thing as? UIImage logo.image = thing as? UIImage
  • 无故障例外: try? NSJSONSerialization.JSONObjectWithData(data, []) try? NSJSONSerialization.JSONObjectWithData(data, [])

如果你想使用! ,仔细考虑你为什么不使用它总是好的? 代替。 如果你的程序崩溃真的是最好的select! 操作失败? 有没有一个合理的恢复path你的代码可以采取在零/错误的情况下? 如果是这样,编码。

我定期search我的整个代码库! 并审核其每一次使用。 很less有用法经过审查。 (在撰写本文时,整个Siesta框架恰好有两个 实例 。)

这并不是说你永远不要使用! 在你的代码中 – 只是你应该经常使用它,并且永远不要使它成为默认选项。

这里有些例子:

 var name:String = "Hello World" var word:String? 

其中word是一个可选值。 意味着它可能包含或不包含一个值。

 word = name 

这里name有一个值,所以我们可以分配它

 var cow:String = nil var dog:String! 

dog被强行打开的地方意味着它必须包含一个值

 dog = cow 

应用程序将崩溃,因为我们分配nil来解包

在这种情况下…

var约翰:人!

这意味着,最初约翰将没有价值,它将被设定,一旦设定,将永远不会再被领导。 因此,为了方便起见,我可以使用更简单的语法来访问可选的var,因为这是一个“Implicitly unwrapped optional”

如果你来自C族语言,你会想到“指向Xtypes对象的指针,它可能是内存地址0(NULL)”,如果你来自dynamictypes语言,你将会思考“可能是typesX但可能是未定义types的对象”。 这两者其实都不是正确的,尽pipe迂回的第一个接近。

你应该考虑的方式就好像它是一个对象一样:

 struct Optional<T> { var isNil:Boolean var realObject:T } 

当你用foo == niltesting你的可选值时,它真的返回foo.isNil ,并且当你说foo! 它返回的foo.realObject的断言foo.isNil == false 。 注意这一点很重要,因为如果你做foo!时候foo实际上是零的foo! ,这是一个运行时错误,所以通常你会想使用条件let,除非你确定这个值不会是零。 这种欺骗意味着语言可以被强制types化,而不用强迫你去testing值是否到处都是零。

在实践中,它并不像这样做,因为这个工作是由编译器完成的。 在高层有一个types的Foo? 这与Foo是分开的,并且阻止了接受Footypes的Foo接收一个nil值,但是在低级别,一个可选值不是一个真正的对象,因为它没有属性或者方法。 实际上它可能是一个指针,它可以通过NULL(0)和相应的testing进行解压。

在其他情况下,您会看到一个感叹号,如下所示:

 func foo(bar: String!) { print(bar) } 

这大致相当于接受一个可选的强制解包,即:

 func foo(bar: String?) { print(bar!) } 

你可以使用这个方法在技术上接受一个可选的值,但是如果它是零,将会有一个运行时错误。 在当前版本的Swift中,这显然会绕过is-not-nil断言,所以你将会有一个低级的错误。 一般来说不是一个好主意,但是当从另一种语言转换代码时它可能是有用的。

在目标C中,没有值的variables等于'nil'(也可以使用与'0'相同的'nil'值和false),因此可以在条件语句中使用variables(variables的值与'TRUE '和没有值的那些等于'FALSE')。

Swift通过提供“可选值”来提供types安全性。 即防止分配不同types的variables而形成的错误。

所以在Swift中,只有条件语句可以提供布尔值。

 var hw = "Hello World" 

在这里,虽然'hw'是一个string,但是它不能用在像if中的if语句中。

 //This is an error if hw {..} 

为此,需要将其创build为,

 var nhw : String? = "Hello World" //This is correct if nhw {..} 

那! 意味着你是强制解开对象的! 如下。 更多信息可以在苹果文档中find,可以在这里find: https : //developer.apple.com/library/ios/documentation/swift/conceptual/Swift_Programming_Language/TheBasics.html

如果您熟悉C#,那么这就像使用问号声明的可空types:

 Person? thisPerson; 

在这种情况下,感叹号等同于访问可空types的.Value属性,如下所示:

 thisPerson.Value 

那! 在一个对象的末尾说对象是一个可选的,如果它可以返回一个零。 这通常用于捕获会导致程序崩溃的错误。

简而言之(!):你已经声明了一个variables,并且你确定variables正在保存一个值。

 let assumedString: String! = "Some message..." let implicitString: String = assumedString 

否则你将不得不在每一个传递价值之后做到这一点…

 let possibleString: String? = "An optional string." let forcedString: String = possibleString! // requires an exclamation mark 

约翰是一个可选的人,这意味着它可以持有一个价值或零。

 john.apartment = number73 

如果john不是可选的,则使用它。 既然约翰从来不是零,我们可以肯定它不会以无价值的方式给公寓打电话。 而

 john!.apartment = number73 

承诺编译器,约翰不是零,然后解开可选获得约翰的价值,并访问约翰的公寓属性。 如果你知道约翰不是零,请使用这个。 如果你调用这个nil可选,你会得到一个运行时错误。

该文档包括一个很好的例子,用于转换的数字是可选的。

 if convertedNumber { println("\(possibleNumber) has an integer value of \(convertedNumber!)") } else { println("\(possibleNumber) could not be converted to an integer") } 

简单地说,感叹号意味着一个可选的正在解包。 一个可选的是一个可以有或没有值的variables – 所以你可以检查variables是否为空,使用如此处所示的if let语句,然后强制解包。 如果你强制展开一个空的可选项,你的程序将会崩溃,所以要小心! 通过在明确赋值给variables的末尾加一个问号来声明可选项,例如我可以这样写:

 var optionalExample: String? 

这个variables没有值。 如果我打开它,程序会崩溃,Xcode会告诉你,你试图打开一个值为零的可选项。

希望有所帮助。

简单的话

使用感叹号表示variables必须包含非零值(它永远不会为零)

整个故事从一个叫做可选variables的特征开始。 这些是可能具有价值或可能没有价值的变数。 一般来说,swift不允许我们使用一个未初始化的variables,因为这可能会导致崩溃或意外的原因,并为后门服务器占位符。 因此,为了声明一个最初不确定的variables,我们使用'?'。 当声明这样一个variables时,要将它用作某个expression式的一部分,在使用之前必须解开它们,解包是一个操作,通过该操作发现variables的值,这适用于对象。 如果您尝试使用它们,则不会解包,您将有编译时错误。 要打开一个variables,它是一个可选的variables,感叹号“!” 用来。

现在有些时候,你知道这样的可选variables将被例如系统或你自己的程序赋值,但是在某些时候,例如UI出口,在这种情况下,而不是使用问号“?”来声明可选variables。 我们用 ”!”。

因此系统知道这个variables是用“!”声明的 现在是可选的,没有任何价值,但会在其后的一生中获得价值。

因此感叹号包含两种不同的用法:1.声明一个variables,它将是可选的,并且将在以后得到值2.在一个expression式中使用它之前解开一个可选variables。

以上描述避免了太多的技术性的东西,我希望。

如果您将其作为可选项使用,则会打开可选项并查看是否有任何内容。 如果你在if-else语句中使用它,那么代码是NOT。 例如,

 if (myNumber != 3){ // if myNumber is NOT 3 do whatever is inside these brackets. ) 

一个可选的variables可能包含一个值,或者可能不是

情况1: var myVar:String? = "Something" var myVar:String? = "Something"

情况2: var myVar:String? = nil var myVar:String? = nil

现在,如果你问myVar !,你告诉编译器返回一个值的情况下,它会返回"Something"

在情况2它会崩溃。

含义! 标记将强制编译器返回一个值,即使它不在那里。 那为什么这个名字强制解包

 Simple the Optional variable allows nil to be stored. var str : String? = nil str = "Data" To convert Optional to the Specific DataType, We unwrap the variable using the keyword "!" func get(message : String){ return } get(message : str!) // Unwapped to pass as String 

问你自己

  • types的person?apartment成员/财产? 要么
  • typesperson是否有apartment成员/财产?

如果您不能回答这个问题,请继续阅读:

要了解您可能需要对generics有超级基本的理解。 看到这里 。 Swift中的很多东西都是使用generics编写的。 包括可选项目

下面的代码已经可以从这个斯坦福的video 。 强烈推荐你观看前5分钟

一个可选是一个只有2个枚举的枚举

 enum Optional<T>{ case None case Some(T) } let x: String? = nil //actually means: let x = Optional<String>.None 

 let x :String? = "hello" //actually means: let x = Optional<String>.Some("hello") 

 var y = x! // actually means: switch x { case .Some(let value): y = value case .None: // Raise an exception } 

可选绑定:

 let x:String? = something if let y = x { // do something with y } //Actually means: switch x{ case .Some(let y): print)(y) // or whatever else you like using case .None: break } 

当你说var john: Person? 你其实是这样的:

 enum Optional<Person>{ case .None case .Some(Person) } 

上面的枚举是否有任何名为apartment 属性 ? 你有看到它吗? 根本没有 ! 但是,如果你打开它,即person! 那么你可以…它下面做的是: Optional<Person>.Some(Person(name: "John Appleseed"))


你是否定义了var john: Person而不是: var john: Person? 那么你将不再需要拥有! 使用,因为Person本身确实有一个apartment的成员


作为未来讨论为什么使用! 有时不推荐看这个问答