C

C 知识量:16 - 74 - 317

9.1 什么是函数><

函数简介- 9.1.1 -

函数(function)是完成特定任务的独立程序代码单元。C的设计思想是把函数用作构件块。使用函数有很多好处,例如:

  • 避免编写重复代码,增强代码的重用性。

  • 提供模块化编程手段,提高程序代码的可读性和灵活性。

  • 使用成熟的函数代码,减少错误的发生。

  • 提高编程效率。

许多人喜欢把函数看作是根据传入信息及其生成的值或响应的动作来定义的“黑盒”。如果不是自己编写函数,根本不需要关心黑盒内部如何实现。这有助于把注意力集中在程序的整体设计,而不是函数的实现细节上。因此,在动手编写代码之前,应仔细考虑一下函数应该完成什么任务,以及函数与程序整体的关系。

函数示例- 9.1.2 -

以下是一个打印40个星号的函数,并在一个打印一行语句的程序中使用该函数的示例,程序由main()和starbar()组成。

#include <stdio.h>

void starbar(void); //函数原型

int main(void) {
    starbar(); //函数调用
    printf("Hello pnotes.cn!\n");
    starbar(); //函数调用
    system("pause");
    return 0;
}

//函数定义
void starbar(void) {
    int count;
    for (count = 1; count <= 40; count++)
        putchar('*');
    putchar('\n');
}

运行结果为:

****************************************
Hello pnotes.cn!
****************************************

分析程序- 9.1.3 -

以上程序示例中需要了解以下几点:

  • 程序在4处使用了starbar标识符:函数原型告诉编译器函数starbar()的类型;函数调用(两次)表明在此处执行函数;函数定义明确的指出了函数要做什么。

  • 函数和变量一样,有多种类型。任何程序在使用函数之前都要声明该函数的类型。starbar的函数原型为:

    void starbar(void);

    圆括号表明starbar是一个函数名。第1个void是函数类型,表明函数没有返回值。第2个void(在圆括号中)表明该函数不带参数。分号表明这是在声明函数,不是定义函数,告诉编译器需要到别处查找该函数的定义。

    对于一些老版本的编译器识别不了void的问题,需要将没有返回值的函数声明为int类型,参数部分则空着不填。

  • 函数原型指明了函数的返回值类型和函数接受的参数类型。这些信息称为该函数的签名。对于starbar()函数来说,其签名是该函数没有返回值,也没有参数。

  • 程序将starbar()原型放置于main()函数的前面。当然,也可以放到main()函数里面的声明变量处。

  • 在main()函数中,使用语句:starbar();调用了starbar()函数,当计算机执行到starbar();语句时,会找到该函数的定义并执行其中的内容。执行完starbar()中的代码后,计算机返回主调函数(即main()函数)继续执行下一行。

  • 程序中starbar()和main()的定义形式相同。函数头包括函数类型、函数名和圆括号,接着是左花括号、变量声明、函数表达式语句,最后以右花括号结束。函数头中的starbar()后面没有分号,告诉编译器这是定义starbar(),而不是调用函数或声明函数原型。

  • 程序将starbar()和main()函数放在一个文件中。也可以将它们分别放在两个文件中。单个文件形式比较容易编译,多个文件方式则方便在不同的程序中使用同一个函数(即函数的重用)。

  • starbar()函数中的变量count是局部变量,该变量只属于starbar()函数,只在函数内部起作用。可以在程序的其他地方使用count而不会引起名称冲突,它们是同名的不同变量。

如果把starbar()函数看作是一个黑盒,那么它的行为是打印一行星号。不用给它提供任何输入,它的调用不需要其他信息。而且,它也没有返回值,也不需要给main()函数提供(或返回)任何信息。总之,starbar()不需要与主调函数(即main()函数)进行通信。

带参数的函数- 9.1.4 -

以上示例中,如果想让文字居中,可以通过在打印文字之前打印一定数量的空格来实现,这与打印一定数量的星号类似,因此,可以写一个更通用的函数,用来打印星号和空格。修改后的代码如下:

#include <stdio.h>
#define HELLO "Hello pnotes.cn!"

void show_n_char(char mark, int number); //函数原型

int main(void) {
    show_n_char('*', 40); //函数调用
    putchar('\n');
    show_n_char(' ', (40 - strlen(HELLO)) / 2);//函数调用
    printf("%s\n", HELLO);
    show_n_char('*', 40);//函数调用
    system("pause");
    return 0;
}

//函数定义
void show_n_char(char mark, int number) {
    int count;
    for (count = 1; count <= number; count++)
        putchar(mark);
}

运行结果为:

****************************************
            Hello pnotes.cn!
****************************************

定义带形式参数的函数- 9.1.5 -

show_n_char()函数的定义从函数头开始:

void show_n_char(char mark, int number)

该行告知编译器show_n_char()使用两个参数:mark和number,mark是char类型,number是int类型。这两个变量称为形式参数,简称形参。与定义在函数中的变量一样,形参也是局部变量,属函数私有,因此,在其他函数中使用同名变量不会引起冲突。每次调用函数时,需要给这些形参赋值。

虽然show_n_char()接受来自main()的值,但是它没有返回值,因此,show_n_char()的类型是void。

声明带形参函数的原型- 9.1.6 -

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

void show_n_char(char mark, int number);

当函数有形参时,函数原型用逗号分隔的列表指明参数的数量和类型。根据个人喜好,也可以省略形参的变量名,例如:

void show_n_char(char, int);

在原型中使用变量名并没有实际创建变量,char仅代表了一个char类型的变量,int也是如此。

调用带实际参数的函数- 9.1.7 -

在函数调用中,实际参数提供了mark和number的值。实际参数简称实参。例如:第1次调用show_n_char():

show_n_char('*', 40);

实际参数是星号和40。这两个值被赋给show_n_char()中对应的形式参数:变量mark和number。简而言之,形参是被调用函数中的变量,实参是主调函数赋给被调函数的具体值。实参可以是常量、变量或更复杂的表达式。无论实参是何种形式,都必须可以求值,该值被拷贝给被调用函数相应的形参。例如:第2次调用show_n_char():

show_n_char(' ', (40 - strlen(HELLO)) / 2);

第1个实参是空格,第2个实参是一个表达式。该表达式的作用是计算语句前应打印的空格数量。需要注意的是:因为是拷贝赋值,所以,无论被调用函数对实参数据进行什么操作,都不会对主调函数中提供实参的原始数据产生任何影响。

黑盒视角- 9.1.8 -

从黑盒视角看show_n_char()函数,待显示的字符和显示的次数是输入,执行后的结果是打印指定数量的字符,输入以参数的形式被传递给函数,这些信息都清楚的表明了如何在main()函数中使用该函数。

黑盒方法的核心部分是:mark、number和count都是show_n_char()函数的私有局部变量。如果在main()函数中使用同名变量,那么它们相互独立,互不影响。黑盒里发生了什么对主调函数是不可见的。

用return从函数中返回值- 9.1.9 -

除了从主调函数中传递信息给被调函数外,也可以把信息从被调函数传回主调函数,通过使用return语句即可实现。下面是一个返回两个整数中最大值的函数。

#include <stdio.h>

int imax(int, int);

int main(void) {
    int i1, i2;
    printf("Please enter a pair of integers(q to quit):\n");
    while (scanf("%d %d", &i1, &i2) == 2) {
        printf("The bigger of %d and %d is %d.\n", i1, i2, imax(i1, i2));
        printf("Please enter a pair of integers(q to quit):\n");
    }
    printf("Done.\n");
    system("pause");
    return 0;
}

//函数定义
int imax(int x, int y) {
    int max;
    if (x > y)
        max = x;
    else
        max = y;
    return max;
}

运行结果为:

Please enter a pair of integers(q to quit):
365 568
The bigger of 365 and 568 is 568.
Please enter a pair of integers(q to quit):
-65 8
The bigger of -65 and 8 is 8.
Please enter a pair of integers(q to quit):
q
Done.

关键字return后面的表达式的值就是函数的返回值。以上示例中,返回值就是变量max的值。因为max是int类型的变量,所以imax()函数的类型也是int。

返回值不一定是变量的值,也可以是任意表达式的值。例如可以将imax函数代码修改为:

int imax(int x, int y) {
    return (x > y) ? x : y;
}

以上代码更加简洁,条件表达式的值是x和y中的较大值,该值将返回给主调函数。

使用return语句返回的值可以用于变量赋值,也可以直接用于表达式计算或着用于其他函数中(例如printf()函数)。

使用return语句的一个作用是,终止当前函数并将控制权返回给主调函数的下一条语句。在被调用函数中,return语句后面的语句都不会被执行,因为程序执行的控制权已经由return语句交还给了主调函数,即程序控制流永远不会走到return语句后面的语句。

函数类型- 9.1.10 -

声明函数时必须声明函数的类型,函数的类型同样也是函数定义的一部分。函数类型指的是返回值的类型,不是函数参数的类型。带返回值的函数类型应该与其返回值的类型相同,没有返回值的函数应当声明为void类型。

要正确的使用函数,程序在第1次使用函数之前必须知道函数的类型。方法之一是把完整的函数定义放在第1次调用函数的前面。但是,这种方法增加了程序的阅读难度。因此,通常使用另一种方法,就是提前声明函数,把函数信息告知编译器,而在主调函数后面给出函数的完整定义。

在ANSI C标准中,函数被分成多个系列,每一系列都有各自的头文件。这些头文件中除了其他内容,还包含了本系列所有函数的声明。

一定不要混淆函数的声明和定义。函数声明告知编译器函数的类型,而函数定义则提供实际的代码。