第7章 函数

  1. 函数的概念和模块化程序设计方法。
    1. 函数的概念。
      1. 函数是一个可以反复使用的程序段,从其他的程序段中均可以通过函数调用语句来执行这段程序,完成既定功能。
      2. 建立函数称为“函数的定义”,使用函数称为“函数的调用”。
      3. 调用其他函数的函数称为“主调函数”,而被调用的函数称为“被调函数”。
      4. 主函数只能是主调函数。
      5. 每一个函数都能完成特定的任务,称为“函数的功能”;
      6. 每次调用函数时的某些待处理的数据和函数运行后获得的某些结果统称为“函数的参数”;
      7. 把主调函数中加工对象代入函数,称为“函数参数的输入”;
      8. 把函数运行后的某些结果带回到主调函数,称为“函数参数的输出”;
      9. 函数参数的输入和输出统称为“函数间数据的传递”。
      10. 函数的分类。从定义的角度可以分为用户函数和系统函数;从形式上可以分为有参函数和无参函数;从返回值角度可以分为有返回值函数和无返回值函数;从函数起作用的范围可以分为外部函数和内部函数。
      11. C语言允许将一个源程序清单放在若干个程序文件中,采用分块编译方法编译、连接生成一个目标程序。其中每个程序文件称为一个“编译单位”。每个编译单位中可以包含若干个函数。外部函数是可以被任何编译单位调用的;内部函数只能在本编译单位中被调用。
    2. 函数的定义。
      1. 函数的一般定义格式。
        存储类型符 数据类型符 函数名(形式参数)
        {数据定义语句序列;
        执行语句序列;
        }
      2. 通常把一对花括号括住的部分称为“函数体”,函数体前面的部分称为“函数头”。
      3. 存储类型符 可以是extern(外部函数,可以被其他编译单位的函数调用)或static(内部函数)两种。默认为外部函数。
      4. 数据类型符 规定了本函数返回值的数据类型,可以是各种基本数据类型,也可以是指针型(只要函数名的前面加一个“*”即可)。还可以是“void”,它表示本函数是无返回值的。
      5. 函数名 是一个标识符。
      6. 形式参数表 是用逗号分隔的若干个形式参数及其数据类型的说明,格式如下:
        数据类型符 形式参数1, 数据类型符 形式参数2, ...
        其中每个形式参数都可以是一个变量名、一维数组名[长度]、二维数组名[行长度][列长度]、指针变量名,指针数组名[长度]等。
    3. 函数的调用。
      1. 函数调用的一般格式。
        1. 以表达式方式来调用:
          函数名(实际参数表)
          使用这种调用格式的结果是获得一个返回值,就是函数的返回值。这种调用方式本身就是表达式,它也可以作为表达式的一部分,组成更为复杂的表达式。
        2. 以语句方式来调用函数:
          函数名(实际参数表)
          使用这种调用格式组成了一条“函数调用语句”。
      2. 函数调用的过程。
        1. 首先暂停函数调用所在的语句执行,转向被调用的函数继续执行。
        2. 为函数的所有形式参数分配内存;再将所有实际参数的值计算出来,依次赋予对应的形式参数(如果形参是数组则不给形参分配内存)。如果是“无参函数”,则本项操作不执行。
        3. 进入函数体,先执行数据定义语句,为函数中定义的变量、数组等分配内存。
        4. 再执行函数体中的可执行语句。
          1. 如果是“无返回值函数”,则执行到“返回语句”。若“返回语句”被省略,则执行到函数体的右花括号。
          2. 如果是“有返回值函数”,执行到“返回语句”时,计算表达式的值作为函数的返回值。
        5. 收回分配给本函数体中定义的变量、数组、形式参数等内存单元(注意,静态型变量、数组将不收回所分配的内存)。
        6. 返回主调函数继续执行。
          1. 如果函数调用的形式是“语句”,则执行其后的语句。
          2. 如果函数调用的形式是“表达式”,则继续执行表达式所在的语句。
      3. 关于被调函数的声明。为了帮助编译程序在编译时能找到被调函数,需要注意对被调函数的声明,声明的方式有以下几种:
        1. 系统函数的声明。调用系统函数时,除了少数系统函数(如scanf()、printf())外,都要求在程序的开始用包含命令“#include <头文件名.h>”将定义系统函数的头文件包含在本程序中。
        2. 如果被调函数和主调函数在同一个编译单位中,在书写顺序上被调函数在主调函数之前出现,可以不对被调函数加以声明;或者被调函数虽然在主调函数之后出现,而被调函数是有返回值的,且返回值的数据类型是整型字符型,可以不对被调函数加以声明。除上述两种情况外,都要对被调函数加以声明。
          声明的位置一般在主调函数的函数体开头的数据定义语句部分。
          声明格式如下:
          数据类型符 被调函数名(形式参数表)
        3. 如果被调函数和主调函数不在同一个编译单位中,则在定义函数的编译单位中必须将该函数定义成外部函数。
          同时在主调函数的函数体中,或主调函数所在的编译单位的开头对将要调用的函数按照下列格式进行声明:
          extern 数据类型符 被调函数名(形式参数表)
    4. 模块化程序设计方法。
      1. 在大型实用程序设计过程中,需要将整个问题分割成若干个较小的部分,每个参加程序设计的人只需要承担其中某些较小部分的编程工作。每个较小部分的程序就称为一个“程序模块”。
      2. 每个程序模块都需要有确定的功能,以及输入的待处理的数据和输出的处理结果数据。当大型程序的所有模块均调试并验证后,将它们拼接在一起,通过相互调用的方式组成一个完整的大型程序。这种程序设计方法就成为模块化程序设计方法。
      3. 在C语言中,每个函数就是一个“程序模块”。
  2. 函数调用时的数据传递方法。C语言规定在函数间传递数据有四种方式:值传递方式、地址传递方式、返回值方式、全局变量传递方式。
    1. 利用形参与实参传递数据的值传递方式。
      1. 值传递方式传递的是参数值。
      2. 调用函数时,将实际参数的值计算出来赋予对应的形式参数。在函数体中对形式参数的加工与实际参数已完全脱离关系。
      3. 值传递方式的特点是“参数值的单向传递”。
      4. C语言对值传递方式的形式参数的变量将会分配内存,然后将实际参数的值存入对应的内存,完成值传递。
    2. 当形参是数组时的数据传递方式。
      1. 如果形参是数组名,则传递方式称为“地址传递方式”。调用该函数时的实参是地址型的表达式。例如数组的首地址,或已经赋值的指针变量、指针数组元素等。
      2. 调用函数时,将实际参数的地址赋予对应的形式参数数组作为其首地址。形参的首地址和实参的首地址占用相同的内存,这些内存中的数据是被主调函数和被调函数共享的。
      3. 使用这种“地址传递方式”调用函数时,形式参数数组将不分配内存。
    3. 当形参是指针变量时的数据传递方式。
      1. 指针变量也是变量,所以其数据传递方式仍然是“值传递”方式。
      2. 形参是指针变量,实参必须是地址表达式。
      3. 函数调用时,为形参(指针变量)分配内存,计算实参表达式的值(地址值)赋予形参(指针变量)。调用结束时,并不把形参(指针变量)中的地址值回带给实参。执行返回语句 时,将收回分配给形参(指针变量)的内存,然后返回主调函数。
    4. 利用返回值的数据传递方式。
      1. 返回值方式是通过函数调用后直接返回一个值到主函数中。因此这种方式通常适用于从被调函数中将一个值传回主调函数。
      2. 利用返回值的方式传递数据,在定义函数时要注意:
        1. 函数头中要有“数据类型符”,说明该函数返回值的数据类型。
        2. 函数体中应有“return (表达式);”语句,其中表达式值就是函数返回值。
  3. 变量的存储类型与作用域。
    1. 变量的存储类型。
      1. 在计算机的内存和CPU的寄存器中都可以存放数据,而内存中又可以分为一般数据区和堆栈区。我们把变量存放在哪个区域称为变量的存储类型。用户可以通过变量的定义语句中的存储类型符来选择变量的具体存储区域。
      2. 含有存储类型符的变量定义语句格式如下:
        存储类型符 数据类型符 变量名1, 变量名2, ...;
      3. (表头)存储类型 存储类型符 存储区域
        自动型    auto    内存的堆栈区(默认)
        寄存器型    register    CPU的通用寄存器
        静态型    static    内存的数据区
        外部参照型    extern
      4. 假定某个函数(复合语句)中定义了自动型变量,函数被调用(复合语句被执行时),C语言就在堆栈区给该变量分配内存用于存放变量的值。当函数调用(复合语句执行)结束时,C语言就释放该变量。
      5. C程序中允许定义的寄存器型变量一般以2个左右为宜。一般在函数中定义。
      6. 静态型变量在程序开始运行时就分配了固定的内存,在程序运行过程中不释放。只有程序运行结束后,才释放程序所占用的内存。
      7. 外部参照型变量是专用于多个编译单位之间传递数据用的。当编译单位甲中要使用在编译单位乙中定义的变量,则编译单位甲就要将该变量声明是外部参照型,以便C语言编译系统在编译单位甲之外的其他编译单位中寻找该变量的定义。而在编译单位乙中要定义该变量的存储类型和数据类型。
      8. 外部参照型变量不允许初始化。其他类型都可以初始化。
      9. 自动型和寄存器型变量如果进行初始化,则每次进入所定义的函数或复合语句都随着重新定义而重新初始化。如果不初始化,则它们的变量值将不确定。
      10. 静态型变量如果进行初始化,只有第一次执行定义语句时随着分配内存赋予初值,当退出所定义的函数或复合语句时,将保留分配给它的内存和其中的值。在此进入时,不再重新分配内存,也不进行初始化。此时,变量的值是上次离开时的值。
      11. 静态型变量如果不进行初始化,C语言编译系统将自动为其赋予“零值”。
    2. 变量的生存期和作用域。
      1. 内部变量与外部变量。变量的定义可以在函数(或某个复合语句)内部(称为“内部变量”),也可以在函数外部(即两个函数之间)(称为“外部变量”)。注意,“外部变量”与“外部参照型”是两个不同的概念。
      2. 变量的生存期。我们把变量从开始分配内存单元(或寄存器)到被收回的期间称为“变量的生存期”。
      3. 全局变量和局部变量。从变量的生存期来分,我们把生存期覆盖了定义点到整个程序结束的变量称为具有全局寿命的变量,简称“全局变量”;把生存期只覆盖某个函数(或某个复合语句)的变量称为具有局部寿命的变量,简称“局部变量”。
      4. 变量的作用域。在变量的生存期中,我们把变量可以使用的程序区域称为变量的作用域。
      5. 外部变量只能定义成无存储类型或静态型,不能被定义成自动型或寄存器型。
      6. 外部变量总是全局变量。作用域是从定义点到整个程序结束。定义后的任何一个函数都可以使用。
      7. 被说明为auto或register存储类型的内部变量是局部变量,只能在所定义的函数或复合语句中存活。作用域只是所定义的函数或复合语句。
      8. 被说明为static存储类型的内部变量是全局变量,在整个程序运行期间都不释放。它的作用域只是所定义的函数或复合语句。
      9. 外部变量是不能重名的。
    3. 利用全局外部变量的数据传递方式。
      1. 在函数之间利用全局变量传递数据,只能使用“外部变量”。
      2. 如果全部外部变量和函数体内部定义的“内部变量”重名,则C语言规定内部变量优先。
      3. 从模块化程序设计的角度来说,并不推荐使用这种方式,因为它破坏了模块的独立性,在程序模块中无法确定某些全局外部变量的变化情况,必须在模块之外去寻找,造成了程序可读性差的缺点。
  4. 数据的嵌套调用和递归调用。
    1. 函数的嵌套调用。
      1. 就是指函数甲调用了函数乙,而函数乙又调用了函数丙。
      2. 原则上说,C语言不限制嵌套调用的层数,嵌套调用层数仅受计算机内存的限制。
    2. 函数的递归调用。
      1. 就是指函数调用自己。
      2. 我们把向下的递归调用过程称为“递归过程”,把向上的携带返回值计算返回表达式的过程称为“回溯过程”。
      3. 从程序设计角度来说,递归过程必须解决两个问题:一是递归计算的公式,二是递归结束的条件和此时函数的返回值。在程序设计中这两个条件可以采用if-else双分支语句来实现。
      4. 计算n!的递归调用函数。
      5. 计算斐波拉契第n项值的递归调用函数 。
      6. 某些递归函数可以利用局部静态变量设计成非递归调用函数。
  5. 指针型函数及其调用。所谓指针型函数,是指该函数的返回值是指针型的;接受返回值的必须是指针变量、指针数组元素等能存放地址值的对象。
    1. 指针型函数的定义。定义指针型函数时,需要在函数名前面加一个“*”,表示函数返回值是指针型数据。
    2. 指针型函数的调用。只能用指针变量或指针型数组元素来接受指针型函数的返回值,不能使用数组名来接受指针型函数的返回值,因为数组名是地址常量,不是地址型变量,不能接受地址型数据。
  6. 文件包含命令与多文件程序的处理。
    1. 文件包含命令。
      1. 文件包含命令是以“#include”开头的编译预处理命令。格式如下:
        【格式1】#include <文件名>
        【格式2】#include "文件名"
      2. 文件名是由C语言程序组成的文本文件。
      3. 在编译预处理时,用指定“文件名”中的文本内容代替该语句,使文件的全部内容成为本程序清单的一部分。
      4. 用格式1,则系统仅按规定的路径搜索文件。
      5. 用格式2,系统现在本程序清单所在磁盘和路径下寻找文件;若找不到,再按系统规定的路径搜索文件。
    2. 多文件程序的处理。
      1. 若一个大型程序被分散在多个源程序文件中,就称为多文件程序。对于这种程序的调试,首先要将多个源程序清单拼接成一个完整的程序清单,然后再对其进行编译和调试。
      2. 包含文件可以将多个源程序清单合并成一个源程序清单。
  7. 常用系统函数。
    1. 常用的数学处理函数。均包含在头文件“math.h”中。
      1. 求整型绝对值函数。int abs(int x)
      2. 求长整型绝对值函数。long labs(long x)
      3. 求实型绝对值函数。double fabs(double x)
      4. 求小于或等于x的最大整数函数。double floor(double x)
      5. ...
    2. 常用的类型转换函数。
    3. 常用的字符处理函数。
    4. 其它常用函数。
      1. 能发出鸣笛声的函数。void sound(int x)
      2. 中止鸣笛声的函数。void nosound()
      3. 延时函数。void delay(int x)
      4. 随机发生器初始化函数。void randomize()
      5. 随机数发生函数。int random(int num)
  8. 函数设计举例。

发表评论

电子邮件地址不会被公开。 必填项已用*标注