预计阅读本页时间:-
首先,什么是函数?函数(function)是用于完成特定任务的程序代码的自包含单元。尽管C中的函数和其他语言中的函数、子程序或子过程等扮演着相同的角色,但是在细节上会有所不同。某些函数会导致执行某些动作,比如printf()可使数据呈现在屏幕上;还有一些函数能返回一个值以供程序使用,如strlen()将指定字符串的长度传递给程序。一般来讲,一个函数可同时具备以上两种功能。
为什么使用函数?第一,函数的使用可以省去重复代码的编写。如果程序中需要多次使用某种特定的功能,那么只需编写一个合适的函数即可。程序可以在任何需要的地方调用该函数,并且同一个函数可以在不伺的程序中调用,就像在许多程序中需要使用putchar()函数一样。第二,即使某种功能在程序中只使用一次,将其以函数的形式实现也是有必要的,因为函数使得程序更加模块化,从而有利于程序的阅读、修改和完善。例如,假设您想编写一个实现以下功能的程序:
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元
● 读入一行数字。
● 对数字进行排序。
● 找到它们的平均值。
● 打印出一个柱状图。
可以编写如下程序:
当然,4个函数readlist()、sort()、average()和bargraph()的实现细节需要您自己编写。描述性的函数名可以清楚地表明程序的功能和组织结构,然后可以对每个函数进行独立设计直至完成需要的功能。如果这些函数足够通用化,那么还可以在其他程序中调用它们。
许多程序员喜欢把函数看作“黑盒子”,即对应一定的输入会产生特定的结果或返回某个数值,而黑盒的内部行为并不需要考虑,除非是该函数的编写者。例如使用printf()时,只需向其传递一个控制字符串,或许还有其他一些参数,然后就可以预测到printf()的执行结果,而无须了解printf()内部的代码。以这种方式看待函数有助于把精力投入到程序整体设计而不是其实现细节。因此,编写函数代码之前首先需要考虑的是函数的功能以及函数和程序整体上的关系。
对函数需要了解什么?您需要掌握如何正确定义函数、如何调用函数和如何建立函数间的通信。为了让您对这些有个清晰的思路,我们首先给出一个非常简单的例子,然后进行详细讲述。
9.1.1 编写和使用一个简单的函数
我们的第一个目标很容易实现,只是编写一个在一行中输出40个星号的函数。然后我们在一个程序中使用该函数打印一个简单的信头。程序清单9.1给出了完整的程序,它由main()函数和starbar()函数组成。
程序清单9.1 lethead1.c程序
程序输出如下:
9.1.2 程序分析
关于这个程序有以下几点需要注意:
● starbar标识符在不同位置被使用了 3次:函数原型(function prototype)告知编译器starbar()的函数类型,函数调用(function call)导致该函数的执行,而函数定义(function definition)则确切指定了该函数的具体功能。
● 函数同变量一样有多种类型。任何程序在使用函数之前都需要声明该函数的类型。因此,下面这个ANSI C风格的原型出现在main()函数的定义之前:
圆括号表明starbar是一个函数名。第一个void指的是函数类型;它的意思是该函数没有返回值。第二个void(位于圆括号内)表明该函数不接受任何参数。分号的作用是表示该语句是进行函数声明而不是函数定义。也就是说,这一行声明了程序将使用一个名为starbar().且函数类型为void的函数,同时通知编译器需要在其他位置找到该函数的定义。对于不识别ANSI C原型的编译器,只需声明函数的类型,就像下面这样:
注意:一些老版本的编译器不能识别void类型。这时,需要把没有返回值的函数声明为int类型。
● 程序把starbar()原型置于main()之前;也可将其置于main()之内,可以放置变量声明的任何位置。这两种方法都正确。
● 程序在main()中通过使用函数名后跟圆括号和分号的格式调用函数starbar(),语句如下:starbar();
这是void类型函数的一般调用形式。当计算机执行到starbar();语句时,它找到starbar()函数并执行其中的指令。执行完starbar()中的代码后,计算机返回到调用函数(calling function)的下一行继续执行。在本例中,调用函数是main()(请参见图9.1)。
图9.1 letheadl.c(程序清单9.1)的控制流程
● 程序中starbar()和main()具有相同的定义格式,即首先以类型、名称和圆括号开始,接着是开始花括号、变量声明、函数语句定义以及结束花括号(请参见图9.2)。注意此处的starbar()后没有分号,这告诉编译器您是在定义函数starbar(),而不是在调用它或声明它的原型。
图9.2 一个简单函数的结构
● 程序把starbar()和main()包含在同一个文件中,您也可以将它们放在不同的两个文件之中。单文件形式比较容易编译,而使用两个文件则有利于在不同的程序中使用相同的函数。如果您把函数写在了另外一个单独的文件中,则在那个文件中必须加入#define 和#include指令。在后续内容中我们将讲述两个或多个文件的使用。就目前而言,我们将所有函数都包含在一个文件中。main()的结束花括号告诉编译器该函数在这里结束,后面的starbar()函数头表示starbar()是一个函数。
● starbar()中的变量count是一个局部(local)变量。这意味着该变量只在starbar()中可用。即使您在其他函数(包括main()函数)中使用名称count,也不会出现任何冲突,您将得到具有同一名称的多个单独的、互不相关的变量。
如果把starbar()看作一个黑盒子,那么它的执行结果是打印出一行星号。因为不需要来自调用函数的任何信息,所以它没有输入参数。同时它不向main()提供(返回)任何信息,因此starbar()也没有返回值。简言之,starbar()不需要同调用函数进行任何通信。
下面将给出一个函数之间需要通信的例子。
9.1.3 函数参数
在上例中,如果文字居中显示那么信头就会更漂亮。可以通过在打印文字之前打印一定数目的空格来达到此目的。这和starbar()函数类似。在starbar()中打印的是一定数量的星号,而现在要打印的是一定数目的空格。遵循C的设计思想,我们不应为每个任务编写一个单独的函数,而应该编写一个可以同时胜任这两个任务的更为通用的函数。新函数将命名为show_n_char()(意思是把某字符显示n次)。惟一的改变是要显示的字符和显示次数将被作为参数传递给函数show_n_char(),而不是把它们置于函数内部。
具体一点说,假如一行是40个字符宽。40个星号恰好填满一行,调用函数show_n_char(‘*’,40)可以同starbar() 一样实现该功能。而将GIGATHINK, INC.居中需要多少个空格?因为GIGATHINK,INC.是15个字符宽,因此在前面的例子中该短语后跟有25个空格。为使其居中,必须先输出12个空格,这样该短语两边就会分别有13个和12空格。所以,可以调用show_n_char(‘’,12)输出12个空格。
除了使用参数外,在其他方面show_n_char()函数和starbar()非常相似。两者的一个不同之处是show_n_char()不像starbar()那样输出换行符,因为在同一行中可能还需要输出其他文字。程序清单9.2给出了改进后的程序。为了强调参数的使用,程序中使用了多种参数形式。
程序清单9.2 Iethead2.c程序
程序的执行结果如下
下面我们将详细讨论如何编写使用参数的函数,然后介绍这种函数的使用方法。
9.1.4 定义带有参数的函数:形式参量
函数定义以下面的ANSI C函数头开始:
这行代码通知编译器show_n_char()使用名为ch和num的两个参数,并且这两个参数的类型分别是char和int。变量ch和num被称为形式参数(formal argument)或形式参量(formal parameter,现在这个名称更为正式)。如同函数内部定义的变量一样,形式参量是局部变量,它们是函数所私有的。这意味着可以在其他函数中使用相同的变量名。每当调用函数时,这些变量就会被赋值。
注意:ANSI C形式要求在每个变量前声明其类型。也就是说,不能像通常的变量声明那样使用变量列表来声明同一类型的变量,如下所示:
ANSI C也接受ANSI之前的形式,但将其视为废弃不用的形式:
此处圆括号内是参数名列表,而参数类型的声明在后面给出。注意参数声明需要位于标志函数体开始的花括号之前,而普通的局部变量在开始花括号之后声明。使用这种形式时,对于相同类型的变量,可以使用逗号分隔的变量名列表,如下所示:
制定标准的目的是为了淘汰ANSI之前的形式。为了理解以前的代码,您也需要了解ANSI之前的形式。但是,以后的程序中应尽量使用新的形式。
尽管函数show_n_char()接收来自main()的数值,但是它没有返回值。因此,show_n_char()的类型是void。
下面讨论函数show_n_char()的使用。
9.1.5 带参数函数的原型声明
使用函数之前需要用ANSI原型声明该函数:
当函数接受参数时,函数原型通过使用一个逗号分隔的类型列表指明参数的个数和类型。在函数原型中可以根据您自己的喜好省略变量名:
在原型中使用变量名并没有实际地创建变量。这只是说明char代表了一个char类型变量,依此类推。
ANSI C也支持旧的函数声明形式,即圆括号内不带有任何参数:
这种形式最终将会被从标准中删除。即使没有被删除,原型形式设计比它更具有优势,正如在下文中将要讲述的那样。了解这种形式的主要原因只是为了您能正确识别并理解以前的代码。
9.1.6 调用带有参数的函数:实际参数
函数调用中,通过使用实际参数(actual argument)对ch和num赋值。请考虑对show_n_char()的第一次使用:
实际参数是空格字符和12。这两个数值被赋给show_n_char()中相应的形式参量:变量ch和num。换句话说,形式参量是被调函数中的变量,而实际参数是调用函数分配给被调函数变量的特定数值。正如上例中所示,实际参数可以是常量、变量或一个复杂的表达式。但是无论何种形式的实际参数,执行时首先要计算其值,然后将该值复制给被调函数中相应的形式参量。以最后一次使用show_n_char()的语句为例:
求得构成第二个实际参数的表达式的值为10。然后把数值10赋给变量num。被调函数不知道也不必知道这个数值是来自于常量、变量或是更一般的表达式。再次,实际参数是赋给被称为形式参量的函数变量的具体值(请参见图9.3)。因为被调函数使用的值是从调用函数中复制而来的,所以不管在被调函数中对复制数值进行什么操作,调用函数中的原数值不会受到任何影响。
图9.3 形式参量和实际参数