1、1,第五章 C+程序的结构,鲁莹,C+语言程序设计,2,本章主要内容,作用域与可见性 对象的生存期 类的静态成员 友元 常类型 编译预处理命令 多文件结构和工程,3,函数原形的作用域,函数原型声明时中行参的作用域始于 “(“,结束于“)“。 例如,设有下列原型声明: double Area(double radius);,radius 的作用域仅在于此,不能用于程序正文其它地方,因而函数原型中形参标识符可有可无。,作用域与可见性,4,块作用域,在块中声明的标识符,其作用域自声明处起,限于块中,例如: void fun(int a) int b(a);if (b0)int c; 函数的形参与函数
2、体属于一个块。,作用域与可见性,5,类作用域,类作用域作用于特定的成员名。 类X的成员M具有类作用域,对M的访问方式如下: 如果在X的成员函数中没有声明同名的局部作用域标识符,那么在该函数内可以访问成员M。 通过表达式x.M或者X:M访问。 通过表达式prt-M,作用域与可见性,6,文件作用域,不在前述各个作用域中出现的声明,具有文件作用域,这样声明的标识符的作用域开始于声明点,结束于文件尾。 函数、全局变量和常量具有文件作用域。,作用域与可见性,7,可见性,可见性是从对标识符的引用的角度来谈的概念。 程序运行到某一处,能够引用到的标识符称为该处可见的标识符。 可见性表示从内层作用域向外层作用
3、域“看”时能看见什么。 如果标识在某处可见,则就可以在该处引用此标识符。,作用域与可见性,8,可见性,标识符应声明在先,引用在后。 如果某个标识符在外层中声明,且在内层中没有同一标识符的声明,则该标识符在内层可见。 对于两个嵌套的作用域,如果在内层作用域内声明了与外层作用域中同名的标识符,则外层作用域的标识符在内层不可见。,作用域与可见性,9,#include using namespace std; int i; /全局变量,文件作用域 void main() i=5; /文件作用域的i赋初值 /子块1int i; /局部变量,块作用域i=7;cout“i=“iendl; /输出7cout“
4、i=“iendl; /输出5 ,作用域与可见性,例 5.1,10,对象的生存期,对象从产生到结束的这段时间就是它的生存期。在对象生存期内,对象将保持它的值,直到被更新为止。,11,静态生存期,指一旦为其分配了存储单元,则在整个程序执行期间,它们将固定地占有分配给它们的存储单元。 这种生存期与程序的运行期相同。 在文件作用域中声明的对象具有这种生存期。 在函数内部声明静态生存期对象,要冠以关键字static 。,对象的生存期,12,#include using namespace std; int i=5; /文件作用域 int main() cout“i=“iendl;return 0; i具
5、有静态生存期,对象的生存期,例,13,动态生存期,程序运行期间动态分配存储空间。 块作用域中声明的,没有用static修是的对象是动态生存期的对象(习惯称局部生存期对象)。 开始于程序执行到声明点时,结束于命名该标识符的作用域结束处。,对象的生存期,14,#include using namespace std; void fun(); void main() fun();fun(); void fun() static int a=1;int i=5;a+;i+;cout“i=“i“,a=“aendl; ,运行结果: i=6, a=2 i=6, a=3 i是动态生存期 a是静态生存期,对象的
6、生存期,例,15,例5-2 变量的生存期与可见性,#include using namespace std; int i=1; / i 为全局变量,具有静态生存期。 void main(void) static int a; / 静态局部变量,有全局寿命,局部可见。int b=-10; / b, c为局部变量,具有动态生存期。int c=0;void other(void);cout“-MAIN-n“;cout“ i: “i“ a: “a“ b: “b“ c: “cendl;c=c+8; other();cout“-MAIN-n“;cout“ i: “i“ a: “a“ b: “b“ c: “
7、cendl;i=i+10; other(); ,对象的生存期,void other(void) static int a=2;static int b;/ a,b为静态局部变量,具有全局寿命,局部可见。/只第一次进入函数时被初始化。int c=10; / C为局部变量,具有动态生存期,/每次进入函数时都初始化。a=a+2; i=i+32; c=c+5;cout“-OTHER-n“;cout“ i: “i“ a: “a“ b: “b“ c: “cendl;b=a; ,17,运行结果: -MAIN-i: 1 a: 0 b: -10 c: 0 -OTHER-i: 33 a: 4 b: 0 c: 15
8、 -MAIN-i: 33 a: 0 b: -10 c: 8 -OTHER-i: 75 a: 6 b: 4 c: 15,18,18,例5-3具有静态、动态生存期对象的时钟程序,#include using namespace std; class Clock /时钟类声明 public: /外部接口Clock();void SetTime(int NewH, int NewM, int NewS); /三个形参均具有函数原型作用域void ShowTime();Clock() private: /私有数据成员int Hour,Minute,Second; ;,对象的生存期,/时钟类成员函数实现
9、Clock:Clock() /构造函数 Hour=0;Minute=0;Second=0; void Clock:SetTime(int NewH, int NewM, int NewS) Hour=NewH;Minute=NewM;Second=NewS; void Clock:ShowTime() coutHour“:“Minute“:“Secondendl; ,20,Clock globClock; /声明对象globClock,/具有静态生存期,文件作用域 void main() /主函数 cout“First time output:“endl; /引用具有文件作用域的对象:glob
10、Clock.ShowTime(); /对象的成员函数具有类作用域globClock.SetTime(8,30,30); Clock myClock(globClock); /声明具有块作用域的对象myClockcout“Second time output:“endl; myClock.ShowTime(); /引用具有块作用域的对象 ,21,程序的运行结果为: First time output: 0:0:0 Second time output: 8:30:30,22,22,类的静态成员,类的静态成员包括: 静态数据成员 静态成员函数 提出类的静态成员目的: 数据共享,23,类的静态数据成
11、员,静态数据成员是类中所有对象共享的成员,而不是某个对象的成员。 对静态数据成员的操作和一般数据成员一样,定义为私有的静态数据成员不能由外界访问。 因为静态数据成员不从属于任何一个具体对象,所以必须对它初始化,而且初始化不能在构造函数中进行。,24,类的静态数据成员的使用,静态数据成员的定义与一般数据成员相似,但前面要加上static关键字。 静态数据成员的初始化格式: := 引用静态数据成员的格式: :,25,类的静态成员函数,静态成员函数的定义: static 一般函数定义 调用静态成员函数的格式: :(); 静态成员函数只能访问静态数据成员、静态成员函数和类以外的函数和数据,不能访问类中
12、的非静态数据成员(因为非静态数据成员只有对象存在时才有意义。)。,26,友元,在程序设计中,将数据与操作数据的行为进行有机地结合,这就是封装。 C+语言提供类来实现封装,类是属性和操作的结合体,并且在定义类的属性和操作时,规定了它们的可见性。 通过封装将部分成员作为类与外部的接口,而将其它的成员隐藏起来,以防外界的干扰和误操作,使程序的不同模块之间的相互影响减小到最低限度。,友 元,27,友元,在类中,一般将数据成员定义为私有成员,并通过公有成员访问函数对外提供的界面。 友元是C+提供的一种破坏数据封装和数据隐藏的机制。 友元(friend)函数:不是类的一部分,但又需要频繁地访问类的所有成员
13、。 友元:友元函数和友元类。 友元提高程序的运行效率,但也牺牲了类的封装型。,友 元,28,友元函数,定义:friend (); 友元函数说明的位置可在类的任何部位,既可以在public区,也可以在protected区,意义完全一样。 友元函数定义则在类的外部,一般与类的成员函数定义放在一起。,友 元,29,例5-6 使用友元函数计算两点距离,#include #include using namespace std; class Point /Point类声明 public: /外部接口Point(int xx=0, int yy=0) X=xx;Y=yy;int GetX() return
14、 X;int GetY() return Y;friend float Distance(Point ,友 元,double Distance( Point ,36,31,友元类,若一个类为另一个类的友元,则此类的所有成员函数都是类B的友元函数。 声明语法:将友元类名在另一个类中使用friend修饰说明。,友 元,32,友元类举例,class A friend class B;/B是A的友元类 public:void Display()coutxendl;private:int x; class B public:void Set(int i);void Display();private:A
15、 a; ;,友 元,void B:Set(int i) a.x=i; void B:Display() a.Display(); ,39,34,友元关系是单向的,如果声明B类是A类的友元,B类的成员函数就可以访问A类的私有和保护数据,但A类的成员函数却不能访问B类的私有、保护数据。,35,友元关系不能传递,B类是A类的友元,C类是B类的友元,C类和A类之间,如果没有声明,就没有任何友元关系,不能进行数据共享。,36,常类型,常类型:用const说明的类型 常类型的变量或对象的值是不能改变的,所以能够达到既保证数据共享又防止改变数据的目的。,37,常引用,常引用:被引用的对象不能被更新。 con
16、st 其中,n是一个常引用,它所引用的对象不会被更新。 常对象:必须进行初始化,不能被更新。 类名 const 对象名,38,常对象,常对象:指对象常量 定义常对象是必须进行初始化,且不能被更新。const ,39,常对象成员,包括常成员函数和常数据成员 常成员函数 使用const关键字说明的函数。 常成员函数不更新对象的数据成员,也不能调用该类中没有用const修饰的成员函数。 常成员函数说明格式: ()const; 如果将一个对象说明为常对象,则通过该类对象只能调用它的常成员函数而不能调用其他成员函数。 const关键字可以被用于参与对重载函数的区分,40,常数据成员,如同一般数据一样,类
17、的成员数据可以是常量和常引用。 如果在一个类中说明了常数据成员,那么构造函数就只能通过初始化列表对该数据成员进行初始化。,41,编译预处理命令,#include 包含指令 将一个源文件嵌入到当前源文件中该点处。 #include 按标准方式搜索,文件位于C+系统目录的include子目录下 #include“文件名“ 首先在当前目录中搜索,若没有,再按标准方式搜索。 #define 宏定义指令 定义符号常量,很多情况下已被const定义语句取代。 定义带参数宏,已被内联函数取代。 #undef 删除由#define定义的宏,使之不再起作用。,42,条件编译指令 #if 和 #endif,条件编
18、译: 一般情况,程序中的所有语句都参加编译,但有时也希望根据一定的条件去编译文件的不同部分。 #ifdef /当“ 标识符”非零时编译#endif ,编译预处理命令,43,条件编译指令#else,#ifdef /当“ 标识符”非零时编译#else/当“ 标识符”为零时编译#endif,编译预处理命令,44,条件编译指令 #elif,#ifdef /当“ 标识符”非零时编译 #elif /当“ 标识符”非零时编译 #else/其它情况下编译 #endif,编译预处理命令,45,条件编译指令,#ifdef 标识符程序段1 #else程序段2 #endif 如果“标识符”经#defined定义过,且
19、未经undef删除,则编译程序段1,否则编译程序段2。,编译预处理命令,46,条件编译指令,#ifndef #else#endif 如果“标识符”未被定义过,则编译程序段1,否则编译程序段2。,编译预处理命令,47,多文件结构(例5-10),一个源程序可以划分为多个源文件: 类声明文件(.h文件) 类实现文件(.cpp文件) 类的使用文件(main()所在的.cpp文件) 利用工程来组合各个文件。,48,不使用条件编译的头文件,/main.cpp #include “file1.h“ #include “file2.h“ void main() /file1.h #include “head.
20、h“,/file2.h #include “head.h“/head.h class Point ,多文件结构,49,使用条件编译的头文件,/head.h #ifndef HEAD_H#define HEAD_Hclass Point #endif,多文件结构,50,实验内容,编写一个程序,设计一个类,用于统计一个办的学生成绩,其中使用一个静态数据成员sumfs存储总分和一个静态成员函数rusmfs()返回该总分。 编写一个程序采用友元函数求一个点到直线的距离。 设计一个函数模板max求一个数组中的最大的元素,并以整数数组和字符数组进行调用。 在上题的基础上进行改进,设计一个类模板Max,求一个数组中的最大的元素,并以整数数组和字符数组进行调用。,