C Primer Plus 第9章 函数 9.1 函数概述

不打扰是莪最后的温柔 提交于 2019-12-02 18:10:23

9.1  函数概述

首先,什么是函数?函数(funcation)是用于完成特定任务的程序代码的自包含单元

为什么使用函数?第一,函数的使用可以省去重复代码的编写。第二,即使某种功能在程序中只使用一次,将其以函数的形式实现也是有必要的,因为函数使得程序更加模块化,从而有利于程序的阅读、修改和完善。

许多程序员喜欢把函数看作“黑盒子”,即对应一定的输入会产生特定的结果或返回某个数值,而黑盒子的内部行为并不需要考虑,除非是该函数的编写者。以这种方式看待函数有助于把精力投入到程序整体设计而不是其实现细节。因此,编写函数代码之前首先要考虑的是函数的功能 以及函数和程序整体上的关系

9.11  编写和使用一个简单的函数

编写一个在一行中输出40个星号的函数,然后我们使用该函数打印一个简单的信头。程序清单9.1给出了完整的程序,它由main()函数和starbar()函数组成。

程序清单 9.1 lethead1.c程序

/*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  程序分析

关于函数有以下几点需要注意:

1、starbar标识符在不同位置使用了3次:函数原型(funcation prototype)告知编译器starbar()的函数类型函数调用(funcation call)导致该函数的执行而函数定义(funcation definition)则确切指定了该函数的具体功能

2、函数同变量一样有多种类型。任何程序在使用函数之前都需要声明该函数的类型

void starbar(void)

圆括号表明starbar是一个函数名第一个void指的是函数类型,它的意思是该函数没有返回值。第二个void位于圆括号内表明该函数不接受任何参数分号的作用是表示该语句是进行函数声明而不是函数定义。也就是说,这一行声明了程序将使用一个名为starbar()且函数类型为void的函数,同时通知编译器需要在其他位置找到该函数的定义。对于不识别ANSI C原型的编译器,只需要声明函数的类型,就像下面这样:void starbar();

注意:一些老版本的编译器不能识别void类型。这时,需要把没有返回值的函数声明为INT类型。

3、程序把starbar()原型置于main()之前,也可以置于main()之内,可以放置变量声明的任何位置,这两种方法都正确。

4、程序在main()中通过使用函数名后跟圆括号和分号的格式调用函数starbar(),语句如下:starbar();这是void类型函数的一般调用形式。当计算机执行到starbar();语句时,它找到starbar()函数并执行其中的指令。执行完starbar()中的代码后,计算机返回到调用函数(calling funcation)的下一行继续执行。在本例中,调用函数是main()。

5、程序中starbar()和main()具有相同的定义格式,即首先以类型、名称和圆括号开始,接着是开始花括号、变量声明、函数语句定义以及结束花括号。注意此处的starbar()后没有分号,这告诉编译器您是在定义函数starbar()而不是在调用它或声明它的原型。

6、可以将starbar()和main()放在同一个文件中,也可以将它们放在不同的文件中。单文件形式比较容易编译,而使用两个文件则有利于在不同程序中使用相同的函数。如果您把函数写在了另外一个单独的文件中,则在那个文件中必须加入#define和#include指令。

7、starbar()中的变量count是一个局部变量。这意味着该变量只在starbar()中可用。即使您在其他函数包括main()函数中使用名称count,也不会出现任何冲突,您将得到具有同一名称的多个单独的、互不相关的变量。

因为不需要来自调用函数的任何信息,所以它没有输入参数。同时它不向main()提供任何信息,因此starbar()也没有返回值。简言之,starbar()不需要同调用函数进行任何通信。

9.1.3  函数参数

在上例中,如果文字居中显示那么信头就会更漂亮。可以通过在打印文字之前打印一定数目的空格 来达到此目的 。这和starbar()函数类似。在starbar()中打印的是一定数量的星号,而现在要打印的是一定数目的空格。遵循c的设计思想,我们不应为每个任务编写一个单独的函数,而应该编写一个可以同时胜任这两个任务的更为通用的函数。新函数将命名为show_n_char()。唯一改变是要显示的字符和显示次数将被作为参数传递给函数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 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;
}

void show_n_char(char ch,int num)
{
    int count ;
    for (count=1;count <= num; count++)
        putchar(ch);
}

9.1.4  定义带有参数的函数:形式参量

函数定义以下面的ANSI C 函数头开始

void show_n_char(char ch,int num)

这行代码通知编译器show_n_char()使用名为ch和num的两个参数,并且这两个参数的类型分别是char和int。变量ch和num被称为形式参数(formal argument)或形式参量(formal parameter,现在这个名称更为正式)。如同函数内部定义的变量一样,形式参量是局部变量它们是函数私有的。这意味着可以其他函数中使用相同的变量名。每当调用函数时,这此变量就会被赋值。

注意,ANSI C 形式要求在每个变量前声明其类型。也就是说,不能像通常的变量声明那样使用变量列表来声明同一类型的变量,如下所示:

void dibs (int x,y,z)               /*不正确的函数头*/

void dubs (int x, int y,int z)   /*正确的函数头*/

尽管show_n_char()接收来自main()的数值,但是它没有返回值。因此,show_n_char()的类型是void。

9.1.5  带参数函数的原型声明

使用函数之前需要用ANSI原型声明该函数:

void show_n_char(char ch,int num);

当函数接受参数时,函数原型通过使用一个逗号分隔的类型列表指明参数的个数和类型。在函数原型中,可以根据个人的喜好省略变量名:

void show_n_char(char,int);

在原型中使用变量名并没有实际的创建变量。这只是说明char代表了一个char类型变量,依此类推。

9.1.6  调用带有参数的函数:实际参数

函数调用中,通过使用实际参数(actual argument)对ch 和 num赋值

show_n_char(SPACE,12);

实际参数是空格字符和12。这两个数值被赋给show_n_char()中相应的形式参量:变量ch和num。换句话说,形式参量是被调函数中的变量,而实际参数是调用函数分配给被调用函数变量的特定数值。实际参数可以是常量、变量或一个复杂的表达式。但是无论何种形式的实际参数,执行时首先要计算其值,然后将该值复制给被调函数中相应的形式参量。

再次,实际参数是赋给被称为形式参量的函数变量的具体值。因为被调函数使用的值是从调用函数中复制而来的,所以不管在被调函数中对复制数值进行什么操作,调用函数中的原数值不会受到任何影响。

*当一个函数被调用时,将创建被声明为形式参量的变量,然后用计算后得到的实际参数的值初始化该变量。

9.1.7  黑盒子观点

黑盒子方法的核心部分在于ch num 和count都是show_n_char()私有的局部变量。也就说,如果在main()中使用相同名字的变量,它们相互独立,互不影响。例如,如果在main()中存在一个count变量,那么该 变量值的改变不会影响show_n_char()中的count变量,其余变量也是如此。黑盒子的一切操作对调用函数来说是不可见的。

9.1.8  使用return从函数中返回一个值

前面讨论了从调用函数到被调用函数的通信方法。需要沿相反方向传递信息时,可以使用函数返回值。为了进一步说明,我们将构建一个比较两个参数大小并将小数值返回的函数。因为比较的是int类型的数值,所以函数被命名为imin()。同时,为了检查imin()的执行结果,需要编写一个简单的main()函数。这种用来测试函数的程序有时被称为驱动程序。驱动程序实际调用了被测试的函数。如果该函数成功通过了测试,那么它就可以在一个更为重要的程序中使用。

程序清单9.3

/*lesser.c --找出两个整数中的较小者*/
#include <stdio.h>
int imin(int,int);
int main(void)
{
    int evil1,evil2;
    printf("Enter a pair of integers (q to quit): \n");
    while(scanf("%d %d",&evil1,&evil2)==2)
    {
        printf("The lesser of %d and %d is %d.\n",
               evil1,evil2,imin(evil1,evil2));
        printf("Enter a pair of integer (q to quit): \n");
    }
    printf("Bye.\n");
    return 0;
}

int imin(int n,int m)
{
    int min;
    if(n<m)
        min=n;
    else
        min=m;
    return min;
}

关键字return指明了其后的表达式的数值即是该函数的返回值。在本例子中,函数返回变量min的数值。因为min的类型是int,所以函数imin()的类型也是int。

变量min是imin()私有的,但是return语句把min的数值返回给了调用函数。下面这个语句的作用相当于把min的值赋给lesser:

lesser=imin(n,m);

能否用下厕所这个语句代替上句:

imin(n,m); lesser=min;

答案是否定的,因为调用函数并不知道min变量的存在。imin()中的变量是该函数的局部变量。函数调用imin(evil1,evil2)只是复制了两个变量的数值。

返回值不仅可以被赋给 一个变量,也可以被用作表达式的一部分。例如:

answer=2*imin(z,zstar)+25;

printf("%d\n",imin(-32+answer,LIMIT));

返回值可以由任何表达式计算得出,而不是仅仅来自于变量。例如:

/*最小函数值的第2个版本*/

imin(int n,int m)

{

        return ((n<m) ? n : m) ;

}

当函数返回值的类型和声明的类型不相同时会有什么结果呢?

这时,实际返回值是当把指定要返回的值赋给 一个具有所声明的返回类型的变量时得到的数值

return语句的另一作用是终止执行函数,并把控制返回给调用函数的下一个语句。即使return语句不是函数最后一个语句,其执行结果也是如此。因此,可以用下面的方式编写imin()函数:

/*最小值函数的第3个版本*/

imin(int n,int m)

{

    if (n<m)

        return n;

    else

        return m;

}

许多C程序员更倾向于只在函数结尾使用一次return语句,因为这样更有利于阅读程序的人明白函数的执行流程。

您也可以使用以下语句:

return ;

这个语句会终止执行函数并把控制返回给调用函数。因为return后没有任何表达式,所以没有返回值,这种形式只能用于void类型的函数之中。

9.1.9  函数类型

函数应该进行类型声明。同时其类型应该和返回值类型相同。而无返回值的函数应该被声明为void类型

类型声明是函数定义的一部分。但需要注意的是该 类型是返回值类型,而不是函数参数类型

为正确的使用函数,程序在首次调用函数之前需要知道该函数的类型。途径之一是在第一次调用之前进行完整的函数定义。但是,这种方式会使得程序难于阅读。而且,需要的函数可能在C库中或其他文件中。因此,通常的做法是预先对函数进行声明,以便将函数的信息通知给编译器。

在上例的代码中,函数的预先声明被放在调用函数之外。也可以在调用函数内部预先声明被调函数

在以上两种形式中,需要重点注意的是函数声明要在使用函数之前进行。

不要把函数声明和函数定义混淆。函数声明只是将函数类型告诉编译器,而函数定义部分则是函数的实际实现代码。引用math.h头文件只向编译器说明了sqrt()的返回值类型double,但是sqrt()的实现代码则位于另外一个库函数文件中。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!