第六章 函数
教学目标
教学内容
小结教学目标
掌握函数的定义与应用。
掌握函数的传值调用与传址调用方式。
掌握主被调函数间的参数传递。
掌握命令行参数及其应用。
了解函数的递归调用。
教学内容
函数的定义和调用及返回值
数据在函数间的传递方式
数组名作实参
通过指针调用函数
带参数的主函数 (命令行参数 )
两类特殊函数什么是函数?
( 1)函数是程序的基本组成单元,是实现特定功能的模块。
( 2)一个 C语言程序是由一个 main()函数和若干个其它函数组成的。
( 3) C语言的函数包括标准库函数与用户自定义函数。
函数示例 1
float circle_area (int r)
{ float s;
s=.1416*r*r;
return(s);
}
main( )
{
float area;
int r;
printf("请输入圆的半径,");
scanf("%d",&r);
printf("\n area=%f\n",circle_area(r));
}
函数的定义与声明类型标识符 函数名(类型标识符 形参 1,类型标识符 形参
2,… )
{
函数体变量说明语句;
函数体可执行语句
}
类型标识符 函数名 ( 形式参数表 )
形式参数说明;
{
函数体变量说明语句;
函数体可执行语句
}
前面示例的方式该方式已过时函数的定义与声明
( 1)函数与变量一样,必须先定义后使用。
( 2)如果没有声明函数类型,则编译程序自动将整型作为函数的缺省类型。
( 3)函数声明中,末尾的分号不可缺少。
函数示例 2
#include <stdio.h>
float rectangle_area(float length,float width);
int main()
{
float length,width;
printf("请输入矩形的长与宽,");
scanf("%f,%f",&length,&width);
printf("rectangle area=%10.3f\n",rectangle_area(length,width));
return 0;
}
float rectangle_area(float length,float width)
{
float s;
s=length*width;
return s;
}
函数的调用方式假定我们定义了一个函数 int max(int a,int b);,那么函数的调用方式可以采用
( 1)作为表达式的一部分,例如:
a=max(x,y)*z+s/t;
( 2)作为一条独立的语句,例如:
max(x,y);
( 3)作为另一个函数的实参,例如:
printf(“Maximum =%d\n”,max(x,y));
函数的调用 -1
(1)函数调用遵循“先定义,后调用”的原则。即,
一般被调用函数应放在调用函数之前定义。
例如,/*被调函数 */
int fun1(int a2,int b2,int c2,….,)
{… … }
main( )
{…
fun1(a1,b1,c2,….,); /* 调用函数 */
}
函数的调用与函数声明 -1
若被调用函数和调用函数不在同一编译单位(即不在同一文件)中,调用前应先对函数进行声明。
方式 1:在调用函数前声明。例如:
int fun1(int a2,int b2,int c2,….,) ; /*函数声明 */
main( )
{…
fun1(a1,b1,c2,….,); /* 函数调用 */
}
int fun1(int a2,int b2,int c2,….,) /* 函数定义 */
{… …}
函数的调用与函数声明 -2
方式 2:在调用函数内声明。例如:
main( )
{ … …
int fun1(int a2,int b2,int c2,….,) ; /*函数声明 */
fun1(a1,b1,c2,….,); /*函数调用 */
}
int fun1(int a2,int b2,int c2,….,) /*函数定义 */
{ … …
}
函数的调用与函数声明 -3
若被调用函数的函数类型是整型或字符型,则均可省略上述函数声明。
方式 3:省略函数声明。例如:
main( )
{ … …
/*可省略函数声明 */
fun1(a1,b1,c2,….,); /*函数调用 */
}
int max(int a,int b)
{
if(a>b)
return a;
else
return b;
}
函数的返回语句
( 1)主调函数通过实参向形参传递数据。
( 2)被调函数计算结果通过 return语句传递回主调函数。函数 return语句的一般形式为:
return (变量或表达式 );
或,return 变量或表达式;
例如:
return x>y?x:y ;
return (x>y?x:y);
一个函数可以有多个 return语句,最终是哪个
return语句被执行将由控制流程决定。
函数的返回语句
( 3)若被调函数体内没有 return语句,就一直执行到函数体的末尾,然后返回调用函数。
这时有一个不确定的值被带回(无用)。
( 4)若确实不要求带回函数值,则应将被调函数定义为 void类型。函数的定义形式如下:
void 函数名 (参数表 )
( 5) return语句中表达式的类型应与函数值的类型一致,若不一致时,则以函数值的类型为准。
(6) 用 return语句只能返回一个值。
库函数调用
( 1)当源程序包含标准头文件,将可以使用标准库中提供的库函数了。
( 2)标准库包括,
标准输入输出库( stdio.h)
数学库( math.h)
字符串处理库( string.h)
动态内存分配库( malloc.h)
库函数调用 -示例
#include <stdio.h>
#include <math.h>
double cal(double r)
{
double d=cos(r*3.1415926535/180.0);
d = 2.0*d*d-1;
return d;
}
void main()
{
printf("cal(30.0) = %10.3f\n",cal(30.0));
}
函数的参数
double cal(double a,int m,int n)
{
… …
}
void test(void)
{
printf("Test void\n");
}
void main()
{
printf("cal(30.0) = %10.3f\n",cal(30.0,2,3));
test();
}
函数的参数
( 1)函数的参数声明是以逗号分割的参数列表例如,double cal(double a,int m,int n)
( 2)如果函数没有参数,则函数可定义为:
int show(); 或 int show(void);
( 3)函数的 return语句只能返回一个结果,而函数的参数,却容许主调函数与被调函数之间的数据,
采用多种方式进行交互。
数据在函数间传递数的方式数据在函数间传递数的方式有三种:
( 1)值传递 ---通过数据传递(单向)
( 2)地址传递 ---通过地址传递(双向)
( 3)全局传递 ---通过全局变量传递传值调用
( 1)传值调用时,系统先计算主调函数的实参值,
然后再将该值复制给对应的被调函数形参。
( 2)如果主调函数中传入的实参是常量、普通变量或数组元素,那么,主调函数实参与被调函数的形参之间的参数传递是“值”传递,函数的调用类型为传值调用。
传址调用
( 1)传址调用时,系统先计算主调函数实参变量的地址,复制到被调函数形参指针变量的内存单元中,即地址复制。
( 2)如果主调函数中传入的实参是:变量的地址或指针变量、数组名、字符串名等,那么,主调函数实参与被调函数形参之间的参数传递是“地址”
传递,函数的调用类型为传址调用。
传址调用
( 1)传址调用中,实参与形参的类型必须一致。
( 2)由于此时实参和形参使用同一内存单元,利用这种地址值的传送,能达到改变实参变量内容的目的。
( 3)传址传递是一种双向传递方式,即通过形参将被调用函数中形参值的变化传递回原调用处,
以改变实参的值,从而实现返回多值的目的。
传址调用-示例 1
例:交换两个整型参数。
void swap(int *p,int *q);
void main( )
{ int a=10,b=100;
swap(&a,&b);
printf(“%d%d\n”,a,b);
}
void swap(int *p,int *q)
{ int t;
t=*p;
*p=*q;
*q=t;
}
思考,1,返回了多少个值?
2,如果实参用指针,如何编写程序?
传址调用-示例 2
main( )
{
int b1[5]={1,2,3,4,5};
int b2[5]={3,1,2,4,6};
int b3[5]={3,5,2,4,1};
testArray(b1,b2,b3,2);
}
void testArray(int a[5],int b[ ],
int *c,int n)
{
int k;
for(k=0;k<5;k++){
*(a+k)*=n;
*(b+k)*=n;
*(c+k)*=n;
}
}
3种类型的形参,C编译程序都将形参处理成指针 。
传址调用-示例 3
#include <stdio.h>
int f(char s[ ],char t[ ]);
void main()
{//比较两个字符串 (即字符数组 )是否相等
char a[20],b[20];
int i;
scanf("%s%s",a,b);
i=f(a,b);
printf("%d\n",i);
}
int f(char s[ ],char t[ ])
{
int i=0;
while(s[i]==t[i] && s[i]!='\0') i++;
return ((s[i]=='\0' && t[i]=='\0')? 1,0);
}
二维数组名作实参例如,int x[3][4]; fun (x); 被调函数的形参形式可为:
( 1) void fun(int x[3][4]); //(数组大小可以不一致)
( 2) void fun(int x[ ][4]); //(只有行下标可以省略)
( 3) void fun(int (*num) [4]);
二维数组名作实参-示例 1
#include <stdio.h>
void fun1(int x[3][3]);
void fun2(int x[][3]);
void fun3(int (*x)[3]);
void main()
{ int x[3][3]={1,2,3,4,5,6,7,8,9},i,*p;
fun1(x);
fun2(x);
fun3(x);
p=x;
for(i=0;i<9;i++)
printf(" %d ",*(p+i));
}
void fun1( int x[3][3] )
{ x[1][0]=14; }
void fun2( int x[ ][3] )
{ x[1][1]=15; }
void fun3( int (*x)[3] )
{ *(*(x+1)+2)=16; }
三个函数,分别改变了数组中的哪几个函数?
二维数组名作实参-示例 2
#include <stdio.h>
#define N 30
void average(int (*ss)[4],float *p,int n)
main()
{int i,s[N][4];
float ave[N];
for(i=0;i<N;i++)
for(j=0;j<4;j++)
scanf(“%d”,&s[i][j]);
average(s,ave,N);
printf("平均分 = \n");
for(i=0;i<N;i++)
printf("ave[%d]= %5.2f\n",i,ave[i]);
}
void average(int (*ss)[4],float *p,
int n)
{
int i,j;
float sum;
for(i=0;i<n;i++)
{
sum=0.0;
for(j=0;j<4;j++)
sum=sum+*(*(ss+i)+j);
p[i]=sum/4;
}
}
例:现有 30个学生的四科考试成绩,求每个学生的平均成绩 。
字符串作为函数的参数字符串作为实参,相应的形参应是字符型一维数组或字符指针变量。
由于字符串常量被隐含处理成无名的字符型一维数组,传送的将是该字符串的首地址;
字符串变量实际就是字符型一维数组,所以,字符串作实参的情况与一维数组名作实参时相同函数指针
( 1) C程序是由函数组成的。每个函数的原代码在内存中占一连续的存储单元。其首地址称为函数的入口地址。
( 2)指针变量既然可以指向整型变量、字符串、数组、也可以指向函数,即可以存放函数的入口地址,
这种指针变量称为函数指针。
( 3)定义函数指针的一般形式是:
类型定义符 ( *指针变量) ( );
如,int (*fp)( ); /* fp为函数指针 */
这样函数的调用就有两种方式:
(1) 函数名调用 (2) 函数指针调用函数指针 -示例
#include <stdio.h>
int max(int x,int y); //声明函数
int (*fp)(int,int); //声明函数指针(习惯上不采用 (int x,int y))
void main()
{
fp=&max; //将 max函数的地址赋给函数指针变量 fp
printf(“Test max(2,3)=%d\n”,max(2,3)); //使用函数名调用函数
printf(“Test (*fp)(2,3)=%d\n”,(*fp)(2,3)); //使用函数指针调用函数
}
int max(int x,int y)
{
int z;
z=x>y?x:y;
return (z);
}
函数指针函数指针和其他指针的基本性质相同,其不同点是:
( 1)函数调用使用函数指针时,是将程序的控制流程转移到函数指针所指向的地址执行该函数体。
( 2)函数指针指向的是函数代码存储区,而不是通常的数据存储区。
( 3)函数指针变量的圆括号不能省略,否则编译系统将把函数指针编译成指针函数。
指针函数 -示例
#include <stdio.h>
int *max1(int *x,int *y); //声明指针函数
void main()
{
int x=2,y=3;
printf("Test **max1(&x,&y)=%d\n",*max1(&x,&y));//使用指针函数
}
int *max1(int *x,int *y)
{
int *z;
z=*x>*y?x:y;
return(z);
}
命令行参数 -1
在 DOS系统下,要把 file1复制为 file2,必须执行以下命令:
copy file1 file2
这种执行命令的方式称为命令行方式,以上的输入称为命令行。显然,命令行中必须要有表示命令的可执行文件名以及命令所需的参数(如果需要参数),
并以回车符作为输入命令的结束。其中:
copy file1 file2
然而前面介绍的由 C程序形成的可执行文件一般无命令行参数,如何通过命令行给 C程序传递参数呢?
可执行文件名参数命令行参数 -2
C编译程序为 main( )函数设置了两个特殊 的内部形参,argc和 argv。其形式如下:
void main(int argc char *argv[])
程序运行时系统首先检查命令行各参数,并对其进行初始化。
( 1) argc的值是命令行中可执行文件名和所有实参个数之和。
( 2) argv[ ]指针数组的各元素分别指向命令行中可执行文件名和各个实参的字符串。
命令行参数 -3
对应于以下 DOS命令:
copy file1 file2
argc=3:总共有 3个参数 (copy,file1,file2)
*argv[]:为一指针数组,其中每一个元素均为指针。各个指针元素分别指向:
argv[0]-----> copy
argv[1]-----> file1
argv[2]-----> file2
在 C语言程序中,可以引用这些变量。
命令行参数 -示例已知下列程序:
# include,stdio.h”
main(int argc,char *argv[ ])
{
int i;
for(i=0;i<argc;i++) printf(“%s\n”,argv[i]);
}
该程序的可执行文件名为 program.exe,运行时键入的命令行为,program.exe apple orange banana 则执行结果为:
A,PROGRAM B,PROGRAM.EXE
apple apple
orange orange
banana banana
C,apple D,p
orange a
banana o
b
递归调用
函数的嵌套调用即在调用一个函数的过程中可以再调用另一个函数。
函数的递归调用递归函数是指在函数体中有调用它自己的语句。
这种方法多用在求阶乘的运算中,由于存在自调用过程,程序控制将反复进入它的函数体,为了防止产生自调用过程无休止地继续下去,在函数内必须设置某种结束自调用过程的条件 (如,If语句)。
递归调用-示例
int factorial1(int n) //计算 n的阶乘
{
int m=1;
while(n>1)
m*=n--;
return m;
}
int factorial2(int n) { //递归方式计算 n的阶乘
if(n==0)
return 1;
else
return n*factorial2(n-1);
}
void main(int argc,char *argv[])
{ printf("计算 n的阶乘,factorial1(100) = %d\n",factorial1(10));
printf("递归方式计算 n的阶乘,factorial2(100) = %d\n",factorial2(10));
}
函数部分小结
1、函数定义及参数说明
2、函数调用及数据传递
3、命令行程序