第七章 用函数实现模块化程序设计

7.1 为什么要用函数

函数就是功能。每一个函数用来实现一个特定的功能。函数的名字应反映其代表的功能。

一个C程序可由一个主函数和若干个其他函数构成。由主函数调用其他函数,其他函数也可以互相调用。同一个函数可以被一个或多个函数调用任意多次。

除了可以使用库函数外,有的部门还编写一批本领域或本单位常用到的专用函数,供本领域或本单位的人员使用。在程序设计中要善于利用函数,以减少重复编写程序段的工作量,也更便于实现模块化的程序设计。

#include <stdio.h>
int main()
{void print_star();//声明print_star函数
void print_message();
//声明print_message函数
print_star();//调用print_star函数
print_message();//调用print_message函数
print_star();//调用print_star函数
return 0;
}

void print_star()//定义print_star函数
{printf("******************\n");}

void print_message()
//定义print_message函数
{printf("How do you do!\n");}

程序分析:
print_star和print_message都是用户自定义的函数名,分别用来输出一排"*“和一行文字信息。在定义这两个函数时指定函数的类型为void,意为函数无类型,即无函数值,也就是说,执行这两个函数后不会把任何值带回main函数。
在程序中,定义print_star和print_message函数的位置是在main函数的后面,在这种情况下,应当在main函数之前或main函数中的开头部分,对以上两个函数进行"声明”。函数声明的作用是把有关函数的信息(函数名,函数类型,函数参数的个数与类型)通知编译系统,以便在编译系统对程序进行编译时,在进行到main函数调用print_star( )和print_message( )时并知道它们是函数而不是变量或其他对象。此外,还对调用函数的正确性进行检查(如类型,函数名,参数个数,参数类型等是否正确)。

7.2 怎样定义函数

C语言要求,在程序中用到的所有函数,必须"先定义,后使用"。

定义函数应包括以下几个内容:

  1. 指定函数的名字,以便以后按名调用。
  2. 指定函数的类型,即函数返回值的类型。
  3. 指定函数的参数的名字和类型,以便在调用函数时向它们传递数据。对无参函数不需要这项。
  4. 指定函数应当完成什么操作,即函数的功能。这是最重要的,是在函数体中解决的。

定义函数的方法

  1. 定义无参函数
    类型名 函数名( )
    {
    函数体
    }

    类型名 函数名(void)
    {
    函数体
    }
    函数体包括声明部分和语句部分。
  2. 定义有参函数
    类型名 函数名(形式参数表列)
    {
    函数体
    }
    函数体包括声明部分和语句部分。
int max(int x,int y)
{
int z;  //声明部分
z=x>y?x:y;  //执行语句部分
return(z);
//将z的值作为函数返回值带回到主调函数,max=z
}
  1. 定义空函数
    类型名 函数名( ) { }
    在编写程序的开始阶段,可以在将来准备扩充功能的地方写上一个空函数(函数名取将来采用的实际函数名),只是这些函数暂时还未编写好,先用空函数占一个位置,等以后扩充程序功能时用一个编好的函数代替它。这样做,程序的结构清楚,可读性好,以后扩充新功能方便,对程序结构影响不大。空函数在程序设计中常常是有用的。

7.3 调用函数

函数调用的形式
函数名(实参表列)

如果是调用无参函数,则"实参表列"可以没有,但括号不能省略。如果实参表列包含多个实参,则各参数间用逗号隔开。

  1. 函数调用语句 printf_star( )
  2. 函数表达式 c=max(a,b);
  3. 函数参数:函数调用作为另一个函数调用的实参。
    m=max(a,max(b,c));
    printf("%d",max(a,b));

函数调用时的数据传递

1.形式参数和实际参数
在定义函数时函数名后面括号中的变量名称为"形式参数",在主调函数中调用一个函数时,函数名后面括号中的参数称为"实际参数"。实际参数可以是常量、变量或表达式。但要求它们有确定的值。

2.实参和形参间的数据传递
实参的值传递给形参
实参和形参的类型应相同或赋值兼容。如果不同,则按不同类型数值的赋值规则进行转换。字符型与int型可以互相通用。

输入两个整数,要求输出其中值较大者。要求用函数来找到大数。

#include <stdio.h>
int main()
{
int max(int x,int y);//对max函数的声明
int a,b,c;
printf("please enter two integer numbers:");
scanf("%d,%d",&a,&b);//输入两个整数
c=max(a,b);
//调用max函数,有两个实参。大数赋给变量c
printf("max is %d\n",c);//输出大数c
return 0;
}

int max(int x,int y)//定义max函数,有两个参数
{
int z;//定义临时变量z
z=x>y?x:y;//把x和y中的大者赋给z
return (z);//把z作为max函数的值带回main函数
}

函数调用的过程
(1)在定义函数中指定的形参,在未出现函数调用时,它们并不占内存中的存储单元。在发生函数调用时,函数max的形参才被临时分配内存单元。
(2)将实参的值传递给对应形参,单向传递。 因为实参和形参在内存中占有不同的存储单元,实参无法得到形参的值。
(3)在执行max函数期间,由于形参已经有值,就可以利用形参进行有关的运算。
(4)通过return语句将函数值带回到主调函数。应当注意返回值的类型与函数类型一致。如果函数不需要返回值,则不需要return语句。这时函数的类型应定义为void类型。
(5)调用结束,形参单元被释放。注意:实参单元仍保留并维持原值。这是因为实参与形参是两个不同的存储单元。

函数的返回值
(1)函数的返回值是通过函数中的return语句获得的。 如果需要从被调用函数带回一个函数值(供主调函数使用),被调用函数中必须包含return语句。如果不需要从被调用函数带回函数值可以不要return语句。

一个函数中可以有一个以上的return语句,执行到哪一个return语句,哪一个return语句就起作用。return语句后面的括号可以不要,如"return z;"与"return (z);"等价。return后面的值可以是一个表达式。

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

这样的函数体更简短,只用一个return语句就把求值和返回都解决了。
(2)函数值的类型。既然函数有返回值,这个值应该属于某一个确定的类型,应当在定义函数时指定函数值的类型。
int max(float x,float y)
char letter(char c1,char c2)
(3)在定义函数时指定的函数类型一般应该和return语句中的表达式类型一致。函数类型决定返回值的类型。
(4)对于不带回值的函数,应当用定义函数为"void类型"(或称"空类型")。此时在函数体中不得出现return语句。

7.4 对被调用函数的声明和函数原型

在一个函数中调用另一个函数需具备的条件:
(1)被调用函数必须是已定义的函数(库函数或用户自己定义的函数)。
(2)如果使用库函数,应在本文件开头用#include指令将调用有关库函数时所需用到的信息"包含"到本文件中来。stdio.h文件中包含了输入输出库函数的声明,使用数学库中的函数应该用#include <math.h>
(3)如果使用用户自己定义的函数,而该函数的位置在调用它的函数(即主调函数)的后面(在同一个文件中),应该在主调函数中对被调用函数作声明。

#include <stdio.h>
int main()
{
float add(float x,float y);
//对add函数作声明
float a,b,c;
printf("Please enter a and b:");
scanf("%f,%f",&a,&b);
c=add(a,b);//调用add函数
printf("sum is %f\n",c);
return 0;
}

float add(float x,float y)//定义add函数
{
float z;
z=x+y;
return (z);//把变量z的值作为函数返回值
}

说明:函数的首行(即函数首部)称为函数原型。简单地照写已定义的函数原型,再加一个分号,就成了函数的"声明"。使用函数原型作声明是C的一个重要特点,能减少编写程序时可能出现的错误。实际上,在函数声明中的形参名可以省写,而只写形参的类型。编译系统只关心和检查参数个数和参数类型,而不检查参数名,因为在调用函数时只要求保证实参类型与形参类型一致,而不必考虑形参名是什么。因此在函数声明中形参名可写可不写,形参名是什么都无所谓。但写形参名只须照抄函数首部就可以了,不易出错,而且用了有意义的参数名有利于理解程序。

注意:对函数的"定义"和"声明"不是同一回事。函数的定义是指对函数功能的确立,包括指定函数名、函数值类型、形参及其类型以及函数题体等,它是一个完整的、独立的函数单位。而函数的声明的作用则是把函数的名字、函数类型以及形参的类型、个数和顺序通知编译系统,以便在调用该函数时系统按此进行对照检查(例如函数名是否正确,实参与形参的类型和个数是否一致),它不包含函数体。

由于在文件的开头(在函数的外部)已对要调用的函数进行了声明(这些称为"外部的声明"),因此在程序编时,编译系统已从外部声明中知道了函数的有关信息,所以不必在主调函数中再重复进行声明。写在所有函数前面的外部声明在整个文件范围中有效。

7.5 函数的嵌套调用

C语言的函数定义是互相平行、独立的,也就是说在定义函数时,一个函数内不能再定义另一个函数,即不能嵌套定义,但可以嵌套调用函数,即在调用一个函数的过程中,又调用另一个函数。

输入4个整数,找出其中最大的数。用函数的嵌套调用来处理。

#include <stdio.h>
int main()
{int max4(int a,int b,int c,intd);
int a,b,c,d,max;
printf("Please enter 4 interger numbers:");
scanf("%d %d %d %d",&a,&b,&c,&d);
max=max4(a,b,c,d);
printf("max=%d\n",max);
return 0;
}

int max4(int a,int b,int c,int d)
{int max2(int a,int b);
int m;
m=max2(a,b);
m=max2(m,c);
m=max2(m,d);
return (m);
}

int max2(int a,int b)
{if(a>=b)
   return a;
 else
   return b;
}

程序改进:
(1)可以将max2函数的函数体改为只用一个return语句,返回一个条件表达式的值:
int max(int x,int b) {return(a>=b?a:b);}
(2)在max4函数中,3个调用max2的语句可以用以下一行代替:
m=max2(max2(max2(a,b),c),d);
//把函数调用作为函数参数
甚至可以取消变量m,max4函数可写成:

int max4(int a,int b,int c,int d)
{
int max2(int a,int b);
return max2(max2(max2(a,b),c),d);
}

7.6 函数的递归调用

在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用。

有5个学生坐在一起,第5个学生比第4个学生大两岁,第4个学生比第3个学生大两岁,第3个学生比第2个学生大两岁,第2个学生比第1个学生大两岁,第1个学生是10岁,请问第5个学生多大?

int age(int n)  //求年龄的递归函数
{
int c;  //用c作为存放函数返回值的变量
if(n==1)
  c=10;
else
  c=age(n-1)+2;
return(c);
}
用一个主函数调用age函数,求得第5个学生的年龄
#include <stdio.h>
int main()
{int age(int n);  //对age函数的声明
printf("NO.5,age:%d\n",age(5));
return 0;
}
int age(int n)  //定义递归函数
{int c;
if(n==1)
  c=10;
else
  c=age(n-1)+2;
return(c);  //返回年龄
}

age函数共被调用5次,其中age(5)是main函数调用的,其余4次是在age函数中调用自己的,即递归调用4次。

用递归方法求n!

#include <stdio.h>
int main()
{
int fac(int n);  //fac函数声明
int n;
int y;
printf("input an integer number:");
scanf("%d",&n);
y=fac(n);
printf("%d!=%d\n",n,y);
return 0;
}

int fac(int n)
{
int f;
if(n<0)
  printf("n<0,data error!");
else if(n==0||n==1)
  f=1;
else f=fac(n-1)*n;
return(f);
}

注意:程序中的变量是int型,如果用Visual C++等多数C编译系统为int型数据分配4个字节,能表示的最大数为2147483647,当n=12时,运行正常。如果输入13,企图求13的阶乘,是得不到预期结果的,因为求出的结果超过了int型数据的最大值。可将f,y和fac函数定义为float或double型。

7.7 数组作为函数参数

数组元素的作用与变量相当,一般来说,凡是变量可以出现的地方,都可以用数组元素代替。 因此,数组元素也可以用作函数实参,其用法与变量相同,向形参传递数组元素的值。此外,数组名也可以作实参和形参,传递的是数组第一个元素的地址(首地址)。

数组元素作函数实参

数组元素可以用作函数实参,但是不能用作形参。 因为形参是在函数被调用时临时分配存储单元的,不可能为一个数组元素单独分配存储单元(数组是一个整体,在内存中占连续的一段存储单元)。在用数组元素作函数实参时,把实参的值传给形参,是 “值传递” 方式。数据传递的方向是从实参传到形参,单向传递

输入10个数,要求输出其中值最大的元素和该数是第几个数。

#include <stdio.h>
int main()
{
int max(int x,int y);
int a[10],m,n,i;
printf("enter 10integer numbers:");
for(i=0;i<10;i++)
  scanf("%d",&a[i]);
printf("\n");
for(i=1,m=a[0],n=0;i<10;i++)
{
  if(max(m,a[i])>m))
  {
    m=max(m,a[i]);
    n=i;
  }
}
printf("The largest number is %d\nit is the %dth number.\n",m,n+1);
return 0;
}

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

一维数组名作函数参数

除了可以用数组元素作为函数参数外,还可以用数组名作函数参数(包括实参和形参)。

注意:用数组元素做实参时,向形参变量传递的是数组元素的值,而用数组名作函数实参时,向形参(数组名或指针变量)传递的是数组首元素的地址。

有一个一维数组score,内存放10个学生成绩,求平均成绩。

#include <stdio.h>
int main()
{float average(float array[10]);
float score[10],aver;
int i;
printf("input 10 scores:\n");
for(i=0;i<10;i++)
  scanf("%f",&score[i]);
printf("\n");
aver=average(score);
printf("average score is %5.2f\n",aaver);
return 0;
}

float average(float array[10])
{
int i;
float aver,sum=array[0];
for(i=1;i<10;i++)
  sum=sum+array[i];
aver=sum/10;
return(aver);
}

程序分析:
(1)用数组名做函数参数,应该在主调函数和被调用函数分别定义数组,例中array是形参数组名,score是实参数组名,分别在其所在函数中定义,不能只在一方定义。
(2)实参数组与形参数组类型应一致(今都为float型),如不一致,结果将出错。
(3)在定义average函数时,声明形参数组的大小为10,但在实际上,指定其大小是不起任何作用的,因为C语言编译系统并不检查行形参数组大小,只是将实参数组的首元素的地址传给形参数组名。形参数组名获得了实参数组的首元素的地址,前已说明,数组名代表数组的首元素的地址,因此,可以认为,形参数组首元素(array[0])和实参数组首元素(score[0])具有同一地址,它们共占同一存储单元,score[n]和array[n]指的是同一单元,具有相同的值。也就是说,形参数组中各元素的值如发生变化会使实参数组元素的值同时发生变化。
(4)形参数组可以不指定大小,在定义数组时在时数组名后面跟一个空的方括号。
float average(float array[ ])
效果是相同的。

有两个班级分别有35名和30名学生,调用一个average函数,分别求这两个班的学生的平均成绩。

#include <stdio.h>
int main()
{float average(float array[],int n);
float score1[5]={98.5,97,91.5,60,55};
float score2[10]={67.5,89.5,99,69.5,77,89.5,76.5,54,60,99.5};
printf("The average of class A is %6.2f\n",average(score1,5));
printf("The average of class B is %6.2f\n",average(score2,10));
return 0;
}

float average(float array[],int n)
//定义average函数,未指定形参数组长度
{int i;
float aver,sum=array[0];
for(i=1;i<n;i++)
  sum=sum+array[i];
aver=sum/n;
return(aver);
}

用选择法对数组中10个整数按由小到大排序
在这里插入图片描述

#include <stdio.h>
int main()
{
void sort(int array[],int n);
int a[10],i;
printf("enter array:\n");
for(i=0;i<10;i++)
  scanf("%d",&a[i]);
sort(a,10);
//调用sort函数,a为数组名,大小为10
printf("The sorted array:\n");
for(i=0;i<10;i++)
  printf("%d",a[i]);
printf("\n");
return 0;
}

void sort(int array[],int n)
{int i,j,k,t;
for(i=0;i<n-1;i++)
  {k=i;
  for(j=i+1;j<n;j++)
    if(array[j]<array[k])
      k=j;
  t=array[k];
  array[k]=array[i];
  array[i]=t;}
}

多维数组名作函数参数

可以用多维数组名作为函数的实参和形参,在被调用函数中对形参数组定义时可以指定每一维的大小,也可以省略第一维的大小说明。但是不能把第二维以及其他高维的大小说明省略。例如:int array[3][10];int array[ ][10];二者都合法而且等价。
在第二维大小相同的前提下,形参数组的第一维可以与实参数组不同。例如,实参数组定义为int score[ ][10];而形参数组定义为int array[ ][10];int array[8][10];这时形参数组和实数组组都是由相同类型和大小的一维数组组成的。C语言编译系统不检查第一维的大小。

有一个3×4的矩阵,求所有元素中的最大值

#include <stdio.h>
int main()
{int max_value(int array[][4]);
int a[3][4]={{1,3,5,7},{2,4,6,8},{15,17,34,12}};
printf("Max value is %d\nn,max_value(a));
return 0;
}

int Max_value(int array[][4])
{int i,j,max;
max=array[0][0];
for(i=0;i<3;i++)  
  for(j=0;j<4;j++)    
    if(array[i][j]>max)max=array[i][j];
return(max);
}

7.8 局部变量和全局变量

定义变量可能有三种情况:
(1)在函数的开头定义:局部变量
(2)在函数内的复合语句内定义:局部变量
(3)在函数的外部定义:全局变量

局部变量

说明:

  1. 主函数中定义的变量也只在主函数中有效,并不因为在主函数中定义而在整个文件或程序中有效。主函数也不能使用其他函数中定义的变量。
  2. 不同函数中可以使用同名的变量,他们代表不同的对象,互不干扰。
  3. 形式参数也是局部变量。
  4. 在一个函数内部,可以在复合语句中定义变量,这些变量只在本复合语句中有效,这种复合语句也称为"分程序"或"程序块"。离开本复合语句,这些变量就无效,系统会把它们占用的内存单元释放。

全局变量

全局变量和局部变量同名,以局部为准,局部大于全局。 (不是真的局部变量,而是形参,它们的值是由实参传给形参的。)

7.9 变量的存储方式和生存期

动态存储方式于静态存储方式

静态存储方式是指在程序运行期间由系统分配固定的存储空间的方式,而动态存储方式则是在程序运行期间根据需要进行动态的分配存储空间的方式。

数据分别存放在静态存储区和动态存储区中。全局变量全部存放在静态存储区中,在程序开始执行时给全局变量分配存储区,程序执行完毕就释放。在执行过程中它们占据固定的存储单元,而不是动态地进行分配和释放。

在动态存储区中存放以下数据:
①函数形式参数。在调用函数时给形参分配存储空间。
②函数中定义的没有用关键字static声明的变量,即自动变量。
③函数调用时的现场保护和返回地址等。

对以上这些数据,在函数调用开始时分配动态存储空间,函数结束时释放这些空间。在程序执行过程中,这种分配和释放是动态的,如果在一个程序中两次调用同一函数,而在此函数中定义了局部变量,在两次调用时分配给这些局部变量的存储空间的地址可能是不相同的。

局部变量的存储类别

C的存储类别包括4种:自动的(auto),静态的(static),寄存器的(register),外部的(extern)。根据变量的存储类别,可以知道变量的作用域和生存期。

  1. 自动变量(auto变量)
    函数中的局部变量,如果不专门为static(静态)存储类别,都是动态地分配存储空间的,数据存储在动态存储区中。函数中的形参和在函数中定义的局部变量(包括在复合语句中定义的局部变量),都属于此类。在调用该函数时,系统会给这些变量分配存储空间,在函数调用结束时就自动释放这些存储空间。因此这类局部变量称为自动变量。自动变量用关键词auto作存储类别的声明。实际上,关键字auto可以省略,不写auto则隐含指定为"自动存储类别",它属于动态存储方式。程序中大多数变量属于自动变量。

  2. 静态局部变量(static局部变量)
    有时希望函数中的局部变量的值在函数调用结束后不消失而继续保留原值,即其占用的存储单元不释放,在下一次再调用该函数时,该变量已有值(就是上一次函数调用结束时的值)。这时就应该指定该局部变量为"静态局部变量",用关键字static进行声明。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是Python语言实现一个简单的计算器的代码,其中包含加法、减法、乘法、除法、阶乘等运算: ```python # 定义加法函数 def add(x, y): return x + y # 定义减法函数 def subtract(x, y): return x - y # 定义乘法函数 def multiply(x, y): return x * y # 定义除法函数 def divide(x, y): return x / y # 定义阶乘函数 def factorial(x): if x == 0: # 0的阶乘为1 return 1 else: return x * factorial(x-1) print("请选择要进行的运算:") print("1.加法") print("2.减法") print("3.乘法") print("4.除法") print("5.阶乘") # 用户输入选项 choice = input("请输入选项(1/2/3/4/5): ") if choice in ('1', '2', '3', '4'): # 加、减、乘、除运算 num1 = float(input("请输入第一个数字: ")) num2 = float(input("请输入第二个数字: ")) if choice == '1': print(num1, "+", num2, "=", add(num1, num2)) elif choice == '2': print(num1, "-", num2, "=", subtract(num1, num2)) elif choice == '3': print(num1, "*", num2, "=", multiply(num1, num2)) elif choice == '4': if num2 == 0: # 除数不能为0 print("除数不能为0") else: print(num1, "/", num2, "=", divide(num1, num2)) elif choice == '5': # 阶乘运算 num = int(input("请输入一个正整数: ")) print(num, "的阶乘为", factorial(num)) else: print("非法输入") ``` 代码注释如下: - 第1行:定义了一个名为`add`的函数,用于实现加法运算,接收两个参数`x`和`y`,返回它们的和。 - 第4行:定义了一个名为`subtract`的函数,用于实现减法运算,接收两个参数`x`和`y`,返回它们的差。 - 第7行:定义了一个名为`multiply`的函数,用于实现乘法运算,接收两个参数`x`和`y`,返回它们的积。 - 第10行:定义了一个名为`divide`的函数,用于实现除法运算,接收两个参数`x`和`y`,返回它们的商。 - 第14-18行:定义了一个名为`factorial`的函数,用于计算阶乘,接收一个参数`x`,递归调用自身,返回`x`的阶乘。 - 第20-25行:输出可选的运算类型。 - 第28行:让用户输入运算类型。 - 第30-31行:如果用户选择加、减、乘、除运算,则让用户输入两个数字,进行相应的运算,并输出结果。 - 第33行:如果用户选择阶乘运算,则让用户输入一个正整数,进行阶乘计算,并输出结果。 - 第35-40行:如果用户输入的选项不在1-5之间,则输出“非法输入”。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值