目录 7.1 用户自定义函数的种类 7.2 函数的定义 7.3 被调函数的声明 7.4 函数的调用 7.5 函数的嵌套调用 7.6 函数的递归调用 7.7 数组作函数参数 7.8 变量的作用域 7.9 变量的存储类别 1
7.1 用户自定义函数的种类 1. 有返回值函数和无返回值函数 7.1 用户自定义函数的种类 1. 有返回值函数和无返回值函数 C语言的函数兼有其它语言中的函数和过程两种功能,从这个角度看,又可把函数分为有返回值函数和无返回值函数两种。 1)有返回值函数 此类函数被调用执行完后将向调用者返回一个执行结果,称为函数返回值,如数学函数即属于此类函数。由用户定义的这种要返回函数值的函数,必须在函数定义和函数声明中明确返回值的类型,在函数调用时要接收函数的返回值。 2)无返回值函数 此类函数用于完成某项特定的处理任务,执行完成后不向调用者返回函数值,这类函数类似于其它语言的过程。由于函数无须返回值,用户在定义此类函数时可指定它的返回为空类型,空类型的声明符为void,在函数调用时不能得到函数的返回值。 2
7.1 用户自定义函数的种类 2. 无参函数和有参函数 从主调函数和被调函数之间数据传送的角度看又可分为无参函数和有参函数两种。 7.1 用户自定义函数的种类 2. 无参函数和有参函数 从主调函数和被调函数之间数据传送的角度看又可分为无参函数和有参函数两种。 1)无参函数 函数定义、函数声明及函数调用中均不带参数。主调函数和被调函数之间不进行参数传送。此类函数通常用来完成一组指定的功能,可以返回或不返回函数值。 2)有参函数 也称为带参函数。在函数定义及函数声明时都有参数,称为形式参数(简称为形参)。在函数调用时也必须给出参数,称为实际参数(简称为实参)。形参和实参的个数及对应位置参数的类型必须一致。进行函数调用时,主调函数将把实参的值传送给形参,供被调函数使用,这一过程叫形实结合方式,也叫参数传递方式。 3
7.2 函数的定义 1.无参函数的定义 无参函数的定义形式为: 类型标识符 函数名() { 声明部分 执行部分 } 1)函数首部 7.2 函数的定义 1.无参函数的定义 无参函数的定义形式为: 类型标识符 函数名() { 声明部分 执行部分 } 1)函数首部 类型标识符和函数名为函数首部。 (1)类型标识符指明了本函数返回值的类型。无参函数可以带回或不带回函数值,但一般以不带回函数值的居多,此时函数类型声明符可以写为void。 4
1.无参函数的定义 2)函数体 7.2 函数的定义 1)函数首部 7.2 函数的定义 1.无参函数的定义 1)函数首部 (2)函数名是由用户定义的标识符,应符合标识符命名规则。函数名后有一个空括号,其中无参数,但括号不可少,作为函数的标志。 2)函数体 {}中的内容称为函数体,包括声明部分和执行部分。声明部分是对函数体内部所用到的变量的类型声明;执行部分完成函数的功能。 5
7.2 函数的定义 2.有参函数的定义 有参函数定义的一般形式为: 类型标识符 函数名(形式参数表列) { 声明部分 执行部分 } 7.2 函数的定义 2.有参函数的定义 有参函数定义的一般形式为: 类型标识符 函数名(形式参数表列) { 声明部分 执行部分 } 有参函数比无参函数多了一个内容,即形式参数表列。在形参表中给出的参数称为形式参数,它们可以是各种类型的变量,各参数之间用逗号分隔。在进行函数调用时,主调函数将赋给这些形式参数实际的值。形参既然是变量,必须在形参表中给出形参的类型声明。 6
7.2 函数的定义 3.带返回值的函数定义 带返回值的函数,函数体的执行部分必须通过return语句给函数返回指定类型的值。 7.2 函数的定义 3.带返回值的函数定义 带返回值的函数,函数体的执行部分必须通过return语句给函数返回指定类型的值。 return语句的一般形式为: return 表达式; 或者为: return (表达式); 该语句的功能是计算表达式的值,并将该值返回给主调函数。在函数中允许有多个return语句,但每次调用只能有一个return语句被执行,因此只能返回一个函数值。在定义函数时指定的函数类型一般应该和return语句中的表达式类型一致;如果函数值的类型和return语句中表达式的值不一致,则以函数类型为准。 7
7.2 函数的定义 4.不带返回值的函数定义 不带返回值的函数应明确定义为空类型,类型声明符为void。一旦函数被定义为空类型,就不能在主调函数中使用被调函数的函数值了。 5.函数定义的位置 在C程序中,一个函数的定义可以放在任意位置,既可放在主函数main之前,也可放在main之后。 在C语言中,所有函数的定义,包括主函数main在内,都是平行的。也就是说,在一个函数的函数体内,不能再定义另一个函数,即不能嵌套定义。 8
对于用户自定义函数,不仅要在程序中定义函数,而且在主调函数中还要对该被调函数进行声明,然后才能使用。 7.3 被调函数的声明 对于用户自定义函数,不仅要在程序中定义函数,而且在主调函数中还要对该被调函数进行声明,然后才能使用。 在一个函数中调用另一个自己定义的函数,如果被调函数在主调函数之后定义,那么在主调函数中调用被调函数之前应对该被调函数进行声明,这与使用变量之前要先进行变量声明是一样的。在主调函数中对被调函数作声明的目的是使编译系统知道被调函数返回值的类型,以便在主调函数中按此种类型对返回值作相应的处理。 9
7.3 被调函数的声明 1.函数声明的一般形式 类型声明符 被调函数名(类型 形参,类型 形参…); 或为: 7.3 被调函数的声明 1.函数声明的一般形式 类型声明符 被调函数名(类型 形参,类型 形参…); 或为: 类型声明符 被调函数名(类型,类型…); 括号内给出了形参的类型和形参名,或只给出形参类型。便于编译系统进行检错,以防止可能出现的错误。 例如: int max(int a,int b); 或写为: int max(int,int); 10
7.3 被调函数的声明 (1)当被调函数的函数定义出现在主调函数之前时,在主调函数中也可以不对被调函数再作声明而直接调用。 7.3 被调函数的声明 2.可以省略主调函数中对被调函数的函数声明 C语言中又规定在以下几种情况时可以省去主调函数中对被调函数的函数声明。 (1)当被调函数的函数定义出现在主调函数之前时,在主调函数中也可以不对被调函数再作声明而直接调用。 (2)如在所有函数定义之前,在函数外预先声明了各个函数的类型,则在以后的各主调函数中,可不再对被调函数作声明。 例如: char str(int a); void main() { } char str(int a) { } 11
1.函数调用的一般形式 2.函数调用的方式 7.4 函数的调用 1)函数表达式 7.4 函数的调用 1.函数调用的一般形式 C语言中,函数调用的一般形式为: 函数名(实际参数表列) 对无参函数调用时则无实际参数表列。实际参数表列中的参数可以是常量、变量、函数、表达式或其它构造类型数据。各实参无需指定类型,多个实参之间用逗号分隔。 2.函数调用的方式 1)函数表达式 函数调用出现在另一个表达式中,这时要求函数带回一个确定的值以参加表达式的运算。例如:z=max(x,y)是一个赋值表达式,把max的返回值赋给变量z。 12
7.4 函数的调用 2.函数调用的方式 2)函数语句 函数调用单独作为一个语句,即在一般形式末尾加上分号构成函数语句。例如:printf("%d",a);就是以函数语句的方式调用函数。这时不要求函数带返回值,只要求函数完成一定的操作。 3)函数实参 函数调用作为另一个函数调用的实际参数出现。这种情况是把该函数的返回值作为实参进行传送,因此要求该函数必须是有返回值的。 例如: printf("%d",max(x,y)); 把max函数调用的返回值又作为printf函数的实参来使用。 13
7.4 函数的调用 2.函数调用的方式 #include<stdio.h> int max(int a,int b) 7.4 函数的调用 2.函数调用的方式 【例7_1】函数的定义、声明及调用举例。 #include<stdio.h> int max(int a,int b) { if(a>b)return a; else return b; } void 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\n",z); 14
7.4 函数的调用 3.函数调用的参数传递 1)函数的形参和实参的特点 7.4 函数的调用 3.函数调用的参数传递 1)函数的形参和实参的特点 (1)形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只有在函数内部有效。函数调用结束返回主调函数后则不能再使用该形参变量。 (2)实参可以是常量、变量、函数、表达式或其它构造类型数据,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此应预先用赋值、输入等办法使实参获得确定值。 (3)实参和形参的数量、类型、顺序应严格一致,否则会发生类型不匹配的错误。 2)参数传递方式 (1)单向值传递 当实参是常量、变量、函数、表达式时,函数调用中发生的数据传送是单向值传递的,即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。 15
7.4 函数的调用 3.函数调用的参数传递 2)参数传递方式 【例7_2】单向值传递 #include<stdio.h> 7.4 函数的调用 3.函数调用的参数传递 2)参数传递方式 【例7_2】单向值传递 #include<stdio.h> int s(int n) { int i; for(i=n-1;i>=1;i--) n=n+i; printf("n=%d\n",n); } void main() { int n; printf("input number\n"); scanf("%d",&n); s(n); 16
3.函数调用的参数传递 2)参数传递方式 (2)双向地址传递 7.4 函数的调用 7.4 函数的调用 3.函数调用的参数传递 2)参数传递方式 (2)双向地址传递 当实参是数组名或指针时,函数调用中发生的参数传送是双向的。即把实参的地址传送给形参,也就是形参和实参共用同一地址空间,形参的值发生改变,就会改变这段地址空间的值,所以实参中的值也会跟着变化。 17
7.5 函数的嵌套调用 C语言允许在一个函数的定义中出现对另一个函数的调用。这样就出现了函数的嵌套调用,即在被调函数中又调用其它函数。b函数⑤①⑨结束a函数③调用b函数⑦②⑧④⑥main函数图7_3函数嵌套调用a函数 例如:图7_3中执行main函数中调用a函数的语句时,即转去执行a函数,在a函数中调用b函数时,又转去执行b函数,b函数执行完毕返回a函数的断点继续执行,a函数执行完毕返回main函数的断点继续执行。 18
【例7_3】计算s=(1+2)!+(1+2+3)! 7.5 函数的嵌套调用 7.5 函数的嵌套调用 【例7_3】计算s=(1+2)!+(1+2+3)! 本题可编写两个函数,一个是用来计算平方值的函数f1,另一个是用来计算阶乘值的函数f2。主函数先调f1计算出平方值,再在f1中以平方值为实参,调用f2计算其阶乘值,然后返回f1,再返回主函数,在循环程序中计算累加和。 #include<stdio.h> long f1(int p) { int k,r=0; int f2(int); for(k=1;k<=p;k++)r=r+k; return f2(r); } 19
int f2(int q) 7.5 函数的嵌套调用 { int i,c=1; for(i=1;i<=q;i++)c=c*i; 7.5 函数的嵌套调用 int f2(int q) { int i,c=1; for(i=1;i<=q;i++)c=c*i; return c; } void main() { int i,s=0; for (i=2;i<4;i++)s=s+f1(i); printf("s=%d\n",s); 20
7.6 函数的递归调用 一个函数在它的函数体内直接或间接调用它自身,称为递归调用,这种函数称为递归函数。在递归调用中,主调函数又是被调函数。执行递归函数将反复调用其自身,每调用一次就进入新的一层。为了防止递归调用无终止地进行,必须在函数内有终止递归调用的手段。常用的办法是加条件判断,满足某种条件后就不再作递归调用,然后逐层返回。 【例7_4】用递归法计算n! 计算n!可用下述公式表示: #include<stdio.h> long ff(int n) { long f; 21
if(n<0) printf("n<0,input error"); 7.6 函数的递归调用 if(n<0) printf("n<0,input error"); else if(n==0||n==1) f=1; else f=ff(n-1)*n; return(f); } void main() { int n; long y; printf("input a inteager number:"); scanf("%d",&n); y=ff(n); printf("%d!=%ld\n ",n,y); 22
1.数组元素作函数实参 用数组元素作实参的特点: 7.7 数组作函数参数 7.7 数组作函数参数 1.数组元素作函数实参 用数组元素作实参的特点: (1)用数组元素作实参时,只要数组类型和函数的形参变量的类型一致,那么作为下标变量的数组元素的类型也和函数形参变量的类型是一致的。因此,并不要求函数的形参也是下标变量。换句话说,对数组元素的处理是按简单变量对待的。 (2)在简单变量或下标变量作函数参数时,形参变量和实参变量是由编译系统分配的两个不同的内存单元。在函数调用时,发生值传送是把实参变量的值赋给形参变量,不能从形参传回实参。形参的初值和实参相同,而形参的值发生改变后,实参并不变化。 23
7.7 数组作函数参数 1.数组元素作函数实参 【例7_5】判别一个整数数组中各元素的值,若大于0则输出该值,若小于等于0则输出0值。编程如下: void nzp(int v) { if(v>0) printf("%d ",v); else printf("%d ",0); } void main() { int a[5],i; printf("input 5 numbers:\n"); for(i=0;i<5;i++) {scanf("%d",&a[i]); nzp(a[i]);} printf("\n"); 24
2.数组名作函数实参 7.7 数组作函数参数 1)用数组名作函数参数的特点 7.7 数组作函数参数 2.数组名作函数实参 1)用数组名作函数参数的特点 (1)用数组名作函数参数时,要求形参和相对应的实参都必须是类型相同的数组,都必须有明确的数组定义。 (2)在用数组名作函数参数时,不是进行值的传送,即不是把实参数组的每一个元素的值都赋给形参数组的各个元素。因为实际上形参数组并不存在,编译系统不为形参数组分配内存。因此在数组名作函数参数时,在函数执行时,把实参数组的首地址赋给形参数组名。形参数组名取得该首地址之后,也就等于有了实在的数组,实际上是形参数组和实参数组为同一数组,共同拥有一段内存空间。因此当形参数组发生变化时,实参数组也随之变化,相当于实现了地址双向传递。 25
7.7 数组作函数参数 2.数组名作函数实参 1)用数组名作函数参数的特点 7.7 数组作函数参数 2.数组名作函数实参 1)用数组名作函数参数的特点 【例7_6】数组a中存放了一个学生5门课程的成绩,对这些成绩上调10分。 #include<stdio.h> void inc(int a[5]) { int i; for(i=0;i<5;i++) a[i]+=10; } void main() { int i,sco[5]; printf("input 5 scores:\n"); for(i=0;i<5;i++) scanf("%d",&sco[i]); inc(sco); printf("output 5 scores:\n"); for(i=0;i<5;i++) printf("%d\t",sco[i]); printf("\n"); 26
7.7 数组作函数参数 2.数组名作函数实参 2)用数组名作为函数参数时的注意事项 7.7 数组作函数参数 2.数组名作函数实参 2)用数组名作为函数参数时的注意事项 (1)在函数形参表中,允许不给出形参数组的长度,或用一个变量来表示数组元素的个数。例如,可以写为: void p(int a[])或写为 void p(int a[],int n) 其中形参数组a没有给出长度,而由n值动态地表示数组的长度。n的值由主调函数的实参进行传送。 (2)多维数组也可以作为函数的参数。在函数定义时对形参数组可以指定每一维的长度,也可省去第一维的长度。因此,以下写法都是合法的。 int max(int a[3][10]) 或 int max(int a[][10]) 27
7.8 变量的作用域 1.局部变量 局部变量也称为内部变量。局部变量是在函数内作定义的,其作用域仅限于函数内,离开该函数后再使用这种变量是非法的。 【例7_7】局部变量的作用域。 int f1(int a) /*函数 f1*/ { int b,c; // a,b,c 有效 } void main() int m,n; //m,n 有效 28
1.局部变量 7.8 变量的作用域 关于局部变量的作用域还要说明以下几点: 7.8 变量的作用域 1.局部变量 关于局部变量的作用域还要说明以下几点: (1)主函数中定义的变量也只能在主函数中使用,不能在其它函数中使用。同时,主函数中也不能使用其它函数中定义的变量。原因在于主函数也是一个函数,它与其它函数是平行关系。 (2)形参变量是属于被调函数的局部变量,实参变量是属于主调函数的局部变量。 (3)允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不干扰,也不会发生混淆。 (4)在复合语句中也可定义变量,其作用域只在复合语句范围内。 29
1.局部变量 7.8 变量的作用域 【例7_8】复合语句中定义的局部变量的作用域。 #include<stdio.h> 7.8 变量的作用域 1.局部变量 【例7_8】复合语句中定义的局部变量的作用域。 #include<stdio.h> void main() { int i=2,j=3,k; k=i+j; { int k=8; printf("%d\n",k); } 30
7.8 变量的作用域 2.全局变量 全局变量也称为外部变量,它是在函数外部定义的变量。它不属于哪一个函数,它属于一个源文件,其作用域是整个源文件。 只有在函数内经过声明的全局变量才能使用。全局变量的声明符为extern。但在一个函数之前定义的全局变量,在该函数内使用可不再加以声明。 【例7_9】全局变量的作用域。 int a,b; /*外部变量*/ void f1() /*函数 f1*/ { extern x,y; /*声明外部变量*/ } float x,y; /*外部变量*/ void main() /*主函数*/ { } 31
7.8 变量的作用域 2. 全局变量 【例7_10】输入正方体的长宽高l、w、h。求体积及三个面x*y、x*z、y*z的面积。 7.8 变量的作用域 2. 全局变量 【例7_10】输入正方体的长宽高l、w、h。求体积及三个面x*y、x*z、y*z的面积。 #include<stdio.h> 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; } void main() { int v,l,w,h; printf("input length,width and height:\n"); scanf("%d%d%d",&l,&w,&h); v=vs(l,w,h); printf("v=%d,s1=%d,s2=%d,s3=%d\n",v,s1,s2,s3); 32
2. 全局变量 7.8 变量的作用域 【例7_11】外部变量与局部变量同名。 #include<stdio.h> 7.8 变量的作用域 2. 全局变量 【例7_11】外部变量与局部变量同名。 #include<stdio.h> int a=3,b=5; /*a,b 为外部变量*/ int max(int a,int b) /*a,b 为内部变量*/ { int c;c=a>b?a:b; return(c); } void main() { int a=8; printf("%d\n",max(a,b)); 33
1)静态存储方式及静态存储区 7.9 变量的存储类别 1. 动态存储方式与静态存储方式 7.9 变量的存储类别 1. 动态存储方式与静态存储方式 1)静态存储方式及静态存储区 静态存储方式是指在程序运行期间分配固定的存储空间的方式。全局变量全部存放在静态存储区,在程序开始执行时给全局变量分配存储区,程序行完毕就释放。在程序执行过程中它们占据固定的存储单元,而不是动态地进行分配和释放。 2)动态存储方式及动态存储区 动态存储方式是在程序运行期间根据需要进行动态的分配存储空间的方式。在函数开始调用时分配动态存储空间,函数结束时释放这些空间 34
7.9 变量的存储类别 2. 用auto声明动态局部变量 函数中的局部变量,如不专门声明为static存储类别,都是动态地分配存储空间的,数据存储在动态存储区中。函数中的形参和在函数中定义的变量(包括在复合语句中定义的变量),都属此类。在调用该函数时系统会给它们分配存储空间,在函数调用结束时就自动释放这些存储空间。这类局部变量称为自动变量。自动变量用关键字auto进行存储类别的声明。关键字auto可以省略,隐含为自动存储类别,属于动态存储方式。 int f(int a) //定义 f 函数,a 为参数 { auto int b,c=3; //定义 b,c 自动变量 …… } 执行完f函数后,自动释放a、b和c所占的存储单元。 35
7.9 变量的存储类别 3. 用static声明静态局部变量 7.9 变量的存储类别 3. 用static声明静态局部变量 函数中的局部变量的值在函数调用结束后不消失而保留原值,指定局部变量为静态局部变量,用关键字static进行声明。 【例7_12】考察静态局部变量的值。 #include<stdio.h> int f(int a) { auto b=0; static c=3; b=b+1; c=c+1; return(a+b+c); } void main() { int a=2,i; for(i=0;i<3;i++) printf("%d\n",f(a)); 36
3. 用static声明静态局部变量 静态局部变量和动态局部变量的区别: 7.9 变量的存储类别 7.9 变量的存储类别 3. 用static声明静态局部变量 静态局部变量和动态局部变量的区别: (1)静态局部变量属于静态存储类别,在静态存储区内分配存储单元。在程序整个运行期间都不释放;而自动变量(即动态局部变量)属于动态存储类别,占动态存储空间,函数调用结束后即释放。 (2)静态局部变量在编译时赋初值,即只赋初值一次;而对自动变量赋初值是在函数调用时进行,每调用一次函数重新给一次初值,相当于执行一次赋值语句。 (3)如果在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值0(对数值型变量)或空字符(对字符变量);而对自动变量来说,如果不赋初值则它的值是一个不确定的值。 37
4. 用register声明寄存器变量 7.9 变量的存储类别 7.9 变量的存储类别 4. 用register声明寄存器变量 为了提高效率,C语言允许将局部变量的值放在CPU中的寄存器中,这种变量叫寄存器变量,用关键字register进行声明。 【例7_13】使用寄存器变量。 #include<stdio.h> int fac(int n) { register int i,f=1; for(i=1;i<=n;i++)f=f*i; return(f); } void main() { int i; for(i=0;i<=5;i++)printf("%d!=%d\n",i,fac(i)); 38
4. 用register声明寄存器变量 说明: 7.9 变量的存储类别 (1)只有局部自动变量和形式参数可以作为寄存器变量; 7.9 变量的存储类别 4. 用register声明寄存器变量 说明: (1)只有局部自动变量和形式参数可以作为寄存器变量; (2)一个计算机系统中的寄存器数目有限,不能定义任意多个寄存器变量; (3)局部静态变量不能定义为寄存器变量; (4)现在的计算机能够识别使用频繁的变量,从而自动地将这些变量放在寄存器中,而不需要程序设计者指定。 注意:用auto、register、static声明变量时,是在定义变量的基础上加上这些关键字,而不能单独使用。 39
7.9 变量的存储类别 5. 用extern声明外部变量 外部变量(即全局变量)是在函数的外部定义的,它的作用域为从变量定义处开始,到本程序文件的末尾。如果在定义之前的函数想引用该外部变量,则应该在引用之前用关键字extern对该变量进行外部变量声明,就可以从声明处起,合法地使用该外部变量。 【例7_14】用extern声明外部变量,扩展程序文件中的作用域。 #include<stdio.h> int max(int x,int y) { int z; z=x>y?x:y; return(z); } void main() { extern a,b;printf("%d\n",max(a,b)); int a=13,b=-8; 40