C语言程序设计-8 函 数

8.1 概述

在前面已经介绍过,C源程序是由函数组成的。虽然在前面各章的程序中大都只有一个
主函数 main(),但实用程序往往由多个函数组成。函数是C源程序的基本模块,通过对函
数模块的调用实现特定的功能。C语言中的函数相当于其它高级语言的子程序。C语言不仅
提供了极为丰富的库函数(如 Turbo C,MS C 都提供了三百多个库函数),还允许用户建立自
己定义的函数。用户可把自己的算法编成一个个相对独立的函数模块,然后用调用的方法来
使用函数。可以说C程序的全部工作都是由各式各样的函数完成的,所以也把C语言称为函
数式语言。
由于采用了函数模块式的结构,C语言易于实现结构化程序设计。使程序的层次结构清
晰,便于程序的编写、阅读、调试。
在C语言中可从不同的角度对函数分类。

  1. 从函数定义的角度看,函数可分为库函数和用户定义函数两种。
  1. 库函数:由C系统提供,用户无须定义,也不必在程序中作类型说明,只需在程序
    前包含有该函数原型的头文件即可在程序中直接调用。在前面各章的例题中反复用
    到 printf、scanf、getchar、putchar、gets、puts、strcat 等函数均属此类。
  2. 用户定义函数:由用户按需要写的函数。对于用户自定义函数,不仅要在程序中定
    义函数本身,而且在主调函数模块中还必须对该被调函数进行类型说明,然后才能
    使用。
  1. C语言的函数兼有其它语言中的函数和过程两种功能,从这个角度看,又可把函数分为
    有返回值函数和无返回值函数两种。
  1. 有返回值函数:此类函数被调用执行完后将向调用者返回一个执行结果,称为函数
    返回值。如数学函数即属于此类函数。由用户定义的这种要返回函数值的函数,必
    须在函数定义和函数说明中明确返回值的类型。
  2. 无返回值函数:此类函数用于完成某项特定的处理任务,执行完成后不向调用者返
    回函数值。这类函数类似于其它语言的过程。由于函数无须返回值,用户在定义此
    类函数时可指定它的返回为“空类型”, 空类型的说明符为“void”。
  1. 从主调函数和被调函数之间数据传送的角度看又可分为无参函数和有参函数两种。
  1. 无参函数:函数定义、函数说明及函数调用中均不带参数。主调函数和被调函数之
    间不进行参数传送。此类函数通常用来完成一组指定的功能,可以返回或不返回函
    数值。
  2. 有参函数:也称为带参函数。在函数定义及函数说明时都有参数,称为形式参数(简
    称为形参)。在函数调用时也必须给出参数,称为实际参数(简称为实参)。进行函
    数调用时,主调函数将把实参的值传送给形参,供被调函数使用。
  1. C语言提供了极为丰富的库函数,这些库函数又可从功能角度作以下分类。
  1. 字符类型分类函数:用于对字符按 ASCII 码分类:字母,数字,控制字符,分隔符,
    大小写字母等。
  2. 转换函数:用于字符或字符串的转换;在字符量和各类数字量(整型,实型等)之间进行转换;在大、小写之间进行转换。
  3. 目录路径函数:用于文件目录和路径操作。
  4. 诊断函数:用于内部错误检测。
  5. 图形函数:用于屏幕管理和各种图形功能。
  6. 输入输出函数:用于完成输入输出功能。
  7. 接口函数:用于与 DOS,BIOS 和硬件的接口。
  8. 字符串函数:用于字符串操作和处理。
  9. 内存管理函数:用于内存管理。
  10. 数学函数:用于数学函数计算。
  11. 日期和时间函数:用于日期,时间转换操作。
  12. 进程控制函数:用于进程管理和控制。
  13. 其它函数:用于其它各种功能。
    以上各类函数不仅数量多,而且有的还需要硬件知识才会使用,因此要想全部掌握则需
    要一个较长的学习过程。应首先掌握一些最基本、最常用的函数,再逐步深入。由于课时关
    系,我们只介绍了很少一部分库函数,其余部分读者可根据需要查阅有关手册。
    还应该指出的是,在C语言中,所有的函数定义,包括主函数 main 在内,都是平行的。
    也就是说,在一个函数的函数体内,不能再定义另一个函数,即不能嵌套定义。但是函数之
    间允许相互调用,也允许嵌套调用。习惯上把调用者称为主调函数。函数还可以自己调用自
    己,称为递归调用。
    main 函数是主函数,它可以调用其它函数,而不允许被其它函数调用。因此,C程序
    的执行总是从 main 函数开始,完成对其它函数的调用后再返回到 main 函数,最后由 main
    函数结束整个程序。一个C源程序必须有,也只能有一个主函数 main。

8.2 函数定义的一般形式

【例 8.1】

#include "string.h"
#include <stdio.h>

int max(int a, int b)
{
    if (a > b)
        return a;
    else
        return b;
}
main()
{
    int max(int a, int b);
    int x, y, z;
    printf("input two numbers:\n");
    scanf("%d%d", &x, &y);
    z = max(x, y);
    printf("maxmum=%d", z);
}

8.3 函数的参数和函数的值

8.3.1 形式参数和实际参数

【例 8.2】可以说明这个问题。

#include "string.h"
#include <stdio.h>

main()
{
    int n;
    printf("input number\n");
    scanf("%d", &n);
    s(n);
    printf("n=%d\n", n);
}
int s(int n)
{
    int i;
    for (i = n - 1; i >= 1; i--)
        n = n + i;
    printf("n=%d\n", n);
}

8.3.2 函数的返回值

8.4 函数的调用

8.4.1 函数调用的一般形式

8.4.2 函数调用的方式

【例 8.3】

#include <stdio.h>

main()
{
    int i = 8;
    printf("%d\n%d\n%d\n%d\n", ++i, --i, i++, i--);
}

8.4.3 被调用函数的声明和函数原型

8.5 函数的嵌套调用

【例 8.4】计算s=(2^2)! + (3^2)!;

#include <stdio.h>

long f1(int p)
{
    int k;
    long r;
    long f2(int);
    k = p * p;
    r = f2(k);
    return r;
}
long f2(int q)
{
    long c = 1;
    int i;
    for (i = 1; i <= q; i++)
        c = c * i;
    return c;
}
main()
{
    int i;
    long s = 0;
    for (i = 2; i <= 3; i++)
        s = s + f1(i);
    printf("\ns=%ld\n", s);
}

8.6 函数的递归调用

【例 8.5】用递归法计算 n!
用递归法计算 n!可用下述公式表示:
n!=1 (n=0,1)
n×(n-1)! (n>1)
按公式可编程如下:

#include <stdio.h>

long ff(int n)
{
    long f;
    if (n < 0)
        printf("n<0,input error");
    else if (n == 0 || n == 1)
        f = 1;
    else
        f = ff(n - 1) * n;
    return (f);
}
main()
{
    int n;
    long y;
    printf("\ninput a inteager number:\n");
    scanf("%d", &n);
    y = ff(n);
    printf("%d!=%ld", n, y);
}

【例 8.6】Hanoi 塔问题

一块板上有三根针,A,B,C。A 针上套有 64 个大小不等的圆盘,大的在下,小的在上。
如图 5.4 所示。要把这 64 个圆盘从 A 针移动 C 针上,每次只能移动一个圆盘,移动可以借
助 B 针进行。但在任何时候,任何针上的圆盘都必须保持大盘在下,小盘在上。求移动的步
骤。
本题算法分析如下,设 A 上有 n 个盘子。
如果 n=1,则将圆盘从 A 直接移动到 C。
如果 n=2,则:
1.将 A 上的 n-1(等于 1)个圆盘移到 B 上;
2.再将 A 上的一个圆盘移到 C 上;
3.最后将 B 上的 n-1(等于 1)个圆盘移到 C 上。
如果 n=3,则:
A. 将 A 上的 n-1(等于 2,令其为 n)个圆盘移到 B(借助于 C),步骤如下: (1)将 A 上的 n-1(等于 1)个圆盘移到 C 上。
(2)将 A 上的一个圆盘移到 B。
(3)将 C 上的 n-1(等于 1)个圆盘移到 B。 B. 将 A 上的一个圆盘移到 C。 C. 将 B 上的 n-1(等于 2,令其为 n)个圆盘移到 C(借助 A),步骤如下:
(1)将 B 上的 n-1(等于 1)个圆盘移到 A。 (2)将 B 上的一个盘子移到 C。 (3)将 A 上的 n-1(等于 1)个圆盘移到 C。
到此,完成了三个圆盘的移动过程。
从上面分析可以看出,当 n 大于等于 2 时,移动的过程可分解为三个步骤:
第一步 把 A 上的 n-1 个圆盘移到 B 上;
第二步 把 A 上的一个圆盘移到 C 上;
第三步 把 B 上的 n-1 个圆盘移到 C 上;其中第一步和第三步是类同的。
当 n=3 时,第一步和第三步又分解为类同的三步,即把 n-1 个圆盘从一个针移到另一 个针上,这里的 n=n-1。 显然这是一个递归过程,据此算法可编程如下:

#include <stdio.h>

move(int n, int x, int y, int z)
{
    if (n == 1)
        printf("%c-->%c\n", x, z);
    else
    {
        move(n - 1, x, z, y);
        printf("%c-->%c\n", x, z);
        move(n - 1, y, x, z);
    }
}
main()
{
    int h;
    printf("\ninput number:\n");
    scanf("%d", &h);
    printf("the step to moving %2d diskes:\n", h);
    move(h, 'a', 'b', 'c');
}

8.7 数组作为函数参数

【例 8.7】判别一个整数数组中各元素的值,若大于 0 则输出该值,若小于等于 0 则输出 0
值。编程如下:

#include <stdio.h>

void nzp(int v)
{
    if (v > 0)
        printf("%d ", v);
    else
        printf("%d ", 0);
}
main()
{
    int a[5], i;
    printf("input 5 numbers\n");
    for (i = 0; i < 5; i++)
    {
        scanf("%d", &a[i]);
        nzp(a[i]);
    }
}

【例 8.8】数组 a 中存放了一个学生 5 门课程的成绩,求平均成绩。

#include <stdio.h>

float aver(float a[5])
{
    int i;
    float av, s = a[0];
    for (i = 1; i < 5; i++)
        s = s + a[i];
    av = s / 5;
    return av;
}
void main()
{
    float sco[5], av;
    int i;
    printf("\ninput 5 scores:\n");
    for (i = 0; i < 5; i++)
        scanf("%f", &sco[i]);
    av = aver(sco);
    printf("average score is %5.2f", av);
}

【例 8.9】题目同 8.7 例。改用数组名作函数参数。

#include <stdio.h>

void nzp(int a[5])
{
    int i;
    printf("\nvalues of array a are:\n");
    for (i = 0; i < 5; i++)
    {
        if (a[i] < 0)
            a[i] = 0;
        printf("%d ", a[i]);
    }
}
main()
{
    int b[5], i;
    printf("\ninput 5 numbers:\n");
    for (i = 0; i < 5; i++)
        scanf("%d", &b[i]);
    printf("initial values of array b are:\n");
    for (i = 0; i < 5; i++)
        printf("%d ", b[i]);
    nzp(b);
    printf("\nlast values of array b are:\n");
    for (i = 0; i < 5; i++)
        printf("%d ", b[i]);
}

【例 8.10】如把例 8.9 修改如下:

#include <stdio.h>

void nzp(int a[8])
{
    int i;
    printf("\nvalues of array aare:\n");
    for (i = 0; i < 8; i++)
    {
        if (a[i] < 0)
            a[i] = 0;
        printf("%d ", a[i]);
    }
}
main()
{
    int b[5], i;
    printf("\ninput 5 numbers:\n");
    for (i = 0; i < 5; i++)
        scanf("%d", &b[i]);
    printf("initial values of array b are:\n");
    for (i = 0; i < 5; i++)
        printf("%d ", b[i]);
    nzp(b);
    printf("\nlast values of array b are:\n");
    for (i = 0; i < 5; i++)
        printf("%d ", b[i]);
}

【例 8.11】

#include <stdio.h>

void nzp(int a[], int n)
{
    int i;
    printf("\nvalues of array a are:\n");
    for (i = 0; i < n; i++)
    {
        if (a[i] < 0)
            a[i] = 0;
        printf("%d ", a[i]);
    }
}
main()
{
    int b[5], i;
    printf("\ninput 5 numbers:\n");
    for (i = 0; i < 5; i++)
        scanf("%d", &b[i]);
    printf("initial values of array b are:\n");
    for (i = 0; i < 5; i++)
        printf("%d ", b[i]);
    nzp(b, 5);
    printf("\nlast values of array b are:\n");
    for (i = 0; i < 5; i++)
        printf("%d ", b[i]);
}

本程序 nzp 函数形参数组 a 没有给出长度,由 n 动态确定该长度。在 main 函数中,函
数调用语句为 nzp(b,5),其中实参 5 将赋予形参 n 作为形参数组的长度。
d. 多维数组也可以作为函数的参数。在函数定义时对形参数组可以指定每一维的
长度,也可省去第一维的长度。因此,以下写法都是合法的。
int MA(int a[3][10])

int MA(int a[][10])。

8.8 局部变量和全局变量

8.8.1 局部变量

【例 8.12】

#include <stdio.h>

main()
{
    int i = 2, j = 3, k;
    k = i + j;
    {
        int k = 8;
        printf("%d\n", k);
    }
    printf("%d\n", k);
}

8.8.2 全局变量

【例 8.13】输入正方体的长宽高 l,w,h。求体积及三个面 xy,xz,y*z 的面积。

#include <stdio.h>

int s1, s2, s3;
int vs(int a, int b, int c)
{
    int v;
    v = a * b * c;
    s1 = a * b;
    s2 = b * c;
    s3 = a * c;
    return v;
}
main()
{
    int v, l, w, h;
    printf("\ninput length,width and height\n");
    scanf("%d%d%d", &l, &w, &h);
    v = vs(l, w, h);
    printf("\nv=%d,s1=%d,s2=%d,s3=%d\n", v, s1, s2, s3);
}

【例 8.14】外部变量与局部变量同名。

#include <stdio.h>

int a = 3, b = 5; /*a,b 为外部变量*/
max(int a, int b) /*a,b 为外部变量*/
{
    int c;
    c = a > b ? a : b;
    return (c);
}
main()
{
    int a = 8;
    printf("%d\n", max(a, b));
}

8.9 变量的存储类别

8.9.1 动态存储方式与静态动态存储方式

8.9.2 auto 变量

8.9.3 用 static 声明局部变量

【例 8.15】考察静态局部变量的值。

#include <stdio.h>

f(int a)
{
    auto b = 0;
    static c = 3;
    b = b + 1;
    c = c + 1;
    return (a + b + c);
}
main()
{
    int a = 2, i;
    for (i = 0; i < 3; i++)
        printf("%d", f(a));
}

对静态局部变量的说明:

  1. 静态局部变量属于静态存储类别,在静态存储区内分配存储单元。在程序整个运行期间
    都不释放。而自动变量(即动态局部变量)属于动态存储类别,占动态存储空间,函数
    调用结束后即释放。
  2. 静态局部变量在编译时赋初值,即只赋初值一次;而对自动变量赋初值是在函数调用时
    进行,每调用一次函数重新给一次初值,相当于执行一次赋值语句。
  3. 如果在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值 0(对
    数值型变量)或空字符(对字符变量)。而对自动变量来说,如果不赋初值则它的值是
    一个不确定的值。

【例 8.16】打印 1 到 5 的阶乘值。

#include <stdio.h>

int fac(int n)
{
    static int f = 1;
    f = f * n;
    return (f);
}
main()
{
    int i;
    for (i = 1; i <= 5; i++)
        printf("%d!=%d\n", i, fac(i));
}

输出结果:

1!=1
2!=2
3!=6
4!=24
5!=120

8.9.4 register 变量

【例 8.17】使用寄存器变量。

#include <stdio.h>

int fac(int n)
{
    register int i, f = 1;
    for (i = 1; i <= n; i++)
        f = f * i return (f);
}
main()
{
    int i;
    for (i = 0; i <= 5; i++)
        printf("%d!=%d\n", i, fac(i));
}

输出结果

1!=1
2!=2
3!=6
4!=24
5!=120

说明:

  1. 只有局部自动变量和形式参数可以作为寄存器变量;
  2. 一个计算机系统中的寄存器数目有限,不能定义任意多个寄存器变量;
  3. 局部静态变量不能定义为寄存器变量。

8.9.5 用 extern 声明外部变量

【例 8.18】用 extern 声明外部变量,扩展程序文件中的作用域。
函数声明

// header.h
extern void printMessage(void); // 声明函数

// file1.c
#include "header.h"
void printMessage() {
    printf("Hello, World!\n");
}

// main.c
#include "header.h"
int main() {
    printMessage(); // 使用声明的函数
    return 0;
}

变量声明

// file1.c
int globalVar = 10; // 定义并初始化全局变量

// file2.c
extern int globalVar; // 声明globalVar是在别处定义的

相关推荐

  1. C语言程序设计-8 函 数

    2024-06-18 20:42:04       6 阅读
  2. C语言——函数练习程序

    2024-06-18 20:42:04       20 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-18 20:42:04       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-18 20:42:04       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-18 20:42:04       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-18 20:42:04       18 阅读

热门阅读

  1. 德旺之人,福耀华夏!

    2024-06-18 20:42:04       6 阅读
  2. 建立fabric-ca-serve集群

    2024-06-18 20:42:04       7 阅读
  3. fastapi对视频播放加速方法

    2024-06-18 20:42:04       4 阅读
  4. 嵌入式就业前景好么

    2024-06-18 20:42:04       6 阅读
  5. 数据库回表及优化方法(附示例)

    2024-06-18 20:42:04       6 阅读
  6. 计算机网络模型面试题50题

    2024-06-18 20:42:04       7 阅读
  7. 图解ZGC

    图解ZGC

    2024-06-18 20:42:04      4 阅读
  8. [python日常]获取指定文件夹下,指定后缀的文件

    2024-06-18 20:42:04       8 阅读