资源描述
在结构化程序设计中,函数是将任务进行模块划分的基在结构化程序设计中,函数是将任务进行模块划分的基本单位。一个函数实现一项功能。本单位。一个函数实现一项功能。 在面向对象在面向对象程序设计中,程序设计中,函数是对数据的一项操作,也函数是对数据的一项操作,也是实现一项功能。是实现一项功能。第三章第三章 函数函数 要掌握函数的使用,必须理解函数调用时的内部实现机要掌握函数的使用,必须理解函数调用时的内部实现机制,以及与此相关的内存分配机制、变量生命期和作用域。制,以及与此相关的内存分配机制、变量生命期和作用域。 本章还将介绍关于函数重载的概念,介绍递归算法本章还将介绍关于函数重载的概念,介绍递归算法、内联函数、默认参数函数以及多文件组织、编译预处、内联函数、默认参数函数以及多文件组织、编译预处理、工程文件的概念和运行库函数。理、工程文件的概念和运行库函数。 练习:编写程序,输入练习:编写程序,输入x x,根据下面,根据下面y y和和x x的关的关系,计算系,计算y y值并输出值并输出3x-11 (x10)x (x1)2x-1 (1xx; cinx; if(x1) y=x; if(x1) y=x; else if(1=x10) y=2 else if(1=x10) y=2* *x-1;x-1; else y=3 else y=3* *x-11;x-11; couty; coutx; cinx; if(x1) y=x; if(x1) y=x; else if(1=x&x10) y=2 else if(1=x&x10) y=2* *x-1;x-1; else y=3 else y=3* *x-11;x-11; couty; coutx; cinx; if(x1) y=x; if(x1) y=x; else if(1 else if(1x10) y=2x10) y=2* *x-1;x-1; else y=3 else y=3* *x-11;x-11; couty; coutx; cinx; x=10?y=3x-11:y=2x-1 x=10?y=3x-11:y=2x-1 couty; coutx; cinx; if(x1) y=x; if(x1) y=x; else if(x10) y=2 else if(x10) y=2* *x-1;x-1; else y=3 else y=3* *x-11;x-11; couty; couty; 第三章第三章 函数函数 3.1 函数的定义与调用函数的定义与调用 3. 5 作用域与标识符的可见性作用域与标识符的可见性 3.4 函数调用机制函数调用机制 3.3 全局变量和局部变量全局变量和局部变量 3.2 函数的参数传递函数的参数传递,返回值及函数声明返回值及函数声明 3.10 编译预处理编译预处理 3.9 头文件与多文件结构头文件与多文件结构 3.6 存储类型与标识符的生命期存储类型与标识符的生命期 3.8 函数的一些高级议题函数的一些高级议题 3.7 函数的递归调用函数的递归调用 3.1 函数的定义与调用函数的定义与调用3.1.1 函数概述函数概述3.1.2 函数的定义函数的定义3.1.3 函数的调用函数的调用 3.1.1 函数概述函数概述函数是函数是C+C+程序的基本组成模块。程序的基本组成模块。通过函数,可以把一个复杂任务分解成为若干通过函数,可以把一个复杂任务分解成为若干个易于解决的小任务。充分体现逐步细化的设计思个易于解决的小任务。充分体现逐步细化的设计思想。想。组成组成C+C+程序的若干函数中,有一个称为程序的若干函数中,有一个称为main()main()(Winmain()Winmain())函数,是程序执行的入口,它可以)函数,是程序执行的入口,它可以调调用其他函数,但不可以被调用用其他函数,但不可以被调用。而其他一般。而其他一般函数既可函数既可以调用也可以被调用。以调用也可以被调用。函数概念的引入:函数概念的引入:入口函数:入口函数:3.1.1 函数概述函数概述main ( )fun2( )fun1( )fun3( )funa( )funb( )func( )图图3.1 3.1 函数调用层次关系函数调用层次关系3.1.1 函数概述函数概述3.1.1结束结束库函数和自定义函数:库函数和自定义函数: 库函数库函数或或标准函数标准函数,是由编译系统预定义的,是由编译系统预定义的,如一些常用的数学计算函数、字符串处理函数、图如一些常用的数学计算函数、字符串处理函数、图形处理函数、标准输入输出函数等。形处理函数、标准输入输出函数等。 库函数都按功能分类,集中说明在不同的头文库函数都按功能分类,集中说明在不同的头文件中件中。用户只需在自己的程序中包含某个头文件,。用户只需在自己的程序中包含某个头文件,就可直接使用该文件中定义的函数。就可直接使用该文件中定义的函数。 用户根据需要将某个具有相对独立功能的程序用户根据需要将某个具有相对独立功能的程序定义为函数,称定义为函数,称自定义函数自定义函数。四个要素:四个要素:返回值类型,函数名,参数列表和函数体返回值类型,函数名,参数列表和函数体3.1.2 函数的定义函数的定义无参函数定义格式为:无参函数定义格式为:数据类型数据类型函数名函数名( (voidvoid)函数体函数体 说明:说明: 数据类型指函数数据类型指函数返回值类型返回值类型,可以是任一种数据类型。,可以是任一种数据类型。没有返回值应将返回值类型定义为没有返回值应将返回值类型定义为voidvoid。 函数名函数名采用合法标识符表示。采用合法标识符表示。 对无参函数,参数括号中的对无参函数,参数括号中的voidvoid通常省略,但括号不通常省略,但括号不能省略。能省略。 函数体由一系列语句组成。函数体由一系列语句组成。函数体函数体可以为空,称为空可以为空,称为空函数。函数。 3.1.2 函数的定义函数的定义例例: 打印一个表头打印一个表头void TableHead ( ) cout*endl;cout* example *endl;cout*=b?a:b; return(x); 有参函数的参数表中列出所有有参函数的参数表中列出所有形式参数形式参数的类型和参数名的类型和参数名称。各参数即使类型相同也必须分别加以说明。称。各参数即使类型相同也必须分别加以说明。 形式参数简称形参,形式参数简称形参,只能是变量名只能是变量名,不允许是常量或表,不允许是常量或表达式。达式。 Why?找错误int f()string s;.return s;f2(int i).int f(int v1,int v2).double square(double x)return x*x;问题:问题:定义函数时究竟哪些变量应当作为函数的参数?哪些定义函数时究竟哪些变量应当作为函数的参数?哪些应当定义在函数体内?应当定义在函数体内?原则原则:函数在使用时被看成函数在使用时被看成 “ “黑匣子黑匣子”,除了输入输出外,除了输入输出外,其他部分可不必关心其他部分可不必关心。从函数的定义看出,函数头正是用来。从函数的定义看出,函数头正是用来反映函数的功能和使用接口,它所定义的是反映函数的功能和使用接口,它所定义的是“做什么做什么”。即。即明确了明确了“黑匣子黑匣子”的输入输出部分,的输入输出部分,输出就是函数的返回值,输出就是函数的返回值,输入就是参数输入就是参数。因此,只有那些功能上起自变量作用的变量。因此,只有那些功能上起自变量作用的变量才必须作为参数定义在参数表中;函数体中具体描述才必须作为参数定义在参数表中;函数体中具体描述“如何如何做做”,因此除参数之外的为实现算法所需用的变量应当定义,因此除参数之外的为实现算法所需用的变量应当定义在函数体内。在函数体内。 C+C+中不允许函数的嵌套定义,即在一个函数中定义另一中不允许函数的嵌套定义,即在一个函数中定义另一个函数。个函数。提示提示3.1.3 函数的调用函数的调用函数调用:函数调用: 所谓函数调用,就是使程序转去执行函数体。所谓函数调用,就是使程序转去执行函数体。 在在C+C+中,除了主函数外,其他任何函数都不能单独作中,除了主函数外,其他任何函数都不能单独作为程序运行。任何函数功能的实现都是通过被主函数直接或为程序运行。任何函数功能的实现都是通过被主函数直接或间接调用进行的。间接调用进行的。 无参函数的调用格式:无参函数的调用格式: 函数名函数名( )( ) 有参函数的调用格式:有参函数的调用格式: 函数名函数名( (实际参数表实际参数表) )其中实际参数简称其中实际参数简称实参实参,用来将实际参数的值传递给形参,用来将实际参数的值传递给形参,因此因此可以是常量、具有值的变量或表达式可以是常量、具有值的变量或表达式。【例【例3.1】 输入两个实数,输出其中较大的数输入两个实数,输出其中较大的数 函数的调用规则1、调用函数时,函数名必须与调用处的函数名完全一致。2、实参的个数必须与形参的个数一致。3、函数必须先声明或定义,后调用。4、函数可以直接或间接地自己调用自己,称为递归调用。int main() int add(int x, int y); int a=100,b=200,c ; c =add(a,b); .int add (int x, int y ) int z ; z=x+y; return(z);错误:c=ad(a,b);c=add(a);int add(int , int );3.2 函数的参数传递、返回值及函数的参数传递、返回值及函数声明函数声明 321 函数的参数传递及传值调用函数的参数传递及传值调用 323 函数声明函数声明322 函数返回值函数返回值 参数传递:参数传递: 函数调用首先要进行参数传递,参数传递的方向是函数调用首先要进行参数传递,参数传递的方向是由实由实参传递给形参参传递给形参。 传递过程是,传递过程是,先计算实参表达式的值,再将该值传递给先计算实参表达式的值,再将该值传递给对应的形参变量对应的形参变量。一般情况下,。一般情况下,实参和形参的个数和排列顺实参和形参的个数和排列顺序应一一对应,并且对应参数应类型匹配序应一一对应,并且对应参数应类型匹配(赋值兼容)(赋值兼容), ,即即实参的类型可以转化为形参类型。而对应参数的参数名则不实参的类型可以转化为形参类型。而对应参数的参数名则不要求相同。要求相同。 3.2.1 函数的参数传递及传值调用函数的参数传递及传值调用 传值调用和引用调用:传值调用和引用调用:按照参数形式的不同,按照参数形式的不同,C+有两种调用方式:有两种调用方式:传值调用传值调用和和引引用调用用调用。传值调用传递的是实参的值,本章介绍传值调用。传值调用传递的是实参的值,本章介绍传值调用。在调用函数时,一般在主调函数和被调用函数之间有 信息传递 这是由函数中的参数参数来完成的。调用函数和被调用函数之间的参数传递实际参数实际参数 形式参数形式参数主调用函数中的参数被调用函数中的参数(实参实参)(形参形参)int main() int a=5,b=10, c ; c=max(a,b); printf(“c=%dn”, c); return 0;int max(int x, int y) int t; if (xy) t=x; else t=y; return(t); yxa5b10510ct101010int main() int a=5,b=10, c ; c=max(a,b); printf(“c=%dn”, c); return 0; int max(int x, int y) int t; if (xy) t=x; else t=y; return(t); 信息传递原则:信息传递原则:实参到形参实参到形参单向值传递单向值传递特点:实参与形参各占用不同的内存单元 3.2.1 函数的参数传递及传值调用函数的参数传递及传值调用 传值调用:传值调用:将实参的值复制给形参,在函数中参加运算的将实参的值复制给形参,在函数中参加运算的是形参,而实参不会发生任何改变。传值调用是形参,而实参不会发生任何改变。传值调用起了一种隔离作用。起了一种隔离作用。【例【例3.2】 实参和形参对应关系的示例。实参和形参对应关系的示例。注意:注意:【例【例1.3】中调用函数】中调用函数strcpy(s3, s2),却实现了字符数,却实现了字符数组组s2的内容复制到字符数组的内容复制到字符数组s3中。这是因为数组名实际中。这是因为数组名实际上代表存储数组的内存的首地址,复制给形参的是实参数上代表存储数组的内存的首地址,复制给形参的是实参数组的首地址,结果参加运算的是实参数组。数组作为参数组的首地址,结果参加运算的是实参数组。数组作为参数,定义时形参用数组名加一对方括号,调用时实参只用数,定义时形参用数组名加一对方括号,调用时实参只用数组名组名 100aFFC2200bFFC4int add (int x, int y) int z ; z=(+x)+(+y) ; return(z); int main() int a=100,b=200,c ; c =add(a,b); coutcendl; return 0; 100 xFFA2200yFFA4101201302z1094302cFFC6 当形参值在函 数中发生变化时, 且不影响调用它的 实参值的变化。 用途 采用这种数据复制的参数传递方式,每次每个参数只能传递一个数据。缺点add (a , b) ;int add ( x, y ) A) 11 B) 20 C) 21 D) 31请写出下列程序的运行结果 main() int x=6,y=7,z=8,r ; r=f (x-,y+,x+y),z-); coutr=ry) t=x; else t=y; return(t); 下面是一个使用结构化程序设计思想开发的企业管理下面是一个使用结构化程序设计思想开发的企业管理报表程序的框架。它使用了报表程序的框架。它使用了函数声明函数声明。void menu_print();void account_report();void engineering_report();void marketing_report();int main() int choice; do menu_print();cinchoice; while(choice=4); switch(choice) case 1: account_report(); break; case 2: engineering_report(); break; case 3: marketing_report(); break; return 0;void menu_print() cout”系统功能:系统功能:”endl; cout”1 财务报表财务报表”endl; cout”2 工程报表工程报表”endl; cout”3 市场报表市场报表”endl; cout”选择业务序号:选择业务序号:”; void account_report() /生成财务报表生成财务报表void engineering_report() /生成工程报表生成工程报表 void marketing_report() /生成市场报表;生成市场报表;3.2.3 函数声明函数声明【例【例3.4】 输出所有满足下列条件的正整数输出所有满足下列条件的正整数m:10m1000且且m、m2、m3均为回文数。均为回文数。分析:分析:回文指左右对称的序列。如回文指左右对称的序列。如121、353等就是回等就是回文数。判断整数是否回文数用函数实现,其思想是将文数。判断整数是否回文数用函数实现,其思想是将该数各位拆开后反向组成新的整数,如果该整数与原该数各位拆开后反向组成新的整数,如果该整数与原数相等则为回文数。数相等则为回文数。m m*m m*m*m11 121 1331101 10201 1030301111 12321 1367631 运行结果:运行结果:3.3 全局变量和局部变量全局变量和局部变量 3.3.1 变量的存储机制与变量的存储机制与C+的内存布局的内存布局自由存储区自由存储区 ( (动态数据动态数据) )栈区(函数局部数据)栈区(函数局部数据)(main()main()函数局部数据)函数局部数据)全局数据区全局数据区( (全局、静态全局、静态) )代码区(程序代码)代码区(程序代码) 操作系统为一个操作系统为一个C+C+程序的运行所分配的内程序的运行所分配的内存分为四个区域,如图存分为四个区域,如图3.33.3 所示:所示:存储区域说明:存储区域说明:(1)代码区()代码区(Code area):存放程序代码,即程序):存放程序代码,即程序中各个函数的代码块;中各个函数的代码块;(2)全局数据区()全局数据区(Data area):存放全局数据和静):存放全局数据和静态数据;态数据;分配该区时内存全部清零分配该区时内存全部清零,结果变量的所有字,结果变量的所有字节自动初始化为零。节自动初始化为零。(3)栈区()栈区(Stack area):存放局部变量,如函数中):存放局部变量,如函数中的变量等;的变量等;分配栈区时不处理内存分配栈区时不处理内存,即变量取随机值。,即变量取随机值。(4)自由存储区(自由存储区(Free store area):):存放与指针相存放与指针相关的动态数据。关的动态数据。分配分配自由存储自由存储区时不处理内存区时不处理内存。参见第。参见第七章。七章。3.3.1 变量的存储机制与变量的存储机制与C+的内存布局的内存布局 3.3.2 全局变量全局变量 在所有函数之外定义的变量称为在所有函数之外定义的变量称为全局变量全局变量。全局变量在全局变量在编译时编译时建立在全局数据区,在未给建立在全局数据区,在未给出初始化值时系统自动出初始化值时系统自动初始化为全初始化为全0。全局变量可定义在程序开头,也可定义在中间全局变量可定义在程序开头,也可定义在中间位置,该全局变量位置,该全局变量在定义处之后在定义处之后的任何位置都是可的任何位置都是可以访问的,称为可见的。以访问的,称为可见的。【例【例3.5】 多个函数使用全局变量的例子。多个函数使用全局变量的例子。全局变量引入:全局变量引入:3.3.3 局部变量局部变量 定义在函数内或块内的变量称为定义在函数内或块内的变量称为局部变量局部变量。程序中使用的绝大多数变量都是局部变量。程序中使用的绝大多数变量都是局部变量。局部变量在局部变量在程序运行程序运行到它所在的块时建立在栈中,到它所在的块时建立在栈中,该块执行完毕局部变量占有的空间即被释放。该块执行完毕局部变量占有的空间即被释放。局部变量在定义时可加修饰词局部变量在定义时可加修饰词auto,但通常省略。但通常省略。局部变量在定义时若未初始化,其值为随机数。局部变量在定义时若未初始化,其值为随机数。局部变量引入:局部变量引入:【例【例3.6】 使用局部变量的例子。使用局部变量的例子。思考:如果定义思考:如果定义一个对象一个对象/变量为变量为全局模式还是局全局模式还是局部模式?部模式? 1、主函数中定义的变量也只用在主函数中有效;关于局部变量使用的几点说明 2、不同函数中可以使用相同的变量名,且它们代 表不同的存储单元,互不干扰; 3、形式参数也是局部变量。其它函数是不能调用 该形参的; 4、在一个函数内部,可以在复合语句中定义变量 ,这些变量只能在本复合语句中有效。3.4 函数调用机制函数调用机制 局部变量占用的内存是在程序执行过程中局部变量占用的内存是在程序执行过程中“动态动态”地建立地建立和释放的。这种和释放的。这种“动态动态”是通过栈由系统自动管理进行的。是通过栈由系统自动管理进行的。(1)建立栈空间;)建立栈空间;(6)恢复现场:取主调函数运行状态及返回地址,释放栈空间;)恢复现场:取主调函数运行状态及返回地址,释放栈空间;(7)继续主调函数后续语句。)继续主调函数后续语句。(5)释放被调函数中局部变量占用的栈空间;)释放被调函数中局部变量占用的栈空间;(4)执行被调函数函数体;)执行被调函数函数体;(3)为被调函数中的局部变量分配空间,完成参数传递;)为被调函数中的局部变量分配空间,完成参数传递;(2)保护现场:主调函数运行状态和返回地址入栈;)保护现场:主调函数运行状态和返回地址入栈;调用过程:调用过程:3.4 函数调用机制函数调用机制 void fun1(int, int);void fun2(float);int main() int x=1;y=2; fun1(x, y); return 0;void fun1(int a,int b) float x=3; fun2(x);void fun2(float y) int x; x栈顶栈顶栈底栈底y3fun2()fun1()运行状态及返回地址运行状态及返回地址x3b2a1fun1()main()运行状态及返回地址运行状态及返回地址y2x1main()操作系统运行状态及返回地址操作系统运行状态及返回地址此图例说明在程序执行过程中怎样通过栈此图例说明在程序执行过程中怎样通过栈“动态动态”地建立和地建立和释放局部变量占用的内存的释放局部变量占用的内存的地址地址.0XFE8020X5984D0XFE154 3.5 作用域与标识符的可见性作用域与标识符的可见性3 文件作用域文件作用域 2 函数声明作用域函数声明作用域 作用域:作用域:指标识符能够被使用的范围。只有在作指标识符能够被使用的范围。只有在作用域内标识符才可以被访问(称为可见)。用域内标识符才可以被访问(称为可见)。本节重点讨论本节重点讨论局部域局部域和文件域(全局域),其中和文件域(全局域),其中局部域包括局部域包括块域块域和和函数声明域函数声明域。任何。任何标识符标识符作用域的作用域的起始点均为起始点均为标识符说明标识符说明处。处。下面分别介绍下面分别介绍:1 块作用域块作用域 函数中定义的标识符,包括形参和函数体中定义函数中定义的标识符,包括形参和函数体中定义的局部变量,作用域都在该函数内,也称作的局部变量,作用域都在该函数内,也称作函数域函数域。1. 块域块域块块指一对大括号括起来的程序段。块中定义的标指一对大括号括起来的程序段。块中定义的标识符,作用域在块内。识符,作用域在块内。复合语句是一个块。复合语句是一个块。函数也是一个块。函数也是一个块。复合语句中定义的标识符,复合语句中定义的标识符,作用域仅在该复合语句中。作用域仅在该复合语句中。【例【例3.7】 输入两数,按从大到小的顺序保存。输入两数,按从大到小的顺序保存。块的引入:块的引入:1. 块域块域由由VC+平台运行,结果如下:平台运行,结果如下:输入两整数:输入两整数:3 5调用前:实参调用前:实参a=3,b=5调用中调用中交换前:形参交换前:形参a=3,b=5交换后:形参交换后:形参a=5,b=3调用后:实参调用后:实参a=3,b=5 交换失败交换失败局部变量具有局部作用域使得程序在不同块中可以局部变量具有局部作用域使得程序在不同块中可以使用同名变量。这些同名变量各自在自己的作用域使用同名变量。这些同名变量各自在自己的作用域中可见,在其它地方不可见。中可见,在其它地方不可见。【例【例3.8】设计函数完成两数交换,用主函数进行测试。设计函数完成两数交换,用主函数进行测试。1. 块域块域 对于块中对于块中嵌套嵌套其它块的情况,如果嵌套块中有同其它块的情况,如果嵌套块中有同名局部变量,服从局部优先原则,即在内层块中名局部变量,服从局部优先原则,即在内层块中屏屏蔽蔽外层块中的同名变量,换句话说,内层块中局部外层块中的同名变量,换句话说,内层块中局部变量的作用域为内层块;外层块中局部变量的作用变量的作用域为内层块;外层块中局部变量的作用域为外层除去包含同名变量的内层块部分。域为外层除去包含同名变量的内层块部分。如果块内定义的局部变量与全局变量同名,块内仍如果块内定义的局部变量与全局变量同名,块内仍然局部变量优先,但与块作用域不同的是,在块内然局部变量优先,但与块作用域不同的是,在块内可以通过域运算符可以通过域运算符“:”访问同名的全局变量。访问同名的全局变量。【例【例3.9】 显示同名变量可见性。显示同名变量可见性。2函数声明作用域函数声明作用域 函数声明不是定义函数,在作函函数声明不是定义函数,在作函数声明时,其中的形参作用域只在数声明时,其中的形参作用域只在声明中,即声明中,即作用域结束于右括号作用域结束于右括号。正是由于形参不能被程序的其他地正是由于形参不能被程序的其他地方引用,所以通常只要声明形参个方引用,所以通常只要声明形参个数和类型,数和类型,形参名可省略形参名可省略。3 3 文件作用域文件作用域 文件作用域文件作用域也称全局作用域。定义在所有函数之外的也称全局作用域。定义在所有函数之外的标识符,具有文件作用域,作用域为从定义处到整个标识符,具有文件作用域,作用域为从定义处到整个源文件结束。文件中定义的全局变量和函数都具有文源文件结束。文件中定义的全局变量和函数都具有文件作用域。件作用域。如果某个文件中说明了具有文件作用域的标识符,该如果某个文件中说明了具有文件作用域的标识符,该文件又被另一个文件包含,则该标识符的作用域延伸文件又被另一个文件包含,则该标识符的作用域延伸到新的文件中。如到新的文件中。如cincin和和coutcout是在头文件是在头文件iostreamiostream中说明中说明的具有文件作用域的标识符,它们的作用域也延伸到的具有文件作用域的标识符,它们的作用域也延伸到嵌入嵌入iostreamiostream的文件中。的文件中。存储类型(存储类型(storage class)决定标识符的存储)决定标识符的存储区域,即编译系统在不同区域为不同存储类型的标区域,即编译系统在不同区域为不同存储类型的标识符分配空间。由于存储区域不同,标识符的生命识符分配空间。由于存储区域不同,标识符的生命期也不同。所谓生命期,指的是标识符从获得空间期也不同。所谓生命期,指的是标识符从获得空间到空间释放之间的期间,标识符只有在生存期中、到空间释放之间的期间,标识符只有在生存期中、并且在其自己的作用域中才能被访问。并且在其自己的作用域中才能被访问。3.6 存储类型与标识符的生命期存储类型与标识符的生命期 自动变量为用自动变量为用auto说明的变量,通常说明的变量,通常auto缺省。局部变量都是自缺省。局部变量都是自动变量,生命期开始于块的执行,结束于块的结束,其原动变量,生命期开始于块的执行,结束于块的结束,其原因是自动变量的空间分配在栈中,块开始执行时系统自动因是自动变量的空间分配在栈中,块开始执行时系统自动分配空间,块执行结束时系统自动释放空间。故自动变量分配空间,块执行结束时系统自动释放空间。故自动变量的生命期和作用域是一致的。的生命期和作用域是一致的。3.6.1 存储类型存储类型 为提高程序运行效率,可以将某些变量保存在寄存器中,即用为提高程序运行效率,可以将某些变量保存在寄存器中,即用register说明为寄存器变量,说明为寄存器变量,但不提倡使用但不提倡使用。C+中关于存储类型的说明符(中关于存储类型的说明符(storage class specifier)有四)有四个:个:auto、register、static和和extern。其中用。其中用auto和和register修修饰的称为自动存储类型,用饰的称为自动存储类型,用static修饰的称为静态存储类型,修饰的称为静态存储类型,用用extern修饰的称为外部存储类型。修饰的称为外部存储类型。1 1 自动存储类型自动存储类型static说明的变量称为静态变量。根据定义的位置不同,还说明的变量称为静态变量。根据定义的位置不同,还分为局部静态变量和全局静态变量,也称内部静态变量和外部分为局部静态变量和全局静态变量,也称内部静态变量和外部静态变量。静态变量均存储在全局数据区,如果程序未显式给静态变量。静态变量均存储在全局数据区,如果程序未显式给出初始化值,系统自动初始化为全出初始化值,系统自动初始化为全0,且初始化只进行一次;静,且初始化只进行一次;静态变量占有的空间要到整个程序执行结束才释放,故静态变量态变量占有的空间要到整个程序执行结束才释放,故静态变量具有全局生命期。具有全局生命期。3.6.1 存储类型存储类型局部静态变量是定义在块中的静态变量,当块第一次被执行时,局部静态变量是定义在块中的静态变量,当块第一次被执行时,编译系统在全局数据区为其开辟空间并保存数据,该空间一直编译系统在全局数据区为其开辟空间并保存数据,该空间一直到整个程序结束才释放。局部静态变量具有局部作用域,但却到整个程序结束才释放。局部静态变量具有局部作用域,但却具有全局生命期。具有全局生命期。【例【例3.10】 自动变量与局部静态变量的区别自动变量与局部静态变量的区别 int f( int a) auto int b=0; static int c=3; b=b+1;c=c+1; return(a+b+c); int main() int a=2, i ; for(i=0; i 3; i +) cout f(a)endl; 0b3c1举举例例2a2a4701580691 fun(int a, int b) static int m, i=2; i+=m+1; m=i+a+b; return(m); int main() int k=4,m=1,p; p=fun(k,m); coutpendl; p=fun(k,m); coutpendl; 0m4k4a1m1b p2i388120178 17817int main() fun(); fun(); void fun() static int a3=0,1,2; int i ; for(i =0; i 3; i +) ai +=ai ; for(i =0; i 3; i +) coutai ; coutendl; return; 0 12 a0 a1 a20240483.6.1 存储类型存储类型一个一个C+程序可以由多个源程序文件组成。多文件程序系统可程序可以由多个源程序文件组成。多文件程序系统可以通过以通过外部存储类型外部存储类型的变量和函数来的变量和函数来共享共享某些数据和操作。某些数据和操作。在一个程序文件中定义的全局变量和函数缺省为外部的,即其作在一个程序文件中定义的全局变量和函数缺省为外部的,即其作用域可以延伸到程序的其他文件中。其他文件如果要使用这个文用域可以延伸到程序的其他文件中。其他文件如果要使用这个文件中定义的全局变量和函数,应该在使用前用件中定义的全局变量和函数,应该在使用前用“extern”作外部声作外部声明。外部声明通常放在文件的开头(明。外部声明通常放在文件的开头(函数函数总是总是省略省略extern)。)。外部变量声明不同于全局变量定义,变量定义时编译器为其分外部变量声明不同于全局变量定义,变量定义时编译器为其分配存储空间,而变量声明则表示该全局变量已在其他地方定义配存储空间,而变量声明则表示该全局变量已在其他地方定义过,编译系统不再分配存储空间。过,编译系统不再分配存储空间。外部的全局变量或函数加上外部的全局变量或函数加上static修饰,就成为静态全局变量修饰,就成为静态全局变量或静态函数。静态的全局变量和函数作用域限制在本文件,其或静态函数。静态的全局变量和函数作用域限制在本文件,其他文件即使使用外部声明也无法使用该全局变量或函数。他文件即使使用外部声明也无法使用该全局变量或函数。 【例【例3.11】外部存储类型的例子外部存储类型的例子全局变量的static:如果程序是由多个源文件组成时,此全局变量的作用域只限于本文件中。extern int x; int x;main() a1.cppstatic char cc; extern int x; a2.cpp extern char cc; a3.cpp3.6.2 生命期生命期1.1. 静态生命期静态生命期 静态生命期静态生命期(Static extent或或Static storage duration)指)指的是标识符从程序开始运行时就存在,具有存储空间,到程的是标识符从程序开始运行时就存在,具有存储空间,到程序运行结束时消亡,释放存储空间。具有静态生命期的标识序运行结束时消亡,释放存储空间。具有静态生命期的标识符存放在全局数据区,如符存放在全局数据区,如全局变量、静态全局变量、静态局全局变量、静态全局变量、静态局部变量部变量。具有静态生命期的标识符在未被用户初始化的情况。具有静态生命期的标识符在未被用户初始化的情况下,系统会自动将其初始化为下,系统会自动将其初始化为0。 函数驻留在代码区,也具有静态生命期。所有具有文件函数驻留在代码区,也具有静态生命期。所有具有文件作用域的标识符都具有静态生命期。作用域的标识符都具有静态生命期。 3.6.2 生命期生命期2. 2. 局部生命期局部生命期 在函数内部或块中定义的标识符具有在函数内部或块中定义的标识符具有局部生命期局部生命期(Automatic extent或或Automatic storage duration),),其生命期开始于执行到该函数或块的标识符定义处,其生命期开始于执行到该函数或块的标识符定义处,结束于该函数或块的结束处。具有局部生命期的标识结束于该函数或块的结束处。具有局部生命期的标识符存放在栈区。具有局部生命期的标识符如果未被初符存放在栈区。具有局部生命期的标识符如果未被初始化,其内容是随机的,不可引用。始化,其内容是随机的,不可引用。 具有局部生命期的标识符必定具有局部作用域;具有局部生命期的标识符必定具有局部作用域;但反之不然,但反之不然,静态局部变量静态局部变量具有局部作用域,但却具具有局部作用域,但却具有静态生命期。有静态生命期。3.6.2 生命期生命期具有具有动态生命期动态生命期(dynamic extent或或dynamic storage duration)的标识符存放在自由存储区,)的标识符存放在自由存储区,由特定的函数调用或运算来创建和释放,如用由特定的函数调用或运算来创建和释放,如用new运算符(或调用运算符(或调用malloc()函数)为变量分配函数)为变量分配存储空间时,变量的生命期开始,而用存储空间时,变量的生命期开始,而用delete运运算符(或调用算符(或调用free()函数)释放空间或程序结束函数)释放空间或程序结束时,变量生命期结束。关于时,变量生命期结束。关于new运算符和运算符和delete运算符将在第七章中介绍。运算符将在第七章中介绍。3. 3. 动态生命期动态生命期3.7 函数的递归调用函数的递归调用1n 1)!-(n*n1n 1 0n 1 n! 递归是一种描述问题的方法,或称算法。递归的思想递归是一种描述问题的方法,或称算法。递归的思想可以简单地描述为可以简单地描述为“自己调用自己自己调用自己”。例如用如下方法定。例如用如下方法定义阶乘:义阶乘:可以看出是用阶乘定义阶乘,这种自己定义可以看出是用阶乘定义阶乘,这种自己定义自己的方法称为递归定义。自己的方法称为递归定义。递归的引入:递归的引入:递归定义的阶乘函数:递归定义的阶乘函数:fac(int n)if (n=0|n=1) return 1;else return n*fac(n-1);只要设计主函数调用阶乘函数,即可实现计算阶乘。只要设计主函数调用阶乘函数,即可实现计算阶乘。3.7 函数的递归调用函数的递归调用【例【例3.12】 求求4!运行结果:运行结果:4321126244!=24说明:说明:cout”n4!=”fac(4)endl;执行时是先算函数值,然后再从左到右输出各表达式的值。执行时是先算函数值,然后再从左到右输出各表达式的值。所以有两行输出,而不是第一行插在第二行赋值号与所以有两行输出,而不是第一行插在第二行赋值号与24之间。之间。3.7 函数的递归调用函数的递归调用探讨:探讨:计算是先右后左。请看下一条输出语句:计算是先右后左。请看下一条输出语句:cout”n4!=”fac(4) ”n3!=”fac(3)=0&ch=9?1:0;int main() char ch; while(cin.get(ch), ch!= n) if (IsNumber(ch) cout是数字字符是数字字符 endl; else cout不是数字字符不是数字字符 endl; return 0;因使用频度很高,说明为内联函数。因使用频度很高,说明为内联函数。3.9 头文件与多文件结构头文件与多文件结构 (选读)(选读)3.9.1 头文件头文件标准库头文件:标准库头文件:考虑标识符在其他文件中的可见性。使用头文件是很有效的方考虑标识符在其他文件中的可见性。使用头文件是很有效的方法。如:法。如:#includeusing namespace std; 其中的其中的iostream是是在在标准名字空间域标准名字空间域std中定义的头文件。对中定义的头文件。对应的传统方式的文件名为应的传统方式的文件名为,头文件以,头文件以“.h”为后缀为后缀。 系统定义的头文件系统定义的头文件中定义了一些常用的公用标识符和函数中定义了一些常用的公用标识符和函数,用户只要将头文件包含进自己的文件,就可使头文件中定义,用户只要将头文件包含进自己的文件,就可使头文件中定义的标识符在用户文件中变得可见,也就可以直接使用头文件中的标识符在用户文件中变得可见,也就可以直接使用头文件中定义的标识符和函数。定义的标识符和函数。 3.9.1 头文件头文件自定义头文件:自定义头文件:除了系统定义的头文件外,用户还可以除了系统定义的头文件外,用户还可以自定义头文件自定义头文件。对于具有外部存储类型的标识符,可以在其他任何。对于具有外部存储类型的标识符,可以在其他任何一个源程序文件中经声明后引用,因此用户完全可以一个源程序文件中经声明后引用,因此用户完全可以将一些具有将一些具有外部存储类型的标识符的声明外部存储类型的标识符的声明放在一个头放在一个头文件中。具体地说,头文件中可以包括:文件中。具体地说,头文件中可以包括:用户构造的用户构造的数据类型(如枚举类型),外部变量,外部函数、常数据类型(如枚举类型),外部变量,外部函数、常量和内联函数等具有一定通用性或常用的量,量和内联函数等具有一定通用性或常用的量,而一般而一般性的变量和函数定义不宜放在头文件中。性的变量和函数定义不宜放在头文件中。3.9.2 多文件结构多文件结构 在开发较大程序时,通常将其分解为多个源程序文件在开发较大程序时,通常将其分解为多个源程序文件,每个较小的程序用一个源程序文件建立。程序经过建立,每个较小的程序用一个源程序文件建立。程序经过建立、编译、连接,成为一个完整的可执行程序。、编译、连接,成为一个完整的可执行程序。多文件结构多文件结构通过工程进行管理,在工程中建立若干用户定义的头文件通过工程进行管理,在工程中建立若干用户定义的头文件.h和源程序文件和源程序文件.cpp。头文件中定义用户自定义的数据类。头文件中定义用户自定义的数据类型,所有的程序实现则放在不同的源程序文件中型,所有的程序实现则放在不同的源程序文件中。编译时。编译时每个源程序文件单独编译,如果源程序文件中有编译预处每个源程序文件单独编译,如果源程序文件中有编译预处理指令,则首先经过编译预处理生成临时文件存放在内存理指令,则首先经过编译预处理生成临时文件存放在内存,之后对临时文件进行编译生成目标文件,之后对临时文件进行编译生成目标文件.obj,编译后临,编译后临时文件撤销。所有的目标文件经连接器连接最终生成一个时文件撤销。所有的目标文件经连接器连接最终生成一个完整的可执行文件完整的可执行文件.exe。 图图3.11是一个多文件系统的开发过程。是一个多文件系统的开发过程。3.9.2 多文件结构多文件结构 编译 预编译编译 预编译 预编译编译图图3.11 C+程序开发过程程序开发过程file1.hfile1.cppfile2.hfile2.cppfilen.hfilen.cpp临时文件临时文件1临时文件临时文件2临时文件临时文件nfile1.objfile2.objfilen.objFilename.exe.libC+标准类库标准类库连接连接运行运行3.10 编译预处理编译预处理(选读选读) 3.10.1 宏定义指令宏定义指令 3.10.2 文件包含指令文件包含指令 3.10.3 条件编译指令条件编译指令 3.10.1 宏定义指令宏定义指令1 1 不带参宏定义不带参宏定义用来产生与一个字符串对应的常量字符串,格式为:用来产生与一个字符串对应的常量字符串,格式为:#define #define 宏名宏名 常量串常量串 预处理后文件中凡出现该字符串处均用其对应的常量预处理后文件中凡出现该字符串处均用其对应的常量串代替。替换过程称为宏替换或宏展开。例如,如果串代替。替换过程称为宏替换或宏展开。例如,如果使用指令使用指令#define PI 3.1415926#define PI 3.1415926则程序中可以使用标识符则程序中可以使用标识符PIPI,编译预处理后产生一个,编译预处理后产生一个中间文件,文件中所有中间文件,文件中所有PIPI被替换为被替换为3.14159263.1415926。宏替换只是字符串和标识符之间的简单替换,预处理宏替换只是字符串和标识符之间的简单替换,预处理本身不做任何数据类型和合法性检查,也不分配内存本身不做任何数据类型和合法性检查,也不分配内存单元单元。3.10.1 宏定义指令宏定义指令2 2 带参数的宏定义带参数的宏定义带参宏定义的形式很象定义一个函数,格式为:带参宏定义的形式很象定义一个函数,格式为:#define 宏名宏名 ( 形参表形参表 ) 表达式串表达式串 例如作如下宏定义:例如作如下宏定义:#define S(a,b) (a)*(b)/2程序中可使用程序中可使用S(a,b),预处理后产生中间文件,其中,预处理后产生中间文件,其中S(a,b)被被替换成替换成(a)*(b)/2。注意,宏定义时形参通常要用括号括起来。注意,宏定义时形参通常要用括号括起来,否则容易导致逻辑错误。例如,如果定义:,否则容易导致逻辑错误。例如,如果定义:#define S(a,b) a*b/2那么程序中的那么程序中的S(3+5,4+2)就会被宏展开为就会被宏展开为3+5*4+2/2,不,不符合定义的真正的意图。符合定义的真正的意图。带参宏定义形式上象定义函数,但它与函数的本质不同,宏定带参宏定义形式上象定义函数,但它与函数的本质不同,宏定义仍然只是产生字符串替代,不存在分配内存和参数传递。义仍然只是产生字符串替代,不存在分配内存和参数传递。 3.10.2 文件包含指令文件包含指令文件包含文件包含用用#include指令指令,预处理后将指令中指明的源程序,预处理后将指令中指明的源程序文件嵌入到当前源程序文件的指令位置处。格式为:文件嵌入到当前源程序文件的指令位置处。格式为:#include 或或#include 文件名文件名第一种第一种方式称为方式称为标准方式标准方式,预处理器将在,预处理器将在include子目录下子目录下搜索由文件名所指明的文件。这种方式适用于嵌入搜索由文件名所指明的文件。这种方式适用于嵌入C+提供提供的头文件,因为这些头文件一般都存在的头文件,因为这些头文件一般都存在C+系统目录的系统目录的include子目录下。而子目录下。而第二种方式第二种方式编译器将首先在当前文件编译器将首先在当前文件所在目录下搜索,如果找不到再按标准方式搜索。这种方式所在目录下搜索,如果找不到再按标准方式搜索。这种方式适用于嵌入用户自己建立的头文件。适用于嵌入用户自己建立的头文件。一个被包含的头文件中还可以有一个被包含的头文件中还可以有#include指令,指令,即即include指令可以嵌套指令可以嵌套,但是,如果同一个头文,但是,如果同一个头文件在同一个源程序文件中被件在同一个源程序文件中被重复包含重复包含,就会出现,就会出现标标识符重复定义的错误识符重复定义的错误。例如:头文件。例如:头文件f2.h中包含了中包含了f1.h,如果文件,如果文件f3.cpp中既包含中既包含f1.h,又包含,又包含f2.h,那么编译将提示错误,原因是那么编译将提示错误,原因是f1.h被包含了两次,被包含了两次,那么其中定义的标识符在那么其中定义的标识符在f3.cpp中就被重复定义。中就被重复定义。避免重复包含可以用避免重复包含可以用条件编译指令条件编译指令。3.10.2 文件包含指令文件包含指令3.10.3 条件编译指令条件编译指令 1 用用宏名宏名作为编译的条件作为编译的条件格式为:格式为:#ifdef#else#endif2 表达式的值表达式的值作为编译条件作为编译条件格式为:格式为:#if #else#endif当希望在不同条件下编译程序的不同部分。这种情况就要使当希望在不同条件下编译程序的不同部分。这种情况就要使用条件编译指令。用条件编译指令。 其中程序段可以是程序也可以是编译预处理指令。可以通过其中程序段可以是程序也可以是编译预处理指令。可以通过在该指令前面安排宏定义来控制编译不同的程序段。在该指令前面安排宏定义来控制编译不同的程序段。例:在调试程序时常常要输出调试信息,而调试完后不需要例:在调试程序时常常要输出调试信息,而调试完后不需要输出这些信息,则可以把输出调试信息的语句用条件编译指输出这些信息,则可以把输出调试信息的语句用条件编译指令括起来。形式如下:令括起来。形式如下:#ifdef DEBUGcouta=atx=xendl;#endif在程序调试期间,在该条件编译指令前增加宏定义:在程序调试期间,在该条件编译指令前增加宏定义:#define DEBUG调试好后,删除调试好后,删除DEBUG宏定义,将源程序重新编译一次。宏定义,将源程序重新编译一次。条件编译指令包括:条件编译指令包括:#if、#else、#ifdef、#ifndef、#endif、#undef等。等。#ifndef与与#ifdef作用一样,只是选择的条件相反。作用一样,只是选择的条件相反。#undef指令用来取消指令用来取消#define指令所定义的符号,这样指令所定义的符号,这样可以根据需要打开和关闭符号。可以根据需要打开和关闭符号。第三章第三章 函数函数再见谢谢!谢谢!3.1.3 函数的调用函数的调用【例【例3.1】 main( )函数函数调用调用max(2.5,4.7 )函数函数max(2.5,4.7 )return 4.7 主程序后主程序后续语句续语句【例【例3.1】 输入两个实数,输出其中较大的数。其中输入两个实数,输出其中较大的数。其中求两个实数中的较大数用函数完成。求两个实数中的较大数用函数完成。程序如下程序如下:#include using namespace std;float max(float a,float b)return(a=b?a:b);int main()float x,y;cout输入两个实数:输入两个实数:xy;coutx和和y中较大数为中较大数为max(x,y)endl;return 0;形式参数形式参数实际参数实际参数 3.2.1 函数的参数传递及传值调用函数的参数传递及传值调用【例【例3.2】 调用调用power(4.6,3 )函数函数power(4.6,3 )return 97.336 主程序后续语主程序后续语句句n= 3x= 4.6c= a【例【例3.2】 实参和形参对应关系的示例。实参和形参对应关系的示例。float power(float x,int n) /求求x x的的n n次幂次幂float p=1;while(n-) p*=x;return p; int main()int n=3;float x=4.6;char c=a;coutpower(x,n)=power(x,n)endl;c
展开阅读全文