C语言学习知识程序设计实验规范标准答案数据的共享与保护.doc
.实验05 数据的共享与保护(2学时)(第5章 数据的共享与保护)一、实验目的(1) 观察程序运行中变量的作用域、生存期和可见性。(2) 学习类的静态成员的使用。(3) 学习多文件结构在C+程序中的使用。二、实验任务5_1 运行下面的程序,观察变量x、y的值。/lab5_1.cpp#include using namespace std;void fn1();int x=1,y=2;int main()cout开始.endl;coutx=xendl;couty=yendl;cout在main()中求x和y的值.endl;int x=10,y=20;coutx=xendl;couty=yendl; cout进入函数fn1().endl;fn1();cout返回main()endl;coutx=xendl;couty=yendl; return 0;void fn1()int y=200;coutx=xendl;couty=yendl;5_2 实现客户机(CLIENT)类。声明字符型静态数据成员ServerName,保存其服务器名称;声明整型静态数据成员ClientNum,记录已定义的客户数量;定义静态函数ChangeServerName()改变服务器名称。在头文件client.h中声明类,在文件client.cpp中实现,在文件lab5_2.cpp中测试这个类,观察相应的成员变量取值的变化情况。三、实验步骤1.(验证)运行lab5_1.cpp程序,观察程序输出。全局变量的作用域为文件作用域,在整个程序运行期间有效,但如果在局部模块中声明了同名的变量,则在局部模块中,可见的是局部变量,此时,全局变量不可见;而局部变量的生存期只限于相应的程序模块中,离开相应的程序模块,局部变量x、y就不再存在,此时同名的全局变量重新可见。程序名:lab5_1.cpp。 程序的运行结果:2.(编程)实现客户机(CLIENT)类。新建一个空的项目lab5_2,添加头文件client.h,在其中声明类CLIENT,注意使用编译预处理命令;再添加源程序文件client.cpp,在其中实现CLIENT类,注意静态成员变量的使用方法;再添加文件lab5_2.cpp,在其中定义main()函数,测试CLIENT类,观察 相应的成员变量取值的变化情况。提示:访问一台服务器的客户总数。静态成员为类的属性,为所有的类的对象共同拥有。再定义两个静态成员函数,分别显示服务器名和客户总数。构造函数用于增加一个客户,析构函数用于减少一个客户。定义一个对象,再定义第二个对象,然后减少一个对象。参考程序输出结果: 程序及运行结果:(1) 类声明头文件client.h/client.h#ifndef CLIENT_H#define CLIENT_H/其中的静态成员为类的属性,为所有的类的对象共同拥有class clientpublic:client();client();static void ChangeServerName(char ss);/改变服务器名称static void showServerName();static void showClientNum();private:static char ServerName;/保存服务器名,引用性说明static int ClientNum;/记录已定义的客户(即对象)数量,引用性说明;#endif(2) 类实现程序文件client.cpp/client.cpp#include #include client.husing namespace std;client:client() ClientNum+;/增加一个对象(客户)client:client() ClientNum-; /减少一个对象(客户)void client:ChangeServerName(char sn) ServerName=sn; /无staticvoid client:showServerName() cout服务器名:ServerNameendl; void client:showClientNum() cout客户总数: ClientNum endl; /必须在文件作用域的某处用类名限定进行定义性说明,这时也可以进行初始化。char client:ServerName=A; /无staticint client:ClientNum=0;(3) 主函数文件lab5_2.cpp/lab5_2.cpp#include #include client.husing namespace std;void main()client:showServerName();/初始状态client:showClientNum();/类名引用静态成员函数client:ChangeServerName(B);client a;/增加一个客户a. showServerName();/对象名引用静态成员函数a. showClientNum();client b; /增加一个客户b. showServerName();b. showClientNum();/减少一个客户client:showServerName();client:showClientNum();(4) 运行结果附:第5章 数据的共享与保护5.1 标识符的作用域与可见性P1465.1.1 作用域作用域是一个标识符在程序正文中有效的区域。C+的作用域有:函数原型作用域、块作用域(局部作用域)、类作用域、文件作用域。1. 函数原型作用域在函数原型声明时形式参数的作用范围。例, double Area(double radius);其中标识符radius的作用(或称有效)范围在形参列表的左、右括号之间,称标识符radius的作用域是函数原型作用域。由于在函数原型的形参表中起作用的只是形参类型,标识符并不起作用,可省略。2. 局部作用域(块作用域)例:void fun(int a)int b=a;cinb;if(b0)int c;.a的作用域c的作用域b的作用域形参作用域:从形参列表中的声明处开始,到整个函数体结束处止。b和c都具有块作用域,是不同的块作用域。块是一对大括号括起来的一段程序。此例,函数体是一个块,if语句后的分支体又是一个较小的块,二者是包含关系。在块中声明的标识符,其作用域从声明处开始,一直到块结束的大括号为止。具有块作用域的变量也称为局部变量。3. 类作用域类是一组有名成员的集合,类X的成员m具有类作用域,对m的访问方式如下:(1) 若在X的成员函数中无同名的局部作用域标识符,则在该函数内可访问成员m。(2) 通过表达式x.m或X:m。这正是程序中访问对象成员的最基本方法。(3) 通过prt-m这样的表达式,其中prt为指向X类的一个对象的指针。4. 命名空间作用域命名空间大型程序通常由不同模块构成,不同模块中的类和函数之间可能发生重名,将引发错误。命名空间可以解决类名、函数等的命名冲突。命名空间语法形式:namespace 命名空间名命名空间内的各种声明(函数声明、类声明、)例namespace SomeNs class SomeClass . ;特殊的命名空间_ 全局命名空间:默认的命名空间_ 匿名命名空间:对每个源文件是唯一的命名空间作用域一个命名空间确定了一个命名空间作用域引用其它命名空间作用域中的标识符_ 命名空间名:标识符名_ 例:声明一个SomeClass型的对象SomeNs:SomeClass obj1;将其它命名空间作用域的标识符暴露于当前作用域_ 对指定标识符using 命名空间名:标识符名;_ 对所有标识符using namespace命名空间名;例5-1中所声明的全局变量就具有文件作用域,它们在整个文件中都有效。例5-1 作用域实例。#include using namespace std; /使得在当前文件中可直接引用std命名空间的标识符coutint i;/全局变量,文件作用域namespace Nsint j;/在Ns命名空间中的全局变量void main()i=5;/文件作用域的i赋初值Ns:j=6;/为全局变量j赋值/子块1using namespace Ns;/使得在当前块中可直接引用Ns命名空间的标识符int i;/局部变量,块作用域i=7;couti=iendl;/输出7coutj=jendl;couti=iendl;/输出5参考(cout, endl是命名空间std中的全局变量):#include /using namespace std; /使得在当前文件中可直接引用std命名空间的标识符cout,endlint i;/全局变量,文件作用域namespace Nsint j;/在Ns命名空间中的全局变量void main()i=5;/文件作用域的i赋初值Ns:j=6;/为全局变量j赋值/子块1using namespace Ns;/使得在当前块中可直接引用Ns命名空间的标识符int i;/局部变量,块作用域i=7;std:couti=i std:endl;/输出7std:coutj=j std:endl;std:couti=i std:endl;/输出5具有文件作用域的变量也称为全局变量。5.1.2 可见性P150可见的标识符 程序运行到某一点,能够引用到的标识符。文件作用域最大,接下来依次是类作用域和块作用域。图5-1 作用域关系图可见性表示从内层作用域向外层作用域“看”时能看到什么。作用域可见性的一般规则:_ 标识符声明在前,引用在后。_ 同一作用域,不能声明同名标识符。_ 在没有包含关系的不同作用域中声明的同名标识符互不影响。_ 在具有包含关系的作用域中声明了同名标识符,则外层标识符在内层不可见。例5-l中,文件作用域与块作用域相互包含。#include using namespace std;int i;/全局变量,文件作用域namespace Nsint j;/在Ns命名空间中的全局变量void main()i=5;/文件作用域的i赋初值Ns:j=6;/为全局变量j赋值/子块1using namespace Ns;int i;/局部变量,块作用域i=7;couti=iendl;/输出7coutj=jendl;couti=iendl;/输出5在主函数内子块1前,可引用到具有文件作用域的变量,它是可见的。当进入子块1,只能引用到具有局部作用域的同名变量,文件作用域的同名变量不可见,称内层屏蔽,或同名覆盖,即内层的变量覆盖了外层的同名变量。5.2 对象的生存期P150对象(包括简单变量)从诞生到结束的这段时间就是它的生存期。5.2.1 静态生存期若对象的生存期与程序的运行期相同,称它具有静态生存期。在文件作用域中声明的对象具有静态生存期的。若要在函数内部的块作用域中声明具有静态生存期的对象,则要使用关键字static。 static int i;i称为静态变量。其余的对象都具有动态生存期。定义时未指定初值的基本类型静态生存期变量,初值为0;动态生存期变量,不指定初值时值不确定。5.2.2 动态生存期在块作用域中声明的具有动态生存期的对象,称局部生存期对象。动态生存期对象诞生于声明点,结束于该标识符作用域结束处。例5-2 变量的生存期与可见性。局部变量就是具有块作用域的变量。全局变量就是具有文件作用域的变量。#include using namespace std;int i=1;/i为全局变量,具有静态生存期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;couti:i a:a b:b c:cendl;b=a;void main()static int a;/a为静态局部变量,具有全局寿命,局部可见int b=-10;/b, c为局部变量,具有动态生存期int c=0;cout-MAIN-n;couti:i a:a b:b c:cendl;c=c+8; other();cout-MAIN-n;couti:i a:a b:b c:cendl;c=c+10; other();例5-3 具有静态、动态生存期对象的时钟程序。声明具有函数原型作用域、块作用域、类作用域和文件作用域的多个变量及对象,并分析各自的可见性和生存期。#include using namespace std;class Clock/时钟类定义public:Clock();void setTime(int newH,int newM,int newS);/三个形参具有函数原型作用域void showTime();private:int hour,minute,second;/时钟类成员函数实现Clock:Clock():hour(0),minute(0),second(0) /构造函数void Clock:setTime(int newH,int newM,int newS)/3个形参均具有局部作用域 hour=newH; minute=newM; second=newS; void Clock:showTime() couthour:minute:secondendl;Clock globClock;/声明对象,具有静态生存期,文件作用域void main()cout第一次输出:endl;/引用文件作用域的对象globClock:globClock.showTime();/对象的成员函数具有类作用域globClock.setTime(8,30,30);Clock myClock(globClock);/声明具有块作用域的对象cout第二次输出:endl;myClock.showTime();/引用具有块作用域的对象5.3 类的静态成员P153静态成员是解决同一个类的不同对象之间的数据和函数共享问题的。例,抽象出某公司全体雇员的共性,设计如下的雇员类:class Employee/雇员类private:int empNo;int id;string name;/字符串对象.若需要统计雇员总数,这个数据存放在什么地方呢?若以类外的变量来存储总数,不能实现数据的隐藏。若在类中增加一个数据成员用以存放总数,必然在每一个对象中都存储一个副本,不仅冗余,且每个对象分别维护一个“总数”,势必造成数据的不一致性。比较理想的方案是类的所有对象共同拥有一个用于存放总数的数据成员。5.3.1 静态数据成员P154实例属性“一个类的所有对象具有相同的属性”,是指属性的个数、名称、数据类型相同,各个对象的属性值则可各不相同。以类的非静态数据成员表示。类属性是描述类的所有对象的共同特征的一个数据项,对于任何对象实例,它的属性值是相同的。通过静态数据成员来实现“类属性”。静态数据成员的访问静态数据成员不属于任何一个对象,只能通过类名对它访问,用法是“类名:标识符”。静态数据成员的说明和定义在类的声明中仅仅对静态数据成员进行引用性说明,必须在文件作用域的某处用类名限定进行定义性说明,这时也可进行初始化。在UML中,静态数据成员下方添加下划线。例5-4 具有静态数据成员的Point类。引入静态数据成员的Point类。图5-2 包含静态数据成员的Point类的UML图Point x : int y : int count : int=0+Point(xx : int=0, yy : int=0)+getX() : int+getY() : int+Point(p : Point&)+showCount() : void#include using namespace std;class Point/Point类定义public:Point(int xx=0,int yy=0):x(xx),y(yy) count+; /所有对象共同维护countPoint(Point &p) x=p.x; y=p.y; count+; Point() count-; int getX() return x; int getY() return y; void showCount() cout 对象count=countendl; /输出静态数据成员private:int x,y;static int count;/静态数据成员声明,用于记录点的个数;int Point:count=0;/静态数据成员定义和初始化,使用类名限定void main()Point a(4,5);/定义对象a,其构造函数会使count增1coutPoint a: a.getX(),a.getY();a.showCount();/输出对象个数Point b(a); /定义对象b,其拷贝构造函数会使count增1coutPoint b: b.getX(),b.getY();b.showCount();在对类的静态私有数据成员初始化的同时,还可引用类的其他私有成员。例,若一个类T存在类型T的静态私有对象,则可引用该类的私有构造函数将其初始化。5.3.2 静态函数成员P156静态成员函数是使用static关键字声明的函数成员。静态成员函数属于整个类,由同一个类的所有对象共同维护,并共享。对于公有的静态成员函数,可通过类名或对象名来调用。静态成员函数可直接访问该类的静态数据和函数成员,而访问非静态数据成员,必须通过参数传递方式得到对象名,然后通过对象名来访问。class Apublic:static void f(A a);private:int x;void A:f(A a)coutx;/对x的引用是错误的。可用A:f(c)调用,此时,无当前对象。couta.x;/正确在UML中,静态函数成员前添加。例5-5 具有静态数据和函数成员的Point类。图5-3 包含静态函数成员的Point类的UML图Point x : int y : int count : int=0+Point(xx : int=0, yy : int=0)+getX() : int+getY() : int+Point(p : Point&)+showCount() : void#include using namespace std;class Pointpublic:Point(int xx=0,int yy=0):x(xx),y(yy) count+; ;Point(Point &p) x=p.x; y=p.y; count+; Point() count-; int getX() return x; int getY() return y; static void showCount() cout 对象count=countendl; /静态函数成员private:int x,y;static int count;/静态数据成员声明;int Point:count=0;/静态数据成员定义及初始化,使用类名限定void main()Point a(4,5);coutPoint a: a.getX(),a.getY();Point:showCount();/输出对象号,类名引用Point b(a);coutPoint b: b.getX(),b.getY();b.showCount();/输出对象号,对象名引用采用静态函数成员的好处可不依赖于任何对象,直接访问静态数据。5.4 类的友元P158友元提供了不同类或对象的成员函数之间、类的成员函数与一般函数之间进行数据共享的机制。在一个类中,可用关键字friend将其他函数或类声明为友元。若友元是一般函数或类的成员函数,称友元函数;若友元是一个类,称友元类,友元类的所有成员函数都自动成为友元函数。5.4.1 友元函数P160友元函数是在类中用关键字friend修饰的非成员函数。友元函数不是本类的成员函数,但是在它的函数体中可通过对象名访问类的私有和保护成员。在UML中,友元函数前添加。例5-6 使用友元函数计算两点间的距离。图5-4 包含友元函数成员的Point类的UML图Point x : int y : int+Point(xx : int=0, yy : int=0)+getX() : int+getY() : int+dist(a : Point&, b : Point&) : foat#include #include using namespace std;class Point/Point类定义public:Point(int xx=0,int yy=0):x(xx),y(yy)int getX() return x; int getY() return y; friend float dist(Point &p1,Point &p2);/友元函数声明private:int x,y;float dist(Point &p1,Point &p2)/友元函数实现double x=double(p1.x-p2.x);/通过对象访问私有数据成员double y=double(p1.y-p2.y);return static_cast(sqrt(x*x+y*y);void main()Point myp1(1,1),myp2(4,5);cout距离是:;coutdist(myp1,myp2)endl;5.4.2 友元类P161若A类为B类的友元类,则A类的所有成员函数都是B类的友元函数,都可以访问B类的私有和保护成员。class B. /B类的成员声明friend class A;/声明A为B的友元类.;声明友元类,是建立类与类之间的联系,实现类之间数据共享的一种途径。在UML中,两个类之间的友元关系是通过构造型依赖来表征。图5-5 类A和类B友元关系的UML图B+set(i : int) : void+display() : voidA x : int+display() : void+getx() : int-a#include using namespace std;class Apublic:void display() coutxendl; int getx() return x; friend class B;private:int x;class Bpublic:void set(int i);void display() couta.xendl; private:A a;void B:set(int i)a.x=i;/由于B是A的友元,所以在B的成员函数中可以访问A类对象的私有成员void main()B b;b.set(5);b.display();注意:第一,友元关系是不能传递的。第二,友元关系是单向。第三,友元关系是不被继承的。5.5 共享数据的保护P1635.5.1 常对象声明格式:const 类型说明符 对象名;或类型说明符 const 对象名;常对象必须进行初始化,且不能被更新。语法如何保障常对象的值不被改变呢?改变对象的数据成员值有两个途径:一是在类外通过对象名访问其公有数据成员,这时语法会限制不能再赋值。二是在类的成员函数中改变数据成员的值,规定不能通过常对象调用普通的成员函数。5.5.2 用const修饰的类成员P1641. 常成员函数声明格式:类型说明符 函数(参数表)const;注意: const是函数类型的一个组成部分。 常成员函数不能更新对象的数据成员,也不能调用该类中没有用const修饰的成员函数。 常对象只能调用它的常成员函数。 const可用于对重载函数的区分。例如, void print(); void print() const;这是对print的有效重载。在UML中,常成员函数前添加。例5-7 常成员函数举例。图5-6 包含常成员函数的R类的UML图R r1 : int r2 : int+R(rr1 : int, rr2 : int)+print() : void+print() : void#include using namespace std;class Rpublic:R(int rr1,int rr2):r1(rr1),r2(rr2)void print();void print() const;/常成员函数private:int r1,r2;void R:print() coutr1:r2endl; void R:print() const coutr1;r2endl; void main()R a(5,4);a.print();const R b(20,52);b.print();2. 常数据成员类的成员数据也可以是常量。使用const说明的数据成员为常数据成员。任何函数中都不能对常数据成员赋值。构造函数对常数据成员进行初始化,就只能通过初始化列表。在UML中,常数据成员前添加const。例5-8 常数据成员举例。图5-7 包含常数据成员的A类的UML图A a : const int b : const int=10+A(i : int)+print() : void#include using namespace std;class Apublic:A(int i);void print();private:const int a;/常数据成员static const int b;/静态常数据成员;const int A:b=10;/静态常数据成员在类外说明和初始化A:A(int i):a(i) /常数据成员只能通过初始化列表来获得初值 void A:print() couta:bendl; void main()/*建立对象a1和a2,并以100和0作为初值,分别调用构造函数,通过构造函数的初始化列表给对象的常数据成员赋初值*/A a1(100),a2(0);a1.print();a2.print();5.5.3 常引用P166常引用的说明形式:const 类型说明符 &引用名;常引用所引用的对象不能被更新。若用常引用做形参,便不会发生对实参意外的更改。例5-9 常引用做形参#include #include using namespace std;class Point/Point类定义public:Point(int xx=0,int yy=0):x(xx),y(yy)int getX() return x; int getY() return y; friend float dist(const Point &p1,const Point &p2);/友元函数声明private:int x,y;float dist(const Point &p1, const Point &p2)/友元函数实现double x=double(p1.x-p2.x);/通过对象访问私有数据成员double y=double(p1.y-p2.y);return static_cast(sqrt(x*x+y*y);void main()const Point myp1(1,1),myp2(4,5);cout距离是:;coutdist(myp1,myp2)endl;*5.6 多文件结构和编译预处理命令P1685.6.1 C+程序的一般组织结构通常一个项目至少划分为三个文件:类声明文件(*.h文件)类实现文件(*.cpp文件)类的使用文件(*.cpp,主函数文件)每个源程序文件称为一个编译单元。C+语法要求一个类的声明必须出现在所有使用该类的编译单元中。惯用的做法将类的声明写在头文件中,使用该类的编译单元则包含这个头文件。例5-10 具有静态数据、函数成员的Point类,多文件组织。/文件1,类的声明,point.h#include using namespace std;class Pointpublic:Point(int xx=0,int yy=0)X=xx; Y=yy; countP+; ;Point(Point &p);Point()countP-;int GetX()return X;int GetY()return Y;static void GetC() cout对象id=countPendl; /静态函数成员private:int X,Y;static int countP;/静态数据成员声明;/文件2,类的实现,point.cpp#include “point.h”int Point:countP=0;/静态数据成员定义及初始化,使用类名限定Point:Point(Point &p) X=p.X; Y=p.Y; countP+; /文件3,主函数,pmain.cpp#include “point.h”void main()Point A(4,5);coutPoint A,A.GetX(),A.GetY();
收藏