C语言程序设计第6章(姜恒远著).ppt_第1页
C语言程序设计第6章(姜恒远著).ppt_第2页
C语言程序设计第6章(姜恒远著).ppt_第3页
C语言程序设计第6章(姜恒远著).ppt_第4页
C语言程序设计第6章(姜恒远著).ppt_第5页
已阅读5页,还剩123页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第6章 模块化程序设计与函数,2,主要内容,6.1 函数概述 6.2 函数定义 6.3 函数返回 6.4 函数调用 6.5 标识符的作用域 6.6 变量的存储属性 6.7* 参数个数可变函数的定义及调用 6.8 宏定义与宏调用,3,6.1 函数概述,6.1.1 模块化程序设计 6.1.2 函数,4,6.1.1 模块化程序设计,大规模程序开发策略模块化 模块化程序开发基本步骤 (1) 设计阶段 自顶向下将一个原始复杂任务分解为多个较简单的子任务。 (2)编码阶段 自低向上为每个单一功能的子任务设计算法,将描述其算法的一组语句封装为一个独立代码块,为每个独立代码块定义一个名字和能与其他独立代码块通信的接口。 模块化策略开发程序的优点 从整体上简化概念结构,降低程序开发和修改的复杂度,提高程序可读性。,5,6.1.2 函数,函数是什么 将一组语句封装为一个独立代码块的实现方法。 与函数有关的3个语法概念 函数声明,函数调用,函数返回。,6,6.1.2 函数,#include #include int max(int x,int y); /* 函数引用性声明 */ int min(int x,int y); /* 函数引用性声明 */ int main(void) /* 函数定义性声明 */ char cmd; int a,b,c; printf(“nInput two number:”); scanf(“%d%d”, ,7,6.1.2 函数,printf(“n result is %d”,c); return 0; int max(int x,int y) /* 函数定义性声明 */ int z; if(xy) z= x; else z=y; return z; int min(int x,int y) /* 函数定义性声明 */ if(xy) return x; else return y; ,8,6.1.2 函数,(1) 函数定义性声明(简称“函数定义”) 将一组语句和一组可接收外部数据的变量定义为一个独立代码块,用函数名(符号)标识。 例如: int max(int x,int y) /* 函数定义 */ int z; if(xy) z= x; else z=y; return z; ,函数声明,9,6.1.2 函数,函数定义是C源程序必不可少的构件。 一个源程序中包含的所有函数定义可按任意次序保存在一个源文件(扩展名“.c” )中,也可以经分割后保存在多个源文件中。 不允许将一个函数定义分割开来保存在多个源文件中。 函数定义的分类: 库函数:不需定义,可多次调用,例 sqrt 自定义函数:定义一次,可多次调用,例 min,函数定义性声明,10,6.1.2 函数,函数引用性声明(简称“函数声明”) 描述一个函数的特性及扩大函数名的作用域。 例如:int max(int x,int y);,函数引用性声明,11,6.1.2 函数,从当前正在执行的函数跳转到另一个函数执行或重新开始执行本函数的操作。 例如: max(a,b) min(a,b) printf(“n result is %d”,c),函数调用,12,6.1.2 函数,结束被调函数执行返回主调函数的操作。 函数返回时,被调函数可以将一个结果值带回主调函数,这个值被称为“函数返回值” 。 例如: int max(int x,int y) int z; if(xy) z= x; else z=y; return z;/* 函数返回语句,z:返回值*/ ,函数返回,13,6.2 函数定义,6.2.1 函数定义形式 6.2.2 函数名 6.2.3 返回值类型 6.2.4 形式参数 6.2.5 函数体 6.2.6 存储类型,14,6.2.1 函数定义形式,int min(int x,int y) /* 函数头部 */ /* 函数体开始 */ int z; /* 局部变量声明 */ z=xy? x:y; /* 语句 */ return z; /* 语句 */ /* 函数体结束 */,函数名,返回值类型,形式参数声明,15,6.2.1 函数定义形式,函数定义一般形式 存储类型说明符 返回值类型说明符 函数名(形式参数声明列表) 声明序列 语句序列 可缺省:存储类型说明符、返回值类型说明符、形式参数声明列表,16,6.2.2 函数名,函数名是函数的标识,按标识符命名规则确定。 自定义函数名的作用域规则 不允许与同一源程序中其它自定义函数同名,允许与库函数同名; 不允许与同一源程序中全局变量同名,允许与局部变量同名,允许与形式参数同名。,17,6.2.3 返回值类型,定义无返回值函数时返回值类型说明符为void 例如: void print_value(int x) printf(“x = %d”,x); ,18,6.2.3 返回值类型,定义有返回值函数时必须在返回值类型说明符位置给出返回值的数据类型。 函数返回值的数据类型允许是基本类型、指针类型、结构类型、联合类型和枚举类型; 函数返回值的数据类型不允许是数组类型和函数类型。 C89标准允许缺省返回值类型说明符,缺省时编译系统假定函数的返回值类型为int。,19,6.2.4 形式参数,形式参数是一个函数与其他函数之间传递数据的接口 定义无参函数时形式参数声明为void 例如: void print_word(void) printf(“hello”); ,20,6.2.4 形式参数,定义有参函数时形式参数的一般声明形式 存储类型说明符 数据类型说明符 形式参数名 存储类型说明符: 表示形式参数的存储属性,只能是auto与register,允许缺省,缺省时为auto。 形式参数名: 是形式参数的标识,是一种局部变量,仅在该函数体内使用,在其他函数中不可见。 不允许在一个函数中定义同名的多个形式参数,允许在不同函数中定义同名的形式参数。,21,6.2.4 形式参数,数据类型说明符: 指定形式参数的数据类型。 形式参数允许: 基本类型 指针类型 结构类型 联合类型 枚举类型 数组类型 函数类型,22,*6.2.4 形式参数,编译系统自动将“数据类型为T的一维数组类型”调整为“基类型为T的指针类型” 例如: void fun(int x10) /*或void fun(int x )*/ x+; 形式参数声明中的“int x10”或“int x”在编译时被调整为“int *x”,23,*6.2.4 形式参数,编译系统自动将“返回T类型值的函数类型”调整为“指向返回T类型值函数的指针类型” 例如: double fun(double pf(double x) printf(“%f“,pf(1.5); 形式参数声明中的“double pf(double x)”在编译时被调整为“double (*pf)(double)”,24,*6.2.4 形式参数,当定义一个形式参数时使用了const限定,表示在函数体中不允许改变该形式参数的值。 例如: int f(const int x) return +x; /* 编译错: Cannot modify a const object */ ,25,*6.2.4 形式参数,C99增加了一个类型限定符restrict,该限定符仅用于指针型标识符,表示被限定的指针型标识是引用该指针所指向对象的唯一标识符。 例如: void f(char *restrict p1, char *restrict p2) Restrict在这里表示p1和p2指向的对象只能通过p1和p2访问,26,6.2.5 函数体,函数体是一个复合语句(代码块) 当复合语句中有多条声明(变量定义性声明与引用性声明、函数引用性声明)时,C89要求所有声明必须出现在第一条语句之前,C99则允许分散的声明和语句。例如: int main(void) int a; a=1; int b; /* C89报错, C99正确 */ for(int i=1; i10; i+) /* C89报错,C99正确 */ a*=i; ,27,6.2.6 存储类型,函数头部中的存储类型说明符定义了函数名作为一个外部标识符在多文件程序中的连接属性 可使用的存储类型说明符是extern和static。 缺省存储类型说明符时系统默认为extern。,28,6.3 函数返回,函数返回通过return语句实现 return 语句的三个用途 结束函数的执行并返回到主调函数 向主调函数传递回一个函数类型的返回值; 归还函数中定义的局部对象的存储空间,29,6.3 函数返回,return语句的两种形式 return; 返回主调函数 void型函数用“return;”完成返回操作; 函数体结束的右花括号等同于一个“return;”语句。 return (e); 返回主调函数并将e值带回主调函数 非void类型函数用“return (e);”完成返回操作并将e值带回主调函数。 e:返回值表达式,圆括号可省略。数据类型允许是基本类型、指针类型、结构类型、联合类型、枚举类型。,30,6.3 函数返回,C89允许非void类型函数中使用“return;”返回,对于int 型函数将返回给主调函数0值;对于其他类型函数将返回一个不确定的值并常导致返回值数据错误程序运行出错。 C99规定非void类型函数必须使用“return (e);”返回。,31,6.3 函数返回,一个函数是否有返回值及返回值类型的决定因素是使用什么返回值类型说明符。 函数每次调用后返回什么值的决定因素是执行什么“return (e);”语句。允许“return (e);”中e的类型与返回值类型说明符不同,但两者应赋值兼容。 例如: int f(double x) return x*x; 最终f函数返回一个int类型值给主调函数。,32,6.3 函数返回,不能通过return语句返回多个值给主调函数 例如: int f(int x,int y) return (x,y); f函数被调用时总是返回y的值,33,6.3 函数返回,当函数的返回值类型为指针类型时,应返回指针类型形式参数的值或static型局部变量的地址。 例如: char *f(char *x, char y) char z=y; *x=y; return f函数返回z的地址或y的地址是不可靠的,返回x的值是可靠的。,static,34,6.3 函数返回,main函数的特殊性 函数名不能自定义; 既允许带形式参数也允许不带形式参数。若有其形式参数,其数量和类型不能随意定义; 函数头部:C89允许“int main()”、”main()”或“void main()”;C99规定必须为“int main()”。 程序开始运行时main函数首先被操作系统调用;执行到main函数中任一return语句或函数体结束右花括号时,结束程序运行。,35,6.4 函数调用,6.4.1 函数的引用性声明 6.4.2 函数调用 6.4.3 函数调用时的参数传递 6.4.4 函数间数据通信的实现 6.4.5 递归函数,36,6.4 函数调用,一个函数调用另一个函数意味着将程序执行流程转移到被调用函数。 正确实现函数间相互调用必须满足以下条件 被调用函数存在且允许被调用; 给出满足被调函数运行时要求的参数值; 在调用函数前对被调用函数做引用性声明; 按指定格式调用。,37,6.4.1 函数的引用性声明,引用性函数声明的目的 扩大函数名的作用域 为编译系统提供被调函数的形式参数和返回值信息,使得编译系统可以对随后出现的所有对该函数调用的参数与返回值作一致性检查并作适当的类型转换。 函数引用性声明通常被称为“函数原型”,38,6.4.1 函数的引用性声明,库函数的引用性声明(函数原型) 分类保存在一组系统头文件中。 例如: 某程序中调用了sqrt函数,在sqrt调用之前出现“#include”表示在#include处添加了sqrt函数的引用性声明“double sqrt(double x);”。,39,6.4.1 函数的引用性声明,自定义函数的引用性声明(函数原型) 例如: int max(int x,int y) int z; if(xy) z= x; else z=y; return z; max函数的引用性声明通常是:int max(int x,int y);,40,6.4.1 函数的引用性声明,函数引用性声明一般形式 存储类型说明符 返回值类型说明符 函数名(形式参数类型列表); 形式参数类型列表中形式参数允许有两种形式: 数据类型 数据类型 参数名 例如,max函数的引用性声明也允许如下: int max(int a,int b); int max(int,int);,41,6.4.1 函数的引用性声明,函数定义本身也是函数声明。 若将函数定义放在该函数所有调用之前则不需要该函数的引用性声明。 若将函数定义放在该函数调用之后并且在函数调用前没有该函数的引用性声明: C89编译系统在函数调用所在最内层代码块中自动创建一个隐含函数声明“extern int 函数名();” C99编译系统显示警告性错误。,42,6.4.2 函数调用,直接函数调用 用函数名、圆括号及实际参数表达式列表调用函数的形式 例1: 库函数pow的声明为“double pow(double x,double y);” “pow(2,3)”是pow函数的一种直接调用形式,43,6.4.2 函数调用,例2: 自定义函数print_word的定义为 void print_word(void) printf(“hello”); print_word()是print_word函数的一种直接调用形式,44,6.4.2 函数调用,无参函数直接调用一般形式 函数名( ) 有参函数直接调用的一般形式 函数名(实际参数表达式列表) 当实参表达式列表中有多个表达式时,表达式之间用逗号分隔。,45,6.4.2 函数调用,非void型函数调用 函数调用表达式语句 函数调用作为其它表达式的运算分量 例如: y=pow(2,3); 和 pow(2,3); 均是正确语句 void型函数调用 函数调用不能作为运算分量出现其它表达式中 例如:对于以下函数定义 void print_value(int x) printf(“x = %d”,x); ,“print_value(2);” 正确语句 “y=print_value(2);” 错误语句,46,6.4.2 函数调用,若在函数调用前已有该函数的定义或声明,编译时系统会检查函数调用中实参个数是否与被调函数形参个数相同,若发现不同则报错。 当函数调用中某实参数据类型与对应形参数据类型不同但二者赋值兼容时,编译系统先自动将该实参数据类型转换为形参数据类型,再将转换类型后的实参再传给对应的形参。 当函数调用有多个实参时,由于C标准没有规定实参求值顺序,因此不同编译系统采用的求值顺序有所不同(有的自左向右,有的自右向左)。若实参中包含有副作用的表达式,则实参表达式的求值顺序不同,求值结果也不同。,关于函数调用中实参的几点说明,47,6.4.2 函数调用,间接函数调用 用指向函数的指针、圆括号及实际参数列表调用函数的形式。 函数名是一个指针类型的常量。若将一个函数的函数名保存到了一个指针变量中,则这个指针变量便指向该函数。 声明指向函数的指针变量一般形式 函数返回值类型 (*指针变量名)(形式参数类型列表);,48,6.4.2 函数调用,用指向函数的指针间接调用有参函数一般形式 指针(实参列表) 或 (*指针)(实参列表) 用指向函数的指针间接调用无参函数一般形式 指针() 或 (*指针)(),49,6.4.2 函数调用,用指向函数的指针间接调用函数 程序例1 #include #include int main(void) double (*pf)(double,double); /*声明指针变量pf */ pf=pow; /*使pf指向库函数pow */ printf(“%f”,pf(2,3); /*用pf间接调用pow函数 */ /*或 printf(“%f”,(*pf)(2,3);*/ ,50,6.4.2 函数调用,用指向函数的指针间接调用函数 。程序例2 #include #include #include double ctan(double x) return 1/tan(x); int main (void) double (*pf)(double),x; char cmd; printf(“n Intput a value:”); scanf(“%lf”,51,6.4.2 函数调用,printf(“n 1.sin(x) n 2.cos(x) n 3.tg(x) n 4.ctg(x) n Input choice:”); cmd=getch(); switch(cmd) case 1: pf=sin; break; case 2: pf=cos; break; case 3: pf=tan; break; case 4: pf=ctan; printf(“n %f”,pf(x); return 0; ,52,6.4.3 函数调用时的参数传递,调用一个有参函数的执行过程 (1)对实参列表中所有实参表达式求值, (2)为被调函数的所有形式参数分配存储空间, (3)检查每个实参值的类型与对应形参数据类型是否相同,若不同则将实参类型转换为形参要求的类型 (4)将实参值复制给形参(形参=实参,参数传递) (5)转入被调函数执行,53,6.4.3 函数调用时的参数传递,函数调用时参数传递的类型 (1) 传递数值(值传递) 形式参数的数据类型是基本类型,函数调用时传递给该形参的实参应是与形参兼容的基本类型数据。 若实参数据类型级别高于形参数据类型,对实参值做类型转换时会产生转换误差,出现数据损失。,54,6.4.3 函数调用时的参数传递,当主调函数中用值传递方式传递实参值时,被调函数不能通过改变形参的值而改变对应实参对象的值。 例如: #include void swap(int x,int y) int t; t=x; x=y;y=t; /* 交换x、y中数据 */ int main(void) int a=1,b=2; swap(a,b); /* a、b中数据没有被交换 */ printf(“a=%d,b=%d”,a,b); ,55,6.4.3 函数调用时的参数传递,(2) 传递变量地址 形式参数的数据类型是指针类型,函数调用时实参向该形参传递的应是与其形参基类型相同的对象地址。 用运算符&得到的对象地址或指向对象的指针均可用作实参。 被调函数通过指针类型形参可以访问主调函数中声明的局部数据对象。,56,6.4.3 函数调用时的参数传递,例如: #include void swap(int *x,int *y) int t; t=*x;*x=*y; *y;*y=t; /* 交换x指向对象和y指向对象中的数据 */ int main(void) int a=1,b=2; swap( ,57,6.4.3 函数调用时的参数传递,(3) 传递函数地址 形式参数的数据类型是指向返回T类型值函数的指针类型 函数调用时实参向该形参传递的应是返回值类型为T的函数地址 函数名或指向函数的指针均可用作实参 被调函数内部通过该指针类型形参可以间接调用其他函数,58,6.4.3 函数调用时的参数传递,例如: 以下程序中通过调用函数root计算方程3x3+4x2-5x+6=0在1,2内的一个近似根,root函数采用弦截法求方程的根,允许误差为10-6。 root函数4个形参含义是: a和b指出求根区间a,b, eps是对所求方程根的误差要求, pf是指向返回值类型为double的函数的指针变量。 函数f计算方程f(x)=0中f(x)的值。,59,6.4.3 函数调用时的参数传递,#include #include double root(double a,double b,double eps, double (*pf)(double) double c; while(1) c=(a*pf(b)-b*pf(a)/(pf(b)-pf(a); if(fabs(pf(c)eps|fabs(b-a)eps) return c; if(pf(a)*pf(c)0) b=c; else a=c; ,60,6.4.3 函数调用时的参数传递,double f(double x) return 6-x*(5-x*(4-3*x); int main(void) printf(“x=%f”, root(1,2,1e-6,f); ,61,6.4.4 函数间数据通信的实现,通过非指针型形式参数和return语句实现函数之间的通信 例如: #include int f(int x,int y) return x+y; int main(void) int a=1,b=2,c; c= f(a,b); printf(“%d”, c); ,62,6.4.4 函数间数据通信的实现,通过非指针型形式参数、指针型形式参数及return语句实现函数之间的通信。 例如: #include int f(int x,int y,int *p) *p=x-y; return x+y; int main(void) int a=1,b=2,c,d; c=f(a,b, ,63,6.4.4 函数间数据通信的实现,通过外部变量实现函数之间的通信 例如: #include int x,y,z,w; /* 外部变量定义 */ void f( ) z=x+y; w=x-y; int main(void) int a=1,b=2; x=a; y=b; f( ); printf(“a+b=%d,a-b=%d”,z,w); ,64,6.4.5 递归函数,65,6.4.5 递归函数,初步认识递归函数 n!的1计算问题在数学上定义为: n=0是该问题的最小数据规模,0!的值是已知的;n0时可将n!计算问题分解为n和(n-1)!两部分,如果能求得(n-1)!的值,便可以得到n!的值。同样地,可以将(n-1)!的计算问题分解为(n-1)和(n-2)!两部分,其他依此类推。,66,6.4.5 递归函数,计算n!函数: long f(long n) if(n=0) /* 基本问题判断及计算结果 */ return 1; else return n*f(n-1); /* 简化问题,递归调用 */ ,67,6.4.5 递归函数,(1) 用递归函数解决复杂问题时,每次递归调用时要解决的问题都要比上次的调用简单,数据规模越来越小,最终到达最小数据规模(基本问题)。解决完基本问题后,函数沿着调用顺序逐级返回上次调用一个结果值,直到函数的原始调用处为止。 (2) 递归函数一般由一个选择结构组成:条件为真的部分,计算基本问题终止递归调用;条件为假的部分,简化问题继续递归调用。 (3) 递归函数的每次调用系统都为函数的所有动态变量(形式参数和动态局部变量)分配本次调用使用的存储空间,直到本次调用结束返回时方才释放。,递归函数特点,68,6.4.5 递归函数,递归函数执行过程,69,6.4.5 递归函数,例1:用辗转相除法求两个整数的最大公约数。 #include int gcd(int a ,int b) int r ; if(r=a%b)=0) return b; else return gcd(b,r); int main(void) printf(“%d”,gcd(25,30); ,递归法解题程序实例,70,6.4.5 递归函数,例2:用二分法求方程-3x3+4x2-5x+6=0在1,2中的一个近似根,允许误差10-6。 #include #include double f(double x) return 6-x*(5-x*(4-3*x); double root(double a,double b) double c; if(fabs(f(a)1e-6) return a; if(fabs(f(b)1e-6) return b; c=(b+a)/2; if(fabs(f(c)1e-6) return c; if(f(c)*f(a)0) return root(a,c); else return root(c,b); ,int main(void) printf(“%f”,root(1,2); ,71,6.4.5 递归函数,有3个底座(分别标记为A、B和C),A座上已从大到小依次叠放了64个大小不同的盘子,要求将A座上的盘子全部搬到C座上。在搬运过程中可以借助于B座,每次只能搬一个盘子,任何时候每个底座上的盘子都必须保持大盘在下小盘在上的叠放顺序。 简化:A座上有3个盘子,将这3个盘子借助于B座搬到C座上,例3:Hanoi塔问题,72,6.4.5 递归函数,#include void Hanoi (int n, char A, char B, char C) if(n=1) printf(“n move %d from %c to %c”, n,A,C); else Hanoi (n-1,A,C,B); printf(“nmove %d from %c to %c”, n,A,C); Hanoi (n-1,B, A, C); int main(void) Hanoi (3, A , B , C ); ,例3:Hanoi塔问题,73,6.4.5 递归函数,缺少对数据最小规模的正确判断、处理及返回操作。函数一旦被调用将进入无穷递归。 例如: long f(long n) return n*fact (n-1); ,初学者编写递归函数时常犯错误,74,6.4.5 递归函数,(2)函数递归调用时所用的数据规模不小于本次函数被调用时的数据规模。函数一旦被调用也将进入无穷递归。 例如: long f (long n) if (n=1) return 1; else return n*fact (n); ,初学者编写递归函数时常犯错误,75,6.4.5 递归函数,用递归法计算 #include #include long f(int n) int i, s=1; for(i=1;i=n;i+) s*=i ; return s ; double f1(double x, int n) if(n=1) return x; else return f1(x, n-1)+pow(x,n)/ f(n); int main(void) printf(“%lfn”, f1(2,4); ,76,6.4.5 递归函数,用递推法计算 #include double f1(double x , int n) double s=0 , t=1 ; int k ; for(k=1; k=n; k+) t*=x/k; s+=t; return s ; int main(void) printf ( “%lfn”, f1( 2 , 4); ,77,6.4.5 递归函数,递推法 使用循环结构实现循环 循环执行函数中的一段代码(循环语句) 递归法 使用选择结构实现循环 循环执行整个函数体代码(循环调用),比较递推法和递归法,78,6.5 标识符的作用域,6.5.1 标识符的作用域 *6.5.2 外部对象的连接属性,79,6.5 标识符的作用域,标识符(symbol) 代表程序中的各类对象 标识符的作用域 与已定义或声明的标识符相关的一个代码段。 在这个代码段内使用该标识符是合法的,在这个代码段外使用该标识符是非法的。,80,6.5 标识符的作用域,标识符的类型 变量名:代表一个值可改变的数据量 数组名:代表一组值可改变的数据量起始地址 函数名:代表一个函数在内存的存储位置 数据类型说明符:代表一个系统预定义、自定义或用typedef定义的数据类型 宏名:代表一个常量或一组语句 语句标号:代表一条语句的位置 外部变量名既需要定义又需要声明 语句标号既不需要定义也不需要声明,81,6.5.1 标识符的作用域,各类标识符的四类作用域 (1)文件作用域 从标识符定义或声明位置起到源文件结尾的源文件范围 文件作用域标识符:外部对象名 全局变量名 全局数组名 函数名 在函数外自定义的数据类型说明符 一个文件作用域标识符的作用域可通过引用性声明被扩大,82,6.5.1 标识符的作用域,83,6.5.1 标识符的作用域,外部变量定义性声明与引用性声明的差别 定义性声明一个外部变量时可以带有初始化值也可以不带初始化值,引用性声明一个外部变量时不能指定初始化值; 定义性声明一个外部变量时可以使用存储类型extern、static或缺省,引用性声明一个外部变量时使用存储类型extern或缺省。,84,6.5.1 标识符的作用域,(2) 函数作用域 一个函数定义覆盖的区域 函数作用域标识符:语句标号 函数作用域总是嵌入在文件作用域内。 一个文件作用域内可能包含多个函数作用域。,85,6.5.1 标识符的作用域,(3) 块作用域 函数体内一个代码块(复合语句)覆盖的范围 块作用域标识符:函数定义中出现的形式参数名,局部变量名 形式参数名作用域:声明所在函数体代码块内 局部变量名作用域:声明所在代码块内 块作用域总是嵌入在函数作用域内。一个块作用域内还可以包含多个块作用域。,6.5.1 标识符的作用域,87,6.5.1 标识符的作用域,(4) 函数原型作用域 函数原型(引用性函数声明)覆盖的范围。 函数原型作用域标识符:函数原型中出现的形式参数名 例如: “int might (int small, double large);”中的small和large的作用域仅到该行的分号。,88,6.5.1 标识符的作用域, 不允许在同一作用域内定义或声明代表不同对象的两个或多个同名标识符。 例如: #include int f=0; int f(int x) /*编译报错:重复声明f */ int x; /*编译报错:重复声明x */ int main(void) int x=f(1); ,标识符的作用域规则,89,6.5.1 标识符的作用域, 允许在不同的作用域内定义或声明代表不同对象的两个或多个同名的标识符。 例如: #include int x=0; int f( ) int x, y; int main(void) int y; ,标识符的作用域规则,90,6.5.1 标识符的作用域,(3) 当一个作用域内包含了另一个作用域,允许内层作用域和外层作用域中分别声明代表不同对象的两个同名标识符。内层作用域中访问的同名标识符是内层作用域中定义的标识符,此时外层作用域中定义的同名标识符在内层作用域中被自动隐藏而不可访问。,标识符的作用域规则,6.5.1 标识符的作用域,#include int x=0; void f(); int main(void) int x, loop=0; loop: for(x=1;x5;x+) int x=2; printf(“%d ”,x); printf(“%d ”,x); f(); loop+; if(loop1) goto loop; void f() printf(“%d ”,x+); ,92,* 6.5.2 外部对象的连接属性,在一个多文件程序中,外部对象名(函数名,外部变量名)具有其他标识符不具备的两个特殊属性: 在使用之前既需要定义也需要引用性声明 一个外部对象名只允许在一个源程序中定义一次,但允许在定义所在的源文件或其他源文件中被多次地引用性声明。,93,* 6.5.2 外部对象的连接属性, 在多文件程序中定义的一个外部对象名具有连接属性 内部连接:仅允许在对象定义所在源文件内使用该对象标识符 外部连接:允许在组成一个源程序的所有源文件内使用该对象名 定义外部对象连接属性时使用存储类型说明符extern(外部连接)和static(内部连接) 缺省存储类型说明符时系统默认为extern。,94,*6.5.2 外部对象的连接属性,在一个多文件程序中将一个已定义的具有外部连接属性的标识符作用域从定义所在的源文件扩展到其他源文件的方法是:在其他源文件中引用性声明该标识符。 函数引用性声明一般形式 extern 返回值类型说明符 函数名(形式参数类型列表); 全局变量引用性声明一般形式 extern 数据类型说明符 全局变量名;,95,* 6.5.2 外部对象的连接属性,多文件程序举例 源文件F1.c的内容 #include extern void func2(void); /* 声明函数func2 */ void func1(); /* 声明函数func1 */ int x=1; /* 定义外部全局变量x */ int main(void) func1(); void func1() /* 定义外部函数func1 */ x+; printf(“n %d”,x); func2(); void func3() /* 定义外部函数func3 */ x+; printf(“n %d”,x); ,96,* 6.5.2 外部对象的连接属性,源文件F2.c的内容: #include extern void func3(); /* 声明函数func3 */ extern int x; /* 声明变量x */ void func4(void); /* 声明函数func4 */ static int y=1; /* 定义内部全局变量y */ void func2() /* 定义外部函数func2 */ x+; y+; printf(“n %d,%d”,x,y); func3(); func4(); static void func4() /* 定义内部函数func4 */ x+; y+; printf(“n %d,%d”,x,y); 特别注意: 在F1.c中main函数前增加“extern int y;”和“extern void func4(void);”并在main函数中增加语句“y+;”和“func4( );”是错误的。,97,* 6.5.2 外部对象的连接属性,宏名的特殊性 (1) 关键字可以用作宏名 (2) 从#define命令所在行开始到源文件结尾的代码段为该#define命令所定义宏名的作用域 例如: #define PI 3.14 PI:宏名,3.14:宏体 一个宏名作用域内出现的所有宏名引用在预处理时均被替换为宏体。 宏名不应与其作用域内函数定义及函数调用中出现的函数同名,也不应与变量同名。否则编译时会出现语法错误。,98,* 6.5.2 外部对象的连接属性,例如: #define a 3 #define f 4 int main(void) int a=1; /* 该行将被替换成“int 3=1;”,语法错 */ int x; x=f(2); /*该行被替换成“x=4(2);” ,语法错 */ int f(int x) /*该行被替换成”int 4(int x )”,语法错 */ return x*x; ,99,6.6 变量的存储属性,6.6.1 变量的生命期属性 6.6.2 变量的存储器属性,100,6.6.1 变量的生命期属性,生命期属性反映程序运行时变量的存在状态 按照生命期属性分类变量 静态变量:存在周期与程序运行时间相同 动态变量:存在周期与定义它的代码块执行时间相同,101,6.6.1 变量的生命期属性,全局变量都是静态变量 形式参数都是动态变量 局部变量有静态变量、动态变量之分 ,由存储类型说明符决定。用static声明的局部变量是静态变量;用auto或register声明的局部变量是动态变量;缺省存储类型说明符时系统默认为auto。 静态局部变量的存在周期与全局变量相同,作用域与局部变量相同。,102,6.6.1 变量的生命期属性,例1 动态局部变量存储特性 #include void f(int x) int y=0,i; printf(“x=%d y=%d ”,+x,+y); for(i=0;i2;i+) int z=0; printf(“z=%d ”,+z); int main(void) f(1); putchar(n); f(1); 程序运行结果: x=2 y=1 z=1 z=1 x=2 y=1 z=1 z=1,103,6.6.1 变量的生命期属性,例2 计算 #include long f ( int n ) static long s; if(n=1) s=1; else s*=n;return s; int main(void) int i; double k=0; for(i=1;i=10;i+) k+=1.0/f(i); printf(“%lf ”, k); ,104,6.6.2 变量的存储器属性,存储器属性反映程序运行期间变量获得的存储单元所在的存储器 按照存储器属性分类变量 内存变量 寄存器变量,105,6.6.2 变量的存储器属性,静态变量(全局变量,静态局部变量)都是内存变量 动态变量(形式参数,动态局部变量)有内存变量和寄存器变量之分,由存储类型说明符决定。用auto声明的局部变量是内存变量;用register声明的局部变量是寄存器变量;缺省存储类型说明符时系统默认为auto。 寄存器变量的特殊性 (1) 寄存器变量读写速度比内存变量快 (2) 取地址运算符“&”不能用于寄存器变量,106,6.7* 参数个数可变函数定义及调用,参数个数可变函数是指函数每次被调用时可接收不同个数的实参。 例如: printf(“hello”); /* 1个实参 */ printf(“%d+%d=%d”,1,2,1+2); /* 4个实参 */,107,6.7* 参数个数可变函数定义及调用,定义和声明一个参数个数可变的函数时,形式参数列表的最后必须放置一个省略号。 例如: int printf(const char *format,); “const char *”表示调用printf函数时必须有至少一个”char *”类型的实参,省略号“”表示第1个形式参数之后的参数都是无名参数,无名参数的数量和类型是可变的。,108,6.7* 参数个数可变函数定义及调用,例1: average函数计算并返回形参接收的n个浮点数平均值,n值由第1个形参接收。 #include #include double average(int i,.); int main(void) double j; j=average(2,1.5,2.5); printf(“n average = %fn“,j); j=average(4,1.0,2.0,3.0,4.0); printf(“n average = %fn“,j); ,参数个数可变函数定义及调用举例,109,6.7* 参数个数可变函数定义及调用,double average(int n,.) int i; double y=0; va_list ap; va_start(ap,n); for(i=0;in;i+) y+=va_arg(ap,double); return y/n; 数据类型va_list在系统头文件stdarg.h中定义,用于存放省略号()部分的参数。 宏定义va_start用于把省略号()部分参数值复制到va_list类型对象中。 宏定义va_arg用于访问省略号()部分参数,110,6.7* 参数个数可变函数定义及调用,例6.15 mprintf函数模仿库函数printf读取变长参数表中的参数。通过扫描第1个实参字符串中的转换说明符(%d代表int型参数、%f代表double型参数、%c代表char型参数)判断mprintf函数被调用时无名参数的数量和类型并输出无名参数的值。 #include void mprintf(const char *format,.); int main(void) double j; mprintf(“%d”,2); putchar(n); mprintf(“%f %d %c”,1.1,2, a); ,111,6.7* 参数个数可变函数定义及调用,void mprintf(const char *format, .) int i; double d; char c; char *p; void *ap; ap=.; for(p=format;*p;p+) if(*p!= %) putchar(*p); continue; switch(*+p) case d: i=*(int *)ap)+; printf(“%d”,i); break; case f: d=*(double *)ap)+; printf(“%f”,d); break; case c: c=*(char *)ap)+; printf(“%c”,c); ,112,6.8 编译预处理及预处理命令,6.8.1 预处理概念 6

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论