版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、第第2章章 函数和作用域函数和作用域2.1函数定义和调用函数定义和调用2.2C+函数特性函数特性2.3作用域和存储类型作用域和存储类型2.4名称空间名称空间2.5综合应用实例:综合应用实例:Fibonacci数列数列 2.1 函数定义和调用函数定义和调用C+的任何一个程序都可由一个主函数和若干个子函数组合而成。主函数可以调用子函数,子函数还可以调用其他子函数。C+规定主函数名必须是main,而其他函数可以是库函数库函数或自定义函数自定义函数。主函数主函数main不仅是程序的入口函数,而且与其他函数相比较还有许多使用上的限制。例如,它不能被其他函数调用,不能用inline和static来说明等。
2、ANSI/ISO C+还规定主函还规定主函数数main的函数类型必须是的函数类型必须是int,以保证程序的移植性,以保证程序的移植性。库函数库函数,又称标准函数标准函数,是ANSI/ISO C+编译系统已经预先定义好的函数,程序设计时可根据实际需要,直接使用这类函数,而不必重新定义。调用时,必须在程序中包含相应的头文件,并指明使用名称空间std。自定义函数自定义函数是用户根据程序的需要,将某一个功能相对独立的程序定义成的一个函数,或将解决某个问题的算法用一个函数来组织。与变量的使用规则相同,在C+程序中一定要先说明和定义函数,然后才能调用函数。C+中每一个函数的定义都是由4个部分组成的,即函数
3、名、函数类型、形式参数表和函数体,其定义的格式如下: 2.1.1函数定义函数定义 ( ) 函函数数体体2.1.1 函数定义函数定义其中,函数名函数名应是一个合法有效的C+标识符;函数头的形式参数又简称为形参形参。参数表中的每一个形参都是由形参的数据类型和形参名来构成每一个形参都是由形参的数据类型和形参名来构成,形数个数可以是0,表示没有形式参数,但圆括号不能省略,也可以是1个或多个形参,但多个形参间要用逗号分隔。由花括号构成的是函数的函数体函数体部分,它可以有若干条语句,用于实现这个函数执行的功能。根据上述定义格式,可以编写一个函数sum,如图2.1所示,注意它们的书写规范。int sum(i
4、nt x, int y)int z = x + y;return z;对齐函数头函数体缩进函数类型函数名形参图图2.1 定义一个函数定义一个函数sum2.1.1 函数定义函数定义需要说明的是:(1) C/C+不允许在一个函数体中再定义函数,即禁止嵌套定义,但允许嵌套调用。(2) 函数体也可不含有任何语句,这样的函数称为空函数空函数,它仅为程序结构而设定,本身没有任何操作。(3) 函数类型函数类型决定了函数所需要的返回值类型,它可以是除数组类型之外的任何有效的C+数据类型,包括引用、指针等。一旦指定了函数类型,则须在函数体中通过return语句来指定函数的返回值,且返回值的类型应与函数类型相同,
5、若返回值的类型与函数类型不相同,则程序按类型自动转换方式按类型自动转换方式转换成函数的类型或将return后面的表达式进行强制转换。(4) 若函数类型为void时,则表示该函数没有返回值。但仍然可以在函数体中使用return语句“return ;”,此时可将“return;”语句理解为是函数体花括号“”的作用,当流程遇到函数体的“”时,函数调用结束,控制权返回给主调函数。例如:void f1( int a)if (a 10)return;/ return;一旦执行,后面的语句不再被执行当a10条件满足时,“return;”语句将控制权返回给主调函数。2.1.2 函数的调用和声明函数的调用和声明
6、1. 函数的实参和形参函数的实参和形参定义一个函数就是为了以后的调用。调用函数时,先写函数名,然后紧跟括号,括号里是实际调用该函数时所给定的参数,称为实际参数实际参数,简称实参实参,并与形参相对应。要注意形参形参和实参实参的区别:(1) 从模块概念来说,形参是函数的接口,是存在于函数内部的变量。而实参是存在于函数外部的变量。它们不是同一个实体不是同一个实体,也就是说,形参变量和实参变量所对应的内存空间不是同一个内存空间。(2) 按函数定义时所指定的形参类型,实参除变量外还可以是数值或表达式等,而形参只能是变量。(3) 形参在函数调用之前是不存在的,只有在发生函数调用时,函数中的形参才会被分配内
7、存空间,然后执行函数体中的语句,而当调用结束后,形参所占的内存空间又会被释放。2. 函数的调用函数的调用函数调用的一般格式为:( )调用函数时要注意:实参与形参的个数应相等,类型应一致,且按顺序对应,一一实参与形参的个数应相等,类型应一致,且按顺序对应,一一传递数据传递数据。例如,下面的示例用来输出一个三角形的图案。2.1.2 函数的调用和声明函数的调用和声明 例例Ex_Call 函数的调用函数的调用#include using namespace std;void printline( char ch, int n )for (int i = 0 ; in ; i+)coutch;coute
8、ndl ;int main()int row = 5;for (int i = 0; irow; i+)printline(*, i+1);/ Areturn 0;程序运行的结果如下: 2.1.2 函数的调用和声明函数的调用和声明代码中,main函数的for循环语句共调用了5次printline函数(A句),每次调用时因实参i+1值不断改变,从而使函数printline打印出来的星号个数也随之改变。printline函数由于没有返回值,因此它作为一个语句来调用。事实上,对于有返回值的函数也可进行这种方式的调用,只是此时不使用返回值,仅要求函数完成一定的操作。实际上,在C+中,一个函数的调用方式
9、还有很多。例如,对于前面sum函数还可有下列调用方式:int b = 2 * sum(4, 5);/ Bint c = 1; c = sum(c, sum(c, 4);/ C其中,B语句把函数作为表达式的一部分,并将返回值参与运算,结果b = 18;C是将函数作为函数的实参,等价于“c = sum(1, sum(1,4);”,执行sum(1,4)后,等价于“c = sum(1,5) ;”,最后结果为c = 6。3. 函数的声明函数的声明由于前面函数printline的定义代码是放在main函数中调用语句A之前,因而A语句执行不会有问题。但若将函数printline的定义代码放在调用语句A之后,
10、即函数定义在后,而调用在前,就会产生“printline标识符未定义”的编译错误。此时必须在调用前进行函数声函数声明明。2.1.2 函数的调用和声明函数的调用和声明声明一个函数按下列格式进行: ( );可见,函数声明的格式是在函数头的后面加上分号函数声明的格式是在函数头的后面加上分号“;”。但要注意,函数声明的内容应和函数的定义应相同。例如,对于前面sum函数和最后一个printline函数可有如下声明:int sum(int x, int y);void printline( char ch, int n );由于函数的声明仅是对函数的原型进行说明,即函数原型声明函数原型声明,其声明的形参变
11、量名在声明语句中并没有任何语句操作它,因此这里的形参名和函数的定义时的形参名可以不同,且函数声明时的形参名还可以省略,但函数名、函数类型、形参类型及个数应与定义时相同。例如,下面几种形式都是对sum函数原型的合法声明:int sum(int a, int b); / 允许原型声明时的形参名与定义时不同int sum(int, int); / 省略全部形参名int sum(int a, int);/ 省略部分形参名int sum(int, int b);/ 省略部分形参名 不过,从程序的可读性考虑,在声明函数原型时,为每一个形参指定有意义的标识符,并且和函数定义时的参数名相同,是一个非常好的习惯
12、。2.1.3 值传递值传递在C+中,函数的参数传递有两种方式,一是按值传递按值传递,二是地址传递地址传递或引用传递引用传递。这里先来说明按值传递的参数传递方法,地址传递或引用传递在以后来讨论。当函数的形参定义成一般变量形参定义成一般变量时,如前面printline和sum函数的形参都是一般变量,此时函数的参数传递就是按值传递方式按值传递方式,简称值传递值传递,是指当一个函数被调用时,C+根据实参和形参的对应关系将实际参数的值值一一传递给形参,供函数执行时使用。值传递的特点是:(1) 若实参指定是变量,则传递的是实参变量的值实参变量的值而不是实参变量的地址。(2) 在执行函数代码时,由于对实参数
13、据的操作最终是在形参的内存空间形参的内存空间中进行,因此形参值的改变只是改变了形参的内存空间存储的值,而不会改变实参变量所对应的内存空间的值。也就是说,即使形参的值在函数中发生了变化,函数调用结束后,实参的值不会受到影响。例如:2.1.3 值传递值传递例例Ex_SwapValue 交换函数两个参数的值。交换函数两个参数的值。#include using namespace std;void swap(float x, float y);/ 函数原型说明int main()float a = 20, b = 40;couta = a, b = bn;swap(a, b);/ 函数调用couta
14、= a, b = bn;return 0;void swap(float x, float y)/ 函数定义float temp;temp = x; x = y; y = temp;coutx = x, y = yn;2.1.3 值传递值传递程序的运行结果为:可以看出,虽然函数swap中交换了两个形参x和y的值,但交换的结果并不能改变实参的值,所以调用该函数后,变量a和b的值仍然为原来的值。 函数值传递值传递方式的最大好处是保持函数的独立性。在值传递方式下,函数只有通过指定函数类型并在函数体中使用return来返回某一类型的数值。2.1.4 函数的默认形参值函数的默认形参值在C+中,允许在函数
15、的声明或定义时给一个或多个参数指定默认值默认值。这样在调用时,可以不给出实际参数,而按指定的默认值进行工作。例如:void delay(int loops = 1000)/ 函数定义,1000为形参loops的默认值if ( 0 = loops) return;for (int i=0; iloops; i+);/ 空循环,起延时作用这样,当有调用delay();/ 和delay(1000)等效时,程序就会自动将loops当作成1000的默认值来进行处理。当然,也可在函数调用时指定相应的实际的参数值,例如:delay(2000);/ 形参loops的值为2000在设置函数的默认形参值时要注意:
16、(1) 当函数既有原型声明又有定义时,默认参数只能在原型声明中指定,而不能在函数定义中指定。例如:void delay(int loops);/ 函数原型声明/ void delay(int loops = 1000)/ 错误错误:此时不能函数定义中指定默认参数/ 2.1.4 函数的默认形参值函数的默认形参值(2) 当一个函数中需要有多个默认参数时,则形参分布中,默认参数应严格从右到左逐次定义和指定,中间不能跳开。例如:void display(int a, int b, int c = 3);/ 合法void display(int a, int b = 2, int c = 3);/ 合法
17、void display(int a = 1, int b = 2, int c = 3);/ 合法:可以对所有的参数设置默认值void display(int a, int b = 2, int c);/ 错误错误:默认参数应从最右边开始void display(int a = 1, int b = 2, int c);/ 错误错误:默认参数应从最右边开始void display(int a = 1, int b, int c = 3);/ 错误错误:多个默认参数中间不能有非默认参数(3) 当带有默认参数的函数调用时,系统按从左到右的顺序将实参与形参结合,当实参的数目不足时,系统将按同样的顺
18、序用声明或定义中的默认值来补齐所缺少的参数。2.1.4 函数的默认形参值函数的默认形参值例例Ex_Default 在函数定义中设置多个默认参数在函数定义中设置多个默认参数#include using namespace std;void display(int a, int b = 2, int c = 3)/ 在函数定义中设置默认参数couta = a, b = b, c = cn;int main()display(1);display(1, 5);display(1, 7, 9);return 0;程序的运行结果如下:2.1.4 函数的默认形参值函数的默认形参值(4) 由于对同一个函数的
19、原型可作多次声明原型可作多次声明,因此在函数声明中指定多个默认参数时,可用多条函数原型声明语句来指定,但同一个参数的默认值只能指定一次。例如,例Ex_Default可改写为:#include using namespace std;/ 下面2条函数说明语句等效于void display(int a, int b = 2, int c = 3);void display(int a, int b, int c=3);/ 指定c为默认参数void display(int a, int b=2, int c);/ 指定b为默认参数 2.2 C+函数特性函数特性 函数重载函数重载(overloaded
20、)是C+对C的扩展,它允许多个同名的函数存在,但同名的各个函数的形参必须有区别:要么形参的个数不同;要么形参的个数相同,但参数类型要么形参的个数不同;要么形参的个数相同,但参数类型有所不同有所不同。 例例Ex_OverLoad 编程编程求两个或三个操作数之和求两个或三个操作数之和程序的运行结果为: 从上面的例子可以看出:由于使用了函数的重载,因而不仅方便函数名的记忆,而且更主要的是完善了同一个函数的代码功能,给调用带来了许多方便。程序中各种形式的sum函数都称为sum的重载函数重载函数。需要说明的是:(1) 重载函数必须具有不同的参数个数或不同的参数类型,若只有返回值的类型不同是不行的。例如:
21、void fun(int a, int b);int fun(int a, int b);是错误错误的。因为如果有函数调用fun(2, 3)时,编译器无法准确地确定应调用哪一个函数。(2) 当函数的重载带有默认参数时,也要应该注意避免上述的二义性二义性情况。例如:int fun(int a, int b = 0);int fun(int a);是错误错误的。因为如果有函数调用fun(2)时,编译器也是无法准确地确定应调用哪一个函数。2.2.2 函数嵌套调用函数嵌套调用C+允许在函数中再调用其他函数,这种调用称为函数的嵌套调用嵌套调用。例如,下面的示例是通过多个函数以及函数的嵌套调用来求解一元二
22、次方程的根。 例例Ex_Root 函数函数嵌套调用:求解一元二次方程的根嵌套调用:求解一元二次方程的根本例main函数中调用了root函数,root函数中又调用了sdelta和print函数,而sdelta函数还调用了cmath头文件定义的库函数sqrt(求平方根)和fabs(求浮点数的绝对值)。它们的调用关系如图2.2所示。main函数root函数sdelta函数fabs库函数print函数sqrt库函数图图2.2 例例Ex_Root中的各函数调用的关系中的各函数调用的关系2.2.2 函数嵌套调用函数嵌套调用函数print用来输出根的信息,当n=0时,参数r1和r2无效,但调用该函数时还必须
23、指定相应的实际参数,只是此时的实参值在函数执行中不起作用。当n=1时,只有参数r1有效,而当n=2时,参数r1和r2才会都有效。函数sdelta用来计算一元二次方程的系数a、b和c的b2-4ac的平方根。由于“d = b*b - 4.0 * a * c;”计算误差,d值不一定刚好等于0.0,因此调用库函数fabs求d的绝对值,并当小于1.0e-10(1.0 x1010)时,将d值视为0.0,并返回0.0。当d值大于0.0时,函数返回其平方根。当d值小于0.0时,由于无法求出实平方根,因此作为程序技巧,函数返回-1.0。这样当在root函数中调用时,通过返回值的判断来决定有无方程根。root函数
24、用来求解一元二次方程的方程根并输出,函数设计时要考虑:有无根、同根、不同根、系数a为0以及系数b为0等多种情况。程序运行的结果如下: 2.2.3 递归函数递归函数C+允许在调用一个函数的过程中出现直接地或间接地调用函数本身,这种情况称为函数的递归调用递归调用。递归(Recursion)是一种常用的程序方法(算法),相应的函数称为递归函递归函数数。例如,用递归函数编程求n的阶乘n!。n!=n*(n-1)*(n-2)*.*2*1。它也可用下式表示:由于n!和(n-1)!都是同一个问题的求解,因此可将n!用递归函数long factorial(int n)来描述,程序代码如下: 例例Ex_Facto
25、rial 编程求编程求n的阶乘的阶乘n!#include using namespace std;long factorial(int n);int main()coutfactorial(4)0时2.2.3 递归函数递归函数在函数factorial中,当n不等于0时,又调用了该函数本身。下面来分析此函数的执行过程:(1) 因n = 4,不等于0,故执行“result = 4*factorial(3);”,函数返回的值为4*factorial(3),即factorial(4) = 4*factorial(3);(2) 调用“factorial(3);”, n = 3不等于0,执行“result
26、=3*factorial(2);”,函数返回的值为3*factorial(2) ,即factorial(3) =3*factorial(2);(3) 调用“factorial(2);”, n = 2不等于0,执行“result=2*factorial(1);”, 函数返回的值为2*factorial(1) ,即factorial(2) =2*factorial(1);(4) 调用“factorial(1);”,n = 1不等于0,执行“result=1*factorial(0);”, 函数返回的值为1*factorial(0) ,即factorial(1) =1*factorial(0);(5
27、) 调用“factorial(0);”,n等于0,结果函数返回的值为1。上述过程是根据程序运行过程进行推算的,这个过程称为“递推”过程。函数的递归调用在递推过程后还进行另一称为“回归”的过程,它是按“递推”的逆过程,逐一求值回归,一直到达递推的开始处。因此,当factorial(0) = 1后,factorial(1) = 1*1,factorial(2) = 2*1*1,factorial(3) = 3*2*1*1,factorial(4) = 4*3*2*1*1,结果为4!,值为24。2.2.3 递归函数递归函数以上的例子可以看出,函数的递归调用能使程序精巧、高效。但要注意,递归函数中必须
28、要有结束递归过程的条件,否则递归会无限制地进行下去。例如,在上述代码中,if (n=0) result=1;就是结束递归过程的条件,若函数factorial变为:long factorial(int n)long result=0;result = n*factorial(n-1);return result;则递归会无限制地进行下去,此程序将变得毫无用途。需要说明的是:虽然递归调用编写的程序简洁清晰,但每次调用函数时,都需要分配内存来保存现场和返回地址,内存空间开销很大,有时会引起栈内存溢出。2.2.4 内联函数内联函数在程序的执行过程中,调用函数时首先需要保存主调函数的现场和返回地址,然后
29、程序转移到被调函数的起始地址继续执行。被调函数执行结束后,先恢复主调函数的现场,取出返回地址并将返回值赋给函数调用本身,最后在返回地址处开始继续执行。当函数体比较小且执行的功能比较简单时,这种函数调用方式的系统开销相对较大。为了解决这一问题,C+引入了内联函数内联函数的概念,它把函数体的代码直接插入到调用处,将调用函数的方式改为顺序执行顺序执行直接插入的程序代码,这样可以减少程序的执行时间,但同时增加了代码的实际长度。内联函数的使用方法与一般函数相同,只是在内联函数定义时,需在函数的类型前面加上inline关键字。例如: 例例Ex_Inline 用内联函数实现求两个实数的最大值用内联函数实现求
30、两个实数的最大值#include using namespace std;inline float fmax(float x, float y)return xy?x:y;int main()float a;a = fmax(5, 10);/ Acout最大的数为:a10 ? 5 : 10;程序的运行结果如下: 在使用内联函数时,还需要注意的是:(1) 内联函数也要遵循定义在前,调用在后的原则。形参与实参之间的关系与一般函数相同。(2) 关键字inline必须放在函数定义体前才是内联函数,仅在函数声明时使用inline而不在定义体前使用,不能定义内联函数。(3) 在C+中,需要定义成的内联函数
31、不能含有循环、switch和复杂嵌套的if语句。(4) 递归函数不能被用来做内联函数。(5) 编译器是否将用户定义成的内联函数作为真正的内联函数处理,由编译器自行决定。总之,内联函数一般是比较小的、经常被调用的、大多可在一行写完的函数,并常用来代替以后要讨论的带参数的宏定义带参数的宏定义。2.3 作用域和存储类型作用域和存储类型作用域又称作用范围作用范围,是指程序中标识符(变量名、函数名、数组名、类名、对象名等)的有效范围。一个标识符是否可以被引用,称之为标识符的可见性可见性。在一个C+程序项目中,一个标识符只能在声明或定义它的范围内可见,在此之外是不可见的。根据标识符的作用范围,可将其作用域
32、分为5种:函数原型作用域函数原型作用域、函数作用域函数作用域、块作用域块作用域、类作类作用域用域和文件作用域文件作用域。其中,类作用域将在以后介绍,这里介绍其他几种。1. 函数原型作用域函数原型作用域函数原型作用域函数原型作用域指的是在声明函数原型所指定的参数标识符的作用范围。这个作用范围是在函数原型声明中的左、右圆括号之间。正因为如此,在函数原型中声明的标识符可以与函数定义中说明的标识符名称不同。由于所声明的标识符与该函数的定义及调用无关,所以可以在函数原型声明中只作参数的类型声明,而省略参数名。例如:double max(double x, double y);和double max(do
33、uble, double);是等价的。2. 函数作用域函数作用域具有函数作用域的标识符在声明它的函数内可见,但在此函数之外是不可见的。在C+语言中,只有只有goto语句使用的标号是惟一具有函数作用域的标识符语句使用的标号是惟一具有函数作用域的标识符。2.3.1作用域作用域2.3.1 作用域作用域这里的块块就是由“”和“”组成的块语句块语句(复合语句)。在块中声明的标识符,其作用域从声明处开始,一直到结束块的花括号为止。块作用域也称作局部作用域局部作用域,具有块作用域的变量是局部变量局部变量。例如:void fun(void)/ 在形参中指定void,表示没有形参,void可不要a的作用域的作用
34、域b的作用域的作用域int a;/ a的作用域起始处cina;if (a0) a = -a;int b;/ b的作用域起始处/ b的作用域终止处 / a的作用域终止处代码中,声明的局部变量a和b处在不同的块中。其中变量a是在fun函数的函数体块中,因此在函数体这个范围内,该变量是可见的。而b是在if语句块中声明的,故它的作用域是从声明处开始到if语句结束处终止。a的作用域的作用域b的作用域的作用域2.3.1 作用域作用域需要说明的是:当标识符的作用域完全相同时,不允许出现相同的标识符名。而当当标识符的作用域完全相同时,不允许出现相同的标识符名。而当标识符具有不同的作用域时,却允许标识符同名标识
35、符具有不同的作用域时,却允许标识符同名。例如:void fun(void)块块A块块B/ 块A开始int i;/ 块B开始int i;i = 100;/ 块B结束/ 块A结束代码中,在A和B块中都声明了变量i,这是允许的,因为块A和块B不是同一个作用域。但同时出现另外一个问题,语句“i = 100;”中的i是使用A块中的变量i还是使用B中的变量i?C+规定在这种作用域嵌套的情况下,如果内层(块B)和外层(块A)作用域声明了同名的标识符,那么在外层作用域中声明的标识符对于该内层作用域是不可见不可见的。也就是说,在块B声明的变量i与块A声明的变量i无关,当块B中的i=100时,不会影响块A中变量i
36、的值。块块A块块B2.3.1 作用域作用域4. 文件作用域文件作用域在所有函数外定义的标识符称为全局标识符全局标识符。全局标识符的作用域是文件作用域,它从声明之处开始,直到文件结束一直是可见的。具有文件作用域的变量和常量称为全全局变量局变量和全局常量全局常量,例如:const float PI = 3.14;/ 全局常量PI,其作用域从此开始到文件结束int a;/ 全局变量a,其作用域从此开始到文件结束void main( ) / void funA(int x) / 其中,全局常量PI和全局变量a的作用域是文件作用域。标识符的文件作用域一般还有以下几种情况:2.3.1 作用域作用域(1)
37、若函数定义在后,调用在前,必须进行函数原型声明。若函数定义在前,调用在后,函数定义包含了函数的原型声明。一旦声明了函数原型,函数标识符的作用域是从定义开始到源程序文件结束。例如:void funA(int x ); / 函数funA的作用域从此开始到文件结束void funB( ) / 函数funA的作用域从此开始到文件结束/ void main( ) / void funA(int x) / (2) 对于在头文件中定义的标识符,当它们被预编译时,会将头文件的内容在源文件的相应位置展开,所以在头文件中定义的标识符的作用域可以看成从#include该头文件开始的位置到源程序文件结束。例如,以前示
38、例中头文件iostream的std:cin和std:cout的作用域是从#include预处理指令开始一直到源程序文件结束。 注意,若使用若使用ANSI/ISO C+新文件格式的包含,则新文件格式的包含,则cin和和cout的作用域是从的作用域是从using语句开始一直到源程序文件结束。语句开始一直到源程序文件结束。2.3.2 域运算符域运算符在C+中,若在块作用域内使用与局部标识符同名的块外标识符时,则须使用域运算符“:”来引用,且该标识符一定要是全局标识符,即它具有文件作用域。例如: 例例Ex_Process 在块作用内引用文件作用域的同名变量在块作用内引用文件作用域的同名变量#inclu
39、de using namespace std;int i = 10;/ Aint main()int i = 20;/ Bint i = 5;/ Cint j;:i = :i + 4;/ :i是引用A定义的变量i,不是B中的ij = :i + i;/ 这里不加:的i是C中定义的变量cout:i = :i, j = jn;cout:i = :i, i = in;/ 这里不加:的i是B中定义的变量return 0; 程序的运行结果为:需要说明的是,域运算符“:”往往还用于指定引用对象所属的类、名称空间等。2.3.3 存储类型存储类型存储类型是针对变量而言的,它规定了变量的生存期。无论是全局变量还是
40、局部变量,编译系统往往根据其存储方式定义、分配和释放相应的内存空间。变量的存储类型反映了变量在哪开辟内存空间以及占用内存空间的有效期限。在C+中,变量有4种存储类型:自动类型自动类型、静态类型静态类型、寄存器类型寄存器类型和外部类型外部类型,这些存储类型是在变量定义时来指定的,其一般格式如下: ; 1. 自动类型和寄存器类型自动类型和寄存器类型一般说来,用自动存储类型声明的变量都是限制在某个程序范围内使用,即为局部变量。从系统角度来说,自动存储类型变量是采用动态分配方式在栈区栈区中来分配内存空间。因此,当程序执行到超出该变量的作用域时,就释放它所占用的内存空间,其值也随之消失了。在C+语言中,
41、声明一个自动存储类型的变量是在变量类型前加上关键字auto,例如:autoint i;若自动存储类型的变量是在函数内或语句块中声明的,则可省略关键字auto,例如:void fun()int i;/ 省略auto/ 2.3.3 存储类型存储类型使用关键字register声明寄存器类型的变量的目的是将所声明的变量放入寄存器内,从而加快程序的运行速度。例如:register int i;/ 声明寄存器类型变量但有时,在使用register声明时,若系统寄存器已经被其他数据占据时,寄存器类型的变量就会自动当作auto变量。2. 静态类型静态类型从变量的生存期来说,一个变量的存储空间可以是永久的,即在
42、程序运行期间该变量一直存在,如全局变量全局变量;也可以是临时的,如局部变量局部变量,当流程执行到它的说明语句时,系统为其在栈区栈区中动态分配一个临时的内存空间,并在它的作用域中有效,一旦流程超出该变量的作用域时,就释放它所占用的内存空间,其值也随之消失。但是,若在声明局部变量类型前面加上关键字static,则将其定义成了一个静态类型的变量。这样的变量虽具有局部变量的作用域,但由于它是用静态分配方式在静态数据区静态数据区中来分配内存空间。因此,在这种方式下,只要程序还在继续执行,静态类型变量的值就一直有效,不会随它所在的函数或语句块的结束而消失。简单地说,静态类型的局部变量虽静态类型的局部变量虽
43、具有局部变量的作用域,但却有全局变量的生存期具有局部变量的作用域,但却有全局变量的生存期。 值得注意的是,如需定义一个很大的数组时,则应将数组定义成全局数组或将数组的存储类型定义成static,而不应定义成自动存储类型或局部数组,因为静态数据区的内存要比栈区内存大许多。例如:static intarray5000;/ 指定静态存储类型2.3.3 存储类型存储类型需要说明的是,静态类型的局部变量只在第一次执行时进行初始化,正因为如此,在声明静态类型变量时一定要指定其初值,若没有指定,编译器还会将其初值置为0。例如:例例Ex_Static 使用静态类型的局部变量使用静态类型的局部变量#includ
44、e using namespace std;void count()int i = 0;static int j = 0; / 静态类型i+;j+;couti = i, j = jn;int main()count();count();return 0; 2.3.3 存储类型存储类型程序中,当第1次调用函数count时,由于变量j是静态类型,因此其初值设为0后不再进行初始化,执行j+后,j值为1,并一直有效。第2次调用函数count时,由于j已分配内存且进行过初始化,因此语句“static int j = 0;”被跳过,执行j+后,j值为2。故程序运行结果为: 事实上,在程序中声明的全局变量总
45、是静态存储类型全局变量总是静态存储类型,若在全局变量前加上static,使该变量只在这个源程序文件内使用,称之为全局静态变量全局静态变量或静态全局变量静态全局变量。若一个程序由一个文件组成,在声明全局变量时,有无static并没有区别,但若多个文件组成一个程序时,加与不加static,其作用完全不同。例如: 2.3.3 存储类型存储类型 例例Ex_StaticScope 使用静态类型的全部变量使用静态类型的全部变量(1) 用Visual C+中的AppWizard创建一个控制台应用程序Ex_StaticScope,在创建过程中选择项目类型为“一个简单的程序(A simple applicati
46、on)”。(2) 在Ex_StaticScope.cpp文件中添加下列代码:#include stdafx.h#include using namespace std;int n;void f();/ 先作函数原型声明,函数定义在另一个文件中int main()n = 20;coutn = nn;f();return 0;2.3.3 存储类型存储类型代码中,#include stdafx.h语句是Visual C+应用程序向导自动添加的。stdafx.h是每个Visual C+应用程序所必有的预编译头文件,程序所用到的Visual C+头文件包含语句一般均应添加到这个文件中。函数f先在该文件中
47、作函数的原型声明,函数的定义在另一个文件中,这是Visual C+所允许的。选择“文件”“新建”菜单命令,默认时会自动将“新建”对话框切换到“文件”页面,选中“C+ Source File”文件类型,在文件编辑框中输入Ex_StaticScope_1.cpp,如图2.3所示。单击确定按钮,在Ex_StaticScope_1.cpp中添加下列代码:#include stdafx.h#include using namespace std;static int n;/ 默认初值为0void f()/ 函数定义n+;/ 这里的n是本文件定义的静态类型变量coutn = nn; 2.3.3 存储类型存
48、储类型图图2.3 向项目中添加向项目中添加C+文件文件项目Ex_StaticScope运行后,其结果如下: 可见,函数f输出1而不是21,表明Ex_StaticScope.cpp中的全局变量n和Ex_Static- Scope_1.cpp中的静态全局变量n是互不相干的。因此,静态全局变量对组成该程序的其他静态全局变量对组成该程序的其他源文件是无效的,它能很好地解决在程序多文件组织中全局变量的重名问题源文件是无效的,它能很好地解决在程序多文件组织中全局变量的重名问题。同静态全局变量相类似,静态函数也是在某个函数声明前加上static,它的目的也是使该函数只在声明的源文件中使用,对于其他源文件则无
49、效。 2.3.3 存储类型存储类型3. 外部类型外部类型使用关键字extern声明的变量称为外部变量外部变量,一般是指定义在本程序外部的变量。当某个变量被声明成外部变量时,不必再次为它分配内存就可以在本程序中引用这个变量。在C+中,只有在两种情况下需要使用外部变量。第一种情况第一种情况:在同一个源文件中,若定义的变量使用在前,声明在后,这时在使用前要声明为外部变量。例如: 例例Ex_Extern1 同一个源文件中的外部变量使用同一个源文件中的外部变量使用#include using namespace std;extern int a;/ 声明外部变量a,/ 若没有此语句,函数count中的语
50、句将出错void count()a+;coutan;int a = 10;/ 外部变量a的实际声明处int main()count();coutan;return 0;2.3.3 存储类型存储类型运行结果为: 第二种情况第二种情况:当由多个文件组成一个完整的程序时,在一个源程序文件中定义的变量要被其他若干个源文件引用时,引用的文件中要用extern对该变量作外部声明。例如,创建一个Ex_Extern2项目,包含两个源文件,一个是Ex_Extern2.cpp,另一个是Ex_Extern2_1.cpp。 例例Ex_ Extern2 不在同一个源文件中的外部变量使用不在同一个源文件中的外部变量使用
51、Ex_Extern2.cpp文件内容#include stdafx.h#include using namespace std;int n;void f();int main()n = 20;coutn = nn;f();return 0; 2.3.3 存储类型存储类型 Ex_Extern2_1.cpp文件内容#include stdafx.h#include using namespace std;extern int n;/ 外部变量声明,它在另一个文件中定义void f()n+;coutn = nn;项目Ex_StaticScope经运行后,结果如下: 需要注意的是:(1) 可以对同一个
52、变量进行多次extern的声明。若在声明时,给一个外部变量赋初值,则编译器认为是一个具体的变量定义,而不是一个外部变量的声明,此时要注意同名标识符的重复定义。例如:extern int n = 1;/ 变量定义int n;/ 错误:变量n重复定义(2) 虽然外部变量对不同源文件中或函数之间的数据传递特别有用。但也应该看到,这种能被许多函数共享的外部变量,其数值的任何一次改变,都将影响到所有引用此变量的函数的执行结果,其危险性是显然的。 2.4 名称空间名称空间在C+中,定义一个名称空间的格式如下:2.4.1 名称空间的定义名称空间的定义namespace 标识符标识符成员;成员;体体其中,na
53、mespace是C+关键字,标识符标识符用作名称空间的名称,属于该名称空间体体中的变量、函数、结构、枚举、联合以及以后要讨论的类等都可以认为是该名称空间的成员成员。需要说明的是:2.4.1 名称空间的定义名称空间的定义(1) 同一个文件中,可以允许定义多个名称空间。一旦定义名称空间后,标识符标识符就标识名称空间体体的那个区域。例如:using namespace std;namespace DING1char name = this is in DING1 region!;void showname( void )coutnameendl;namespace DING2char name =
54、this is in DING2 region!;void showname( void )coutnameendl;尽管名称空间DIN1和DING2中定义的成员名都相同,但它们由于分属不同的名称区域,因而是合法的。此后,在引用各自的成员时就可使用域作用符来指定DIN1或DING2来标识各自所在的名称区域。2.4.1 名称空间的定义名称空间的定义(2) 名称空间中的函数、类等可称为成员函数成员函数和成员类成员类。成员函数的原型必须在名称空间体体中声明,而此时的函数定义称为函数的实现函数的实现,可以在体内体内完成,也可在体外体外完成。例如,前面名称空间DING1和DING2的成员函数showna
55、me的声明和定义都是在体内一起完成的,若将成员函数showname的实现放在体外完成,则必须用域作用符“:”指明其所属名称空间。例如:using namespace std;namespace DING1char name = this is in DING1 region!;void showname( void );namespace DING2char name = this is in DING2 region!;void showname( void );void DING1:showname(void)/ 在体外实现函数shownamecoutnameendl;void DING2:showname(void)/ 在体外实现函数shownamecoutnameendl;2.4.1 名称空间的定义名称空间的定义(3) 同一个名称空间的区域中,不能出现相同的标识符。 若名称空间定义时省略标识符标识符,则名称空间的区域是全局的、静态的(static)、开放的,可以在本文件中直接使用,而勿需要使用using来指定。2.4.2 名称空间的使用名称空间的使用在C+中,对已定义的名称空间的使用可以有以下几种方式:(1) 直接引用方式直接引用方式对已定义的名称空间的成员可用下列格式来直接引用:名称空间名名
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 《月有阴晴圆缺》课件
- 2025年信阳艺术职业学院马克思主义基本原理概论期末考试模拟题及答案解析(夺冠)
- 2025年上思县招教考试备考题库带答案解析(必刷)
- 2024年鄂城钢铁厂职工大学马克思主义基本原理概论期末考试题带答案解析
- 2024年聂荣县幼儿园教师招教考试备考题库带答案解析
- 2025年会同县幼儿园教师招教考试备考题库带答案解析
- 2025年和平县幼儿园教师招教考试备考题库带答案解析(必刷)
- 2024年缙云县幼儿园教师招教考试备考题库附答案解析(夺冠)
- 2025年唐县幼儿园教师招教考试备考题库含答案解析(夺冠)
- 保山市2025-2026学年(上期)高三期末考试历史试卷(含答案解析)
- 2025年时事政治考试100题(含参考答案)
- 部队禁酒课件
- 2025-2030年中国油套管产业规模分析及发展前景研究报告
- DB11-T 1811-2020 厨房、厕浴间防水技术规程
- 叉车安全管理人员岗位职责
- 验光师年度工作总结
- 2024年浙江温州市苍南县公投集团所属企业招聘笔试人员及管理单位遴选500模拟题附带答案详解
- 新生儿先天性心脏病筛查课件
- 景区与热气球合作合同范本
- 水库除险加固工程施工组织设计
- DL∕T 5210.5-2018 电力建设施工质量验收规程 第5部分:焊接
评论
0/150
提交评论