C++及Windows可视化程序设计第5章_第1页
C++及Windows可视化程序设计第5章_第2页
C++及Windows可视化程序设计第5章_第3页
C++及Windows可视化程序设计第5章_第4页
C++及Windows可视化程序设计第5章_第5页
已阅读5页,还剩172页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、第5章 对象和类,5.1 类及其实例化 5.2 类和对象的性质 5.3 结构和联合 5.4 构造函数 5.5 析构函数 5.6 综合例题,5.7 重载对象的赋值运算符 5.8 对象成员的初始化 5.9 类模板与标准模板库 5.10 面向对象编程的文件规范 实验 习题,本章重点介绍在C+中定义类、建立和使用对象的方法。虽然同类对象在其数据成员的取值方面是不相同的,但可以共用相同的代码。 类是对同类对象的描述,它不但描述了对象之间的公有接口,同时也给出了对象的内部实现(数据成员和成员函数)。,像构造枚举和结构一样,类也是一种用户自己构造的数据类型并遵循C+的规定。 例如,类也要先声明后使用;不管声

2、明的内容是否相同,声明同一个名字的两个类是错误的,类是具有惟一标识符的实体;在类中声明的任何成员不能使用extern、auto和register关键字进行修饰;类中声明的变量属于该类,在某些情况下,变量可以被该类的不同实例所共享。,5.1 类及其实例化 5.1.1 定义类,类和其他数据类型不同的是,组成这种类型的不仅可以有数据,而且可以有对数据进行操作的函数,它们分别叫做类的数据成员和类的成员函数,而且不能在类声明中对数据成员使用表达式进行初始化。,1. 声明类 类是对一组性质相同对象的程序描述。在C+中声明类的一般形式为 class 类名 private: 私有数据和函数 public: 公

3、有数据和函数 protected: 保护数据和函数 ;,类声明以关键字class开始,其后跟类名。 类所声明的内容用花括号括起来,右花括号后的分号作为类声明语句的结束标志。这一对花括号“”之间的内容称为类体。 类中定义的数据和函数称为这个类的成员(数据成员和成员函数)。 类成员均具有一个属性,叫做访问权限,通过它前面的关键字来定义。顾名思义,关键字private 、public和protected 以后的成员的访问权限分别是私有、公有和保护的,把这些成员分别叫做私有成员、公有成员和保护成员。 访问权限用于控制对象的某个成员在程序中的可访问性,如果没有使用关键字,则所有成员默认声明为privat

4、e权限。这些关键字的使用顺序和次数也都是任意的。,【例5.1】 描述点的Point类。 class Point /类名Point private: /声明为私有访问权限 int x,y;/私有数据成员 public: /声明为公有访问权限 void Setxy(int a,int b);/无返回值的公有成员函数 void Move(int a,int b);/无返回值的公有成员函数 void Display();/无返回值的公有成员函数 int Getx(); /返回值为int的公有成员函数 int Gety(); /返回值为int的公有成员函数 ; /类声明以分号结束 x和y是私有成员,Se

5、txy、Display、Move、Getx和Gety是公有成员。因为只是声明函数,所以可只给出函数原型。【例5.2】是其等效的声明方式。,【例5.2】 使用默认关键字及改变关键字顺序和次数的Point类。 class Point /类名Point int x;/默认私有数据成员 public: /声明为公有访问权限 /无返回值的公有成员函数Setxy的函数原型 void Setxy(int,int); /无返回值的公有成员函数Move的函数原型 void Move(int,int); void Display();/无返回值的公有成员函数的函数原型 int Getx(); /返回值为int的公

6、有成员函数的函数原型 int Gety(); /返回值为int的公有成员函数的函数原型,private: /声明为私有访问权限 int y;/私有数据成员 ; /类定义以分号结束 由此可见,成员函数声明的规则与第4章所述的函数声明规则相同。 2. 定义成员函数 类中说明的成员函数用来对数据成员进行操作。例如,Point类的Setxy函数用来为该类的对象设置初始值,而当调用成员函数Getx时,则返回一个对象的数据成员x的值。 在类中只对这些成员函数进行了函数声明,还必须在程序中实现这些成员函数。 定义成员函数的一般形式为,返回类型 类名成员函数名(参数列表) 成员函数的函数体/内部实现 其中“”

7、是作用域运算符,“类名”是成员函数所属类的名字,“”用于表明其后的成员函数是属于这个特定的类。换言之,“类名成员函数名”的意思就是对属于“类名”的成员函数进行定义,而“返回类型”则是这个成员函数返回值的类型。余下的工作就是定义成员函数的函数体。 例如Setxy是类Point的成员函数,它没有返回值,则定义如下: void Point Setxy(int a,int b) x=a; y=b;,将“void PointSetxy(int a,int b)”理解为定义Point的函数成员Setxy(int a,int b),该成员带有两个整型参数,函数没有返回值(void)。按此方法,可写出其他几个

8、成员函数的定义: void Point Move(int a,int b) x=x+a; y=y+b; void Point Display() coutx,yendl; int Point Getx() return x; int Point Gety() return y;,也可以使用关键字inline将成员函数定义为内联函数,例如: inline int Point Getx() return x; 如果在声明类的同时,在类体内给出成员函数的定义,则默认为内联函数。例如在类中将声明Getx的语句“int Getx();”改为“int Getx()return x;”,则Getx为内联函数

9、。 一般直接在类体内给出简单成员函数的定义。有些成员函数的实现方式不止一种,例如 void Point Display() coutGetx(),Gety()Display(); /显示指针p所指对象A的数据 /成员A.x和A.y之值,【例5.3】 根据上面对Point类的定义,演示使用Point类的对象。 void print(Point a)/使用Point的对象a作为函数参数 a.Display();/显示对象a的数据成员的值 void main() Point A,B;/声明对象 A.Setxy(25,55); /为对象A赋初值 B=A;/B的数据成员取A的数据成员之值 A.Displ

10、ay();/显示A的数据成员 A.Move(-10,20);/移动A print(A);/等价于A.Display(); print(B);/等价于B.Display(),coutA.Getx()endl;/只能使用A.Getx(),不能使用A.x 本例中的print函数使用Point的对象作为参数。C+推荐使用下面的引用的形式: void print(Point for(int i=0; i2; i+) delete ptri; ,必须为每个数组元素调用delete析构相应的对象数组元素所占的空间。输出结果如下: Initializing 2,4 Initializing 7,8 Destr

11、uctor is active Destructor is active 数据成员使用指针时,需要为它们申请空间,要注意区分动态对象数组和指针数组的区别,以及析构时的异同。注意分析下面的例子。,#include using namespace std; class Test private: char *str; public: Test(char *str=Here!); Test(); ;,Test:Test(char *s) /定义构造函数 str=new charstrlen(s)+1; strcpy(str,s); cout Initializing strendl; Test:Te

12、st() cout Delete strendl; ,void main ( ) Test *ptr1=new Test(We are here!); Test *ptr2=new Test2; Test *ptr32=new Test(We), new Test(You) ; delete ptr1; delete ptr2; for(int i=0; i2; i+) delete ptr3i; ,第一个创建的是一个Test对象的指针ptr1,初始化的值为: We are here! 析构需要使用一个单独的语句“delete ptr1;”。 第二个创建的是一个Test对象的动态数组,这个数组

13、有两个元素,初始化的值在内中顺序排列为: Here! 0Here!0 指针ptr2指向首地址,需要使用语句 delete ptr2; 来析构这分配给这两个对象的存储空间。 第三个创建的是一个Test对象的动态数组,这个数组有两个元素,初始化的值分别为: 对象,ptr30内容为 We;ptr31内容为 You。 所以要使用循环语句逐个析构。 输出结果如下: Initializing We are here! Initializing Here! Initializing Here! Initializing We Initializing You Delete We are here! Dele

14、te Here! Delete Here! Delete We Delete You,如果在定义类时没有定义析构函数,C+编译器要为这个类产生一个默认的析构函数。正如没有给类定义构造函数,C+编译系统会产生一个默认构造函数一样,编译器也为它产生一个函数体为空的默认析构函数: TestTest(),5.5.3 默认析构函数,【例5.9】 演示调用构造函数及 析构函数的综合例题。 下面这个程序综合演示了调用构造函数、调用复制构造函数及调用析构函数的顺序,并演示了调用复制构造函数的3种不同情况。 另外,本程序还比较了函数使用对象和对象的引用为参数的情况。,5.6 综合例题,#include usin

15、g namespace std; class Point private: int X,Y; public: Point(int a=0,int b=0) /构造函数 X=a; Y=b; coutInitializingendl; Point(const Point 以绕过类型转换所带来的运行时间开销。 程序运行结果如下: We They We We We Go home!,可以在一个类中说明具有某个类的类型数据成员,这些成员称为对象成员。 在类X中说明对象成员的一般形式为: class X 类名1成员名1; 类名2成员名2; 类名n成员名n; ;,5.8 对象成员的初始化,说明对象成员是在类

16、名之后给出对象成员的名字。为初始化对象成员,X类的构造函数要调用这些对象成员所在类的构造函数,X类的构造函数的定义形式如下: X X(参数表0): 成员1(参数表1), 成员2(参数表2),成员n(参数表n) /其他操作 冒号“:”后由逗号隔开的项组成成员初始化列表,其中的参数表给出为调用相应成员所在类的构造函数时应提供的参数。这些参数一般来自“参数表0”,可以使用任意复杂的表达式,其中可以有函数调用。如果某项的参数表为空,则表中相应的项可以省略。,对象成员构造函数的调用顺序取决于这些对象成员在类中说明的顺序,与它们在成员初始化列表中给出的顺序无关。 当建立X类的对象时,先调用对象成员的构造函

17、数,初始化对象成员,然后才执行X类的构造函数,初始化X类中的其他成员。 析构函数的调用顺序与构造函数正好相反。 下面分析一个实际的例子。,【例5.11】 分析下面程序中析构函数与构造函数的调用顺序。 #include using namespace std; class object private: int val; public: object(): val(0) coutDefault constructor for object“endl; object(int i): val(i) coutConstructor for object“valendl; object() coutDe

18、structor for object“valendl; ;,class container private: object one;/初始化顺序与对象成员产生的顺序有关 object two;/排在前面的先初始化 int data; public: container(): data(0) coutDefault constructor for container“endl; container(int i,int j,int k); container() coutDestructor for container“dataendl; ;,/与初始化列表顺序无关 container cont

19、ainer(int i,int j,int k): two(i),one(j) data=k; coutConstructor for container“datab)?a: b; public: Max4(T,T,T,T); T Max(void); ; template/定义成员函数必须再次声明模板 Max4Max4(T x1,T x2,T x3,T x4): a(x1),b(x2),c(x3),d(x4) template/定义成员函数必须再次声明模板,T Max4Max(void)/定义时要将T Max4看做整体 return Max(Max(a,b),Max(c,d); void m

20、ain() Max4C(W,w,a,A);/比较字符 Max4A(-25,-67,-66,-256);/比较整数 Max4B(1.25,4.3,-8.6,3.5);/比较双精度实数 coutC.Max() A.Max() B.Max()endl; 输出结果为: w -25 4.3,【例5.13】 演示对4个数字求和的类模板程序。 #include using namespace std; template/可以传递程序中的整数参数值 class Sum private: Tmsize;/数据成员 public: Sum(T a,T b,T c,T d)/构造函数 m0=a; m1=b; m2=

21、c; m3=d; T S()/求和成员函数 return m0+m1+m2+m3; ;,void main() Sumnum1(-23,5,8,-2);/整数求和 /单精度求和。使用f显式说明float型 Sumf1(3.5f,-8.5f,8.8f,9.7f); Sumd1(355.4,253.8,456.7,-67.8); /字符减,等效于W-4,结果为S Sumc1(W,-2,-1,-1); coutnum1.S(),f1.S(),d1.S() ,c1.S()endl; 输出结果为: -12,13.5,998.1,S,标准模板库(standard templete library,STL)

22、是美国国家标准委员会(american national standards institute,ANSI)和国际标准化组织(international standardization organization,ISO)于1988年3月制定的标准。 它主要由两种组件构成: 一种是容器(container),包括vector,list,set,map,stack,和deque等类。另一种是用以操作这些容器类的所谓泛型算法(generic algorithm),包括find(),sort(),replace()和merge()等。,5.9.2 标准模板库STL,泛型算法提供了许多可用于容器类的操作

23、行为,而这些算法和想要操作的元素类型无关。 正如第3章3.4节所述,它们和int,double或者string全然无关。 其实,它们与容器类型也彼此独立(不论容器是vector或list)。 泛型算法系通过function template技术,达到“与操作对象的数据类型相互独立”的目的。而达到“与容器无关”的诀窍,就是不直接在容器身上进行操作。这是借助一对iterators(迭代子)实现,它是一种泛型指针。,在3.4节介绍了指示第一元素的begin(),其实还有指示结束的标记end()(最后一个元素后面的结束标记),它们标示要进行迭代的元素空间。 如果begin不等于end,算法便会首先作用

24、于begin所指元素,并将begin前进一个位置,然后作用于当前的begin所指元素身上,如此继续进行,直到begin等于end为止。 图5.3是向量a和迭代子示意图。图中的rbegin和rend是提供给逆向泛型指针的开始和结束标志。逆向泛型指针的加操作是使它向rend方向移动,减操作向rbegin移动。它的定义方法如下: vector reverse_iterator 指针名; /reverse_iterator是逆向泛型指针关键字,图5.3 迭代子示意图,【例5.14】 演示双向访问的例子。 #include #include using namespace std; void main(

25、) char st 11=abcdefghij; vectora(st,st+10); vector iterator p=a.begin();/定义正向泛型指针 /并初始化 vector reverse_iterator ps; /定义逆向泛型指针 for(p=a.begin(); p!=a.end(); +p) /正向访问 cout*p ;/输出a b c d e f g h i j coutendl;,for(p=a.end()-1; p!=a.begin()-1; -p) /使用正向泛型 /指针逆向访问 cout*p ;/输出j i h g f e d c b a coutendl;

26、for(ps=a.rbegin(); ps!=a.rend(); +ps) /使用逆向泛型 /指针正向访问,使用+运算 cout*ps ;/输出j i h g f e d c b a coutendl; for(-ps; ps!=a.rbegin()-1; -ps)/使用逆向泛型指针 /逆向访问,使用-运算 cout*ps ;/输出a b c d e f g h i j ,STL是用在标准数据结构和标准算法领域中的一种模板库,它所提供的每一个组件都具有很强的通用性。它作为一种基本组成单元,用来生成更高层的类、类库和应用程序框架;也可作为软件的构件或粘合剂,把复杂的面向对象的结构集成为一个整体。

27、 因为它的每一个组件都以模板的方式实现,所以C+模板的技术和工作机理是深入了解STL的敲门砖。 由于篇幅关系,仅作简单介绍。STL现在有75个泛型算法,下面给出最常用的算法。, 搜寻类算法: find() adjacent_find() count() find_if() count_if() binary_search() find_first_of() 排序及次序整理类算法: merge() partial_sort() random_shuffle() partition() reverse() rotate() sort() 复制、删除和替换类算法: copy() remove() r

28、emove_if() replace() replace_if() swap() unique() 关系算法: equal() includes() mismatch(), 生成与变更算法: fill() for_earch() generate() transform() 数值类算法: accmulate() adjacent_difference() partial_sum() inner_product() 集合类算法: set_union() set_difference() 第3章介绍了vector的操作,其实也是容器类的共同操作。它们还有如下共同操作: equality(=)和in

29、equality(!=)运算符,返回true或false。 assignment(=)运算符,将某个容器复制给另一个容器。,【例5.15】 演示使用泛型算法及插入操作的例子。 #include #include #include /泛型算法头文件 using namespace std; void main() char st1 11=abcdefghij,st26=LMNOP; vectora(st1,st1+10); vectorb(st2,st2+5); typedef vector iterator iterator; iterator p; /寻找内容为h的元素作为插入位置 iter

30、ator it=find(a.begin(),a.end(),h);,a.insert(it,b.begin(),b.end();/将另一个向量元素插入 for(p=a.begin(); p!=a.end(); p+) /输出新内容 cout*p ; coutendl; it=find(a.begin(),a.end(),h);/查找h的新位置 /查找向量自己元素L的位置 iterator s1=find(a.begin(),a.end(),L); /查找向量自己元素P的位置 iterator s2=find(a.begin(),a.end(),P); a.insert(it,s1,s2+1)

31、;/复制自己的元素 for(p=a.begin(); p!=a.end(); p+) cout*p ; coutendl; it=find(a.begin(),a.end(),c);/查找c的位置 s1=b.begin();,s2=find(b.begin(),b.end(),P);/查找P的位置 a.insert(it,s1,s2);/复制向量b的元素 for(p=a.begin(); p!=a.end(); p+) cout*p(b)? (a):(b) 因宏定义有许多不安全因素,对需要使用无参数宏的场合,应该尽量使用const代替宏定义。对使用有参数宏的场合,可用内联函数达到同样的效果,所

32、以不再过多介绍。 在程序的一个地方定义的宏名,如果不想使其影响到程序的其他地方,可以在不再使用时用#undef删除。,3. 条件编译指令 条件编译指令是#if,#else,#elif和#endif,它们构成类似于C+的if选择结构,其中#endif表示一条指令结束。 编译指令#if用于控制编译器对源程序的某部分有选择地进行编译。该部分从#if开始,到#endif结束。 如果#if后的常量表达值为真,则编译这部分,否则就不编译该部分,这时,这部分代码相当于从源文件中删除掉。 编译指令#else在#if测试失效的情况下建立另外一种选择。 例如可以在#else分支中使用编译指令#error输出出错信

33、息。,#error使用的形式为: #error 出错信息 “出错信息”是一个字符序列。当遇到#error指令时,编译器显示其后面的“出错信息”,并中止对程序的编译。 编译指令可嵌套,嵌套规则和编译器对其处理的方式与C+的if 语句嵌套情况类似。,4. defined操作符 关键字defined不是指令,而是一个预处理操作符。用于判定一个标识符是否已经被#define定义。 如果标识符 identifier 已被#define定义,则 defined(identifier) 为真,否则为假。 例如,在设计程序时,为便于检查程序中的错误,要使用输出语句显示一些调试信息,在程序正式交付使用之后,不应

34、再显示这些信息,程序可以用下面的方式设计:,#include using namespace std; #define DEBUG void main() int i=555; /程序 #if defined(DEBUG) coutDEBUG: value of i isi; #endif ,在程序调试完后,删除程序中的 #define(DEBUG) 指令,重新编译程序即可,程序的其他地方无需改动。 另外,C+提供#ifndef和#ifdef只是为兼容C程序,今后可能被淘汰,因此建议程序员使用defined进行标识符的测试。 条件编译指令#ifdef和#ifndef用于测试其后的标识符是否被#

35、define定义,如果已经被定义,则#ifdef测试为真,#ifndef测试为假;如果没有被定义,则#ifdef 测试为假,#ifndef测试为真。下面两种方式是完全等效的:,#if !defined(HEAD_H) #define HEAD_H /程序定义部分 #endif #indef HEAD_H #define HEAD_H /程序定义部分 #endif 使用defined而不使用#ifdef或#ifndef测试一个标识符是否被定义的好处在于,可以用逻辑运算组成复杂的测试表达式。例如: defined(LARGE)& ! defined(SMALL) ! defined(TINY),在多文件设计中,由于文件包含指令可以嵌套使用,可能会出现不同文件包含了同一个头文件,这会引起变量及类的重复定义。 为了避免重复编译类的同一个头文件

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论