高级程序设计语言吴 凡
TEL,83202682
E-mail,cdwf@tom.com
电子科技大学通信与信息工程学院第八章 函数电子科技大学通信与信息工程学院概述
模块化
一个较大的程序一般被划分为若干个程序模块,
每个模块来实现某一个特定的功能
C语言中,用函数来实现程序模块的功能
例,( P143例 8.1)
结果:
#include "stdio.h"
void printstar()
{
printf("****************\n");
}
void print_message()
{
printf("How do you do!\n");
}
main()
{
printstar();
print_message();
printstar();
}
***************
How do you do
***************
电子科技大学通信与信息工程学院概述
C程序执行的入口点是 main函数,并且程序的执行主体是在 main函数内
在 main函数中调用其它函数(子函数)完成某项功能,该函数执行完成后,任回到 main函数
程序在 main函数结束时结束。
所有的子函数的地位都是平等的,不能在子函数中定义函数电子科技大学通信与信息工程学院概述
C语言函数分类
从函数定义的角度:
标准函数(库函数):由系统提供,用户可以直接使用(在使用库函数的源文件中应首先 #include其对应的头文件)
用户定义函数:由用户定义的函数
从函数形式的角度:
无参函数,例,getchar();
有参函数,例,strcpy(str1,str2);
电子科技大学通信与信息工程学院函数体函数定义的一般形式
函数定义的一般形式:
类型说明 函数名( [形式参数说明 ])
{
定义局部变量声明外部变量、函数语句
}
形式参数说明的一般形式:
类型说明 变量 1 [,类型说明 变量 2,...]
int max(int x,int y)
{
int z;
z = x>y? x,y;
return(z);
}
声明部分电子科技大学通信与信息工程学院函数定义的一般形式
1,无参函数的定义形式类型说明 函数名 ()
{
声明部分语句
}
2,有参函数的定义形式类型说明 函数名 (形式参数说明 )
{
声明部分语句
}
3,空函数的定义形式类型说明 函数名 ([形式参数说明 ])
{
}
void print_message()
{
printf("How do you
do!\n");
}
int max(int x,int y)
{
int z;
z = x>y? x,y;
return(z);
}
int draw_circle()
{
/*draw a circle*/
}
电子科技大学通信与信息工程学院
int max(int x,int y)
{
int z;
z = x>y? x,y;
return(z);
}
一般情况下,主调函数与被调函数之间存在数据传递关系(通信关系)
main()
{
int a,b,g;
scanf("%d%d",&a,&b);
g = max(a,b);
printf("Max_num:%d\n",g);
}
函数的参数和函数的值
a bz
主调函数被调函数
主调函数 将数据传递给 被调函数 ; —— 函数参数
被调函数 处理完成后,
返回主调函数,并将得到的结果返回给 主调函数 —— 函数返回值电子科技大学通信与信息工程学院
#include "stdio.h"
int max(int x,int y)
{
int z;
z = x>y? x,y;
return(z);
}
main()
{
int a,b,g;
scanf("%d%d",&a,&b);
g = max(a,b);
printf("Max_num:%d\n",g);
}
形式参数和实际参数
函数参数:用于函数间数据的 传递
形式参数(形参):
定义 函数时使用的参数
实际参数(实参):
引用 (调用)函数时使用的参数形式参数实际参数电子科技大学通信与信息工程学院形式参数和实际参数
C语言如何实现函数间的 数据传递?
传递方式,值传递
单向传递(实参?形参 )
将实参的 值拷贝 给形参
...
int max(int x,int y)
{
....
return(z);
}
main()
{
...
g = max( a,b );
..,
}
2 5
2 5
主调函数内存空间被调函数内存空间
a b
x y
电子科技大学通信与信息工程学院形式参数和实际参数
值传递 说明:
在内存中,实参与形参存放在在不同的内存区域。在调用函数时,系统给形参分配存储单元,并将实参对应的值 拷贝 给形参,调用结束后,形参单元被释放,
而在函数调用过程中,实参值没有任何改动。因此,
在执行一个被调用函数时,形参的值如果发生改变,
并不会改变主调函数的实参的值。
电子科技大学通信与信息工程学院形式参数和实际参数
注意:
定义函数时,必须说明形参的类型,形参只能是变量(简单、构造类型),而不能是常量
形参只有在函数内部有效(函数调用结束返回主调函数后则不能再使用该形参变量):
1,在函数调用前,形参不占内存;
2,形参变量只有在函数被调用时才分配内存单元;
3,在调用结束时,刻释放所分配的内存单元
实参可以是常量、变量、表达式、函数
实参和形参在 数量,类型,顺序 上应严格一致,否则会发生“类型不匹配”的错误电子科技大学通信与信息工程学院形式参数和实际参数
例:读程序,求程序的输出
#include "stdio.h"
int sum(int b,int a)
{
a = a + b;
b = a + b;
return a;
}
main()
{
int a = 1,b = 2,c;
c = sum(a,b);
printf("sum = %d\n",c);
}
电子科技大学通信与信息工程学院函数的返回值
函数的值就是函数的返回值,是指被调函数返回给主调函数值。函数的值是一个确定的值。
返回函数值的方法
函数的值只能通过 return语句 向主调函数返值
一个函数可以有多个 return语句,但不论执行到哪个
return,都将结束函数的调用,返回主函数
return 语句格式:
return 表达式 ;或者,return (表达式 );
例,int max(int x,int y)
{
if (x>y) return x;
return y;
}
int max(int x,int y)
{
return ( x>y?x:y) ;
}
电子科技大学通信与信息工程学院函数的返回值
函数的类型即是函数返回值的类型
函数的类型和函数定义中函数返回值的类型应保持一致。如果两者不一致,则以函数类型为准,
自动进行类型转换。
在函数定义时如果省去类型说明,则函数的类型默认为整型( int)
不需要返回值的函数
如果一个函数不需要返回值,可以将函数类型设为 void
Void类型不能被引用
int max(int x,int y)
{
int z;
z = x>y? x,y;
return(z);
}
电子科技大学通信与信息工程学院函数的调用
函数调用的一般形式:
函数名 ( 实参列表 );
说明:
如果调用无参函数,实参列表忽略,但括号应保留
实际参数表中的参数可以是常数,变量或其它构造类型数据及表达式,各实参之间用逗号分隔
实参和形参在 数量,类型,顺序 上应意义对应
注意:不同系统,函数实参的计算顺序是不同的,
微机一般是从右向左。为避免混乱,应尽量在调用函数之前,先计算实参。( P151)
电子科技大学通信与信息工程学院函数的调用
例:实参的顺序 #include "stdio.h"int fun(int a,int b)
{
int c;
if (a>b) c = 1;
else if (a==b) c = 0;
else c = -1
return c;
}
main()
{
int i = 2,r;
r = fun(i,++i);
printf("r = %d\n",r);
}
电子科技大学通信与信息工程学院函数调用方式
1,函数语句:
形式:函数名(实参表);
例,printf("%d\n",a);
这种方式不要求函数带返回值,函数仅完成一定操作,适用于函数类型为 void
2,函数表达式
函数返回值参与运算,函数类型 不能为 void
例,m = max(a,b); n = max(a,b)*25;
printf("max =%d\n",max(a,b));
电子科技大学通信与信息工程学院函数原型 声明 ( Declaration)
在程序中调用函数,必须满足“先定义,后使用”
如果被调函数的定义在主调函数之后,则应该在使用被调函数前,进行函数原型声明 ——“先声明(定义),后使用”
函数原型说明格式:
类型符 函数名 (类型 形参,类型 形参 …) ;
或类型符 函数名 (类型,类型 …) ;
电子科技大学通信与信息工程学院
,先声明(定义),后使用,
#include "stdio.h"
int sum(int b,int a)
{
a = a + b;
b = a + b;
return a;
}
main()
{
int a = 1,b = 2,c;
c = sum(a,b);
printf("sum = %d\n",c);
}
#include "stdio.h"
int sum(int,int);
main()
{
int a = 1,b = 2,c;
c = sum(a,b);
printf("sum = %d\n",c);
}
int sum(int b,int a)
{
a = a + b;
b = a + b;
return a;
}
电子科技大学通信与信息工程学院
,先声明(定义),后使用,
对于调用库函数,其函数的原型说明在相应的头文件中(,h),在程序中通过使用 #include预处理命令将其插入
例 #include "stdio.h"#include "math.h"
int func(float a)
{
if (b!=0)return (sin(a)/a);
return (-9999);
}
main()
{
float a,sam;
scanf("%f",&a);
sam = func(a);
printf("sample = %f\n",sam);
}
电子科技大学通信与信息工程学院函数的嵌套调用
函数嵌套调用:被调函数的函数体中又调用其它函数 #include "stdio.h"
void printstar()
{
printf("****************\n");
}
void print_message()
{
printstar();
printf("How do you do!\n");
}
main()
{
print_message();
printstar();
}
电子科技大学通信与信息工程学院
main()
{
...
print_message();
printstar();
}
print_message()
{
printstar();
...
}
printstar()
{
..,
}
printstar()
{
..,
}
第一层 第二层 第三层电子科技大学通信与信息工程学院函数的嵌套调用
例:计算 s = (x!)3+(y!)3
分析:题目中涉及到两种运算:阶乘和立方,
可以考虑使用两个函数来分别实现以上两种运算 ——阶乘( fac( ))立方 (cube( ))
#include "stdio.h"
unsigned long fra(int);
unsigned long pow(int);
main()
{ int x,y;
long s;
scanf("%d%d",&x,&y);
s = cube(x)+cube(y);
printf("s = %ld\n");
}
long cube(int p)
{ int i;
long tmp;
tmp = fac(p);
for (i=0; i<3; i++)
tmp *= tmp;
return tmp;
}
long fac(int q)
{ int i = 1;
long tmp=1;
for (i=1; i <= q; i++)
tmp *= i;
return tmp;
}
电子科技大学通信与信息工程学院数组作函数的参数
数组元素作函数的实参:
数组元素就是普通 变量,仍然是单向值传递
例:求 10个数的最大数 #include "stdio.h"
int max(int x,int y){
return (x>y?x:y);
}
main()
{ int a[10],i,m = 0;
for (i=0; i<10; i++)
scanf("%d",&a[i]);
for (i=0; i<10; i++)
m = max(m,a[i]);
printf("max = %d\n",m);
}
电子科技大学通信与信息工程学院数组名作为函数的实参
数组名作为函数的实参:
如果函数的 形参 是 数组 (或者是数组的首地址)
则 函数的实参必须是数组名(数组名就是数组的 首地址 )
例:求 10个数的最大数
#include "stdio.h"
int max(int x[],int n){
int i,m;
m = x[0];
for (i=0; i<n; i++)
if (m < x[i]) m=x[i];
return m;
}
main()
{
int a[10],i,m = 0;
for (i=0; i<10; i++)
scanf("%d",&a[i]);
printf("max=%d\n",max(a,10));
}
电子科技大学通信与信息工程学院数组名作为函数的实参地址
a[0]a[1]a[2]a[3]a[4]a[5]a[6]a[7]a[8]a[9]
注意:
用 数组名 作 实参 时,传递的是 数组的首地址 ;
有时根据需要,可以增设一个形参,主函数可以传通过此形参传递需要处理的数组元素的个数
形参数组 和 实参数组 共享 同一内存单元
如果在被调函数中修改形参数组的元素值,就相当于在主调函数中直接修改实参数组对应的元素值
202A 202B,.,2033
x[0]x[1]x[2]x[3]x[4]x[5]x[6]x[7]x[8]x[9]
电子科技大学通信与信息工程学院数组名作为函数的实参
例:用冒泡法排序
#include "stdio.h"
void sort(int x[],int n){
int i,j,tmp;
for (i=1; i<=n-1; i++){
for(j=1; j<=n-i; j++)
if (x[j]>x[j+1]){
tmp = x[j];
x[j] = x[j+1];
x[j+1] = tmp;
}
}
}
main()
{
int a[11],i;
for (i=1; i<=10; i++)
scanf("%d",&a[i]);
sort(a,10);
for (i=1; i<=10; i++)
printf("a[%d]=%d ",i,a[i]);
}
电子科技大学通信与信息工程学院多维数组作函数参数
如果 形参 为多维数组,
可以 省略第一维长度,
其它维数不能省。
实参 仍然实数组名
例:有一个 3*4的矩阵,
求出其中的最大元素的值
#include "stdio.h"
int max(int x[3][4]){
int i,j,m;
for (i=0; i<3; i++)
for(j=0; j<4; j++)
if (x[i][j]>m)
m = x[i][j];
return m;
}
main()
{
int a[3][4]={{1,2,3,9},{10,2,7,
4},{0,1,9},{12}};
printf("max=%d\n",max(a));
}
电子科技大学通信与信息工程学院局部变量和全局变量
局部变量
1,在一个函数内部定义的变量,
它的有效范围是它被定义的函数范围内
int f1(int a,int b)
{
int x,y;
float f[10];
scanf("%d",&x);
..,
}
int f2(float pi)
{
char c[]="hello";
int i,j;
..,
}
main()
{
int i,p;
...
}
x,y,
f[10]有效
c[],i,j
有效
i,p
有效电子科技大学通信与信息工程学院局部变量
局部变量
2,函数的形参,
有效范围是在该函数内
3,某个复合语句中定义的变量,
有效范围只在该复合语句中
int f1(int a,int b)
{
int x,y;
scanf("%d",&x);
..,
}
int f2(float pi)
{
..,
}
main()
{
int i,p;
...
{
int i,j;
...
i = 2+j;
}
}
a,b有效
pi
有效
i,j
有效电子科技大学通信与信息工程学院局部变量
说明:
局部变量只在定义它的范围内 有效
有效,可以被其它代码访问、使用,即,可见,
如果局部变量的有效范围有重叠,则有效范围小的优先
main()
{
int a = 1,b,c;
...
{
int a,j;
a = 3+j;
}
}
局部优先
c有效电子科技大学通信与信息工程学院全局变量
全局变量 ( 外部变量 ):在 函数之外定义 的变量
全局变量的有效范围,从定义变量的位置开始,到源文件结束
函数可以使用局部变量和全局变量
int k = 10;
int f1(int a,int b)
{
int x,y;
scanf("%d",&x);
..,
}
char q[]="hello";
int f2(float pi)
{
..,
}
main()
{
int i,p;
...
}
k有效
q[]
有效电子科技大学通信与信息工程学院全局变量
说明:
可以 利用全局变量在函数间传递数据,但应 谨慎 使用
(尽量少用)
如果全局变量与局部变量同名,则 局部变量优先
例:读程序
int a=3,b=5;
int max(int a,int b)
{
return(a>b?a:b);
}
main()
{
int a=8;
printf("max:%d",max(a,b));
}
电子科技大学通信与信息工程学院全局变量
例,P170 例 8.15
10主调函数内存空间被调函数内存空间
score[]
array[] n
ave
Max Min
aver
Max Min
Max Min
电子科技大学通信与信息工程学院变量 存储类别
变量的 作用域 (即“可见”范围 ):
全局变量和局部变量
变量的 存储类别 /生存期 (即变量在内存中存储的位置):动态存储变量和静态存储变量
内存中用户区:
程序区 存放程序的指令
静态存储区 存放全局变量和静态的局部变量
动态存储区 存放函数的局部变量,函数的形参变量,函数调用时的现场保护和返回地址 ;
指令(程序区)
数据段(静态存储区)
堆栈段(动态存储区)
应用程序运行期间,在内存中的映像电子科技大学通信与信息工程学院
#include "stdio.h"
int a = 1;
char b[] = "china";
static double d = 1.2345;
main()
{
int i = 0,j,k;
int p[20] = {0};
printf("Input:\n");
scanf("%d",&k);
...
}
可执行文件头数据段初始化的全局和静态变量文本段可执行代码指令局部变量在可执行文件中没有专门的存储区域,
它们在运行时创建
C源程序 (hello.c) 可执行文件 (hello)
编译,连接电子科技大学通信与信息工程学院可执行文件头数据段初始化的全局和静态变量文本段可执行代码指令指令(程序区)
数据段(静态存储区)
堆栈段(动态存储区)
...
可执行文件 (hello) 内存映像加载电子科技大学通信与信息工程学院指令(程序区)
数据段(静态存储区)
堆栈段(动态存储区)
...
返回地址前一个活动记录函数形参局部变量过程活动记录返回地址前一个活动记录函数形参局部变量电子科技大学通信与信息工程学院堆栈段(动态存储区)
int num=10
2001 main()
{ int a,b;
...
20A1 fun1(a);
}
301A fun1(int x)
{ char c;
...
30EF fun2(c);
...
}
3100 fun2(char p)
{,..
return;
}
返回地址前一个活动记录 (空 )
函数形参 (空 )
局部变量 (a,b)
返回地址 (20A1)
前一个活动记录函数形参 (x)
局部变量 (c)
返回地址 (30EF)
前一个活动记录函数形参 (p)
局部变量 (空 )
静态存储区
num = 10
动态存储区电子科技大学通信与信息工程学院变量 存储类别
存放在 静态存储区 中的变量(静态变量,全局变量)
当程序执行时就分配内存空间;
并且生存周期是程序的 整个执行过程 (直到程序结束)
存放在 动态存储区 中的变量(局部变量,形参)
当定义该变量的函数被调用时,系统才自动为其分配内存空间,
并且生存周期是 函数的执行期间,当函数执行完毕,
系统自动收回为其分配的内存空间电子科技大学通信与信息工程学院动态存储变量和静态存储变量
变量的 存储类别 (生存期)共有四种存储类别:
自动 (auto)
寄存器 (register)
静态 (static)
外部的 (extern)
电子科技大学通信与信息工程学院存储在动态存储区里的变量
自动变量:存储在动态存储区
说明格式,auto 类型说明 变量名;
例,f1(int a)
{
auto float f;
char c;
...
}
1.auto可以 省略,系统默认维自动变量
2.形参 (a),局部变量 (f和
c)都是自动变量,
3.函数执行时,系统为其分配内存空间,
4.函数调用结束时,系统自动收回空间电子科技大学通信与信息工程学院存储在动态存储区里的变量
寄存器变量 (register)
寄存器是计算机 cpu的重要组成部分,在一个程序中,对于频繁使用的变量从内存取数送到运算器中运算,再把运算的结果送回到内存中要花费很多的时间。在 C语言中允许将一些频繁使用的变量存放在计算机的寄存器中,以节省运算时间,提高效率。 (P176)
电子科技大学通信与信息工程学院寄存器变量 (register)
格式,register 类型名 变量名;
例,register int i;
说明:
只有局部动态变量(形参)可以被定义为寄存器变量,而局部静态变量不能被定义为寄存器变量
计算机寄存器个数有限,不能任意定义寄存器变量的个数
某些系统将寄存器变量转化为自动变量处理,
而有些系统会自动设置寄存器变量电子科技大学通信与信息工程学院存储在静态存储区里的变量
局部静态变量
格式,static 类型说明 变量名;
例,static int a = 10;
在函数内定义,其作用域属于 局部,即只能在被定义的函数内使用,其他的函数不能使用这个变量
属于静态存储类别,存储在内存的 静态存储区,
在整个程序运行期间都不释放空间
在编译时赋 初值,且只赋一次(若未赋初值,
系统会自动赋初值),当再次调用该函数时,
不再进行赋初值,而是保留的是上次函数调用结束后的值电子科技大学通信与信息工程学院局部静态变量
例:读程序
例,P174
例,P175例 8.18
#include "stdio.h"
long fac(int n)
{
static long f = 1;
f = f * n;
return f;
}
main()
{ int j=5;
for(j=1; j<=5; j++)
printf("%d!=%ld\n",j,fac(j));
}
电子科技大学通信与信息工程学院存储在静态存储区里的变量
全局变量:都存储在静态存储区中电子科技大学通信与信息工程学院全局变量的作用域(有效范围)
全局变量的有效范围,从定义变量的位置开始,到源文件结束
扩展全局变量的作用域 ——extern
情况 1:在一个源文件中 声明 全局变量
#include "stdio.h"
int max(int x,int y)
{
return (x>y?x:y);
}
main()
{
extern int N1,N2;
printf("%d\n",max(N1,N2));
}
int N1=12,N2=21;
如果在全局变量定义点之前(即有效范围之外)要使用该变量,可以在使用之前,使用
extern来声明该外部变量电子科技大学通信与信息工程学院扩展全局变量的作用域
情况 1:在一个源文件中 声明 全局变量
格式,extern 类型说明 变量名;
extern作用:声明一个全局变量,扩展该全局变量的作用域,作用域为:从 extern声明开始,
到源文件结束。
extern的含义:告诉编译器:代码将使用一个全局变量(类型,变量名),而该变量的定义在源文件的某个地方。
电子科技大学通信与信息工程学院扩展全局变量的作用域
情况 2:在多个源文件中使用全局变量
#include "stdio.h"
extern int N1,N2
int max(int,int);
main()
{
printf("%d\n",max(N1,N2));
}
int N1=12,N2=21;
int max(int x,int y)
{
return (x>y?x:y);
}
file1.c file2.c
使用 extern来声明该外部变量,将全局变量 N1和 N2
的作用范围从源文件 file2扩展到源文件 file1
电子科技大学通信与信息工程学院扩展全局变量的作用域
情况 2:在多个源文件中使用全局变量
全局变量只能被 定义 一次,所以全局变量只能在一个源文件中被定义
定义,告诉编译器按照变量的类型,为其分配内存空间
全局变量可以被 extern声明 多次,当全局变量在多个源文件中被声明时,其作用域也就扩展到多个源文件
extern声明 只是告诉编译器该变量的类型和名字,
并不分配内存空间电子科技大学通信与信息工程学院扩展全局变量的作用域
情况 3:将全局变量的作用域限制在一个源文件中
使用关键字 static
例:
#include "stdio.h"
extern int N1,N2
int max(int,int);
main()
{
printf("%d\n",max(N1,N2));
}
static int N1=12,N2=21;
int max(int x,int y)
{
return (x>y?x:y);
}
file1.c file2.c
电子科技大学通信与信息工程学院注意
关键字 static对局部变量和全局变量的作用不同
修饰局部变量:设定局部变量的存储类别为静态存储
修饰全局变量:使全局变量的作用域限制在一个源文件内电子科技大学通信与信息工程学院程序举例
例:编写一个函数 swap用于交换两个整数方法 1 方法 2(全局变量 )
#include "stdio.h"
void swap(int x,int y)
{
int tmp;
tmp = x;
x = y;
y = tmp;
}
main()
{
int a,b;
scanf("%d%d",&a,&b);
swap(a,b);
printf("%d,%d\n",a,b);
}
#include "stdio.h"
int x,y;
void swap()
{
int tmp;
tmp = x;
x = y;
y = tmp;
}
main()
{
scanf("%d%d",&x,&y);
swap();
printf("%d,%d\n",x,y);
}
电子科技大学通信与信息工程学院程序举例
例:编写一个函数 swap用于交换两个整数方法 3(数组)
#include "stdio.h"
void swap(int x[2])
{
int tmp;
tmp = x[0];
x[0] = x[1];
x[1] = tmp;
}
main()
{
int a[2];
scanf("%d%d",&a[0],&a[1]);
swap(a);
printf("%d,%d\n",a[0],a[1]);
}