我正在学习Swift,我觉得很奇怪为什么在调用函数时,不需要第一个参数的名称。
func say(greeting: String, toName: String) {
print("\greeting), \(toName)!")
}
say("Goodbye", toName: "Hollywood") // <-- why is there no "greeting" required?
答案 0 :(得分:14)
正如其他人所说,这是源于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")
向(姓名“戴夫”的人说“问候”,“你好”,
一旦你习惯它,这是一个非常好的风格。
答案 1 :(得分:5)
Chris Lattner在What's New In Swift 2 Talk:
中谈到了这一点回到Swift 1.x的时代,这种行为只适用于方法。在类,结构或枚举之外编写的函数不需要在函数调用中命名任何参数(除非您在函数定义中使用外部参数名称明确强制它)。
由于Objective-C的约定,方法通常以某种方式命名,第一个参数已经是方法名称的一部分。
例如:indexOf(_:)
代替index(of:)
,或charactersAtIndex(_:)
代替charactersAt(index:)
。
在Objective-C中,这将被写为indexOf:
和charactersAtIndex:
。没有大括号可以将函数名与函数参数分开。因此参数基本上是函数名称的一部分。
如前所述,此行为最初仅适用于方法。这导致程序员之间的混淆,何时向第一个参数添加外部名称,何时不添加。所以最后行为发生了变化,因此默认情况下第一个参数不使用内部名称作为外部名称,但以下所有参数都是如此。
这导致外部和内部参数名称的使用更加一致。这就是今天Swift中存在的行为。
tl; dr行为是Objective-C
的残余答案 2 :(得分:2)
这是swift中函数的默认行为,省略了第一个函数参数的外部名称。
默认情况下,第一个参数省略其外部名称,而 第二个和后续参数使用其本地名称作为他们的 外部名称。
但是请注意,如果您愿意,也可以在第一个函数参数中添加外部名称:
func 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")
但请注意,对于初始化程序,外部名称对于所有函数参数都是必需的,包括第一个。
与函数和方法参数一样,初始化参数也可以 在初始化程序的正文中使用本地名称和 调用初始化程序时使用的外部名称。
但是,初始化程序之前没有标识函数名称 它们的括号与函数和方法的方式相同。因此, 初始化程序参数的名称和类型特别有用 确定应该调用哪个初始化程序的重要作用。 因此, Swift为每个人提供了一个自动外部名称 如果您不提供外部名称,则初始值设定项中的参数 自己。
来自Language Guide - Initialization。
举个例子,考虑
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)
答案 3 :(得分:2)
正如@dfri已经提到的那样,就是这样决定的。
请注意,您可以通过在调用时明确要求参数名称来轻松自定义该行为,并Local C/C++ Application
省略它们:
_
导致您必须通过
调用它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")