1月22日 C Primer Plus学习
9.1 复习函数
函数是完成特定任务的独立程序代码单元。语法规则定义了函数的结构和使用方式。虽然C中的函数和其他语言中的函数、子程序、过程作用相同,但是细节上略有不同。一些函数执行某些动作,如printf()把数据打印到屏幕上;一些函数找出一个值供程序使用,如strlen()把指定字符串的长度返回给程序。一般而言,函数可以同时具备以上两种功能。
函数让程序更加模块化,从而提高了程序代码的可读性,更方便后期修改、完善。
9.1.1 创建并使用简单函数
/* lethead1.c */
#include <stdio.h>
#define NAME "GIGATHINK, INC."
#define ADDRESS "101 Megabuck Plaza"
#define PLACE "Megapolis, CA 94904"
#define WIDTH 40
void starbar(void); /* 函数原型 */
int main(void)
{
starbar();
printf("%s\n", NAME);
printf("%s\n", ADDRESS);
printf("%s\n", PLACE);
starbar(); /* 使用函数 */
return 0;
}
void starbar(void) /* 定义函数 */
{
int count;
for (count = 1; count <= WIDTH; count++)
putchar('*');
putchar('\n');
}
9.1.2 分析程序
该程序要注意以下几点。
-
函数原型告诉编译器函数starbar()的类型;函数调用表明在此处执行函数;函数定义明确地指定了函数要做什么。
-
函数和变量一样,有多种类型。任何程序在使用函数之前都要声明该函数的类型。
分号表明这是在声明函数,不是定义函数。也就是说,这行声明了程序将使用一个名为starbar()、没有返回值、没有参数的函数,并告诉编译器在别处查找该函数的定义。对于不识别ANSI C风格原型的编译器,只需声明函数的类型,如下所示:
void starbar();
注意,一些老版本的编译器甚至连void都识别不了。如果使用这种编译器,就要把没有返回值的函数声明为int类型。当然,最好还是换一个新的编译器。
- 一般而言,函数原型指明了函数的返回值类型和函数接受的参数类型。这些信息称为该函数的签名。
对于starbar()函数而言,其签名是该函数没有返回值,没有参数。
程序把 starbar()原型置于 main()的前面。当然,也可以放在 main()里面的声明变量处。放在哪个位置都可以。
在main()中,执行到下面的语句时调用了starbar()函数:
starbar();
这是调用void类型函数的一种形式。当计算机执行到starbar();语句时,会找到该函数的定义并执行其中的内容。执行完starbar()中的代码后,计算机返回主调函数(calling function)继续执行下一行(本例中,主调函数是 main()),见图9.1(更确切地说,编译器把C程序翻译成执行以上操作的机器语言代码)。程序中strarbar()和main()的定义形式相同。首先函数头包括函数类型、函数名和圆括号,接着是左花括号、变量声明、函数表达式语句,最后以右花括号结束(见图9.2)。注意,函数头中的starbar()后面没有分号,告诉编译器这是定义starbar(),而不是调用函数或声明函数原型。
程序把 starbar()和 main()放在一个文件中。当然,也可以把它们分别放在两个文件中。把函数都放在一个文件中的单文件形式比较容易编译,而使用多个文件方便在不同的程序中使用同一个函数。如果把函数放在一个单独的文件中,要把#define 和#include 指令也放入该文件。我们稍后会讨论使用多个文件的情况。现在,先把所有的函数都放在一个文件中。main()的右花
括号告诉编译器该函数结束的位置,后面的starbar()函数头告诉编译器 starbar()是一个函数。

图9.1 lethead1.c(程序清单9.1)的程序流

图9.2 简单函数的结构
starbar()函数中的变量count是局部变量,意思是该变量只属于starbar()函数。可以在程序中的其他地方(包括main()中)使用 count,这不会引起名称冲突,它们是同名的不同变量。
如果把starbar()看作是一个黑盒,那么它的行为是打印一行星号。不用给该函数提供任何输入,因为调用它不需要其他信息。而且,它没有返回
值,所以也不给 main()提供(或返回)任何信息。简而言之,starbar()不需要与主调函数通信。
接下来介绍一个函数间需要通信的例子。
9.1.3 函数参数
/* lethead2.c */
#include <stdio.h>
#include <string.h> /* 为strlen()提供原型 */
#define NAME "GIGATHINK, INC."
#define ADDRESS "101 Megabuck Plaza"
#define PLACE "Megapolis, CA 94904"
#define WIDTH 40
#define SPACE ' '
void show_n_char(char ch, int num);
int main(void)
{
int spaces;
show_n_char('*', WIDTH); /* 用符号常量作为参数 */
putchar('\n');
show_n_char(SPACE, 12); /* 用符号常量作为参数 */
printf("%s\n", NAME); spaces = (WIDTH - strlen(ADDRESS)) / 2; /* 计算要跳过多少个空格*/
show_n_char(SPACE, spaces); /* 用一个变量作为参数*/
printf("%s\n", ADDRESS);
show_n_char(SPACE, (WIDTH - strlen(PLACE)) / 2);
printf("%s\n", PLACE); /* 用一个表达式作为参数 */
show_n_char('*', WIDTH);
putchar('\n');
return 0;
}
/* show_n_char()函数的定义 */
void show_n_char(char ch, int num)
{
int count;
for (count = 1; count <= num; count++)
putchar(ch);
}
9.1.4 定义带形式参数的函数
形式参数(formal argument,但是最近的标准推荐使用formal parameter),简称形参,和定义在函数中变量一样,形式参数也是局部变量,属该函数私有。这意味着在其他函数中使用同名变量不会引起名称冲突。每次调用函数,就会给这些变量赋值。
9.1.5 声明带形式参数函数的原型
在使用函数之前,要用ANSI C形式声明函数原型:
void show_n_char(char ch, int num);
当函数接受参数时,函数原型用逗号分隔的列表指明参数的数量和类型。根据个人喜好,你也可以省略变量名:
void show_n_char(char, int);
在原型中使用变量名并没有实际创建变量,char仅代表了一个char类型的变量,以此类推。
再次提醒读者注意,ANSI C也接受过去的声明函数形式,即圆括号内没有参数列表:
void show_n_char();
这种形式最终会从标准中剔除。即使没有被剔除,现在函数原型的设计也更有优势(稍后会介绍)。了解这种形式的写法是为了以后读得懂以前写的代码。
9.1.6 调用带实际参数的函数
在函数调用中,实际参数(actual argument,简称实参)提供了ch和num 的值。考虑程序清单9.2中第1次调用show_n_char():
show_n_char(SPACE, 12);
实际参数是空格字符和12。这两个值被赋给show_n_char()中相应的形式参数:变量ch和num。简而言之,形式参数是被调函数(called function)中的变量,实际参数是主调函数(calling function)赋给被调函数的具体值。如上例所示,实际参数可以是常量、变量,或甚至是更复杂的表达式。无论实际参数是何种形式都要被求值,然后该值被拷贝给被调函数相应的形式参数。以程序清单 9.2 中最后一次调用show_n_char()为例: show_n_char(SPACE, (WIDTH - strlen(PLACE)) / 2);
构成该函数第2个实际参数的是一个很长的表达式,对该表达式求值为 10。然后,10被赋给变量num。被调函数不知道也不关心传入的数值是来自常量、变量还是一般表达式。再次强调,实际参数是具体的值,该值要被赋给作为形式参数的变量(见图 9.3)。因为被调函数使用的值是从主调函数中拷贝而来,所以无论被调函数对拷贝数据进行什么操作,都不会影响主调函数中的原始数据。
注意 实际参数和形式参数
实际参数是出现在函数调用圆括号中的表达式。形式参数是函数定义的函数头中声明的变量。调用函数时,创建了声明为形式参数的变量并初始化为实际参数的求值结果。程序清单 9.2 中,'*'和WIDTH都是第1次调用 show_n_char()时的实际参数,而SPACE和11是第2次调用show_n_char()时的实际参数。在函数定义中,ch和num都是该函数的形式参数。

9.1.7 黑盒视角
从黑盒的视角看 show_n_char(),待显示的字符和显示的次数是输入。执行后的结果是打印指定数量的字符。输入以参数的形式被传递给函数。这些信息清楚地表明了如何在 main()中使用该函数。而且,这也可以作为编写该函数的设计说明。
黑盒方法的核心部分是:ch、num和count都是show_n_char()私有的局部变量。如果在main()中使用同名变量,那么它们相互独立,互不影响。也就是说,如果main()有一个count变量,那么改变它的值不会改变show_n_char() 中的count,反之亦然。黑盒里发生了什么对主调函数是不可见的。
9.1.8 使用return从函数中返回值
函数的返回值可以把信息从被调函数传回主调函数。为进一步说明,我们将创建一个返回两个参数中较小值的函数。由于函数被设计用来处理int类型的值,所以被命名为imin()。另外,还要创建一个简单的main(),用于检查imin()是否正常工作。这种被设计用于测试函数的程序有时被称为驱动程序(driver),该驱动程序调用一个函数。如果函数成功通过了测试,就可以安装在一个更重要的程序中使用。
9.1.9 函数类型
声明函数时必须声明函数的类型。带返回值的函数类型应该与其返回值类型相同,而没有返回值的函数应声明为void类型。如果没有声明函数的类型,旧版本的C编译器会假定函数的类型是int。这一惯例源于C的早期,那时的函数绝大多数都是int类型。然而,C99标准不再支持int类型函数的这种假定设置。
类型声明是函数定义的一部分。要记住,函数类型指的是返回值的类型,不是函数参数的类型。
编程练习第1题
点击查看
#include<stdio.h>
double min(double x, double y);
void main(void)
{
double x, y;
printf("输入一个数:\n");
scanf("%lf",&x);//没有将考虑错误输入的情况考虑进去
printf("输入另一个数:\n");
scanf("%lf",&y);
printf("最小值为:%lf", min(x, y));
return;
}
double min(double x, double y)
{
if(x > y)
{
return y;
}
else
{
return x;
}
}
编程练习第4题
点击查看
#include<stdio.h>
double function(double x, double y);
void main(void)
{
double x, y;
printf("输入一个数:\n");
scanf("%lf",&x);//依旧没有将考虑错误输入的情况考虑进去
printf("输入另一个数:\n");
scanf("%lf",&y);
printf("这两个数的调和平均数为:%lf", function(x, y));
return;
}
double function(double x, double y)
{
x = 1/x;y = 1/y;//取倒数
return 2/(x+y);
}
浙公网安备 33010602011771号