1k likes | 1.26k Views
第 8 章函数. 本章主要内容. 函数的 基本概念 数组作为函数参数 函数的嵌套调用和递归调用 变量的作用域和存储方法 内部函数和外部函数. 由 C 程序结构所知,一个 C 程序由一个或多个源程序文件组成。. C 程序的结构:. 一个源程序文件有且仅由一个 main 的函数和若干个其它函数组成,并从 main 函数开始执行,调用其他函数后流程回到 main( ) 函数,在 main( ) 函数结尾结束整个程序的运行。.
E N D
本章主要内容 • 函数的基本概念 • 数组作为函数参数 • 函数的嵌套调用和递归调用 • 变量的作用域和存储方法 • 内部函数和外部函数
由C程序结构所知,一个C程序由一个或多个源程序文件组成。由C程序结构所知,一个C程序由一个或多个源程序文件组成。 C程序的结构: • 一个源程序文件有且仅由一个main的函数和若干个其它函数组成,并从main函数开始执行,调用其他函数后流程回到main( )函数,在main( )函数结尾结束整个程序的运行。 • C程序的设计采用自顶向下、逐步细化和模块化的设计方法。每个模块实现一个特定的功能,在C语言中用函数来实现模块的功能。将程序分块设计的好处在于方便程序员进行代码检查和修改,使C程序结构清晰,功能明了,结构性强。 用户自己定义的函数是本章学习的重点内容。
说明: 1) 在C语言中,程序总是从主函数开始执行,调用完其它函数后,最终在主函数中结束,而无论主函数在程序中的位置如何。 2)函数不可以嵌套定义,但可以相互调用。 ☆主函数(main( ))调用其它函数,main()由系统调用; ☆所有函数在定义时是并列的; ☆在调用时是纵向; ☆可相互调用,也可被多次调用。
[例1]编写一个求x3的函数,并在主函数中调用该函数[例1]编写一个求x3的函数,并在主函数中调用该函数 #include "stdio.h" float cube(float x) { return (x*x*x); } int main( ) { float x, y; printf("Please input x:\n"); scanf("%f", &x); y=cube(x); printf("The cube of %6.2f is %6.2f\n", x, y); return 0; } 调用求X3的函数cube(x)
8.1 函数的基本概念 8.1.1 函数的概念 函数是按规定格式书写的能完成特定功能的一段程序。 从用户使用的角度看,函数有两种: ①标准函数,即库函数。这是由系统提供的,用户不必自己定义这些函数,可以直接使用它们。应该说明,不同的C系统提供的库函数的数量和功能会有一些不同,当然许多基本的函数是共同的。见教材后面的函数表。 ② 用户自己定义的函数。用以解决用户的专门需要,由编程者自己编写代码。 用户自己定义的函数是本章学习的重点内容。
int f1() { …… .. } int f2(int a, int b) { …… .. } 函数定义示意图
8.1.2 函数的定义 函数定义的一般形式: [类型说明符] 函数名(形式参数声明) { [说明与定义部分]; 函数体语句; } float cube(float x) { return (x*x*x); } 如
说明: 1) 函数名的命名规则与变量的命名规则相同,它用来唯一标识一个函数。 2) 当形式参数声明(简称形参表)中有多个形参时,每个形参之间用逗号隔开。形参表也可以是空的,但此时函数名后的括号不能省略。 有参数传递 如 double sum(float a, float b, float c) { …} 无参数传递 又如: disp ( ) { printf("How are you !\n"); }
3) 函数名前面的类型说明符指明了函数的类型。类型说明缺省 时,系统默认为函数的类型为int型。用{ }括起来的部分是函数体,由变量定义部分和语句组成。 例如: [例如] int max (int x,int y) { int z; z=x>y?x:y; return z; } 用{ }括起来的部分是函数体 /*函数体中变量的说明*/ 4)return后面的值作为函数带回的值(称函数返回值)。
5) 函数体中可以既无变量,也无语句,但一对花括号不可省略,此时构成空函数。 空函数的定义形式 类型标识符 函数名( ) { } 例如:dummy( ) { } 特点:调用后什么也不做 用处:建立程序结构,该空函数在需要时补充
[例2 ]求任意圆的面积,半径r由主函数输入。 #define PI 3.14159 float area(float r) { float s; s=PI*r*r; return s; } int main() {float a; scanf("%f",&a); printf("%6.3f\n",area(a)); return 0; }
void dig_to_str(int n) { char string[10]; int i=0; if(n<0) {putchar('-'); n=-n; } while(n) { string[i++]=n%10+'0'; n/=10; } while(--i>=0) putchar(string[i]); } [例3]编写函数:将一个给定的整数转换成相应的字符串后显示出来。 对负数进行处理 #include <stdio.h> int main( ) { int x; scanf("%d",&x); printf("The converted string: "); dig_to_str(x); return 0; } 将分离的数转换成字符并存入字符数组 该函数代码在哪?
8.1.3 函数的调用 函数调用的一般形式为: 函数名(实参参数表列); 或:函数名(); 1)多个实参间用逗号隔开,如:max(a,b); 2)实参与形参间个数相等,类型应一致 3)实参与形参按顺序对应,一一传递数据 4)实参可以是常量、有值的变量或表达式,如: max(3,a+b); 关于形参与实参的说明:
5)在定义函数中指定的形参,在未出现函数调用时,它们并不占内存中的存储单元。只有在发生函数调用时,函数中的形参才被分配内存单元。在调用结束后,形参所占的内存单元也被释放。5)在定义函数中指定的形参,在未出现函数调用时,它们并不占内存中的存储单元。只有在发生函数调用时,函数中的形参才被分配内存单元。在调用结束后,形参所占的内存单元也被释放。 6)实参表求值的顺序与系统有关。有的系统按照自右向左的顺序计算,而有的系统则相反。 int main( ) { float x; float ppp(float x); scanf("%f",&x); printf("%f",ppp(x)); return 0; } float ppp(float x) { float t; t=x*x*x; return t; }
[例4] 实参表求值顺序的影响。 int sum(int x, int y) { return(x+y); } int main( ) { int a=6, b; b=sum(a, a+=4); printf("b=%d\n", b); return 0; }
8.1.4函数参数的传递方式 在函数调用时,将实参的值传递给形参变量,这种传递方式即为值传递。 1.值传递 2.地址传递
1.值传递 :在函数调用时,将实参的值传递给形参变量,这种传递方式即为值传递。 [例5] 分析以下程序的运行结果 void add(int a, int b) { a+=3; b+=6; printf(“a=%d, b=%d\n”, a,b ); } in( ) { int x=1, y=2; void add(int a, int b); add(x, y); printf("x=%d,y=%d\n", x, y); } 主函数 被调用函数
8.1.4函数参数的传递方式 调用函数时,将实参的地址传递给形参。这样实参和形参指向同一个内存单元。形参存储单元内容的变化会影响到实参。 1.值传递 2.地址传递 ?
[例6] 统计单词个数 #include <stdio.h> int wordnumber(char ch[]) { char c; int i=0,word=0,n=0; while( (c=ch[i])!='\0') { if (c==' ') word=0; else if(word==0) {word=1;n++; } i++; } return n; } int main() { char s[80]; gets(s); printf("words numbers:%d\n",wordnumber(s)); return 0; } 主函数
[例7] 分析以下程序的运行结果 int main() { int score[10]={87,98,95,84,86,77,79, 88,4,69}; float average(int a[]); printf("average=%5.1f\n",average(score)); return 0; } 数组名做实参,数组首地址 float average(int a[]) { float ave ,s=0; int i; for(i=0;i<10;i++) s+=a[i]; ave=s/10; return ave; }
void add(int *a, int *b) { *a+=3; *b+=6; printf(“a=%d, b=%d\n”, *a,*b); } main( ) { int x=1, y=2; add(&x, &y); printf(“x=%d, y=%d\n”, x, y); }
void cop(int *p) { *p=10; } int main() { int a=6; printf("before a=%d\n",a); cop(&a); printf("after a=%d\n",a); return 0; } [例8] 分析以下程序的运行结果
8.1.5 函数的返回值 定义 通过函数调用使主调函数得到一个确定的值, 称为函数的返回值。 例如: c=max(3, 5); 此时函数的返回值是5,因此c=5。 2 . 函数的返回值语句return return语句的一般形式: return (表达式);或return 表达式; 说明: 1) return语句有两重作用:即从函数中退出,返回到调用函数中并向调用函数返回一个确定的值。
2) 一个函数可以有一个以上的return语句 。 fun(int x) { if(x>1) return 2*x+1; else if(x>=0) return x* x; else return 3*x–10; } return 语句后面的括弧可以省略。 例如: return z ; 4)当函数有返回值时,凡是允许表达式出现的地方,都可以调用该函数。例如: s=cube(n)+cube(n+6);
5) 函数定义时应该指定函数的类型,应该与return语句的类型一致。如果函数类型和return语句的类型不一致,以函数类型为准。对数值型数据,可以自动进行类型转换。即函数类型决定返回值的类型。 6) 如果被调用函数没有return语句,带回一个不确定的值。如果函数不返回值,可以将函数定义为“空类型”void。例如: void printstar( )
[例9] 用函数计算浮点数x的n次方(其中n为int型, n>=0)并在主函数中调用它。 double power(double x, int n) { double p; int i; for(p=i=1;i<=n;i++) p=p*x; return p; } void main() { printf(“%lf”, power(3,5)); }
8.1.6 函数的原型声明 通常,在调用函数之前,要在调用函数中对被调用函数进行声明,即通知编译系统有关函数返回类型的信息。 函数原型声明的一般形式: 类型标识符 被调函数名( ); 例如,在主函数中调用power函数时,可进行如下声明: main() { double power(); printf(“%lf”, power(3,5)); }
[例10] int main() { float add(float x, float y); float a, b, c; scanf("%f%f",&a, &b); c= add(a, b) ; printf(" sum is %f ", c); return 0; } float add (float x, float y) { float z; z=x+y; return z; } /*对被调用函数的说明*/ 函数作为表达式 被调用 /*定义add函数*/ 运行结果: 3.6, 5.5 sum is 9.100000
1)注意区分函数“定义”与“声明”。函数的定义是确定函数的功能,包括函数名,函数值类型,形参及类型和函数体全部内容。函数的声明只是对要被调用的函数的返回值的类型进行说明,它只包括函数名和函数类型,不包括形参和函数体。1)注意区分函数“定义”与“声明”。函数的定义是确定函数的功能,包括函数名,函数值类型,形参及类型和函数体全部内容。函数的声明只是对要被调用的函数的返回值的类型进行说明,它只包括函数名和函数类型,不包括形参和函数体。 2)现代风格的程序设计采用如下的函数声明形式: [类型说明符] 函数名(类型说明符 [形参1],……,类型说明符 [形参n]); 例如: float fun(float x, float y, int z); 也可写为: float fun(float, float, int); 说明:
3) 下列情况下不必在调用函数前对函数进行类型说明: 如果函数的返回值是整型或字符型,系统自动按整型说明。如: main( ) { int x=1,y=3; printf("%d+%d=%d\n", x, y, sum(x,y)); } sum(int a, int b) { return (a+b); } 直接调用,不需函数说明 整型函数
被调用函数出现在调用函数之前。例如: float add (float x, float y) { float z; z=x+y; return (z); } main( ) { float add(); float a, b, c; scanf(“ %f, %f, “&a, &b); c=printf(“ sum is %f “, add(a, b) ); } 被调函数在调用 函数前定义 此处可以不说明
如果已在所有函数定义之前,在文件的开头,在函数的外部已说明了函数类型,则在各个调用函数中不必对所调用的函数再做说明。如果已在所有函数定义之前,在文件的开头,在函数的外部已说明了函数类型,则在各个调用函数中不必对所调用的函数再做说明。 如: char letter( ); float f( ); int i( ); main( ) { ……. } char letter (char c1, char c2) { ……. } 在所有函数之前 说明函数类型 此处不必说明 定义letter函数
float f(float x,float y) { ……… } int fun (float j, float k ) { ………. } 定义f函数 定义fun 函数
# include “stdio.h” /*调用输入输出函数*/ # include “math.h” /*调用数学函数*/ # include “string.h” /*调用字符,字符串函数*/ # include “graphics.h” /*调用图形函数*/ 库函数及调用 库函数:由人们根据需要编制并提供给用户使用的一些基本函数,在用户定义的函数中常常调用这些函数。 方法:在文件的开头用#include命令将需要的库函数包含到文件中。
8.2 数组作函数参数 • 数组元素可以作函数的实参 • 由于表达式可以做实参,数组元素可以作为表达式的组成部分,因此,数组元素可以做函数的实参,并且可以单向传递给形参。 • 例如:int a[10]; • ······ • x=power( a[6],5); • 数组名可以作函数的实参和形参 • 多维数组可以作函数参数
8.2.1一维数组作函数参数 一维数组作函数参数,形参的写法为: 类型说明符 形参数组名[数组长度 ] 例如: float average(float array[20]) { … }
[例11] 趣味提高题:判断字符串是否是回文数 #include <stdio.h> int fun(char str[]) { int n,k,flag=1; for(n=0;(str[n]!='\0');n++); for(k=0;k<n/2;k++) if(str[k]!=str[n-k-1]) { flag=0;break;} return flag; } int main() { char s[80]; gets(s); if(fun(s)) printf("%s是回文数\n",s); else printf("%s不是回文数\n",s); return 0; }
[例12] 写函数:打印整型一维数组的n个元素 void print_array( int a[ ], int n) { int i; for(i=0;i<n; i++) printf(“%3d”, a[i]); } [例13] 写一函数求n元数组的平均值 double average(int a[ ], int n) { int i; int s=0; for(i=0; i<n; i++) s=s+a[i]; return (double)s/n; }
[例14] 写函数求字符串长度 len(char s[ ]) { int i=0; for( ; s[i]; i++) ; return i; }
[例15] 阅读如下程序,给出打印结果 void swap( int a[ ]) { int t; t=a[0]; a[0]=a[1]; a[1]=t; } main( ) { int b[2]={1,2} ; swap(b); printf(“b[0]=%d,b[1]=%d\n”,b[0],b[1]); }
8.2.2二维数组作函数参数 二维数组作函数参数,形参的写法为: 类型说明符 形参数组名[ 数组长度1 ][数组长度2] 例如: srh_min(int a[3][4]) { … } 或: srh_min(a) int a[3][4]; { … }
说明: 形参数组定义时可以指定或省略第一维的大小。 例如: srh_min(int a[ ][4],int n) { … } 或: srh_min(a,n) int a[ ][4],n; { … }
[例16 ]写函数交换任意一个n*6的二维数组的i,j两行 void exchange(int a[ ][6], int i,int j) { int k, t; for(k=0;k<6;k++) { t=a[i][k]; a[i][k]=a[j][k]; a[j][k]=t;} }
[例17] 写函数打印n行6列二维数组,其中n是任意整数 void print_array(int a[ ][6], int n) { int i, j; for(i=0;i<n; i++) for(j=0;j<6;j++) printf(“%3d”, a[i][j]); printf(“\n”); }
[例18] n行10列int型数组的每一行都有一个最大值,写一函数,求这n个最大值的最小值. max_element(int a[ ],int m) { int max, i; max= a[0]; for(i=1;i<m;i++) if(a[i]>max) max=a[i]; return max; }
max_min( int a[ ], int n) { int i,min, max; min=max_element(a[0], 10); for(i=1;i<n;i++) { max=max_element(x[i],10); if(min>max) min=max; } return min; }
8.2.3数组作为函数参数的函数调用方式 数组做函数参数时,实参应用数组名。 例如: print_array(a, n); exchange(array, 3, 6); 说明: 1)数组做函数参数时,实参和形参应在调用函数和被调用函数中分别定义。 2)实参数组和形参数组类型应一致,大小可以不一致。
[例19]用函数求5行5列数组主对角线最小值,元素从主函数键盘输入。[例19]用函数求5行5列数组主对角线最小值,元素从主函数键盘输入。 int main() { int a[5][5],i,j,min; for(i=0;i<5;i++) for(j=0;j<5;j++) scanf("%d",&a[i][j]); for(i=0;i<5;i++) { for(j=0;j<5;j++) printf("%-4d",a[i][j]); printf("\n"); } min=funmin(a); printf("%d\n",min); return 0; } #include <stdio.h> int funmin(int b[5][5]) { int i,min; min=b[0][0]; for(i=0;i<5;i++) if (min>b[i][i]) min=b[i][i]; return min; }
[例20] 请编写函数 int fun(char str [ ]), 它的功能是判别字符串str是否是“回文”, 若是,返回 1,否则返回 0。(提示:回文是指正反序相同,例如,“13531”、“helleh”是回文, 但 “1353”、“Helleh” 不是回文。) 分析:假设已知字符串的长度为n,检查回文的算法可设计如下: 第0个字符和第n–1个字符比较,不相等则不是回文,相等则继续进行下面的操作; 第1个字符和第n–2个字符比较,不相等则不是回文,相等则继续进行下面的操作; …… 第n/2–1个字符和第n–1–(n/2–1)个字符比较,不相等则不是回文,相等则是回文。