第8章 函数 (4-2学时 )
课堂教学 4学时上机操作2学时教学累计2 4学时上机累计 8学时
函数本质上是一段程序,这段函数可以被其他函数调用,以完成函数特定的功能。
可反复使用
除了主函数,其他函数是不能独立运行的
使用函数前要先定义函数,定义过的函数可以被调用
定义和调用函数时,都要注意如何将函数要加工的数据带入被调用函数,如何把被调函数处理后的结果数据带回主调函数
定义和使用函数的关键,是在主调函数和被调函数之间正确的传递数据
例 8.1:简单的函数调用
1,main( )
2,{printstar( );
3,print_message( );
4,printstar( );
5,}
6,printstar( )
7,{
8,printf("* * * * * * * * * * * * * * * * \n");
9,}
10.print_message( );
11.{
12,printf(" How do you do! \n);
13.}
运行结果:
* * * * * * * * * * * * * * * * *
How do you do !
* * * * * * * * * * * * * * * * *
说明
1,C程序的执行从 main函数开始,调用其他函数后流程回到 main函数,在 main函数中结束整个程序运行
2,从函数使用的角度看,函数有两种:
① 库函数 ② 自定义函数
3,从函数的形式看,函数分两类:
① 无参函数 ② 有参函数函数定义的一般形式
1,无参函数的定义形式类型标识符 函数名 ( )
{ 声明部分语句
}
例,print_message( )
{
printf(" How do you do! \n);
}
2,有参函数的定义形式类型标识符 函数名 (形式参数表列 )
{ 声明部分语句 }
例:
[int] max(int x,int y)
{int z;
z=x>y?x:y;
return z;}
函数参数和函数的值
形式参数和实际参数
例 8.2调用函数时的数据传递
main( )
{int a,b,c;
scanf("%d,%d",&a,&b);
c=max(a,b); /*a,b为实际参数 */
printf("Max is %d",c);}
max(int x,int y) /*x,y为形式参数 */
{int z;
z=x>y?x:y;
return(z);} 运行时输入,7,8
运行结果,Max is 8
例,对三个整型参数求最大值,返回最大值
int max(x1,x2,x3)可以 max(int x1,int x2,int x3)
int x1,x2,x3; 形式参数
{int max;
if(x1>x2)max=x1;
else max=x2;
if(max<x3) max=x3;
return(max);} 实 际 参 数调用形式,max(a,b,c);
关于形参与实参的说明
1,形参只有在发生函数调用时才临时分配内存单元,调用结束后立即释放
2,实参可以是常量、变量、表达式、数组名
3,定义函数时必须指定形参的类型
4,实参与形参的类型应相同或赋值兼容
5,实参与形参的数据传递是“值传递”
即单向传递函数的返回值
1,返回值通过 return语句获得
2,函数的类型就是返回值的类型
3,明确表示“不带回值”用,void”定义“无类型”
4,例,int max(float x,float y)
{ return(x>y?x:y);} 返回值整型或 { float z;
z=x>y?x:y;
return z;} 返回值整型函数的调用
函数调用的一般形式函数名 (实参表列);
函数调用过程:
1,暂时中断主调函数的运行,转向被调函数
2,为被调函数的形参分配内存单元
3,计算主调函数实参的值,并传递给对应的形参
4,执行被调函数的函数体
5,释放被调函数形参的内存单元
6,返回主调函数,继续运行 …
函数调用时注意:
不定因素,p=f(i,++i) ; 设,i =2
按自左至右,f(2,3)
按自右至左,f(3,3)
应避免出现此类运算,改为:
j=i;
k=++i;
p=f(j,k); 或 p=f(k,k) ; 可以明确
函数调用方法
1,函数语句 如,printstar( );
2,函数表达式 如,c=2*max(a,b);
3,函数参数 如,m=max(a,max(b,c));
对被调函数的声明和函数原型
在一个函数中调用另一个函数需要哪些条件?
1,使用库函数,文件中应用 include命令
2,使用自定义函数,调用前须作声明
例 8.5对被调用的函数作声明
main( )
{float add(float x,float y); /*声明 */
float a,b,c;
scanf("%f,%f",&a,&b);
c=add(a,b);
printf("sum is %f",c);
}
float add(float x,float y) /* 定义实型函数 */
{float z;
z=x+y;
return(z);}运行时输入,3.6,6.5
运行结果,sum is 10.000000
定义与声明不同
,定义”是指对函数功能的确立
,声明”的作用是将被调用函数的信息告之编译系统
函数声明
float add(float x,float y);
float add(float,float); /*原型 */
float add( ); /*不提倡 */
函数原型的一般形式
1,函数类型 函数名 (参数类型 1,
参数类型2 ……)
2,函数类型 函数名 (参数类型 1,参数名 1,
参数类型 2,参数名 2……)
对被调函数不用进行说明的两种情况:
1,被调函数的定义出现在主调函数之前
2,被调函数为整型或字符型位置:在文件的开头或在调用函数之前
被调函数在主调函数之前定义
float add(float x,float y) /* 定义实型函数 */
{float z;
z=x+y;
return(z);}
main( )
{ 不用声明
float a,b,c;
scanf("%f,%f",&a,&b);
c=add(a,b);
printf("sum is %f",c);
}
函数的嵌套调用
C语言允许函数嵌套调用,即函数甲调用函数乙,而函数乙又调用了函数丙
例 8.6用弦截法求方程的根
X3-5x2+16x-80=0
1,取两个不同点 x1,x2
2,连接 f(x1)与 f(x2)交 x轴于 x
x点坐标,x1,f(x2)-x2,f(x1)
f(x2)-f(x1)
再从 x求出 f(x)
x=
3,若 f(x)与 f(x1)同号,则根必在 (x,x2)区间内,
此时 x作为新的 x1…… 将 x作为 x2
4,重复步骤 2.3.,直到 |f(x)|<ε为止 (10-6)
此时认为 f(x)≈0
x2
x
xX1 0
f(x)f(x1)
f(x2)
y
ε
输入 x1,x2求 f(x1),f(x2)
直到 f(x1)和 f(x2)异号求 f(x1)与 f(x2)连线与 x轴的交点 x
y=f(x),y1=f(x1)
真 y与 y1同号 假
x1=x x2=x
直到 |y|<ε
root=x 输出 root
图 8.6 图 8.7
分别用几个函数来实现各部分功能:
1,用函数 f(x)来求 x的函数:
X3-5x2+16x-80=0
2,用函数 xpoint(x1,x2)来求 f(x1)和 f(x2)的连线与 x轴的交点 x的坐标
3,用函数 root(x1,x2)来求 (x1,x2)区间的那个实根
执行 root函数过程中要用到函数 xpoint,而执行 xpoint函数过程中要用到 f函数
1.#include <math.h>
2.float f(float x)
3.{float y;
4,y=((x-5.0)*x+16.0)*-80.0;
5,return(y);}
6.float xpoint(float x1,float x2)
7.{float y;
8,y=(x1*f(x2)-x2*f(x1))/(f(x2)-f(x1));
9,return(y);}
1,float root(float x1,float x2)
2,{float x,y,y1;
3,y1=f(x1); /*将作为符号判断 */
4,do
5,{ x=xpoint(x1,x2); /*近似实根 */
6,y=f(x);
7,if(y*y1>0)
8,{x1=x;
9,else
10,x2=x;
11,}while(fabs(y)>=0.0001);
12,return(x);}
1,main( )
2,{float x1,x2,f1,f2,x;
3,do
4,{ printf("input x1,x2:\n);
5,scanf("%f,%f",&x1,&x2);
6,f1=f(x1);
7,f2=f(x2);
8,}while(f1*f2>=0);
9,x=root(x1,x2);
10.printf("A root of equation is%8.4f",x);}
嵌套调用关系
main函数 root函数 xpoint函数 f函数调用 root 调用 xpoint 调用 f
输出根 x
结束函数的递归调用
在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用
f函数 f1函数 f2函数调用 f函数 调用 f2函数 调用 f1函数
以上为无终止的自身调用
递归调用应该有终止条件
例 8.7有 5个人坐在一起,问第 5个人岁数?他说比第 4个人大 2岁,问第 4个人岁数,他说比第 3个人大 2岁,问第 3个人,又说比第 2个人大
2岁,问第 2个人,又说比第 1个人大 2岁,最后问第 1个人,他说是 10岁,请问第 5个人多大?
这是一个递归问题,
用式子表述
age(n)=10 (n=1)
age(n-1)+2 (n>1)
求第 5个人年龄的过程
age(5) age(5)
=age(4)+2 =18
age(4) age(4)
=age(3)+2 =16
age(3) age(3)
=age(2)+2 =14
age(2) age(2)
=age(1)+2 =12
age(1)=10
回推 递推
用一个函数来描述递归过程
1,age(int n)
2,{int c;
3,if(n==1) c=10;
4,else c=age(n-1)+2;
5,return(c);
6,}
7,main( )
8,{
9,printf("%d",age(5));
10.}
运行结果,18
函数调用过程
age函数共被调用5次
只有一次是主函数调用
其余4次是函数递归调用
age(5)
输出 age(5)
main
c= age(4)+2 c= age(3)+2 c= age(2)+2 c= age(1)+2 c= 10
age函数
n=5
age函数
n=4
age函数
n=3
age函数
n=2
age函数
n=1
age(5)=18 age(4)=16 age(3)=14 age(2)=12 age(1)=10
例 8.8用递归方法求 n!
递推法:1 × 2 × 3 × 4 × 5 × 6 …n
递归法,5!=>4!× 5,4!=>3!× 4,3!=>2!× 3,
2!=>1!× 2,1!=>0!× 1,0!,1!为1
递归公式,1
n.(n-1)!
运行结果,input a integer number,10
10!= 3628800.
n!=
(n=0,1)
(n>1)
1,float fac(int n)
2,{float f;
3,if(n<0){printf("n<0,dataerror!");}
4,else if(n==0||n==1)f=1;
5,else f=fac(n-1)*n;
6,return(f);}
7,main( )
8,{int n;
9,float y;
10,printf("input an integer number:");
11,scanf("%d",&n);
12,y=fac(n);
13,printf("%d!=%15.0f",n,y);}
运行时输入,input a integer number,10
运行结果,10!=3628800
回推过程,main fac(10)
fac( ) 10* fac(9)
9* fac(8)
8* fac(7)
…… ……
4* fac(3)
3* fac(2)
fac(2) 2* fac(1)
fac(1)==1
数组作为函数的参数
数组元素及数组名均可作实参和形参
1,数组元素作函数实参数组元素作实参与变量作实参一样,是单向传递,
即“值传递”方法
例 8.10有两个数组 a,b各有 10个元素,将它们对应地逐个相比 (a[0]与 b[0]比,a[1]与 b[1]
比 ……) 。如果 a数组中的元素大于 b数组中相应元素的数目多余 b数组中元素大于 a数组中相应元素的数目则 a数组大于 b数组,分别统计两个数组对应元素大于、等于、小于的次数
main( )
{int large(int x,int y);
int a[10],b[10],i,n=0,m=0,k=0;
printf("enter array a:\n");
for(i=0;i<10;i++)
scanf("%d",&a[i]);
printf("\nenter array b:\n");
for(i=0;i<10;i++)
scanf("%d",&b[i]);
printf("\n");
1,for(i=0;i<10;i++)
2,{if(large(a[i],b[i])==1)n=n+1;
3,else if(large(a[i],b[i])==0)m=m+1;
4,else k=k+1;}
5,printf("a[i]>b[i]%d times\na[i]=b[i]%d times
\na[i]<b[i]%d times\n",n,m,k);
6,if(n>k) printf("array a is larger than array b\n);
7,else if(n<k) printf("array a is smaller than
array b\n);
8,else printf("array a equal to array b\n");}
1,large(int x,int y)
2,{int flag;
3,if(x>y)flag=1;
4,else if(x<y)flag=-1;
5,else flag=0;
6,return(flag);}
运行时,enter array a:
1 3 5 7 9 8 6 4 2
enter array b:
5 3 8 9 -1 -3 5 6 0 4
运行结果,a[i]>b[i] 4 times
a[i]=b[i] 1 times
a[i]<b[i] 5 times
array a is smaller than array b
2,数组名可作函数参数数组名作函数参数,实参与形参都应用数组名
例 8.11求 10个学生的平均成绩
1,float average(float array[10])
2,{int i;
3,float aver,sum=array[0];
4,for(i=1;i<10;i++)
5,sum=sum+array[i];
6,aver=sum/10;
7,return(aver);
8,}
1,main( )
2,{float score[10],aver;
3,int i;
4,printf("input 10 score:\n);
5,for(i=0;i<10;i++)
6,scanf("%f",&score[i]);
7,printf("\n");
8,aver=average(score);
9,printf("average score is %5.2f",aver);}
10,运行,input 10 scores:
11,100 56 78 98.5 76 87 99 67.5 75 97
12,average score is 83.40
说明
1,数组名作函数参数,主调函数和被调函数应分别定义数组
2,实参数组与形参数组类型应一致
3,实参数组和形参数组大小可以不一致,
C编译对形参数组大小不作检查,只是将实参数组的 首址 传给形参数组
4,形参数组也可以不指定大小
例 8.12 改写例 8.11
float average(float array[ ],int n)
{int i;
float aver,sum=array[0];
for(i=1;i<n;i++)
sum=sum+array[i];
aver=sum/n;
return(aver);}
调用,average(score_1,5)
average(score_2,10)
main( )
{float score_1[5]={98.5,97,91.5,60,55};
float score_2[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(score_1,5));
printf("the average of class B is %6.2f\n",
average(score_2,10));}
运行结果,the average of class A is 80.40
the average of class B is 78.20
5,用数组名作函数实参时,不是把数组的值传递给形参,而是把实参数组的起始地址传递给形参数组,两个数组共占同一段内存单元
a为实参数组,b为形参数组
a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9]
b[0] b[1] b[2] b[3] b[4] b[5] b[6] b[7] b[8] b[9]
2 4 6 8 10 12 14 16 18 20起始地址 1000
例 8.13用选择法对数组中 10个整数按由小到大排序。
选择法步骤
a[0] a[1] a[2] a[3] a[4]
3 6 1 9 4 未排序
1 6 3 9 4
1 3 6 9 4
1 3 4 9 6
1 3 4 6 9
1,void sort(int array[ ],int n)
2,{int i,j,k,t;
3,for(i=0;i<n-1;i++)
4,{k=i;
5,for(j=i+1;j<n;j++)
6,if(array[j]<array[k]) k=j;
7,t=array[k];array[k]=array[i];array[i]=t;}
8,}
1,main( )
2,{int a[10],i;
3,for(i=0;i<10;i++)
4,scanf("%d",&a[i]);
5,sort(a,10);
6,for(i=0;i<10;i++)
7,printf("%d",a[i]);
8,printf("\n");
9,}
用多维数组作函数参数
多维数组元素可以作为实参
多维数组名也可以作为实参和形参
形参数组定义时
int array[3][10]; 正确
int array[ ][10]; 正确
int array[ ][ ]; 不正确
例 8.14有一个 3x4的矩阵,求所有元素中的最大值
1,max_value(int array[ ][4])
2,{int i,j,k,max;
3,max=array[0][0];
4,for(i=0;i<3;i++)
5,for(j=0;j<4;j++)
6,if(array[i][j]>max) max=array[i][j];
7,return(max);
8,}
9,main( )
10,{int a[3][4]={{1,3,5,7},{2,4,6,8},{15,17,34,12}};
11,printf("max value is %d\n",max_value(a));
12,}
地址传递方式
1,地址传递方式是在实参和形参之间传递数据的一种方式
2,地址传递方式所传递的是地址。调用函数时,将实参的地址赋予对应的形参作为其地址.由于形参和实参地址相同,即它们占用相同的内存地址,所以可以看成将实参的值传递给形参;返回时,可以看成将形参的值回带给对应的实参
3,其特点是“参数值的双向传递”
值传递和地址传递方式的区别
值传递的是数值;地址方式传递的是地址值
值传递方式是将实际参数的值传递给对应的形式参数,形式参数应分配内存单元,
数据传递是单向的,只能从主调函数向被调函数传递.
地址传递方式是将实际参数的地址值传递给对应的形式参数如果形式参数是数组将不再分配内存单元.接受地址值的形式参数一般是指针变量或数组.实际参数可以是变量的地址、数组名或指针变量局部变量和全局变量
局部变量在函数内定义的变量是内部变量,它只在本函数范围内有效称,局部变量”
如,float f1(int a) /*函数 f1*/
{ int b,c;
…}
char f2(int x,int y) /*函数 f2*/
{int i,j;
… }
main( ) /*主函数 */
{int m,n;
…}
a,b,c 有效
x,y,i,j有效
m,n有效说明
1,主函数中定义的变量也只在主函数中有效
2,不同函数中可以使用相同名字的变量,它们代表不同的对象
3,形式参数也是局部变量
4,在一个函数内部,可以在复合语句中定义变量,它们只在本复合语句中有效
main( )
{int a,b;

{int c;
c=a+b;

}

}
C在此范围内有效 a,b在此范围内有效全局变量
在函数之外定义的变量称为外部变量或称全局变量
全局变量可为本函数的其他函数所共用
它的有效范围是从定义变量的位置开始到本源程序文件结束例,int p=1,q=5;
float f1(int a)
{int b,c;

}
char c1,c2;
main( )
{int m,n;

{int w;
w=m+n;

}
}
a,b,c有效
w m,n 有效全局变量有效全局变量的作用范围
p,q
c1,c2
说明
设全局变量的作用是增加函数之间数据联系的渠道 (return只能带回一个值 )
建议不在必要时不要使用全局变量
如果在同一个源文件中,外部变量与局部变量同名,则局部变量的作用范围内,外部变量被“屏蔽”,即它不起作用
例 8.15有一个一维数组,内放 10个学生成绩,求平均分、最高分、和最低分
1.float Max=0,Min=0;
2.float average(float array[ ],int n)
3.{int i;
4,float aver,sum=array[0];
5,Max=Min=array[0];
6,for(i=1;i<n;i++)
7,{if(array[i]>Max)Max=array[i];
8,else if(array[i]<Min)Min=array[i];
9,sum=sum+array[i]; }
10,aver=sum/n;
11,return(aver);}
main( )
{float ave,score[10];
int i;
for(i=0;i<10;i++)
scanf("%f,&scor[i]);
ave=average(score.10);
printf("max=%6.2f\nmin=%6.2f\naverage=
%6.2f\n",Max,Min,ave);
}
运行时输入,99 45 78 100 67.5 89 92 66 43
运行结果,max=100.00
min=43.00
average=77.65
average函数与外界的联系
ave score 10 Max Min
aver array n Max Min
全局变量
Max Min
主函数
average函数
例 8.16外部变量与局部变量同名
1,int a=3,b=5;
2,max(int a,int b)
3,{int c;
4,c=a>b?a:b;
5,return(c);
6,}
7,main( )
8,{int a=8;
9,printf("%d",max(a,b));
10.}
11.运行结果,8
局部 a
全局 b
局部 a
局部 b
全局 a
全局 b
变量的存储类别
变量不仅有数据类型,还有存储类别
变量的存储类别确定了变量的存储方式、
生命周期、和作用域动态存储方式与静态存储方式
全局、局部变量是从变量的作用域来分
静态、动态存储方式是从变量的值存在的时间来分
静态存储方式是指在程序运行期间分配固定的存储空间的方式
动态存储方式是指在程序运行期间根据需要进行动态的分配存储空间的方式变量的生存期与作用域省略(默认为 auto)
自动型( auto)
寄存器型 (register)
静态型 (static)
变量 作 用 域 允许的存储类型 生 存 期外部变量内部变量定义点到程序结束定义该变量的函数或复合语句内部省略 (无存储类型静态型 (static) 全局变量局部变量全局变量
auto变量
main( )
{auto int x=1;
{auto int x=2;
{int x=3;
printf(“%d,”,x+x+x);
}
printf(“%d,”,x+x+x);
}
printf(“%d\n”,x);
}
运行结果:9,6,1
用 static声明变量
例 8.17考察静态局部变量的值
1,f(int a)
2,{auto int b=0;
3,static c=3;
4,b=b+1;
5,c=c+1;
6,return(a+b+c);
7,}
8,main( )
9,{int a=2,i;
10,for(i=0;i<3;i++)
11,printf("%d",f(a));
12.}
运行结果,7 8 9
a+b+c
2+1+4
2+1+5
2+1+6
返回值对静态变量的说明
静态局部变量在程序整个运行期间都不释放
静态局部变量是在编译时赋初值,只赋初值一次
未赋初值的静态局部变量自动取初值0
register 变量
寄存器的存储速度远高于内存的存储速度
一般用与循环变量用 extern声明外部变量
外部变量就是全局变量,有时需要用 extern声明外部变量,以扩展外部变量的作用域
1,在一个文件内声明外部文件
例 8.20用 extern声明外部变量,扩展作用域
1,int max(int x,int y)
2,{int z;
3,z=x>y?x:y;
4,return(z);
5,}
6,main( )
7,{extern A,B; /*外部变量声明 */
8,printf("%d",max(A,B));
9,}
10.int A=13,B=-8; /*定义外部变量 */
2,在多文件的程序中声明外部变量
一个 c程序可以由一个或多个源程序文件组成
允许在一个文件中定义的外部变量被另一个文件引用
例 8.21用 extern将外部变量的作用域扩展到其他文件
文件 file2.c中的内容,
1,extern A; /*声明 A为已定义的外部变量 */
2,power(int n);
3,{int i,y=1;
4,for(i=1;i<=n;i++)
5,y*=A;
6,return(y);}
文件 file1.c中的内容,
1,int A; /*定义外部变量 */
2,main( )
3,{int power(int);
4,int b=3,c,d,m;
5,print("enter the number a and its power m:\n”);
6,scanf("%d,%d",&A,&m);
7,c=A*b;
8,printf("%d*%d=%d\n",A,b,c);
9,d=power(m);
10,printf("%d**%d=%d",A,m,d);}
用 static声明外部变量
,static”限定外部变量只用于本文件
例,静态外部变量
1,file1.c file2.c
2,static int A; extern [int] A; 错 !!
3,main( ) fun(int n)
4,{… {…
5,} A=A*n;
6,…}
声明与定义
函数的声明是函数的原型
函数的定义是函数本身
变量的声明不建立存储空间,在同一文件中外部变量的声明可以有多次
变量的定义建立存储空间,在同一文件中外部变量的定义只能有一次存储类别小结
静态内部、静态外部整型变量:
static int a;
自动变量,在函数内部定义:
auto char c;
寄存器变量,在函数内定义:
register int d;
声明外部变量,
extern [int]b;
内部函数和外部函数
函数本质上是全局性的,可以被另外的函数调用。也有权指定不允许其他文件调用
1,外部函数
[extern] int fun(int a,int b)
2,内部函数只能本文件中其他函数调用
static int fun(int a,int b)
例 8.22 有一个字符串,内有若干个字符,今输入一个字符,要求程序将字符串中该字符删去。
file1.c( 文件1 )
1,main( )
2,{extern enter_string(char str[80]);
3,extern delete_string(char str[ ],char ch);
4,extern print_string(char str[ ]);
5,char c;
6,char str[80];
7,enter_string(str);
8,scanf("%c",&c);
9,delete_string(str,c);
10,print_string(str);
11.}
file2.c(文件 2)
1,#include <stdio.h>
2,enter_string(char str[80])
3,{gets(str);}
file3.c(文件 3)
1,delete_string(char str[ ],char ch)
2,{int i,j;
3,for(i=j=0;str[i]!='\0';i++)
4,if(str[i]!=ch)
5,str[j++]=str[i];
6,str[j]='\0';
7,}
file4.c(文件 4)
1,print_string(char str[ ])
2,{
3,printf("%s",str);
4,}
a b c d e f g c
a b d e f g \0
\0
运行情况:
入,abcdefgc
入,c
出,abdefg
str
str
运行一个多文件程序
用 turbo C集成环境
1,编辑 4个文件,分别取名,file1.c,file2.c、
file3.c,file4.c
2,在编辑状态建立一个“项目文件”取名,a.prj
(*.prj)其内容为,file1 file2 file3 file4
3,在 Turbo c主菜单中选
Project?Project name
给出项目文件名,a.prj
4,按键 F9 进行编译连接产生 a.exe文件
5,按 Ctrl+F9键运行可执行文件
用 #include 命令将 file2.c,file3.c、包含到 file.c中在 file1.c中的开头加3行,
#include file2.c
#include file3.c
#include file4.c
在编译时,系统自动将这3个文件放到
main函数的前头,作为一个整体编译。