C程序设计
第八章 函数
前言
① 什么是函数?
②为什么要使用函数?
③函数有哪些类型?
④如何自己定义一个
函数?
⑤如何调用一个函数?
⑥函数学习的难点是
什么?
什么是函数?
一个独立的程序模块,
可以定义自己的变量
(仅在本函数内有效),
拥有自己的存储空间。
可以被其他函数或自身
调用(主函数除外)。
① 什么是函数?
②为什么要使用函数?
③函数有哪些类型?
④如何自己定义一个
函数?
⑤如何调用一个函数?
⑥函数学习的难点是
什么?
为什么要使用函数?
?便于实现模块化设计
?便于团队开发
?便于使用现有的或别人的
程序模块提高编程效能
在 C程序设计中,通常:
? 将一个大程序分成几个子
程序模块(自定义函数)
? 将常用功能做成标准模块
(标准函数)放在函数库
中供其他程序调用
如果把编程比做制造一台
机器,函数就好比其零部
件。
?可将这些, 零部件, 单独
设计、调试、测试好,用
时拿出来装配,再总体调
试。
?这些, 零部件, 可以是自
己设计制造 /别人设计制造
/现在的标准产品
前言
① 什么是函数?
②为什么要使用函数?
③函数有哪些类型?
④如何自己定义一个
函数?
⑤如何调用一个函数?
⑥函数学习的难点是
什么?
函数有哪些类型?
根据函数的来源,可分为:
?库函数(标准函数)
由系统提供,编程时可直接
使用之
?自定义函数
由编程者自己编写,使用时
要, 先定义后使用,
根据使用的方式,可分为:
?无参函数
?有参函数 (函数内需要使
用主调函数中的数据)
前言
① 什么是函数?
②为什么要使用函数?
③函数有哪些类型?
④如何自己定义一个
函数?
⑤如何调用一个函数?
⑥函数学习的难点是
什么?
? 如何自己定义一个函数?
(见后)
? 如何调用一个函数?
(见后)
? 函数部分学习的难点是什么?
■ 函数的概念
■ 形参 /实参 /返回值的概念
■ 递归算法
■ 变量的作用域和生存期
前言
8.1、概述
函数使用常识,P144
? 一个源文件由一个或多个函数组成,可为多个 C程序公
用。
? C语言是以源文件为单位而不以函数为单位进行编译的。
? 一个 C程序由一个或多个源(程序)文件组成 ——可分
别编写、编译和调试。
? C程序执行总是从 main函数开始,一般情况下调用其
它函数后总是回到 main函数,最后在 main函数中结束
整个程序的运行。
? 所有函数都是平行的、互相独立的,即在一个函数内
只能调用其他函数,不能再定义一个函数(嵌套定
义)。
? 一个函数可以调用其他函数或其本身,但任何函数均
不可调用 main函数。
8.2 函数定义的一般形式
1,无参函数的定义形式
类型标识符 函数名 ( )
{ 声明部分
语句 }
2,有参函数定义的一般形式
函数返回值的数据类型 函数名 (类型名 变量名 1,类型名
变量名 2,…… )
{ 声明部分
语句 }
* 如果在定义时不知指定函数类型,系统会隐含指定函数类型
为 int型,
3.无函数体的为“空函数”
类型标识符 函数名 ( )
{ }
#include <stdio.h>
#include <conio.h>
main( ) /*主调函数 */
{
int a,b,c;
int max(int,int); /*函数原型
*/
clrscr( );
printstar(); /*函数原型 */
scanf("%d,%d",&a,&b);
c=max(a,b);
printf("max=%d\n",c);
}
void printstar( ) /*被调函数 */
{
printf(“请输入两个整数,");
}
int max(int x,int y) /*被调函数 */
{
int z;
if (x>y)
z=x;
else
z=y;
return z;
}
例一 参见 P5例 1.3
8.3 函数参数和函数的值
8.3.1 形式参数和实际参数 P146
1.形式参数和实际参数 的概念 P146
2.关于形参与实参的说明 P147
8.3.2 函数的返回值 P147
说明,1)函数的返回值通过函数中的 return获得,
2)函数值的类型,
3)如果函数值类型与 return语句表达式值的类型不一
致,以函数类型为准(数值型会自动进行类型转换 ).
4)如果明确表示不需返回值,应使用 void作函数返回
值的数据类型,否则即使没有 return语句,仍将带回一个
不确定的值
main( ) /*主调函数 */
{ int a,b,c;
scanf("%d,%d",&a,&b);
c=max(a,b);
printf("max=%d\n",c);
}
max(float x,float y)
{ float z;
z=x>y? x, y ;
return z;
}
运行情况,输入,
1.5,2.5 max=2
8.4 函数的调用
8.4.1 函数调用的一般形式
函数名 (实参列表 )
如果调用无参函数,则, 实参列表, 可省,但括号不能省,
实参与形参个数相等,类型一致,实参与形参按顺序对应,一一传
递数据,如果实参表列包括多个实参,对实参求值的顺序不确定,
许多 C版本是按自右而左的顺序求值,
例二, main()
{int i=2,p;
p=f(i,i++);
printf(“%d”,p);
}
int f(int a,int b)
{ int c;
if(a>b) c=1;
else if(a= =b) c=0;
else c= -1;
return (c);}结果, 1
8.4.2 函数的调用方式 P151
1.函数语句 (把函数调用作为一个语句 )
如例一中的 printstar(); 无返回值,只要求函数完成
一定的操作,
2.函数表达式 (要求函数带回一个确定的值以参加表达
式的运算 )
如,c=2 * max(a,b);
3.函数参数 (函数调用作为一个函数的实参 )
如,m=max(a,max(b,c));
8.4.3 对被调用函数的声明和函数原型
1.库函数的调用
必须在源程序中用 include命令将定义该库函数
的头文件, 包含进来, 。
如,# include,math.h”
调用方式:
◆独立语句 执行某项操作,如
clrscr( ),printf(“Input a,b=”) 等
◆表达式中 作运算对象,如
a=sqrt(x)+pow(r,3),c=exp(a) 等
2、自定义函数
自定义函数和变量一样,在其主
调函数中也必须, 先声明,后使
用, 。
如右例中的
float add(float,float);
右例中的自定义函数声明也可以
用以下两种形式,P153
float add(float x,float y);
(多余,因为编译系统并不检查
参数名,因此参数名是什么无所
谓,若把参数名 x,y换成 a,b效果
完全相同 )
或 float add( );
(编译系统将不检查参数类型和
参数个数,不提倡用,检查不全面)
main( )
{
float add(float,float);
/*对被调函数的声明 */
float a,b,c;
scanf("%f,%f",&a,&b);
c=add(a,b);
printf(“sum=%f\n",c);
}
float add(float x,float y)
{ float z;
z=x + y ;
return z;
}
运行情况,输入,
3.6,6.5
sum=10.100000
以下情况时,被调函数在主调函数中可以不先声
明,P154
?被调函数的返回值为整型时函数值是整型
( int)或字符型( char)时 ——系统自动按
整型说明 ;为了程序清晰和安全,建议都加以
声明为好,
?被调函数的定义出现在主调函数之前时,
?在所有函数定义之前,在函数的外部已做了
函数声明时,
调用方式同库函数。
8.5 函数的嵌套调用
float f(float x)
{

}
float xpoint(float x1,float x2)
{
y=… 调用 f(x1),f(x2)
}
float root(float x1,float x2);
{
y1=f(x1)
x=xpoint(x1,x2)
y=f(x)
}
main()
{

f1=f(x1);
f2=f(x2);

x=root(x1,x2);

}
例 8.6嵌套调用关系(简化)
1、递归的概念 P158
?直接递归调用 调用函数的过程中又调用该函数本身
?间接递归调用 调用 f1函数的过程中调用 f2函数,而 f2中
又需要调用 f1。
以上均为无终止递归调用。
为此,一般要用 if语句来控制使递归过程到某一条件满足时
结束。
8.6 函数的递归调用
2,递归算法
类似于数学证明中的反推法,从后一结果与前一结
果的关系中寻找其规律性。
归纳法可以分为:
?递推法 从初值出发,归纳出新值与旧值间直到最后值
为止存在的关系。
要求通过分析得到,初值 +递推公式
编程:通过循环控制结构实现(循环的终值是最后
值)
?递归法 从结果出发,归纳出后一结果与前一结果直到
初值为止存在的关系。
要求通过分析得到,初值 +递归函数
编程:设计一个函数(递归函数),这个函数不断
使用下一级值调用自身,直到结果已知处 —— 选择控制
结构。
【 例一 】 ( P160例 8.8)用递归法求 n!
分析比较:
实际上,递归程序分两个阶段执行 ——
① 回推(调用):欲求 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;
}
【 例二 】 有 5个人,第 5个人说他比第 4个
人大 2岁,第 4个人说他比第 3个人大 2岁,
第 3个人说他比第 2个人大 2岁,第 2个人说
他比第 1个人大 2岁,第 1个人说他 10岁。
求第 5个人多少岁。 ( P158例 8.7)
通过分析,设计递归函数如下:
10 (n=1)
age(n)=
age(n-1)+2 (n>1)
递归函数:
10 (n=1)
age(n)=
age(n-1)+2 (n>1)
age(int n)
{
int c;
if (n==1) c=10;
else c=age(n-1)+2;
return c;
}
main()
{
clrscr( );
printf("%d",age(5));
}
程序如下:
请看看单步运行的情况 ……
age(5)
c=age(4)+2;
return c;
age(int n)
{
int c;
if (n==1) c=10;
else c=age(n-1)+2;
return c;
}
递归过程
跟踪分析:
P159图 8.11
P160图 8.12
age(4)
c=age(3)+2;
return c;
age(3)
c=age(2)+2;
return c;
age(2)
c=age(1)+2;
return c;
age(1)
c=10
return c;
c=10
c=12c=14c=16c=18
1、用数组元素作函数实参 P164
此时可把数组元素看作普通变量 (单向的值传送 )。
? 特点:
主调函数中的实参 ——数组元素
(带下标)
被调函数中的形参 ——普通变量
? 调用结果:形参值的变化对实参值无影响
(二者分占不同内存)。
8.7 数组作为函数参数
例一,
#include <math.h>
main()
{
int i;
float sum=0,x[10];
float mean(float,int);
clrscr();
for(i=0;i<10;i++)
{
printf("请输入 x[%d]=",i);
scanf("%f",&x[i]);
sum+=mean(x[i]);
}
printf("结果是,%f\n",sum);
}
在主函数中输入 10个
数值,并调用功能函数
求其正数的算术平方
根之和。
float mean(float t)
{ if (t>0)
return sqrt(t);
else
return 0.0;
}
2、用数组名作函数实参 P165
若 int a[6]; 则数组名 a表示数组的起
始地址。
所以用数组名,实际上是把实参数组的起始地
址, 传给, 形参数组。
? 本质:对应的数组元素(不论形参与实参) 共享
同一段内存单元(所谓, 双向的地址传送, )。
? 特点:
主调函数中的实参 ——数组名(不带下标)
被调函数中的形参 ——数组名或数组定义式
? 调用结果:两数组同下标者为同值。
float aver(float array[10])
{
int i;
float a,sum=array[0];
for( i=1;i<10;i++ )
sum=sum+array[i];
a=sum/10;
return(a);
}
main()
{ int i;
float score[10],a;
printf(“请输入十个成绩,\n”);
for( i=1;i<10;i++ )
scanf(“%f”,&score[i]);
a=aver(score);
printf(“平均成绩是 %5.2f”,a);
}
例二,有一个一维数组 score,内放 10个学生成绩,求平
均值, P165
【 例二 】 以下程序的运行结果是什么?注意 P165
1,形参数组和实参数组应分别
在各自函数中定义;
2,形参数组可不定义大小(用
空方格);
3.实参数组与形参数组类型应
一致,
4,二者大小可一致或不一致
( C编译程序不检查形参),
但注意引用形参时不要超过
实参界。
5.数组名做函数参数时,不是把
数组元素的值传给形参,而是
把实参数组的起始地址传递
给形参数组,这样两个数组就
共占同一段内存单元, 结果,1 2 3 4 4
void fun (int b[ ],int n)
{
int i,max=4;
for (i=0;i<n;i++)
if(b[i]>max)
b[i]=max;
}
main()
{ int i,a[5]={1,2,3,5,6};
fun(a,5);
for(i=0;i<5;i++)
printf("%d ",a[i]);
}
8.8 局部变量和全局变量 P168
1,不同函数中可以使用相同名字的变量,他们代表
不同的变量,互不干扰,
2,所有形参都是局部变量,
3,局部变量只在本函数或本复合语句内才能使用,
在此之外不能使用(视为不存在) —— main函数
也不例外。
8.8.1、局部变量 —— 函数内部或复合语句内定
义的变量
例,
#include <stdio.h>
main()
{
int a=3,b=2,c=1;
{
int b=5,c=12;
c-=b*2;
printf("a=%d,b=%d,c=%d\n",a,b,c);
a+=c;
}
printf("a=%d,b=%d,c=%d\n",a,b,c);
}
【 结果 】
a=3,b=5,c=2
a=5,b=2,c=1
8.8.2、全局变量 — 在函数之外定义的变量
?有效作用范围:从定义变量
位置开始直到本源文件结束
如, P170
?如果需要将全局变量的作用
范围扩展至整个源文件 ——
法 1 全部在源文件开头处
定义
法 2 在引用函数内,用
extern说明
法 3 在源文件开头处,用
extern说明
【 例三 】 求程序运行结果
extern int x,y;
main( )
{
clrscr();
printf("x=%d,y=%d\n",x,y);
}
int x=100,y=200;
结果,x=100,y=200
去掉第一行试
试 ……
■ 如果在同一个源文件中,外部变量与局部变量同
名,则在局部变量的作用范围内,外部变量被, 屏
蔽,,即它不起作用,P172
int a=3,b=5;
max(int a,int b)
{
int c;
c=a>b?a:b;
return c;
}
main()
{
int a=8;
printf("%d\n",max(a,b));
}
如果主函数中没有 int
a=8,结果?
【 结果 】 5
如果让主函数中 int a=4
或 a=-1,结果?
【 结果 】 均为 5
【 结果 】 8
8.9 变量的存储类别 P172
1,动态存储方式与静态存储方式 (按生存期分 )
概念 P172
2,变量的生存期
静态存储区中的变量 与程序, 共存亡,
动态存储区中的变量 与函数, 共存亡
寄存器中的变量 同动态存储区
全局变量全部存放在静态存储区中,
形参属于动态存储,
3.变量两大属性,〃 数据类型 〃 存储类别
一个完整的变量说明格式如下:
存储类别 数据类型 变量名
如 static int x,y ;
C程序的存储类别有:
■ register型(寄存器型)
■ auto型(自动变量型)
■ static型(静态变量型)
■ extern型(外部变量型)
static如不赋初值,取初值为 0(数值型)或空格(字符型)
■ register型(寄存器型 )
变量值存放在运算器的寄存器
中 —— 存取速度快,一般只允
许 2~ 3个,且限于 char型和
int型,通常用于循环变量
(在微机的 Turbo C中实际上
自动转为 auto型)。
■ auto型(自动变量型)
变量值存放在主存储器的动态
存储区(堆栈方式);
优点 —— 同一内存区可被不同
变量反复使用。
以上两种变量均属于, 动态存
储型,,即调用函数时才为这
些变量分配单元,函数调用结
束其值自动消失。
■ static型(静态变量型)
变量值存放在主存储器的静态
存储区
程序执行开始至结束,始终占
用该存储空间
■ extern型(外部变量型)
同上,其值可供其他源文件使

以上两种均属于, 静态存储,
性质,即从变量定义处开始,
在整个程序执行期间其值都存
在,
未说明存储类别时,
函数内定义的变量默认为 auto型
函数外定义的变量默认为 extern型。
main()
{
int a=2,i;
clrscr( );
for (i=0;i<3;i++)
printf("%4d",f(a));
}
f(int a)
{
int b=0;
static int c=3;
b++;c++;
return a+b+c;
}
P173【 例一 】 求程序运行结果
变量跟踪
main( ) f函数
a i b c f(a)
2 0 0→1 4 7
1 0→1 5 8
2 0→1 6 9
【 结果 】 7 8 9
如果去掉 static呢?
【 结果 】 7 7 7
int a;
fun(int i)
{
a+=2*i;
return a;
}
main()
{
int a=10;
clrscr( );
printf("%d,%d\n",fun(a),a);
}
【 例二 】 求程序运行结果
【 结果 】 20,10
所有全局变量加不加
static,都属于静态
存储,如不赋初值,
取初值为 0(数值型)
或 空格 (字符型 )
void num()
{ extern int x,y;
int a=15,b=10;
x=a-b;
y=a+b;
}
int x,y;
main()
{ int a=7,b=5;
x=a+b;
y=a-b;
num();
printf("%d,%d\n",x,y);
}
【 例三 】 求程序运行结果
如果第二行不加上 extern呢?
【 结果 】 5,25
【 结果 】 12,2
main()
{
int k=4,m=1,p;
clrscr();
p=func(k,m);
printf("%d,",p);
p=func(k,m);
printf("%d",p);
}
func(int a,int b)
{ static int m=0,i=2;
i+=m+1;
m=i+a+b;
return m;
}
【 例四 】 求程序运行结果
变量跟踪
main( ) func函数
k m a b m i
4 1 4 1 0→8 2→3
4 1 4 1 8→17 3→12
【 结果 】 8,17
如果去掉 static呢?
【 结果 】 8,8
1、以下程序运行结果是
fun(int x)
{
static int a=3;
a+=x;
return a;
}
main( )
{
int k=2,m=1,n;
n=fun(k);
n=fun(m);
printf("%d\n",n);
}


2,以下程序运行结果?
main()
{ int x;
x=fun(4);
printf("%d\n",x);
} fun(int n)
{ int s;
if((n==1)||(n==2))
s=2;
else
s=n+fun(n-1);
return s;
}
3, 以下程序运行结果?
main( )
{ int i=5;
printf("%d\n",sub(i));
}
sub(int n)
{ int a;
if (n==1) return 1;
a=n+sub(n-1);
return a;
}
4, 以下程序运行结果?
main( )
{ int a=2,i,k;
for(i=0; i<2; i++)
k=f(a++);
printf("%d\n",k);
}
f(int b)
{ static int y=3;
return (b+y++);
}