我正在学习Swift,但我觉得很奇怪的是,在调用函数时第一个参数的名称不是必需的。
func say(greeting: String, toName: String) {
print("\greeting), \(toName)!")
}
say("Goodbye", toName: "Hollywood") // <-- why is there no "greeting" required?
我正在学习Swift,但我觉得很奇怪的是,在调用函数时第一个参数的名称不是必需的。
func say(greeting: String, toName: String) {
print("\greeting), \(toName)!")
}
say("Goodbye", toName: "Hollywood") // <-- why is there no "greeting" required?
正如其他人所说的那样,这是一个起源于 Objective-C 的风格问题。
为了理解为什么有人想要采用这种风格,请考虑一下修改过的例子:
func say(greeting: String) {
print("\(greeting)")
}
你会这样调用它:say("Hello!")
当你看到你使用的函数名时,可以说有一些信息是缺失的。对于一个名为 say()
的函数,你可能合理地认为这是一个可以让你说出任何话的函数。但是当你看到参数名时,很明显这个函数是用来问候的,而不是说任何话。
因此,Objective-C 建议你这样编写:
func sayGreeting(greeting: String) {
print("\(greeting)")
}
你可以这样调用:
sayGreeting("Hello!")
现在很明显你是在打招呼。换句话说,函数名本身更清楚地描述了你在做什么。因此,sayGreeting("Hello!")
比say(greeting: "Hello!")
更好,因为一个函数所做的关键事情应该由它的名称来描述,而不是转移到参数名上并给予次要重要性。
但是这个理由只适用于第一个参数。假设你想加一个名字,就像你已经做过的那样。在像 C 这样没有外部参数名称的语言中,你可以编写类似以下的代码:
void sayGreetingToName(char * greeting, char * person) { ...
然后像这样调用它:
sayGreetingToName("Hello", "Dave");
这种方法是可以的,但是当你有重载函数或默认值时,它很快就会崩溃,而在C语言中你没有这些。如果你想写:
func sayGreetingToName(greeting: String, name: String? = nil) {
if let name = name {
print("\(greeting), \(name)!")
}
else {
print("\(greeting)!")
}
}
然后这样调用它:
sayGreetingToName("Hello", "Dave")
看起来基本上还可以,但是:
sayGreetingToName("Hello")
这看起来很荒谬,因为函数名表明你会提供一个名称,但实际上并没有。
因此,如果你这样写:
func sayGreeting(greeting: String, toName: String? = nil) {
if let name = toName {
print("\(greeting), \(name)!")
}
else {
print("\(greeting)!")
}
}
您可以用两种方式调用它:
sayGreeting("Hello")
sayGreeting("Hello", toName: "Dave")
一切看起来非常清晰明了。
总之,这种写作风格的想法是函数名本身应该包含第一个参数名称所包含的任何信息,但将此扩展到后续参数是没有意义的。因此,默认情况下第一个参数没有外部名称,但其余参数有。该函数始终是关于说问候语,因此应该在函数名称中体现(因此不需要通过坚持第一个参数的外部名称来重复)。但它可能或可能不是关于对特定名称说出问候语,因此该信息不应包含在函数名称中。
这种风格还使您基本上可以将函数调用读作英语,因为现在名称和参数大致处于正确的顺序:
sayGreeting("Hello", toName: "Dave")
向名为"Dave"的人问候,说:“你好”
一旦你习惯了,这种方式还是相当不错的风格。
Chris Lattner在Swift 2中的新特性中正好谈到了这个问题:
在Swift 1.x时代,此行为仅适用于方法。在类、结构或枚举之外编写的函数在函数调用时不需要命名任何参数(除非您在函数定义中显式强制使用外部参数名)。
由于Objective-C的约定,方法通常以一种方式命名,即第一个参数已成为方法名称的一部分。
例如:indexOf(_:)
而非index(of:)
或charactersAtIndex(_:)
而非charactersAt(index:)
。
在Objective-C中,这将被写为indexOf:
和charactersAtIndex:
。没有大括号来区分函数名和函数参数。因此,参数基本上是函数名称的一部分。
如前所述,但最初仅适用于方法。这导致程序员困惑,何时添加第一个参数的外部名称,何时不添加。因此,最终更改了该行为,以使第一个参数默认情况下不使用内部名称作为外部名称,但所有后续参数都会使用。
结果是更一致地使用外部和内部参数名称。这就是Swift今天的行为。
简而言之,此行为是Objective-C的遗留物
say(greeting: "Hello", toName: "World")
设置为默认值,并仍然保持 Cocoa 历史中调用位置读起来像英语句子的好处...但如果他们这样做了,要么a)所有导入的 ObjC 方法看起来都与本地 Swift 方法不同,要么b)他们必须使导入程序无缺地将每个 ObjC 方法(例如 sayGreeting:toName:)映射到一个 Swift 方法(例如 say(greeting:toName:))。 - ricksterfunc foo(extBar bar: String, bar2: String) {
print(bar+bar2)
}
foo(extBar: "Hello", bar2: "World")
_
以省略它们的外部名称。func foo2(bar: String, _ bar2: String) {
print(bar+bar2)
}
foo2("Hello", "World")
struct Foo {
var bar : Int
init(extBar: Int) {
bar = extBar
}
}
var a = Foo(extBar: 1)
struct Foo2 {
var bar : Int
init(_ intBar: Int) {
bar = intBar
}
}
var a = Foo2(1)
_
来省略它们来轻松自定义该行为:func say (greeting greeting: String, _ toName: String){
print("\(greeting), \(toName)!")
}
结果是您需要通过调用它来使用
say(greeting: "Goodbye", "Hollywood")
或者
func say (greeting: String, _ toName: String){
print("\(greeting), \(toName)!")
}
say("Goodbye", "Hollywood")
或者
func say (greeting greeting: String, toName: String){
print("\(greeting), \(toName)!")
}
say (greeting: "Goodbye", toName: "Hollywood")