第5章 CHAPTER 5 程 序 结 构 C++的程序由一个或多个函数组成,要对C++的程序结构有一个全面的了解,就必须掌握如何将多个函数组成模块,几个模块如何构成程序,以及如何在模块之间共享数据和调用同一工程文件中其他模块的函数的方法。掌握这些方法的基础是理解程序编译的过程以及系统内存空间的分配和释放。 学习目标 区分各种变量的类型及其分配的内存空间; 掌握全局变量、一般局部变量的特点和使用; 掌握静态局部变量的特性; 能够区分并综合运用不同类型的变量; 理解作用域、可见度和生存期的概念; 掌握不同作用域的范围; 掌握几种预处理命令的用法。 5.1全局变量与局部变量 C++程序中的变量根据定义位置的不同,其可见度和生存期也不同。可见是指变量可以被正常使用,可见区域也称为作用域; 生存期是指变量占用的内存单元。某些变量可能在生存期内某时刻占用内存单元,却不能被使用(即不可见)。根据定义位置的不同,变量可以分为全局变量和局部变量。 图51程序运行时内存空间的分配图 5.1.1内存区域的布局 C++程序运行时所占用的内存空间分为4个区域,程序运行时内存空间的分配如图51所示。 (1) 代码区,存储程序的可执行代码,即程序中的各函数代码块。 (2) 全局数据区,存储一般的全局变量和静态变量(静态全局变量、静态局部变量)。该区的变量在没有初始化的情况下被自动默认为0。 (3) 栈区,存储程序的局部变量,包括函数的形参和定义在函数内的一般变量。分配栈区时,不处理原内存中的值,即栈区中的变量在没有初始化的情况下,其初值是不确定的。 (4) 堆区,存储程序的动态数据,堆区中的数据多与指针有关。分配堆区时,也不处理原内存中的值。 5.1.2全局变量 全局变量是指定义在所有函数体外的变量,在整个程序中都是可见的,能够被所有函数所共享。全局变量存储在全局数据区,在主函数main()运行之前就已经存在了,如果其在定义时没有给出初始值,则自动初始化为0。例如: int i=10; //全局变量 void sub() { i=i+10; cout<<i; } 【例51】全局变量应用举例。 1/************************************************** 2程序文件名:Ex5_1.cpp 3全局变量应用举例 4**************************************************/ 5#include <iostream> 6using namespace std; 7int i=10; //全局变量 8void add() 9{ 10i=i+10; 11cout<<" i= "<<i<<endl; 12} 13main() 14{ 15i=i+5; 16cout<<" i= "<<i<<endl; 17add(); 图52例51运行结果 18i=i+6; 19cout<<" i= "<<i<<endl; 20} 程序运行结果如图52所示。 【程序解释】 (1) 第7行定义了全局变量i,定义在所有函数之外。 (2) 程序从入口第13行main()函数开始,第15行执行了i=i+5后,全局变量i的值变为15。 (3) 程序执行到第17行调用add()函数后,跳转到add()函数定义体执行i=i+10,此时全局变量i的值变为15+10=25; 因为全局变量对程序中所有函数是可见的,所以如果程序中的某个函数修改了全局变量,则其他函数仅能“可见”并共享修改后的结果。 (4) 执行完add()函数后返回到函数调用点执行其后续语句,即i=i+6,这时,全局变量i的值变为25+6=31。 全局变量通常定义在程序顶部,其一旦被定义后就在程序中的任何位置可见,C++允许在程序中的任何位置定义全局变量,但要在所有函数之外。全局变量对其定义之前的所有函数定义是不可见的。例如,若例51中第7行全局变量的定义位置修改到add()与main()之间,即: void add() { i=i+10; cout<<" i= "<<i<<endl; } int i=10; //定义位置修改到此处 main() … 这时,程序就会提示add()函数中“i未定义”的编译错误。 5.1.3局部变量 局部变量是指在一个函数或复合语句中定义的变量。局部变量在栈中分配空间,其类型修饰是auto,但习惯上通常省略auto。局部变量仅在定义它的函数内,从定义它的语句开始到该函数结束的区域是可见的。 由于函数中的局部变量存放在栈区,在函数开始运行时,局部变量在栈区被分配空间,函数结束时,局部变量随之消失,其生命期也结束。在一个函数内可以为局部变量定义合法的任意名字,而无须担心与其他函数中的变量或者全局变量同名。当某函数中的局部变量与全局变量同名时,在该函数内部局部变量的可见区域中,局部变量的优先级要高于同名的全局变量,即局部变量可见,而同名的全局变量不可见。 如果局部变量没有被显式初始化,其值是不可预知的。 【例52】局部变量应用举例。 1/************************************************** 2程序文件名:Ex5_2.cpp 3局部变量应用举例 4**************************************************/ 5#include <iostream> 6using namespace std; 7int i=10;//全局变量i 8void add() 9{ 10i=i+7;//全局变量i 11cout<<" i= "<<i<<endl; //全局变量i 12int i=3; //局部变量i 13i=i+10; //局部变量i 14cout<<" i= "<<i<<endl; //局部变量i 15} 16main() 17{ 18i=i+5; //全局变量i 19cout<<" i= "<<i<<endl; //全局变量i 20add(); 21i=i+6; //全局变量i 22cout<<" i= "<<i<<endl; //全局变量i 图53例52运行结果 23} 程序运行结果如图53所示。 【程序解释】 (1) 第7行定义了全局变量i,定义在所有函数之外。 (2) 在add()函数中,第12行定义了同名的局部变量i,并初始化为3。局部变量i仅在add()函数中是可见的,其生命期从第12行开始,到第15行结束。根据同名变量的特点可以知道,在此区域内同名的全局变量i不可见。 (3) 在add()函数中,局部变量i定义之前的变量i系统都认为是全局变量,如第10行和第11行的语句是对全局变量i的操作。 (4) 局部变量与其同名的全局变量是相互独立的变量,局部变量存储在栈区,而全局变量存储在全局数据区。 (5) 第18行全局变量i的值为10+5=15; 之后调用add()函数,第10行全局变量i的值为15+7=22,第13行局部变量i的值为3+10=13; 返回到函数调用点执行第21行全局变量i的值为22+6=28。 当定义局部变量的函数又调用了其他函数,例如: … 1int i; 2void sub() 3{ 4i=i-5; 5} 6void add() 7{ 8int i=3; 9i=i+6; 10sub(); 11} 12main() 13{ 14… 15add(); … 上面程序段中定义了全局变量i,在add()函数中定义了局部变量i; main()函数调用add()函数,add()函数又调用sub()函数; 局部变量i的可见区域仅为第8~11行,而被调用的sub()函数中的代码不是局部变量i的可见区域,所以第4行是对全局变量i的操作。 局部变量包括函数的形参、函数内定义的变量和复合语句内定义的变量; 由于局部变量具有一定的范围局限,所以在不同的函数中可以定义同名的变量,这些变量之间没有任何逻辑关系,不会相互影响。 视频 5.1.4静态局部变量 用static修饰的变量称为静态变量。定义的语法格式为: static数据类型变量名=初值; 根据变量声明位置的不同,可分为静态全局变量和静态局部变量。静态变量存储在全局数据区,像全局变量一样,如果没有显式地给静态变量初始化,那么该变量将自动默认为0。静态变量的初始化仅进行一次。 静态全局变量是定义在所有函数体外的静态变量,只能供本模块使用,不能被其他模块重新声明为extern变量(外部变量,5.2节介绍); 静态局部变量是定义在函数或复合语句块中的静态变量。静态局部变量既具有局部变量的性质又具有全局变量的性质,即具有局部作用域和全局生命期。静态局部变量实质上是一个可供函数局部存取的全局变量。 【例53】静态局部变量应用举例。 1/************************************************** 2程序文件名:Ex5_3.cpp 3静态局部变量应用举例 4**************************************************/ 5#include <iostream> 6using namespace std; 7void add() 8{ 9static int i=0; 10int j=1; 11i++; 12j++; 13cout<<" i= "<<i<<","<<" j= "<<j<<endl; 14} 15main() 16{ 17for(int i=0;i<5;i++) 18add(); 19} 图54例53运行结果 程序运行结果如图54所示。 【程序解释】 (1) 第9行在add()函数中定义了静态局部变量i,并初始化为0。 (2) 程序的执行过程为: ① 进入main()函数,执行for语句的第一次循环,即调用add()函数; ② 进入add()函数,执行第9行语句“static int i=0”,定义静态局部变量i并赋初值; ③ 执行第10行语句“int j=1”,定义局部变量并赋初值,依次执行第11~13行的语句; ④ 结束add()函数,返回调用点,并判断for循环语句是否结束,若结束跳转到第⑤步,否则,跳转到第③步; ⑤ 程序结束。 (3) 从程序的执行过程可以知道,静态局部变量的定义语句“static int i=0”在程序中仅执行一次。 (4) 因为静态局部变量的生命期与全局变量一样是全局生命期,所以每次add()函数结束时,静态局部变量i的内存空间和值被保留下来,由结果可以看出,后续有关i的运算都是在上一次计算结果的基础上进行的; 而局部变量j在每次add()函数结束时都被释放,再一次调用add()函数时,局部变量j被重新分配空间和初始化。 5.2外部存储类型 一个源文件(.cpp)仅能够完整表达规模较小的程序,本节之前章节中的程序都是由单个源文件组成的。但是,实际应用中的程序大多由若干个源文件组成,几个源文件分别编译成目标文件(.obj),最后连接成一个完整的可执行文件(.exe)。这些源文件一般添加在同一个工程文件(.prj)中,C++规定,同一工程的多个文件中,有且只有一个源文件包含主函数main(),否则会出现程序入口的二义性。 如果存在同一工程的多个文件共同使用的全局变量,就在其中的一个文件中定义全局变量或函数,在其他文件中使用“extern”关键字进行声明,其作用既通知了编译器该变量或函数已经被定义过,又避免了重复定义的错误发生。被使用“extern”说明的变量或函数是外部文件。语法格式如下: extern数据类型变量名\函数原型; 如图55所示为工程文件的创建图。工程文件的创建方法是: 图55工程文件的创建 (1) 选中Projects标签的“Win32 Console Application”(Win32控制台应用)项; (2) 选择存储工程文件的磁盘位置(Location)并给工程命名(Project name),工程名和其包含的文件名可以同名。 【例54】外部存储结构应用举例。 1/************************************************** 2工程文件名:E5_4.prj 3源文件名:Ex5_4_1.cpp 4Ex5_4_2.cpp 5外部存储结构应用举例 6**************************************************/ //Ex5_4_1.cpp 7#include <iostream> 8using namespace std; 9void fun1(); 10extern void fun2(); 11int i; 12main() 13{ 14i=3; 15fun1(); 16cout<<i<<endl; 17} 18void fun1() 19{ 20fun2(); //fun2()的定义体不在本文件中 21} //Ex5_4_2.cpp 1extern int i; //i定义在本工程的其他源文件中 2void fun2() 3{ 4i=18; 5} 【程序解释】 (1) 在Ex5_4_1.cpp中定义了函数fun1()和全局变量i,而函数fun2()在本工程的Ex5_4_2.cpp中定义,因此在Ex5_4_1.cpp的第10行声明函数fun2()前添加了extern,说明其为外部文件。 (2) 由于C++默认所有函数的声明和定义都是extern的,所以文件Ex5_4_1.cpp中extern void fun2()前的extern说明符可以省略,函数fun2()的声明等价于: void fun2(); (3) 主函数main()仅在Ex5_4_1.cpp中进行了定义,由于全局变量i定义在Ex5_4_1.cpp文件中,所以当Ex5_4_2.cpp文件要使用全局变量i时,就需要在其开头声明“extern int i”,表示该变量i不在本文件中分配空间,而在程序其他文件中进行了定义。 (4) 在工程的各个源文件中,使用extern只是将其他源文件中已经定义的变量或函数声明为外部文件,可以在本文件中使用,不是对变量或函数的定义,因此不能进行赋值操作。例如,下面写法是错误的: extern int i=8; (5) 假设某个工程文件由两个或两个以上源文件组成,其中一个源文件中出现了extern修饰的变量或函数,那么在其他某个源文件中必须有对该变量或函数的定义。 5.3作用域 作用域是标识符在程序代码中的有效范围,按其范围可分为函数原型作用域、局部作用域(块作用域)、函数作用域和文件作用域。除标号外的标识符的作用域都开始于标识符的声明处。 5.3.1函数原型作用域 函数原型作用域是C++中范围最小的作用域。函数原型声明中的形参就在该作用域内,其范围起始于函数原型声明的左圆括号,结束于函数原型声明的右圆括号。例如,下面的函数原型声明: void fun1( inti, int j=4, int m=6); 标识符i、j和m的作用域为函数原型声明内部,因此函数原型声明中形参的标识符名可以省略,保留标识符的作用在于增强程序的可读性。习惯上将函数原型声明的形参标识符与其对应的函数定义中形参的标识符保持一致。例如: void fun1( inti, int j=4, int m=6);//函数原型声明 … void fun1( inti, int j, int m)//函数定义 { … } 5.3.2局部作用域 局部作用域也称为块作用域。所谓块是指用一对花括号“{}”括起来的部分。当标识符位于某块内时,其作用域从声明处开始,到块结束处终止。 块可以进行嵌套,即块中内嵌新的块。在出现嵌套块的情况下,标识符的作用域属于包含它的最近的块。 函数定义的形参和函数体同属于一个块; 语句是一个程序单位,如果语句中出现标识符的声明则标识符的作用域在语句中,如if…else语句、switch语句和循环语句等。例如,下面的程序段将出现变量重复定义的错误: void func( int i) { int i; //错误,变量重复定义 } 如果将上面程序段中的“int i”包含到新的内嵌块中,则代码是正确的,即: void func( int i) { { int i; } } 形参中i的作用域从声明处开始到外层花括号结束时终止,函数体i的作用域从声明处开始到内层花括号结束时终止。 而语句中如果出现标识符新的定义,则标识符的作用域从新的声明处开始而不会有重复定义的错误提示。以while循环语句为例: 1while( int i=3) 2{ 3int i=6; 4cout<<i<<endl; 5} 第1行定义的i=3的作用域从声明处开始到第5行结束; 第3行定义的i=6的作用域从声明处开始到第5行结束; 当相互嵌套块中有名字相同的标识符时,则块重叠部分标识符的作用域从属于内层块,因此第4行的i是指第3行定义的i,其值为6; while()后{}中的块相当于while的内嵌块。 5.3.3函数作用域 C++中,goto语句的标号是唯一具有函数作用域的标识符。标号的声明使其在声明的函数内的任何位置都可以被使用。例如: … void fun1() { goto M; int i=5; if( i>5) { M: cout<<"i="<<i<<endl; } } … 5.3.4文件作用域 既不在函数原型中,又不在块中的标识符具有文件作用域。文件作用域是在所有函数定义之外说明的,从声明处开始,到文件结束时终止。全局变量和常量具有文件作用域。 如果头文件(下节介绍)被其他源文件导入“include”,则在头文件的文件作用域中声明的标识符的作用域也扩展到该源文件,直到源文件结束。 C++中函数的定义是平行的,除了main()函数被系统自动调用外,其他函数都可以互相调用。假设存在三个函数: f1()、f2()和f3(),函数f1()既可以直接调用函数f3(),也可以间接调用它,即先调用函数f2(),再由f2()调用f3()。 5.4文 件 结 构 5.4.1头文件 头文件是指以.h作为扩展名的文件,头文件里包含被同一工程文件中多个源文件引用的具有外部存储类型的声明。头文件的引入可以将工程文件中共享的变量或函数的声明集合化,每个需要使用这些共享信息的源文件只需在自身文件顶端导入头文件即可。头文件的导入使用编译预处理“#include”方法。头文件一般可以包含如下声明: 函数声明,如 extern int func1(); 常量定义,如 const int a=3; 数据声明,如 extern int i; 内联函数定义,如 inline int add(int i) {return ++i;} 包含文件,如 #include <math.h> 宏定义,如 #define PI 3.14159; 头文件中不能包含一般函数和数据的定义。 【例55】设计一个包含三个源文件的工程文件,源文件的功能分别为计算球体的体积、计算球体的表面积和输入球体半径并调用另外两个源文件,共享的声明存储在头文件。 /************************************************** 工程文件名:Ex5_5.prj 头文件名: myball.h 头文件应用举例 **************************************************/ double volume(double i); double area(double i); const float PI=3.14; /************************************************** 工程文件名:Ex5_5.prj 源文件名: Ex5_5_1.cpp 计算球体的体积 **************************************************/ #include "myball.h" double volume(double i) { return 4*PI*i*i*i/3; } /************************************************** 工程文件名:Ex5_5.prj 源文件名:Ex5_5_2.cpp 计算球体的表面积 **************************************************/ #include "myball.h" double area(double i) { return 4*PI*i*i; } /************************************************** 工程文件名:Ex5_5.prj 源文件名:Call.cpp 调用函数 **************************************************/ #include <iostream> #include "myball.h" using namespace std; main() { double radius; cout<<" 请输入球体的半径: "; cin>>radius; cout<<" 球体的体积为"<<volume(radius)<<endl; 图56例55运行结果 cout<<" 球体的表面积为" <<area(radius)<<endl; } 程序执行前需要分别对三个源文件进行编译,然后与头文件进行链接,程序运行结果如图56所示。 5.4.2编译预处理 C++中以#开头,以换行符结尾的命令行称为预处理命令。一个程序运行前,首先进行去掉注释行、变换格式等预处理操作,然后再通过编译器将高级语言编写的程序翻译成计算机能识别的机器语言。预处理命令不以分号结尾。 1. #include命令 #include命令也称为文件包含命令,是指在一个C++文件中将另一个文件的内容全部包含进来。文件包含命令的语法格式为: #include <包含文件名> 和 #include "包含文件名" 第一种形式<>表示包含的文件在编译器指定的文件包含目录中; 第二种形式“”表示系统首先到当前目录下查找包含文件,如果没找到,再到系统指定的文件包含目录中查找。通常情况下,<>包含的是系统提供的头文件,而“”包含的是程序员自己定义的头文件。一条include命令只能指定一个被包含文件,如果要包含m个文件,就要使用m条include命令。 2. 条件编译 在有些情况下,不需要程序中的所有语句都参加编译,而是根据一定的条件去编译程序中的不同部分,这称为条件编译。这种机制使同一程序在不同的编译条件下生成不同的目标文件。常用的条件编译指令有: 1) 表达式作为编译条件 其语法格式为: #if 表达式 程序段1 [#else 程序段2] #endif 其中,#if后只能是常量表达式,表示如果表达式的值不为零,则执行程序段1,否则执行程序段2。 2) 宏名已经定义作为编译条件 其语法格式为: #ifdef 宏 程序段1 [#else 程序段2] #endif 表示如果宏已经被定义,则编译程序段1,否则编译程序段2。 3) 宏名未被定义作为编译条件 其语法格式为: #ifndef 宏 程序段1 [#else 程序段2] #endif 表示如果宏未被定义,则编译程序段1,否则编译程序段2。 利用条件编译还可以在调试程序的过程中增加调试语句,借以达到程序跟踪的目的。 5.5实例应用与剖析 【例56】全局变量、局部变量和静态局部变量的综合应用。 1/************************************************** 2工程文件名:Ex5_6.cpp 3全局变量、局部变量和静态局部变量综合应用 4**************************************************/ 5#include <iostream> 6using namespace std; 7void func(); 8int n=1; // 全局变量 9main() 10{ 11static int a; // 静态局部变量 12int b= -10; // 局部变量 13cout <<" a:" <<a<<" b:" <<b<<" n:" <<n <<endl; 14b+=4; 15func(); 16cout <<" a:" <<a<<" b:" <<b<<" n:" <<n <<endl; 17n+=10; 18func(); 19} 20void func( ) 21{ 22static int a=2; // 静态局部变量 23int b=5; // 局部变量 24a+=2; 25n+=12; 26b+=5; 27cout<<" a:"<<a<<" b:"<<b<<" n:"<<n<<endl; 28} 图57例56运行结果 程序运行结果如图57所示。 【程序解释】 (1) 运行结果显示本例共有4行输出。 (2) 本例定义了1个全局变量,2个一般局部变量和2个静态局部变量。局部变量互相重名,但本质上是相互独立的变量。 (3) 程序的执行过程为: 进入main()函数,在第11行定义了main()中的静态局部变量a,系统自动赋初始值为0; 第12行定义了一般局部变量b,其初值为-10,因此第1行分别输出main()中的a、b以及全局变量n分别为0、-10和1。 (4) 第14行语句对main()的一般局部变量b进行加4操作,此时main()中b的值为-6; 第15行调用func()函数,跳转到对应的函数定义体。 (5) 在func()函数中,定义了名为a的静态局部变量并赋初始值为2,此函数中的a与main()函数中的a分配的内存空间不同,它们之间没有任何逻辑关系。第27行分别输出func()中的a、b以及全局变量n,结果分别为4、10和13。 (6) 执行完func()函数后,跳回函数调用点执行第16行语句,此处的变量分别为main()中的a、b和全局变量n,结果分别为0、-6和13。 (7) 第17行对全局变量n进行加10操作,即n的值变为23; 第18行第二次调用func()函数,根据静态局部变量的特点,在执行函数体的过程中,不再执行第22行中变量定义语句,而直接从第23行开始执行; 第24行对a的操作是在上次计算结果a=4的基础上加2,因此a=6,即输出结果第4行的值分别为a=6、b=10、n=35。 【例57】作用域应用举例。 1/************************************************** 2工程文件名:Ex5_7.cpp 3作用域应用举例 4**************************************************/ 5#include <iostream> 6using namespace std; 7int i=36;//全局变量 8void func(int i=12); 9main() 10{ 11cout<<" 作用域示例:"<<endl; 12func(); 13} 14void func(int i) 15{ 16cout<<" 局部作用域: i="<<i<<endl; 17{ 18int i=13; 19cout<<" 内嵌局部作用域: i="<<i<<endl; 20{ 21for(int i=14;i<15;cout<<"for局部作用域:i="<<i<<endl,i++) 22{ 23cout<<" for局部作用域: i="<<i<<endl; 24int i=15; 25i++; 26cout<<" for的内嵌局部作用域:i="<<i<<endl; 27} 图58例57运行结果 28} 29} 30cout<<" 局部作用域: i="<<i<<endl; 31} 程序运行结果如图58所示。 【程序解释】 (1) 第8行为函数声明,形参的定义i=12是范围最小的函数原型作用域。 (2) 第16行显示是func()函数的局部作用域,从第14行开始,到第31行结束; 第19行显示是func()内的内嵌局部作用域,第18行定义的i不同于第14行定义的i,作用域到第29行结束。 (3) 第21定义的i=14的作用域从定义点开始,到for语句结束时终止; 但是在for语句内第24行又定义了i=15,此处的i不同于第21行定义的i,属于不同的块。 (4) 只有在不同的块中才能定义同名的变量,否则系统会提示重复定义的错误。 (5) 第16、30行的i对应的是第14行的定义,第25行的i对应的是第24行的定义,第21行的i对应的是第21行的定义,第19行的i对应的是第18行的定义。 5.6建模扩展与优化 【例58】数学课上,老师让班长一诺带领大家一起复习巩固最大公约数和最小公倍数问题,一诺给全班出了一道题: 请输入两个正整数x(2≤x≤105)和y(2≤y≤106),求出满足以x为最大公约数和以y为最小公倍数的两个正整数(表示为M和N)及个数。 1/**************************************************/ 2程序文件名:Ex5_8.cpp 3最大公约数和最小公倍数 4/**************************************************/ 5#include <bits/stdc++.h> 6using namespace std; 7int gcd(int a,int b)//输出两个数的最大公约数 8{ 9if(b==0) 10{ 11return a; 12} 13return gcd(b,a%b); 14} 15main() 16{ 17int x,y,cnt=0; 18cout<<"请输入两个正整数x和y的值:"; 19cin>>x>>y; 20 cout<<endl; 21for(int i=x;i<=y;i++) 22{ 23for(int j=i;j<=y;j++) 24{ 25int h=gcd(i,j); 26if( h==x && i/h*j==y) 27{ 28cout<<"满足条件的正整数 M= "<<i<<" N= "<<j<<endl; 29cout<<"满足条件的正整数 M= "<<j<<" N= "<<i<<endl; 30cnt++; 31} 32} 33} 34cout<<"满足条件的正整数共 "<<cnt*2<<"个"<<endl; 35} 程序运行结果如图59所示。 图59例58运行结果 【程序解释】 (1) 根据最大公约数和最小公倍数特点可知: x×y=M×N。 (2) 第7~14行定义函数gcd()来求两个参数的最大公约数。 (3) 第23行中for(int j=i; j<=y; j++)用简单暴力枚举法,在逻辑上可写为for(int j=x; j<=y; j++),此时需要删除第29行,同时第34行输出个数为cnt个; 但这样改写后,该for语句的执行次数将翻倍,不满足时间复杂度最低的优化要求。 (4) 第34行中发现了一对M、N实际就发现了两对M、N,因为二者可以互换且不等,如找到了M=12,N=15,则同时可获取M=15,N=12。 【例59】一诺在美术课上给马上要过生日的老师做了张贺卡,为了装饰这张贺卡,一诺买了一条彩带,但是彩带上并不是所有颜色一诺都喜欢,于是一诺决定裁剪这条彩带,以取得最好的装饰效果,请设计程序找出彩带最好装饰效果区间。 现已知彩带由n种不同的颜色顺次相接而成,而每种颜色的装饰效果用一个整数表示(包括正整数、0或负整数),从左到右依次为 a1,a2,…,an,一诺可以从中裁剪出连续的一段用来装饰贺卡,而装饰效果就是这一段上各个颜色装饰效果的总和,一诺需要选取装饰效果最好的一段颜色来制作贺卡(取该段颜色数值之和的最大值)。当然,如果所有颜色的装饰效果都只能起到负面的作用(即ai<0),一诺也可以放弃用彩带来装饰贺卡(获得的装饰效果为0)。 1/**************************************************/ 2程序文件名:Ex5_9.cpp 3装饰彩带效果 4/**************************************************/ 5#include<bits/stdc++.h> 6using namespace std; 7int n,m=0,a[1000]; 8main() 9{ 10int i,j,x,y; 11cout<<"请输入原始彩带上的颜色条数:"; 12cin>>n; 13cout<<"请输入"<<n<<"条颜色的数值表示:"<<endl; 14for(i=1;i<=n;i++) 15{ 16cin>>a[i]; 17} 18for(i=1;i<=n;++i) 19{ 20int tmp =a[i]; 21for(j=i+1;j<=n;++j) 22{ 23tmp+=a[j]; 24if(tmp>m) 25{ 26m=tmp; 27x=i; 28y=j; 29} 30} 31} 32cout<<"获得最佳彩带装饰效果区间为第"<<x<<"段到第"<<y<<"段"<<endl; 33cout <<"最佳彩带装饰效果值为"<< m<<endl; 34} 程序运行结果如图510所示。 图510例59运行结果图 【程序解释】 (1) 第12行输入彩带的颜色条数,如彩带颜色顺次为红、黄、蓝、红、绿、黄,则条数为6,不同颜色的数值不同。 (2) 第14~17行输入不同彩带颜色的数值。 (3) 第27行和第28行使用变量x和y存储当前彩带颜色数值累加和最大区间的起始点和终点的位置。 (4) 第33行中变量m存储颜色数值最大区间的累加结果。 小结 (1) 全局变量是定义在所有函数元外的变量,能够被所有函数所共享; 局部变量是定义在某函数或复合语句内部的变量,只在函数或复合语句内部有效。 (2) 静态局部变量有全局变量和一般局部变量双重性质: 定义在全局数据区,函数被多次调用,变量的值只被初始化一次,且每次都是在上一次计算结果的基础上执行新的操作; 只能在定义的函数体内有效。 (3) 全局变量、静态变量、字符串常量存储在全局数据区,函数和代码存储在代码区,函数参数、局部变量、返回地址存放在栈区,动态内存分配在堆区。 (4) 如果存在同一工程文件多个文件共同使用的全局变量,就在其中的一个文件中定义全局变量或函数,在其他文件中使用“extern”关键字进行声明,extern说明的变量或函数为外部文件。 (5) C++程序的作用域按范围由小到大可分为函数原型作用域、局部作用域(块作用域)、函数作用域和文件作用域。除标号外的标识符的作用域都开始于标识符的声明处。 (6) 以#开头、以换行符结尾的行称为预处理命令。预处理命令在程序编译前由预处理器执行。 习题5 1. 找出下列程序的错误(注: 每道题中的源文件都属于相同的工程文件。) (1) 工程文件中包含func1.cpp和func2.cpp两个源文件。 //func1.cpp int a=6; int b=7; extern double c; //func2.cpp int a; extern double b; extern int c; (2) 工程文件中包含file1.cpp、file2.cpp和file3.cpp三个源文件。 //file1.cpp int a=1; double func() { … } //file2.cpp extern int a; double func(); void add() { a=int(func()); } //file3.cpp extern int a=2; int add(); main() { a=add(); } 2. 选择题 (1) 以下叙述正确的是()。 A. 在相同的函数中不能定义相同的名字变量 B. 函数中的形参是静态局部变量 C. 在一个函数体内定义的变量只在本函数范围内有效 D. 在一个函数内的复合语句中定义的变量在本函数范围内有效 (2) 以下叙述不正确的是()。 A. 预处理命令必须以#开头 B. 凡是以#开头的语句都是预处理命令行 C. 在程序执行前执行预处理命令 D. #define PI=3.14是一条正确的预处理命令 (3) 下列程序的运行结果为()。 main() { int i=100; { i=1000; for(int i=0;i<1;i++) { int i= - 1; } cout<<i; } cout<<","<<i; } A. -1,-1B. 1,1000C. 1000,100D. 死循环 (4) 下列程序的运行结果为()。 #indude <iostream> using namespace std; int i=100; int fun() { static int i=10; return ++i; } main() { fun(); cout<<fun()<<","<<i; } A. 10,100B. 12,100C. 12,12D. 11,100 (5) 下列程序的运行结果为()。 #include <iostream> using namespace std; void func() { static int i=1; cout<<"i="<<++i<<endl; } main() { for(int x=0;x<3;x++) func(); } A. i=1B. i=2C. i=1D. i=2 i=1i=2i=2i=3 i=1i=2i=3i=4 3. 读程序写结果 int a=1; int fun() {static int a=1; return ++a; } main() { fun(); cout<<fun()<<","<<a; }