1 / 76

第 7 章 函数与预处理命令

C 语言程序设计. 第 7 章 函数与预处理命令. 北京科技大学 计算机系. 第 7 章 函数与预处理命令. 7.1 概述 7.2 函数的定义与调用 7.3 数组作函数参数 7.4 函数的嵌套调用和递归调用 7.5 局部变量和全局变量及其作用域 7.6 变量的存储类别及变量的生存期 7.7 函数的存储分类 7.8 编译预处理 7.9 多文件程序的调试方法. 7.1 概述. C 程序. … …. 源程序文件 1. 源程序文件 n. 函数 1. 函数 n. …. …. 函数 1. 函数 m. ① 程序结构清晰,可读性好。

phuong
Download Presentation

第 7 章 函数与预处理命令

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. C语言程序设计 第7章 函数与预处理命令 北京科技大学 计算机系

  2. 第7章 函数与预处理命令 7.1 概述 7.2 函数的定义与调用 7.3 数组作函数参数 7.4 函数的嵌套调用和递归调用 7.5 局部变量和全局变量及其作用域 7.6 变量的存储类别及变量的生存期 7.7 函数的存储分类 7.8 编译预处理 7.9 多文件程序的调试方法

  3. 7.1 概述 C程序 … … 源程序文件1 源程序文件n 函数1 函数n … … 函数1 函数m ① 程序结构清晰,可读性好。 ② 减少重复编码的工作量。 ③ 可多人共同编制一个大程序,缩短程序设计周期,提高程序设计和调试的效率。 使用函数的 好处

  4. 【例7.1】求一个整数的立方。 int cube (int x)/* 函数定义 */ { return (x * x * x); } main( ) { int f, a; printf("\nEnter an integer number:"); scanf("%d", &a); f = cube (a); printf("%d * %d * %d = %d\n", a, a, a, f); } 程序的执行总是 从main函数开始 函数调用 程序运行情况如下: Enter an integer number:2 2 * 2 * 2 = 8

  5. 说明 ⑴ 一个C源程序可以由一个或多个源程序文件组成。C编译系统在对C源程序进行编译时是以文件为单位进行的。 ⑵ 一个C源程序文件可以由一个或多个函数组成。所有函数都是独立的。主函数可以调用其它函数,其它函数可以相互调用。 ⑶ 在一个C程序中,有且仅有一个主函数main。C程序的执行总是从main函数开始,调用其它函数后最终回到main函数,在main函数中结束整个程序的运行。

  6. ⑷ 函数的种类 从函数定义形式分: ① 有参函数: 在主调(用)函数和被调(用)函数之间通过参数进行数据传递, 如: int cube (int x) { … } ② 无参函数: 如:getchar( ) 在调用无参函数时,主调函数不需要将数据传递给无参函数。 从使用的角度看: ① 标准函数(库函数) 库函数是由系统提供的。如:getchar( )、sin(x)等。在程序中可以直接调用它们。附录A列出了C的部分库函数。 ② 用户自定义函数。 如:例7.1中的cube函数。

  7. 【例7.2】无参函数的定义与调用。 void welcome ( ) { printf("*********************************\n"); printf(" Welcome to China \n"); printf("*********************************\n"); } main( ) { welcome( );} 程序的输出结果如下: ********************************* Welcome to China *********************************

  8. 7.2.1 函数的定义 没有形式参数为无参函数 函数定义的一般形式 • 函数类型 函数名(类型名 形式参数1,…) • { 说明语句 • 执行语句 • } 例如:求两个数的最大值。 intmax(int x,int y) { int z; z = x > y ? x : y; return( z ); } 类型省略时默认为int类型

  9. 如下定义都是错误的 形参也可以这样定义 int max(x,y) int x,y; { int z; z = x > y ? x : y; return( z ); } int max(x,y) { int x,y; …… } 或 int max(int x,y) { …… } 或 int max(x,y) int x,y,z; { z = x > y ? x : y; return( z ); } √   花括号中也可以为空,这种函数叫空函数 。 不能在函数体内定义其他函数,即函数不能嵌套定义。

  10. 7.2.2 函数的调用 函数调用的一般形式: 函数名(实参表列) 在C语言中,把函数调用也作为一个表达式。因此凡是表达式可以出现的地方都可以出现函数调用。例如: ① welcome( ); ② if (iabs (a)>max) max=iabs(a); ③ m=max(c,max(a,b));

  11. 【例7.3】求1~100的累加和。 思考:两个程序有何不同 int sum ( int x ) { int i,t=0; for (i=1; i<=x; i++) t+=i; return (t); } main( ) { int s; s=sum (100); printf("%d\n", s); } int sum100( ) { int i,t=0; for (i=1; i<=100; i++) t+=i; return (t);} main( ) { int s; s=sum100( ); printf("%d\n", s); } ? 程序输出结果: 5050 程序输出结果: 5050

  12. 7.2.3 函数参数与函数的返回值 1.函数的形式参数与实际参数 【例7.4】编一程序,将主函数中的两个变量的值传递给swap函数中的两个形参,交换两个形参的值。 void swap(int x, int y) { int z; z=x; x=y; y=z; printf("\nx=%d,y=%d",x ,y); } main( ) { int a= 10,b=20; swap(a,b); printf("\na=%d,b=%d\n",a,b); } 形式参数(形参) 单向值传递 实际参数(实参) 程序输出结果: x=20,y=10 a=10,b=20

  13. 有关形参和实参的说明: ① 当函数被调用时才给形参分配内存单元。调用结束,所占内存被释放。 ② 实参可以是常量、变量或表达式,但要求它们有确定的值。 ③ 实参与形参类型要一致,字符型与整型可以兼容。 ④ 实参与形参的个数必须相等。在函数调用时,实参的值赋给与之相对应的形参。“单向值传递”。 注意:在TC中,实参的求值顺序是从右到左。

  14. 【例7.5】函数调用中实参的求值顺序。 void fun(int a,int b) { printf("a=%d,b=%d\n",a,b); } main( ) { int m=5; fun(3+m, m++); } 程序输出结果: a=9,b=5

  15. 2.函数的类型与函数的返回值 ⑴ 函数的类型 【例7.6】输出两个数中的大数。 max(int x,int y) { int z; z=x>y?x:y; return (z); /* 返回z的值 */ } main( ) { int a,b,c; scanf("%d,%d",&a,&b); c=max(a,b); printf("max is %d\n",c); } 说明: ①函数的类型决定了函数返回值的类型。若省略函数的类型,系统默认其为int型。 ②无返回值的函数应将其类型定义为void (空)类型。

  16. ⑵ 函数的返回值 函数的返回值是通过return语句带回到主调函数的 功能:终止函数的运行,返回主调函数,若有返回值,将返回值带回主调函数。 return 语句格式: return (表达式);或 return 表达式 ; 或 return; 说明: ① 若函数没有返回值,return语句可以省略。 ② return语句中的表达式类型一般应和函数的类型一致,如果不一致,系统自动将表达式类型转换为函数类型。

  17. 【例7.8】计算并输出圆的面积。 s(int r) { return 3.14*r*r;} main( ) { int r,area; scanf("%d",&r); printf("%d\n",s(r)); } 自动转换为int型 程序运行情况如下: 2 12 ? 思考: 若要得到单精度实型的圆面积,程序应如何修改

  18. 7.2.4 对被调函数的声明和函数原型 变量要先定义后使用,函数也如此。即被调函数的定义要出现在主调函数的定义之前。如swap函数: 允许整型函数(且参数也是整型)的定义出现在主调函数之后。如max函数: 如果非整型函数在主调函数之后定义,则应在主调函数中或主调函数之前对被调函数进行声明。 void swap(int x, int y) { …} main( ) { … swap(a,b); } main( ) { … c=max(a,b); } max(int x,int y) { …}

  19. 对被调函数进行声明的一般形式 函数类型 函数名(参数类型1参数名1,…); 或 函数类型 函数名(参数类型1,参数类型2,…); 第二种形式省略了参数名,此种形式也称为函数的原型。 思考: 以下哪种情况需要在主调函数中对被调函数声明 • 被调函数定义在前,主调函数定义在后。 • 主调函数定义在前,被调函数定义在后,且被调函数的类型不是整型的。 • 被调函数定义在后,但被调函数的类型是整型。 ?

  20. 【例7.9】计算并输出两个数的和、差、积、商。【例7.9】计算并输出两个数的和、差、积、商。 main( ) { void calc(float x,float y,char opr); float a,b; char opr; printf("\nInput expression:"); scanf("%f%c%f",&a,&opr,&b); calc(a,b,opr); } void calc(float x,float y,char opr) { switch(opr) { case '+':printf("%5.2f%c%5.2f=%6.2f\n",x,opr,y,x+y);return; case '-':printf("%5.2f%c%5.2f=%6.2f\n",x,opr,y,x-y);return; case '*':printf("%5.2f%c%5.2f=%6.2f\n",x,opr,y,x*y);return; case '/':printf("%5.2f%c%5.2f=%6.2f\n",x,opr,y,x/y);return; default :printf("Operator err! \n"); } } 对被调函数的声明

  21. 【例7.10】 哥德巴赫猜想之一是任何一个大于5的偶数都可以表示为两个素数之和。验证这一论断。 • 输入n的值 • for (a=6; a<=n; a+=2) • for(b=3;b<=a/2;b+=2) • b是素数 ? • T F • c=a-b • c是素数? • T F • 输出: • a、b、c的值 • break; 图 7.2 验证哥德巴赫猜想

  22. 程序如下: 可以在main函数的前面对prime函数进行声明。实际上,该声明可以省略,为什么? #include "math.h" int prime(int n); main( ) { int a,b,c,n; scanf("%d",&n); for (a=6; a<=n; a+=2) for (b=3; b<=a/2; b+=2) if (prime(b)) { c=a-b; if (prime(c)) { printf("%d=%d+%d\n",a,b,c); break; } } } /* 穷举法判断素数 */ int prime(int n) { int i; for (i=2; i<=sqrt(n); i++) if (n%i==0) return 0; return 1; }

  23. 7.3 数组作函数参数 7.3.1 一维数组元素作函数参数 【例7.11】求5个数中的最小值。 int min(int x, int y) { return (x<y?x:y); } main( ) { int a[5],i,m ; for (i=0; i<5; i++) scanf("%d",&a[i]); m=a[0]; for (i=1; i<5; i++) m=min(m,a[i]); printf("%d\n", m); } 用打擂台方法求最小值。m相当于擂主

  24. 数组名表示数组在内存中的起始地址。 例如:数组a在内存中从2000地址开始存放,则a的值为2000。2000是地址值,是指针类型的数据(第8中将介绍指针类型),不能把它看成是整型或其他类型数据。 实参是数组名,形参也应定义为数组形式,形参数组的长度可以省略,但[ ]不能省,否则就不是数组形式了。 【例7.12】用冒泡法将10个整数排序。 7.3.2 一维数组名作函数参数

  25. void printarr(int b[10]) { int i; for (i=0; i<10; i++) printf("%5d",b[i]); printf("\n");} void sort(int b[ ],int n); void printarr(int b[ ]); main( ) { int a[10] = {11,22,63,97,58,80,45, 32,73,36}; printf("Before sort:\n"); printarr(a); sort(a,10); printf("After sort:\n"); printarr(a); } void sort(int b[ ], int n) { int i,j,t; for (i=1; i<n; i++) for (j=0; j<n-i; j++ ) if (b[j]>b[j+1]) { t=b[j];b[j]=b[j+1];b[j+1]=t; } }

  26. a[0] a[1] a[2] a[3] a[4 ] a[5] a[6] a[7] a[8] a[9] b[0] b[1] b[2] b[3] b[4] b[5] b[6] b[7] b[8] b[9] 首地址: 2000 首地址: 2000 (a) 排序前 a[0] a[1] a[2] a[3] a[4 ] a[5] a[6] a[7] a[8] a[9] b[0] b[1] b[2] b[3] b[4] b[5] b[6] b[7] b[8] b[9] (b) 排序后 11 22 63 97 58 80 45 32 73 36 11 22 32 36 45 58 63 73 80 97 实参赋给形参 形参 b 实际是一个 可以存放地址的变量 2000 a:2000 b 图7.3 调用sort函数

  27. 【例7.13】编程序,实现字符串连接。 #include "stdio.h" main( ) { void scat(char str1[ ],char str2[ ]); char s1[50],s2[50]; int i,k; printf("Input s1:"); gets(s1); printf("Input s2:"); gets(s2); scat(s1,s2); printf("Output s1:%s\n",s1); printf("Output s2:%s\n",s2); } void scat(char str1[ ],char str2[ ]) { int i=0,k=0; while (str1[i]!='\0') i++; while (str2[k]!='\0') { str1[i]=str2[k]; i++; k++; } str1[i]='\0'; } scat函数还可简化为: void scat(char str1[ ],char str2[ ]) { int i=0,k=0; while (str1[i]) i++; while (str1[i++]=str2[k++]); }

  28. 7.3.3 多维数组作函数参数 1 59 2 610 3 711 4 812 1 2 3 4 5 6 7 8 9 10 11 12 a= b= 以二维数组为例。二维数组名作实参时,对应的形参也应该定义为一个二维数组形式。对形参数组定义时可以指定每一维的大小,也可以省略第一维的大小说明。 【例7.14】编程序,将矩阵转置。设转置前为a矩阵,转置后为b矩阵,如下所示: 思路:将a[0][0]b[0][0],a[0][1] b[1][0],a[0][2]b[2][0], a[1][0]b[0][1],…,a[i][j]b[j][i],…。

  29. void turn( ); main( ) { int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}}; int i,j,b[4][3]; … turn(a,b); printf("array b:\n"); for (i=0; i<4; i++) { for (j=0; j<3; j++) printf("%5d",b[i][j]); printf("\n"); } } /* 矩阵转置函数 */ void turn(int arra[ ][4],int arrb[ ][3]) { int r, c; for (r=0; r<3;r++) for (c=0; c<4; c++) arrb[c][r]=arra[r][c]; }

  30. 7.4.1 函数的嵌套调用 7.4 函数的嵌套调用和递归调用 main函数 {…… 调用函数 A; …… } 函数 B {…… …… …… } 函数 A { …… 调用函数 B; …… }

  31. 【例7.15】函数的嵌套调用 main( ) { int n=3; printf ("%d\n",sub1(n)); } sub1(int n) { int i,a=0; for (i=n; i>0; i--) a+=sub2(i); return a ; } sub2(int n) { return n+1; } 程序输出结果: 9

  32. 7.4.2 函数的递归调用 直接调用sub 函数本身 1.递归的基本概念 递归调用:一个函数直接或间接地调用了它本身,就称为函数的递归调用。 递归函数:在函数体内调用该函数本身。 例如: int sub(int x) { int y,z; …… if( …… ) z=sub(y); else { …… } return ; }

  33. 2.递归函数的执行过程 1 (n=0, 1) n×(n-1)! (n>1) n! = 【例7.16】编一递归函数求n!。 思路:以求4的阶乘为例: 4!=4*3!,3!=3*2!,2!=2*1!,1!=1,0!=1。 递归结束条件:当n=1或n=0时,n!=1。 递归公式:

  34. 程序如下: float fact (int n) { float f=0; if(n<0) printf("n<0,error!"); else if (n==0 || n==1) f=1; else f=fact(n-1)*n; return (f); } main( ) { int n; float y; printf("\nInput n:"); scanf("%d",&n); y=fact(n); printf("%d!=%-10.0f\n",n,y); } 运行情况如下: Input a integer number:4 4!=24

  35. 递归调用过程 回 推 main( ) fact(4) fact(3) fact(2) fact(1) { { { { { … … … … … y=fact(4); f=4*fact(3); f=3*fact(2); f=2*fact(1); f=1; … … … … … return 24 return 6 return 2 return 1 } } } } } 递 推

  36. 3.编制递归函数的方法 1 (n=0) x × xn - 1(n>0) xn = ⑴ 数值型问题递归函数的编程方法 对于数值型问题,首先要找出解题的数学公式,这个公式必须是递归定义的,且所处理的对象要有规律地递增或递减,然后确定递归结束条件。 【例7.17】编一递归函数求xn。 思路:首先把xn转化成递归定义的公式 再找出递归结束条件:当n=0时,xn=1。

  37. 程序如下: long xn(int x,int n) { long f=0; if (n<0) printf("n<0,data error!\n"); else if (n==0) f=1; else f=x*xn(x,n-1); return (f); } main( ) { int n,x; long y; scanf("%d,%d",&x,&n); y=xn(x,n); printf("%ld\n",y); } 程序运行情况如下: 2,10 1024

  38. ⑵ 非数值型问题递归函数的编程方法 有些问题不能直接用数学公式求解。非数值型问题比数值型问题更难找出递归的算法。它不能用一个递归公式表示。解决这类问题首先要把问题将大化小,将繁化简。将一个复杂的问题化解成若干个相对简单的小问题,而某个小问题的解法与原问题解法相同,并且越来越简单直至有确定的解。 【例7.18】编制一递归函数,将一个十进制正整数(如:15613)转换成八进制数形式输出。

  39. ⑵ 非数值型问题递归函数的编程方法(续) 余数: 商: 15613%8=5 15613/8=1951 1951%8=7 1951/8=243 243%8=3 243/8=30 30%8=6 30/8=3 3%8=3 3/8=0 结果:36375 图 7.5 十进制转换成八进制 思路:十进制整数转换成八进制整数的方法是除8逆向取余。如图7.5所示。

  40. ⑵ 非数值型问题递归函数的编程方法(续) • 该题实际上是要把一个十进制数除以8得到的余数逆向输出。就是先得到的余数后输出,最后得到的余数最先输出。 • 我们先由大化小:求八进制数变成求一系列余数的问题。求第一个余数是将15613除以8取余,因为先得到的余数后输出,所以把这个余数存在一个变量m中,接下去求下一个余数。和求第一个余数的方法相同,只是被除数变成了15613除以8的整数商1951。因此,这是一个递归调用的问题。定义变量m存放余数,x存放被除数。递归算法描述如下:

  41. ⑵ 非数值型问题递归函数的编程方法(续) ① 先求出余数m:m=x%8; ② 求x除以8取余后的整数商:x=x/8; ③ 如果x不等于0,递归调用该函数,否则执行④。 ④ 输出余数m。 ⑤ 返回调用点。

  42. 程序如下: #include "stdio.h" void dtoo(int x) { int m; m=x%8; x=x/8; if (x!=0) dtoo(x); printf("%d",m); } main( ) { int n; scanf("%d",&n); printf("%d=(",n); dtoo(n); printf(")8\n"); } 程序运行情况如下: 15613 15613=(36375)8

  43. 7.5 局部变量和全局变量及其作用域 问题:一个变量在程序的哪个函数中都能使用吗? 7.5.1 变量的作用域 变量的作用域:变量在程序中可以被使用的范围。 根据变量的作用域可以将变量分为局部变量和全局变量。 7.5.2 局部变量及其作用域 局部变量(内部变量):在函数内或复合语句内定义的变量以及形参。 作用域:函数内或复合语句内。 【例7.19】分析下面程序的运行结果及变量的作用域。

  44. 程序输出结果: main: a=1 b= 1 c= 1 sub: a=2 b= 3 c= 1 main: a=1 b= 1 c= 1 comp: a=2 b= 2 c= 1 main: a=1 b= 1 c= 1 局部变量 局部变量 局部变量 void sub(int a,int b) { int c; a=a+b; b=b+a; c=b-a; printf("sub:\ta=%d b= %d c= %d\n",a,b,c); } main( ) { int a=1,b=1,c=1; printf("main:\ta=%d b= %d c= %d\n",a,b,c); sub(a,b); printf("main:\ta=%d b= %d c= %d\n",a,b,c); { int a=2,b=2; printf("comp:\ta=%d b= %d c= %d\n",a,b,c); } printf("main:\ta=%d b= %d c= %d\n",a,b,c); } “分程序”或“程序块”

  45. 7.5.3 全局变量及其作用域 全局变量(外部变量):在函数外部定义的变量。 作用域:从定义变量的位置开始到本源文件结束。如在其作用域内的函数或分程序中定义了同名局部变量,则在局部变量的作用域内,同名全局变量暂时不起作用。 【例7.20】全局变量和局部变量的作用域。

  46. main( ) { int b=6,c=7; f(b,c); printf("%d\t%d\t%d\n",a,b,c); { int a=9,b=8; printf("%d\t%d\t%d\n",a,b,c); {c=10; printf("%d\t%d\t%d\n", a,b,c); } printf("%d\t%d\t%d\n",a,b,c); } printf("%d\t%d\t%d\n",a,b,c); } 全局变量 局部变量 局部变量 局部变量 int a = 5; void f(int x, int y) { int b,c; b=a+x; c=a-y; printf("%d\t%d\t%d\n",a,b,c); } 程序输出结果: 5 11 -2 5 6 7 9 8 7 9 8 10 9 8 10 5 6 10

  47. 7.6 变量的存储类别及变量的生存期 程序代码区 静态存储区 动态存储区 存储分配 思考:1. 何时为变量分配内存单元? 2. 将变量分配在内存的什么区域? 3. 变量占据内存的时间(生存期)? 7.6.1 变量的生存期与变量的存储分类 变量的生存期:变量在内存中占据存储空间的时间。 静态存储变量 动态存储变量

  48. 7.6.2 变量的存储类别 数据类型: 决定为变量分配内存单元的长度, 数据的存放形式, 数的范围。 变量的属性 存储类别: 决定了变量的生存期, 给它分配在哪个存储区。

  49. 变量定义语句的一般形式 可省 main(){int x,y; … } main(){auto int x,y; … } 等价 自动变量 存储类别 数据类型 变量名1, … , 变量名n ; auto(自动的)register(寄存器的)static(静态的)extern(外部的) 1.自动变量(auto类别) 局部变量可以定义为自动变量。

  50. 自动变量 ⑴ 内存分配 调用函数或执行分程序时在动态存储区为其分配存储单元,函数或分程序执行结束,所占内存空间即刻释放。 ⑵ 变量的初值 定义变量时若没赋初值,变量的初值不确定;如果赋初值则每次函数被调用时执行一次赋值操作。 ⑶ 生存期 在函数或分程序执行期间。 ⑷ 作用域 自动变量所在的函数内或分程序内。

More Related