简介
本课程将教你函数的重要性以及它们在 D 语言中的实现方式。
函数
与基本类型是程序数据的构建模块类似,函数也是程序行为的构建模块。
函数还与编程的艺术性密切相关。经验丰富的程序员编写的函数简洁、简单且清晰。这一点是双向的:仅仅尝试识别并编写程序中的更小功能单元,就能帮助你成为一名更好的程序员。
在前面的章节中,我们已经介绍了基本的语句和表达式。尽管后续章节还会引入更多语句,但到目前为止我们所学的内容已经是 D 语言中常用的功能。然而,仅靠这些内容还不足以编写大型程序。我们迄今为止编写的程序都非常简短,每个都只演示了语言的一个简单特性。如果不使用函数,试图编写具有一定复杂度的程序将非常困难,并且容易出错。
本章仅介绍函数的基本特性。关于函数的更多内容将在后续章节中进一步探讨。
函数:现实生活中的类比
函数是一种将语句和表达式组合起来形成程序执行单元的特性。这些语句和表达式被赋予一个名称,用来描述它们共同实现的功能。这个名称之后就可以用来执行这一段代码。
给一系列步骤命名以完成某项任务的想法在我们的日常生活中十分常见。例如,制作一个煎蛋卷的过程可以大致描述如下:
- 拿一个平底锅
- 拿黄油
- 拿一个鸡蛋
- 打开炉灶
- 将锅放在火上
- 当锅热了后放入黄油
- 当黄油融化后放入鸡蛋
- 当鸡蛋煮熟后将锅从火上移开
- 关闭炉灶
显然,如此详细的描述过于繁琐。因此,相关步骤会被合并为一个整体名称:
- 准备材料(拿锅、黄油和鸡蛋)
- 打开炉灶
- 煎蛋(将锅放在火上等操作)
- 关闭炉灶
进一步地,所有步骤也可以用一个统一的名称概括:
- 制作一个单蛋煎蛋卷(所有步骤)
函数正是基于同样的理念:那些可以整体命名的步骤被组合在一起形成一个函数。举个例子,我们来看下面这段代码,它的作用是显示一个菜单:
1import std.stdio; 2 3void main() { 4 5 writeln(" 0 退出"); 6 writeln(" 1 加法"); 7 writeln(" 2 减法"); 8 writeln(" 3 乘法"); 9 writeln(" 4 除法"); 10 11}
由于把这些代码行组合成一个名为 printMenu 的函数是合理的,我们可以使用以下语法将其封装为一个函数:
1void printMenu() { 2 writeln(" 0 退出"); 3 writeln(" 1 加法"); 4 writeln(" 2 减法"); 5 writeln(" 3 乘法"); 6 writeln(" 4 除法"); 7}
现在,只需通过函数名即可在 main() 中调用该函数的内容:
1import std.stdio; 2 3void printMenu() { 4 writeln(" 0 退出"); 5 writeln(" 1 加法"); 6 writeln(" 2 减法"); 7 writeln(" 3 乘法"); 8 writeln(" 4 除法"); 9} 10 11void main() { 12 13 printMenu(); 14 15}
从 printMenu() 和 main() 的定义相似性可以看出,main() 本身也是一个函数。D 程序的执行从名为 main() 的函数开始,然后由此分支调用其他函数。
参数
函数的强大之处之一在于其行为可以通过参数进行调整。
让我们继续以煎蛋卷为例,将其修改为制作五个鸡蛋的煎蛋卷,而不是一个。步骤完全相同,唯一的区别是使用的鸡蛋数量。我们可以相应地更新之前的通用描述:
- 准备工作(拿锅、黄油和五个鸡蛋)
- 打开炉灶
- 煎蛋(将锅放在火上等)
- 关闭炉灶
此时,最通用的一步变为:
- 制作一个五蛋煎蛋卷(所有步骤)
这一次,某些步骤涉及额外的信息:“拿五个鸡蛋”、“煎蛋”、“制作一个五蛋煎蛋卷”。
函数的行为也可以像煎蛋卷的例子一样进行调整。用于调整函数行为的信息通过参数传递。参数以逗号分隔的形式列在函数的参数列表中,该列表位于函数名后的括号内。
上面的 printMenu() 函数定义时使用了空参数列表,因为该函数始终打印相同的菜单。但假设在不同上下文中,菜单可能需要以不同方式打印。例如,在程序的不同部分运行时,第一条菜单项可能更适合显示为“返回”而非“退出”。
在这种情况下,菜单的第一项可以通过参数化来处理——将其作为参数列表的一部分定义。函数随后使用该参数的值,而不是直接写死的 "退出" 字符串:
1void printMenu(string firstEntry) { 2 writeln(" 0 ", firstEntry); 3 writeln(" 1 加法"); 4 writeln(" 2 减法"); 5 writeln(" 3 乘法"); 6 writeln(" 4 除法"); 7}
注意,由于 firstEntry 参数传递的是文本信息,因此在参数列表中指定了其类型为 string。这个函数现在可以根据不同的参数值被调用,从而打印出第一项不同的菜单。只需要根据函数被调用的位置,传入合适的 string 值即可:
1import std.stdio; 2 3void printMenu(string firstEntry) { 4 writeln(" 0 ", firstEntry); 5 writeln(" 1 加法"); 6 writeln(" 2 减法"); 7 writeln(" 3 乘法"); 8 writeln(" 4 除法"); 9} 10 11void main() { 12 13 // 程序中的某个位置: 14 printMenu("退出"); 15 // ... 16 writeln ("\n"); 17 // 程序中的另一个位置: 18 printMenu("返回"); 19}
注意:当你编写并使用带有
string类型参数的自定义函数时,可能会遇到编译错误。如上所述,printMenu()无法接受类型为char[]的参数值。例如,以下代码会导致编译错误:
1char[] anEntry; 2anEntry ~= "求平方根"; 3printMenu(anEntry); // ← 编译错误
1import std.stdio; 2 3void printMenu(string firstEntry) { 4 writeln(" 0 ", firstEntry); 5 writeln(" 1 加法"); 6 writeln(" 2 减法"); 7 writeln(" 3 乘法"); 8 writeln(" 4 除法"); 9} 10 11void main() { 12 13 char[] anEntry; 14 anEntry ~= "求平方根"; 15 printMenu(anEntry); // ← 编译错误 16 17}
另一方面,如果 printMenu() 被定义为接收 char[] 类型的参数,则它就无法再被传入像 "退出" 这样的字符串字面量。这与不可变性以及 immutable 关键字的概念有关,这两个概念将在后续章节中详细介绍。
继续完善菜单函数,假设并不总是应该从数字零开始编号菜单选项。在这种情况下,起始编号也可以作为第二个参数传递给函数。函数的多个参数之间用逗号分隔:
1void printMenu(string firstEntry, int firstNumber) { 2 writeln(' ', firstNumber + 0, ' ', firstEntry); 3 writeln(' ', firstNumber + 1, " 加法"); 4 writeln(' ', firstNumber + 2, " 减法"); 5 writeln(' ', firstNumber + 3, " 乘法"); 6 writeln(' ', firstNumber + 4, " 除法"); 7}
现在,我们可以告诉函数从哪个数字开始编号:
1import std.stdio; 2 3void printMenu(string firstEntry, int firstNumber) { 4 writeln(' ', firstNumber + 0, ' ', firstEntry); 5 writeln(' ', firstNumber + 1, " 加法"); 6 writeln(' ', firstNumber + 2, " 减法"); 7 writeln(' ', firstNumber + 3, " 乘法"); 8 writeln(' ', firstNumber + 4, " 除法"); 9} 10 11void main() { 12 13 printMenu("返回", 1); 14 15}
在下一课中,你将学习函数如何被调用、如何执行任务,以及最终如何返回结果。