C语言教程,函数
学习目的:函数是 C语言程序设计的
基本形式,主要介绍的是自定义函
数。通过讲解,使学生熟悉函数的
一般形式,函数的参数使用,通过
剖析程序,使学生能够正确分析出
函数程序的功能。
函数内容介绍
1 函数概述
2 函数的定义和声明
3 函数的调用、函数参数和返回值
4 嵌套和递归调用
5 数组作函数参数
6 变量的作用域
7 变量的存储类别
8 内部函数和外部函数
C语言称为函数式语言
源程序文件1
预处理命令 全局变量声明
函数首部
局部变量声明 执行语句
函数体
函数 1 函数 n
源程序文件2 源程序文件n
C程 序
函数模块式的结构的好处,
言易于实现结构化程序设计、使程序的
层次结构清晰、便于程序的编写、阅读、
调试。
函数分类,
从函数定义来看,函数分为库函数和用户定义函数。
从函数返回值来看,函数分为有返回值函数和无返回值函数。
从函数和函数之间数据传送来度看,函数分为无参函数和有参函数。
强调,
1、所有的函数定义,包括主函数 main在内,都是平行的。但是函数之间允
许相互调用,也允许嵌套调用。习惯上把调用者称为主调函数。函数还
可以自己调用自己,称为递归调用,如 int f(){……f()……} 。
2,main 函数是主函数,它可以调用其它函数,而不允许被其它函数调用。
3、一个C程序必须有,也只能有一个主函数 main。
函数定义的一般形式
1,无参函数的定义形式
类型标识符 函数名 ()
{声明部分
语句 }
无参数,但括号不可少。
在很多情况下都不要求无参函数有返回值,此时函数类型符可以 写为 void。
2,有参函数定义的一般形式
类型标识符 函数名 (形式参数表列 )
{声明部分
语句 }
形参既然是变量,必须在形参表中给出形参的类型说明。
例如,定义一个函数,用于求两个数中的大数,可写为,
int max(int a,int b)
{ if (a>b) return a;
else return b; }
一个函数的定义可以放在任意位置,既可放在主函数 main之前,
也可放在 main之后。
关于函数的说明或声明:在所有函数之前外部或在函数调用之前。
【 例 】
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);
}
函数的参数和函数的值
1、形式参数和实际参数
要点,
1) 形参变量只有在被调用时才分配内存单元,在调
用结束时,即刻释放所分配的内存单元。因此,形参
只有在函数内部有效。函数调用结束返回主调函数后
则不能再使用该形参变量。
2) 实参可以是常量、变量、表达式、函数等
3) 实参和形参在数量上,类型上,顺序上应严格一
致。
4) 函数调用中发生的数据传送是单向的。
【 例 】 可以说明这个问题。
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);
} //说明:函数 s的功能是求 ∑ni的值。
函数的返回值
函数的值是指函数被调用之后,执行函数体中的程序段所取得的
并返回给主调函数的值。
说明,
1) 函数的值只能通过 return语句返回主调函数。
return 语句的一般形式为,
return 表达式;
或者为,
return (表达式 );
该语句的功能是计算表达式的值,并返回给主调函数。在函数中 允许有多个 return语句,但每次调用只能有一个 return 语句被执行,
因此只能返回一个函数值。
执行 return后,停止函数的执行、即结束函数
2) 函数值的类型和函数定义中函数的类型应保持一致。
如果两者不一致,则以函数类型为准,自动进行类型
转换。
int f(float x){return x/2;}
main(){printf(“%d”,f(3.6));}
3) 如函数值为整型,在函数定义时可以省去类型说明。
main(){}的前面省略了 int。
实际上等价于 int main(){}
4) 不返回函数值的函数,可以明确定义为“空类型”,
类型说明符为,void”。如上例函数 s并不向主函数返函
数值,因此可定义为,
void s(int n) { …… }
注意:此时必须在 main函数中添加对该函数的声明。
一旦函数被定义为空类型后,就不能在主调函数中使
用被调函数的函数值了。例如,在定义 s为空类型后,
在主函数中写后述语句,
sum=s(n);就是错误的。
函数的调用
函数调用的一般形式, 函数名 (实际参数表 )
函数调用的方式,用以下几种方式调用函数。
1.函数表达式:例如,z=max(x,y)是一个赋值表达式,
把 max的返回值赋予变量 z。
2.函数语句:例如,printf ("%d",a);scanf ("%d",&b);
3.函数实参:函数作为另一个函数调用的实际参数出现。
例如,printf("%d",max(x,y));
再次强调,在函数调用中还应该注意的一个问题是实
际参数的求值顺序的问题。所谓求值顺序是指对实参
表中各参数是自左至右使用呢,还是 自右至左 使用。
【 例 】
main()
{
int i=8;
printf("%d\n%d\n%d\n%d\n",++i,--i,i++,i--);
}
如按照从右至左的顺序求值。运行结果应为,
8
7
7
8
如对 printf语句中的 ++i,--i,i++,i--从左至右求值,结果应为,
9
8
8
9
Turbo C现定是自右至左求值,所以结果为 8,7,7,8。
被调用函数的声明和函数原型
函数声明的目的:在主调函数中调用某函数之前应对该被调函数
进行说明(声明),目的是使编译系统在主调函数中作相应的处 理。
其一般形式为,
类型说明符 被调函数名 (类型 形参,类型 形参 …) ;
或为,
类型说明符 被调函数名 (类型,类型 …) ;
前面例子中 main函数中对 max函数的说明为,
int max(int a,int b);
或写为,
int max(int,int);
声明函数的形式称为 函数原型
可以省略声明的几种情况,
1)如果被调函数的返回值是整型或字符型时,可以不对被调函数
作说明,而直接调用。 2)当被调函数的函数定义出现在主调函数
之前时。 3)如在所有函数定义之前,在函数外预先说明了各个函
数的类型,例如,
char str(int a);
float f(float b);
main()
{ …… }
char str(int a)
{ …… }
float f(float b)
{ …… }
4)对库函数的调用不需要再作说明,但必须把该函数的头文件用
include命令包含在源文件前部。
函数的嵌套调用
嵌套调用的形式以及程序执行过程,
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;
}
【 例 】 计算 s=22!+32!,分析如下程序
main()
{ int i;
long s=0;
for (i=2;i<=3;i++)
s=s+f1(i);
printf("\ns=%ld\n",s);
}
函数的递归调用
一个函数在它的函数体内调用它自身称为递归调用。例如有函数 f
如下,
int f(int x)
{
int y;
z=f(y);
return z;
}
这个函数是一个递归函数。
上面函数的问题:运行该函数将无休止地调用其自身,这当然是
不正确的。为了防止递归调用无终止地进行,必须在函数内有终
止递归调用的手段。常用的办法是加条件判断,满足某种条件后 就不再作递归调用,然后逐层返回。
举例说明递归调用的执行过程。
【 例 】 用递归法计算 n!,分析递归程序的执行过程!
用递归法计算 n!可用下述公式表示,n!=1 (n=0,1)
n× (n-1)! (n>1)
按公式可编程如下,
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(“\input a number:\n");
scanf(“%d”,&n); //输入 5
y=ff(n);
printf("%d!=%ld",n,y);
}
上例也可以不用递归的方法来完成。如可以用递推法。分析如下程
序,
main()
{
int i,n;long y=1;
printf("n=");
scanf("%d",&n);
for (i=1;i<=n;i++) y=y*i;
printf("%d!=%ld\n",n,y);
}
递归法的效率比递推法的效率要低。
有些问题用递归法更容易解决问题、更容易理解。
典型的问题是 Hanoi塔问题。 (自已查资料 )
数组作为函数参数
有以下程序
void f(int a[],int i,int j)
{ int t;
if(i<j)
{ t=a[i]; a[i]=a[j];a[j]=t;
f(a,i+1,j-1);
}
}
main( )
{ int i,aa[5]={1,2,3,4,5};
f(aa,0,4);
for(i=0;i<5;i++) printf("%d,",aa[i]); printf("\n");
}
执行后输出结果是()
A)5,4,3,2,1,B)5,2,3,4,1,C)1,2,3,4,5,D)1,2,3,4,5,
数组用作函数参数有两种形式:一种是把数组元素 (下标变量 )作为实参使用,单向值传递;另
一种是把数组名作为函数的形参和实参,地址传递。
数组名作为函数参数
实质:依然是实参到形参的单向值传递;只
不过传递的是数组的首地址
形参数组名实际上是一个存放数组的地址指
针变量
通过传址可以达到间接的双向值传递
【 例 】 数组 a中存放了一个学生 5门课程的成绩,求平均成绩。
void main()
{ float aver(float a[5]); 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);
}
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;
}
用数组名作为函数参数时还应注
意以下几点,
a,形参数组和实参数组的类型必须一致,否则将引起错误。
b,形参数组和实参数组的长度可以不相同,因为在调用时,只传送首地址而不
检查形参数组的长度。当形参数组的长度与实参数组不一致时,虽不至于出现语
法错误 (编译能通过 ),但程序执行结果将与实际不符,这是应予以注意的。
c,在函数形参表中,允许不给出形参数组的长度,或用一个变量来表示数组元
素的个数。
例如,可以写为,
void nzp(int a[])
或写为
void nzp(int a[],int n)
其中形参数组 a没有给出长度,而由 n值动态地表示数组的长度。 n的值由主调函数
的实参进行传送。
d,多维数组也可以作为函数的参数。在函数定义时对形参数组可以指定每一维
的长度,也可省去第一维的长度。因此,以下写法都是合法的。
int MA(int a[3][10])

int MA(int a[][10])。
1、局部变量也称为内部
变量,意思是说位于,
作用于某一个函数内部,
即在函数内作定
义说明,其作用域仅限于
函数内,离开该
函数后再使用这种变量是
非法的。
2、形参变量是局部变量
例如,
int f1(int a) /*函数 f1*/
{ int b,c;
f2(b);
…… /*a,b,c 有效 */
}
int f2(int x) /*函数 f2*/
{ int y,z;
…… /*x,y,z 有效 */
}
main()
{ int m,n;
f(m)
…… /*m,n 有效 */
}
局部变量
关于局部变量的作用域还要说明以下几点,
1) 允许在不同的函数中使用
相同的变量名,它们代表不
同的对象,分配不同的单元,
互不干扰,也不会发生混淆。
此时在嵌套调用时,被调用
函数中的同名变量屏蔽主调
函数中的同名变量。
2) 在复合语句中也可定义变
量,其作用域只在复合语句
范围内。若在所嵌函数中有
同名变量则屏蔽之。
s,a,b




b



例如,
main( )
{int s,a,b;
……
{ int b;
s=a+b;
……
}
b; ……
}//两个 b的作用不同
内层 k的值是,8 ;外层 k的值是,5 。
【 例 】
main( )
{ int i=2,j=3,k;
k=i+j;
{
int k=8;
printf("%d\n",k);
}
printf("%d\n",k);
}
全局变量
全局变量也称为外部变
量,它是在函数外部定
义的变量。它不属于哪
一个函数,它属于一个
源程序文件。
作用域是整个源程序
(在定义之前的函数中
应作全局变量的声明,
否则作用域为从定义处
到源程序末)。
全局变量的说明符为
extern。
例如,
int a,b; /*外部变量 */
void f1( ) /*函数 f1*/
{ extern x,y;
x,y;……
}
float x,y; /*外部变量 */
int fz( ) /*函数 fz*/
{ x,y;…… }
main( ) /*主函数 */
{ …… }
【 例 】 输入正方体的长宽高 l,w,h。求体积及三个
面 x*y,x*z,y*z的面积 。
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
);
}
如果同一个源文件中,外部变量与局部变量同名,则在局部变量的
作用范围内,外部变量被“屏蔽”,即它不起作用。
【 例 】 外部变量与局部变量同名。
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));
}
变量的存储类别
动态存储方式与静态动态存储方式
从变量值存在的作时间(即生存期)角度来分,可以分为静态存储方式和动态存储方式。
静态存储方式:是指在程序运行期间分配固定的存储空间的方式。
动态存储方式:是在程序运行期间根据需要进行动态的分配存储空间的方式。
用户存储空间可以分为三个部分,
1) 程序区;
2) 静态存储区;
3) 动态存储区;
全局变量全部存放在静态存储区,在程序开始执行时给全局变量分配存储区,程序行完毕就 释放。在程序执行过程中它们占据固定的存储单元,而不动态地进行分配和释放;
动态存储区存放以下数据,
1) 函数形式参数;
2) 自动变量(未加 static声明的局部变量);
3) 函数调用时的现场保护和返回地址;
对以上这些数据,在函数开始调用时分配动态存储空间,函数结束时释放这些空间。
在 c语言中,每个变量和函数有两个属性:数据类型和数据的存储类别。
auto变量
函数中的形参和在函数中定义的变量(包括在复合语句中定义的变量),
如不专门声明为 static存储类别,都是动态地分配存储空间的,数据存储
在动态存储区中,在调用该函数时系统会给它们分配存储空间,在函数
调用结束时就自动释放这些存储空间。
这类局部变量称为自动变量。自动变量用关键字 auto作存储类别的声明。
例如,
int f(int a) /*定义 f函数,a为参数 */
{auto int b,c=3; /*定义 b,c自动变量 */
……
}
a是形参,b,c是自动变量,对 c赋初值 3。执行完 f函数后,自动释放 a,b,
c所占的存储单元。
关键字 auto可以省略,auto不写则隐含定为“自动存储类别”,属于动态
存储方式。
用 static声明局部变量
值在函数调用结束后不消失而保留原值,这时就应该指定局部变量为
“静态局部变量”,用关键字 static进行声明。
【 例 】 考察静态局部变量的值。
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(对数值型变量)或
空字符(对字符变量)。
而对自动变量来说,如果
不赋初值则它的值是一个
不确定的值。
【 例 】 打印 1到 5的阶乘值。
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));
}
register变量
局部变量得值放在 CPU中的寄存器中,这种变
量叫“寄存器变量”,用关键字 register作声明。
【 例 】 register int i;
说明,
1) 只有局部自动变量和形式参数可以作为
寄存器变量;
2) 一个计算机系统中的寄存器数目有限,
不能定义任意多个寄存器变量;
3) 局部静态变量不能定义为寄存器变量。
即不允许有 static register int i;
用 extern声明外部变量
【 例 】 用 extern声明外部变量
int max(int x,int y)
{int z;
z=x>y?x:y;
return(z);
}
main()
{extern A,B;
printf("%d\n",max(A,B));
}
int A=13,B=-8;
我们在 main函
数中用 extern
对 A和 B进行
“外部变量声
明”,就可以
从“声明”处
起,合法地使
用该外部变量
A和 B。
内部函数和外部函数
内部函数
static 类型 函数名(形参表)如 static int fun(int a,int b)
内部函数只能被本文件中其他函数所调用
内部函数有称为静态函数
对于内部函数,若在不同的文件中有同名的情况也互不干扰
外部函数
定义形式 extern int fun(int a,int b)
外部函数可以供其他文件调用
若没有 static也没有 extern则默认为外部函数
在需要调用外部函数的文件中,需要用 extern声明所用的函数是外
部函数,也可以省略 extern。
main( ) //文件名 f1.c
{extern enter_string(char str[80]);
extern delete_string(char str[80],char ch);
extern print_string(char str[]);
char c;
char str[80];
enter_string(str);
scanf("%c",&c);
delete_string(str,c);
print_string(str);}
#include <stdio.h> //文件名 f2.c
enter_string(char str[80])
{ gets(str);}
delete_string(char str[],char ch)
{int i,j; //文件名 f3.c
for(i=j=0;str[i]!='\0';i++)
if(str[i]!=ch)
str[j++]=str[i];
str[j]='\0';
}
print_string(char str[])
{ //文件名 f4.c
printf("%s\n",str);
}