第七章 模块化程序设计(函数)

 

一、概述 二、函数的定义
三、函数的调用 四、函数的递归调用
五、变量的存储类型 六、局部变量与全局变量


一、概述

 

回顾:

·程序设计方法:自上而下,逐步细化

·C语言:函数式语言

C程序设计中,通常:

  ·将一个大程序分成几个子程序模块(自定义函数)

·将常用功能做成标准模块(标准函数)放在函数库中供其他程序调用

如果把编程比做制造一台机器,函数就好比其零部件。

·可将这些“零部件”单独设计、调试、测试好,用时拿出来装配,再总体调试。

·这些“零部件”可以是自己设计制造/别人设计制造/现在的标准产品

而且,许多“零部件”我们可以只知道需向它提供什么(如控制信号),它能产生什么(如速度/动力),并不需要了解它是如何工作、如何设计制造的——所谓“黑盒子”。

              控制信号                               速度/动力        

          (输入参数)                           (返回结果)

 

【例】编写一个儿童算术能力测试软件




显示软件封面

检查密码

产生题目

接受回答

评判计分

显示结果

如果要继续练习

告别词

main() {

char ans = ‘y’;

clrscr();

cover();           /*调用软件封面显示函数*/

password();        /*调用密码检查函数*/

while (ans ==’y’|| ans ==’Y’)

{ question();       /*调用产生题目函数*/

 answers();        /*调用接受回答函数*/

 marks();          /*调用评分函数*/

 results();          /*调用结果显示函数*/

 printf(“是否继续练习?(Y/N)\n”);

 ans=getch ();

 }

 printf(“谢谢使用,再见!”);

}

/*定义所用函数*/

cover() { }   /*软件封面显示函数*/

password(){ }    /*密码检查函数*/

question(){ }     /*产生题目函数*/

answers(){ }     /*接受回答函数*/

marks(){ }       /*评分函数*/

results(){ }      /*结果显示函数*/

 

函数使用常识:  P102

1、C程序执行总是从main函数开始,调用其它函数后总是回到main函数,最后在 main函数中结束整个程序的运行。

2、一个C程序由一个或多个源(程序)文件组成——可分别编写、编译和调试。

3、一个源文件由一个或多个函数组成,可为多个C程序公用。

4、C语言是以源文件为单位而不以函数为单位进行编译的。

5、所有函数都是平行的、互相独立的,即在一个函数内只能调用其他函数,不能再定义一个函数(嵌套定义)。

6、一个函数可以调用其他函数或其本身,但任何函数均不可调用main函数。

 

二、函数的定义

 

函数定义——“制造函数”

1、无参函数   P102

 定义格式:

数据类型  函数名()             /*现代风格是:函数名(void*/

{ 函数体(说明部分+ 语句)}

注意】每个函数之前可以有自己的编译预处理命令组。

数据类型为int时,可以省略。

 

2、有参函数   P102

定义格式:

数据类型  函数名(形参表)      /*现代风格是:函数名(带类型形参表)*/

形参类型说明;

{ 函数体(说明部分+ 语句)}

 

函数的返回值通过函数体中的return语句获得。

形式: return  (x);    return (x+y);    return (x>y?x:y);

语句中圆括号亦可省略。

注意】如果函数值类型与return语句表达式值的类型不一致,以函数类型为准(数值型会自动进行类型转换)。如果明确表示不需返回值,可用void作函数的数据类型。

 

【例一】

main()

{ float a=1.5,b=2.5;

  int c;

  c=max(a,b);

  printf(“Max is %d\n”,c);

}

max(x,y)              /*用“值传递”分析法进行变量跟踪*/

float x,y;

{float z;

 z=x>y?x:y;

 return z;

}

结果:Max is 2  (编译通过,结果错误)

讨论】如何改错?

·如果将输出语句中的%d改为%f,结果: Max is 0.125000 (亦错)

·如果将max(x,y)改为float max(x,y),结果编译出错:

Type mismatch in redeclaration of ‘max’

为什么?(原因见下)。

 
三、函数的调用

 

函数和变量一样,在其主调函数中也必须“先说明,后使用”。

注意关系:  函数定义——制造函数

函数使用——说明(准备使用)

  调用(使用函数)

调用方式:

·赋值           如:c=max(x,y);

          ·表达式中           c=1+max(x,y);  printf(“Max=%d\n”,max(x,y));

          ·执行函数           max(x,y);

 

1、调用外部函数(其他源文件中定义的函数)时

   函数说明语句     extern  函数名();  

【例】   文件file1.c中

 main()

{ int x=80,y=90,c;

  extern max();             /*函数说明*/

  c=max(x,y)+20;      /*调用max函数*/

  printf(“Max is %d\n”,c);

}

文件files2.c中(与file1.c同目录)

 

extern max(int a,int b)     /*extern可省*/

{float c;

 c=a>b?a:b;

 return c;

}

 

2、调用同一源文件中的非标准函数时

也必须在主调函数中对所调函数进行说明:

   函数说明语句    数据类型  函数名();      (不说明会出现编译错误)

但三种情况下可以省略说明:

    ① 函数值是整型(int)或字符型(char)时——系统自动按整型说明;

② 所调函数的定义出现在主调函数之前时;

③ 文件一开头,在所有函数之前,对所用函数作了说明

 

3、函数的嵌套调用   ....

四、函数的递归调用

 

1、递归的概念   P115

直接递归调用  调用函数的过程中又调用该函数本身

间接递归调用  调用f1函数的过程中调用f2函数,而f2中又需要调用f1

以上均为无终止递归调用。

为此,一般要用if语句来控制使递归过程到某一条件满足时结束。

2、递归法     

类似于数学证明中的反推法,从后一结果与前一结果的关系中寻找其规律性。

归纳法可以分为:

·递推法 从初值出发,归纳出新值与旧值间直到最后值为止存在的关系

        要求通过分析得到: 初值+递推公式

        编程:通过循环控制结构实现(循环的终值是最后值)

·递归法 从结果出发,归纳出后一结果与前一结果直到初值为止存在的关系

        要求通过分析得到: 初值+递归函数

编程:设计一个函数(递归函数),这个函数不断使用下一级值调用自身,直到结果已知处——选择控制结构

其一般形式是:

在主函数中用终值n调用递归函数,而在递归函数中:

递归函数名f (参数x

{ if (n=初值)

   结果=…;

 else

   结果=f(x-1)的表达式;

 返回结果(return);

}

 

 

例一】(P1187.8)用递归法求n

分析比较:

递推法

递归法

0=1

1=0!×1

2=1!×2

3=2!×3

……

n!=(n1)!×n

分析得Sn=n!的求解

       1   n=1,0

Sn=

Sn-1×n

     (n>1)

其中Sn-1求出

n!=(n1)! ×n

(n1)!= (n2)! ×(n1)

(n2)!= (n3)! ×(n2)

(n3)!= (n4)! ×(n3)

……

2!=1!×2

分析得f(n)=n!的求解

       1   (n=1,0)

f(n)=

f(n1)×n

     (n>1)

其中f(n-1)未求出

 

实际上,递归程序分两个阶段执行——

回推(调用):欲求n! →先求 (n-1)! (n-2)! 1!   1!已知,回推结束。

递推(回代):知道1!→2!可求出→3!→ n

 

程序如下:

main()

{ int n;

  float s;

  float fac();

  clrscr();

  printf("Input n=");

  scanf("%d",&n);

  s=fac(n);

  printf("%d!=%.0f",n,s);

}

 

float fac(int x)

{int f;

 if (x==0||x==1) f=1;

 else f=fac(x-1)*x;

 return f;

 }

执行:Input n=5

结果:5!=120

 

例二】(P1167.7)有5个人,第5个人说他比第4个人大2岁,第4个人说他对第3个人大2岁,第3个人说他对第2个人大2岁,第2个人说他比第1个人大2岁,第1个人说他10岁。求第5个人多少岁。

分析:           10         (n=1)

      age(n)=  

                 age(n-1)+2  (n>1)

程序如下:

main()

{clrscr();

 printf("%d",age(5));

}

 

age(int n)

{ int c;

 if (n==1) c=10;

 else c=age(n-1)+2;

 return c;

}

结果:18

 

例三】在屏幕上显示杨辉三角形

           1                    分析:若起始行为第1行

1           1                  则: 第x行有x个值

1   2   1                     对第x行第y列,其值(不计左侧空格时)

1   3   3   1                               1  y=1 y=x

   1   4   6   4   1                 c(x,y)=

1    5   10  10  5   1                          c(x-1,y-1)+c(x-1,y)

程序如下:

main(){

int i,j,n;

clrscr();

printf("Input n=");

scanf("%d",&n);

for (i=1;i<=n;i++)

 {  for (j=0;j<=n-i;j++)

     printf("  ");         /*为了保持三角形态,此处输出两个空格*/

   for (j=1;j<=i;j++)

     printf("%3d ",c(i,j));

   printf("\n");

  }

 }

 

int c(int x,int y)

{int z;

if (y==1||y==x) return 1;

else

 { z=c(x-1,y-1)+c(x-1,y);

   return z;

 }

}

 

例四Fibonacci数列问题。

                 1                (n=1)

分析:fib(n)=     1                (n=2)

                 fib(n-1)+fib(n-2)   (n>1)

程序如下:

fib (int n)

{

 int f;

 if (n==1||n==2)

   f=1;

 else

   f=fib(n-1)+fib(n-2);

 return (f);

}

 

main()

{

  int i,s=0;

  clrscr();

  for (i=1;i<=12;i++)

    s=s+fib(i);

  printf("n=12,s=%d",s);

}

结果:376

 

例五】运行下列程序,当输入字符序列AB$CDE并回车时,程序的输出结果是什么?

#include <stdio.h>

rev()

{ char c;

  c=getchar();

  if (c=='$') printf("%c",c);

  else

  { rev();

    printf("%c",c);

  }

}

main(){

rev();

}

结果:$BA

 

例六】反向输出一个整数(非数值问题)

非数值问题的分析无法象数值问题那样能得出一个初值和递归函数式,但思路是相同的。

分析方法:

①简化问题:设要输出的正整数只有一位,则“反向输出”问题可简化为输出一位整数。

②对大于10的正整数,逻辑上可分为两部分:个位上的数字和个位以前的全部数字。将个位以前的全部数字看成一个整体,则为了反向输出这个大于10的正整数,可按以下步骤:

   a、输出个位上的数字;

   b、将个位除外的其他数字作为一个新的整数,重复a步骤的操作。

其中b问题只是对原问题在规模上进行了缩小——递归。

所以,可将反向输出一个正整数的算法归纳为:

   if n为一位整数)

                输出n

           else

               { 输出n的个位数字;

                 对剩余数字组成的新整数重复“反向输出”操作;

               }

程序如下:

#include <stdio.h>

void main()

{ void printn(int x);

  int n;

  printf("Input n=");

  scanf("%d",&n);

  if (n<0)

   {n=-n;putchar('-');}

  printn(n);

}

 

void printn(int x)         /*反向输出整数x*/

{if (x>=0&&x<=9)        /*x为一位整数*/

  printf("%d",x);         /*则输出整数x*/

else                   /*否则*/

{printf("%d",x%10);      /*输出x的个位数字*/

 printn(x/10);           /*x中除个位数字外的全部数字形成新的x后,继续递归操作*/

 }

 }

执行:Input n=12345

结果:54321

执行:Input n=12479

结果:97421

讨论Input n=123456

        6167

为什么:123456710=1 1110 0010 0100 00002

        int类型的数实际只能存入16位,即1110 0010 0100 00002