在某些英语口音里,如果把“two for”说得快一些,听起来就像“two fer”。Two-for-one 是一种说法,意思是买一个还能免费再得一个。所以“two-fer”这个说法往往暗示着买一送一的优惠。
想象有一家面包店,在节日期间推出了这样的优惠:花一份钱就能买到两块饼干(“two-fer one!”)。你接受了这个优惠,并且(非常大方地)决定把多出来的那块饼干送给排队中的另一个人。
你的任务是决定:在把多出来的那块饼干送出去时,你要说些什么。
如果你知道对方的名字(例如,对方叫 Do-yun),那么你要说:
One for Do-yun, one for me.
如果你不知道对方的名字,那就改说 you。
One for you, one for me.
下面是一些例子:
| 名字 | 对话 |
|---|---|
| Alice | One for Alice, one for me. |
| Bohdan | One for Bohdan, one for me. |
| One for you, one for me. | |
| Zaphod | One for Zaphod, one for me. |
Odin 支持默认参数。默认参数是一项很实用的特性:当你有多个相关但不相同的 API 时,它允许一个过程用不同的形参组合被调用。
没有默认参数的话,你的 API 需要的过程数量可能会急剧膨胀。
像 Java 这样不支持默认参数的语言,只能依靠方法重载来达到同样的效果(代价是代码更多),或者不得不提供带有大量实参的过程。
我们以greeting过程为例:我们希望可以指定语言、名字是否转为大写,以及问候语末尾是否加感叹号。经过分析,我们发现最大的用户群体指定英语为语言、不把名字转成大写,也不希望在末尾加感叹号;第二大的用户群体则希望在问候语末尾加感叹号。
我们可以写一个包含所有实参的单一过程:
greeting :: proc(name: string, language: Language, do_uppercase: bool, add_exclamation: bool) {
...
}
这样一来,所有用户都被迫阅读每个实参的文档,而且每次调用都得把它们全部填上。
如果没有默认参数,我们也可以定义多个过程,各自使用不同的形参组合:
greeting_common :: proc(name: string) {...}
greeting_with_exclamation :: proc(name: string) {...}
greeting_general :: proc(name: string, language: Language, do_uppercase: bool, add_exclamation: bool) {...}
// Odin supports overloading but it has to be explicitly defined
greeting :: proc {
greeting_common,
greeting_with_exclamation,
greeting_general,
}
如果你的语言像 Odin 一样支持默认参数,你只需给最不常用的形参提供合理的默认值即可。调用方可以跳过这些默认参数,让过程使用提供的默认值,也可以覆盖它们。
greeting :: proc(name: string, language: Language = Language.English, do_uppercase : bool = false, add_exclamation: bool = false) {...}
// Note: because Odin supports type inference, we can simplify the interface.
greeting :: proc(name: string, language:= Language.English, do_uppercase := false, add_exclamation:= false) {...}
调用过程时可以全部使用默认参数:
greeting("John Doe")
对调用方重要的实参可以覆盖:
greeting("John Doe", Language.Spanish)
如果需要的实参恰好按位置顺序排列,就像上面的例子那样,就没有必要像上面那样给它们命名;但如果你需要跳过其中某些,那么从你跳过的第一个实参开始,后面的实参都必须命名。
greeting("John Doe", add_exclamation=true)
在这个练习里,你要实现一个过程,它需要自行分配它返回的字符串。释放这个字符串是过程调用方的责任。
// The procedure will be called like that:
str := two_fer("Alice")
// ...
// and eventually the caller will delete the string
delete(str)