C++程序设计第4章 类与对象_第1页
C++程序设计第4章 类与对象_第2页
C++程序设计第4章 类与对象_第3页
C++程序设计第4章 类与对象_第4页
C++程序设计第4章 类与对象_第5页
已阅读5页,还剩136页未读 继续免费阅读

付费下载

下载本文档

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

文档简介

第四章类与对象面向过程的程序设计方法:面向过程的程序设计方法:缺点:当数据结构改变时,所有相关的处理过程都要进行相应的修改。在编写大的程序时,给调试程序和程序的维护都带来很大的问题。可重用性差、数据安全性差。图形用户界面的应用,很难用过程来描述和实现,开发和维护都很困难。用函数来实现对数据的操作,把描述某一事物的数据与对数据进行操作的函数分开。面向对象的程序设计方法:将数据及对数据的操作方法封装在一起,作为一个相互依存、不可分离的整体——对象。对同类型对象抽象出其共性,形成类。类通过一个简单的外部接口,与外界发生关系。对象与对象之间通过消息进行通讯。优点:程序模块间的关系更为简单,程序模块的独立性、数据的安全性有了良好的保障。通过继承与多态性,可以大大提高程序的可重用性,使得软件的开发和维护都更为方便。

在描述一个事物时可以将数据隐藏起来,可以做到只有通过这一整体中的函数才能修改这一整体中的数据。数据结构的变化,仅影响封装在一起的函数;同样,修改函数时仅影响封装在一起的数据。真正实现了封装在一起的函数和数据不受外界的影响。类:

类是对一组性质相同事物的抽象。

类中定义一类对象共有的变量和方法。把一个类实例化就生成该类的一个对象。

例:可以定义一个汽车类来描述所有汽车的共性。通过类的定义,可以实现代码的复用。即,不用去描述每一个对象(如某辆汽车),而是通过创建类(如汽车类)的一个实例来创建该类的一个对象,这就大大简化了软件的设计。

类是对象的原型(抽象模型),对象是具有类指定特性和方法的实体,由类得到对象的过程称为类的实例化.对象:

对象是类的一个实例,类在程序运行时被用作模板来建立对象。对象的特性:

状态。如,汽车的速度,油量等;对象对外界的信息的反应方法。

对象之间的通讯是通过系统提供的消息机制来实现的。系统或对象可把一个消息发送给一个指定的对象,或某一类对象。接收到消息的对象通常必须处理所接收到的消息,对象对消息的处理是通过激活本对象内相应的函数来实现。

变量:表明对象的状态;方法:表明对象所具有的行为。变量方法一个对象

变量构成对象的核心,包围在它外面的方法使这个对象和其他对象分离开来。一个对象就是变量和相关方法的集合。1.抽象:

抽象是对具体对象(问题)进行概括,抽出这一类对象的公共性质并加以描述的过程。

◆先注意问题的本质及描述,其次是实现过程或细节。●

数据抽象:描述某类对象的属性或状态(对象相互区别的物理量)●代码抽象:描述某类对象的共有的行为特征,或具有的功能◆抽象的实现:通过类的声明。例:学生管理系统:

数据:性别、年龄等行为:选课、课程实践等所有学生的共性——

类;每个学生是一个对象——实例。抽象实例:——

钟表数据抽象:

intHour;intMinute;intSecond;代码抽象:

SetTime();ShowTime();classClock{public:voidSetTime(intNewH,intSewM,intNewS);voidShowTime();private:intHour,Minute,Second;};数据成员成员函数例:抽象实例——人数据抽象:

int*name;char*sex;intage;intid;代码抽象:

生物属性角度:

Eat();……

社会属性角度:

Work();Promote();……注意:同一个问题可能有不同的抽象结果,即,根据解决问题的要求不同,产生的抽象成员可能不同。如:服装设计成绩管理行为不一样

抽象的过程是将有关事物的共性归纳、集中的过程。抽象的作用是表示同一类事物的本质。不同的目的,使用不同层次的抽象。抽象有助于控制所面临问题的复杂性抽象:

类是对象的抽象,而对象则是类的特例,或者说是类的具体表现形式。2.封装

将抽象出来的数据成员、代码成员相结合,将它们视为一个整体-类。目的:增强安全性和简化编程,使用者不必了解具体的实现细节,而只需要通过外部接口,以特定的访问权限来使用类的成员。安全性:钟表(封装,接口:手柄)简化编程:使用该类和对象时可以直接使用,不需考虑内部如何做的。软件对象的封装:

①对变量设置访问限定词;②对函数设置访问限定词。classClock{

public:

voidSetTime(intNewH,intSewM,intNewS);

void

ShowTime();

private:intHour,Minute,Second;};例:边界类体特定的访问权限外部接口(公有的)相当于手表的手柄3.继承与派生

是C++中支持层次分类的一种机制,允许程序员在保持原有类特性的基础上,进行更具体的说明。实现:声明派生类——第8章4.多态性

多态:同一名称,不同的功能实现方法。

目的:达到行为标识统一,减少程序中标识符的个数。函数重载——实现“+”(float,int,double),行为实现相加称:静态多态性(编译时,系统根据调用函数参数不同加以区分)

动态多态性,用虚函数实现,编译时系统不知道,执行时才知道。实现:重载函数和虚函数——第8章5.消息:

对象之间必须进行交互来实现复杂的行为。如,要使汽车加速,必须发给它一个消息,告诉它如何动作(加速)以及实现这种动作所需的参数(需要达到的速度等)。

对象B对象A消息消息的组成:消息的接受者接收对象应采用的方法,这个方法必须是目标对象本身具有的;方法所需要的参数

接收消息的对象在执行相应的方法后,可能会给发送消息的对象返回一些信息。(如,汽车的仪表上会出现已经达到的速度等。

任何一个对象的所有行为都可以用方法来描述,通过消息机制可以完全实现对象之间的交互,同时,处于不同处理过程甚至不同主机的对象间也可以通过消息实现交互。类的声明:类是一种用户自定义类型,声明形式:class

类名{

public:

公有的数据和成员函数;(外部接口)

private:

私有的数据和成员函数;

protected:

保护型的数据和成员函数;};类体public:

类与外部的接口,任何外部函数都可以访问

公有类型数据和函数。private:

只允许本类中的函数访问,而类外部的任何函数都不能访问。注:如果紧跟在类名称的后面声明私有成员,则关键字

private可以省略。

不能被类外访问(这点与私有成员类似),但可以被派生类的成员函数访问。protected:private和protected体现了类具有封装性。类的成员:classClock//类头{public:

voidSetTime(intNewH,intNewM,intNewS);

voidShowTime();private:intHour,Minute,Second;};

成员函数成员数据注意:所有说明都有分号,最后的分号不可少;类体注意:

1.类是一种数据类型,定义时系统不为类分配存储空间,所以不能对类的数据成员初始化。类中的任何数据成员也不能使用关键字extern、auto或register限定其存储类型。(P843.6节存储类型)2.成员函数可以直接使用类定义中的任一成员,可以处理数据成员,也可调用函数成员。私有的数据成员只有通过公有的函数,才能从外部对其进行处理。成员数据:

与一般的变量声明相同,但需要将它放在类声明体中。如果在类体中既不写关键字private,又不写public,默认为private。成员函数:

类的成员函数(类函数)的用法和作用和第3章介绍过的函数基本上是一样的,它也有返回值和函数类型。区别:

类函数是属于一个类的成员,出现在类体中。类函数可以指定为private、public或protected。在使用类函数时,应注意调用它的权限(能否被调用)以及它的作用域(函数能使用什么范围中的数据和函数)。4.1.2类的成员函数

属于类的成员,函数出现在类体中。可以指定为:private、public、protected。私有的成员函数只能被本类中的其他成员函数所调用,不能被类外调用。成员函数可以访问本类中任何成员(包括私有的和公有的),可以引用在本作用域中有效的数据。类的成员函数与一般函数的区别:定义类的函数成员的格式如下:

返回值类型类名::成员函数名(参数说明)

{

函数体

}成员函数的定义:

类的成员函数对类的数据成员进行操作,成员函数的定义体可以在类中定义,也可以在类外定义。例1:成员函数在类中直接定义:classLocation{public:

voidinit(intx,inty){X=x;

Y=y;

}intGetX()

{returnX;}

intGetY()

{returnY;}private:

intX,Y;

};例2:

成员函数在类外定义:classLocation{public:voidinit(intx,inty);intGetX();intGetY();private:intX,Y;};voidLocation::init(intx,inty){

X=x;

Y=y;}intLocation::GetX(){

returnX;}intLocation::GetY(){

returnY;}函数原型1.在类体中直接定义函数时,函数名前面不需要加类名。2.成员函数在类外定义时,必须在函数名前面加上类名,予以限定,“∷”是作用域限定符或称作用域运算符,用它声明函数是属于哪个类的。如果在作用域运算符“∷”的前面没有类名,或者函数名前面既无类名又无作用域运算符“∷”,如:

∷init()或init()

表示init函数不属于任何类,这个函数不是成员函数,而是全局函数,即一般普通函数。

提倡:在类的内部声明成员函数原型,在类体外定义成员函数体。(例2)区别:对象的定义方法:例1:classStudent{//声明类类型

private:

//修饰符可省

intnum;charname[20];charsex;

public:voiddisplay(){//函数在类中定义

cout<<″num:″<<num<<endl;cout<<″name:″<<name<<endl;cout<<″sex:″<<sex<<endl;}};Studentstud1,stud2;//定义了两个Student类的对象,并分配存储空间1.先声明类类型,然后再定义对象:类名对象名;

4.1.3

对象的创建与使用2.采用动态创建类的对象方法(第7章介绍)。动态指:在程序运行时建立对象并允许程序员控制内存的分配和释放。系统存储对象有两种方法:系统为每一个对象分配了全套的内存。数据区存放成员数据,代码区存放成员函数。(图4.1所示)注意:

区别同一个类的各个不同的对象的属性是由数据成员决定的,不同对象的数据成员的内容是不一样的;而行为(操作)是用函数来描述的,这些操作的代码对所有对象都是一样的。两种对象存储方式:一旦创建对象系统就为每个对象分配空间图4.1各对象完全独立地安排内存的方案数据区代码区

对象1

对象2数据区代码区

对象n......数据区代码区方法一:本存储方式:在类定义中定义函数,如例1。特点:若每个对象的数据占内存1KB,代码占50KB,共占51KB,100个对象共占5100KB,其中4950KB是重复的。图4.2各对象的代码区共用的方案数据区

对象1数据区

对象2数据区

对象n......

公共代码区方法二:仅为每个对象分配一个数据区,代码区(放成员函数的区域)为各对象类共用。本存储方式:在类定义外部定义函数,如例2。特点:节约存储空间每个对象所占用的空间只是该对象的数据部分所占用的存储空间,不包括函数代码所占用的空间。例2:

classStudent{//声明类类型

private:

//修饰符可省

intnum; charname[20]; charsex;

public: voiddisplay();//函数仅在类中说明

}; voidStudent::display(){//函数在类外定义

cout<<″num:″<<num<<endl;cout<<″name:″<<name<<endl;cout<<″sex:″<<sex<<endl; }内联成员函数

方法一,在类体中直接定义成员函数的函数体时,如果成员函数中不包括循环等控制结构,C++系统会自动将它们作为内置(inline)函数来处理。称这种成员函数为内置成员函数;(关键字inline可省)

方法二,在类体中只是给出成员函数的函数原型说明,在类体外定义成员函数时,与定义一般的内置函数一样,在成员函数的定义前面加上关键字inline。

在类体外定义内置(inline)函数,和调用一般函数的过程相同,应当用inline作显式声明。如classStudent{public:

inlinevoiddisplay();

//声明此成员函数为内置函数private:intnum;stringname;charsex;};inlinevoidStudent∷display(){//在类外定义display函数为内置函数

cout<<″num:″<<num<<″name:″<<name<<endl;cout<<″sex:″<<sex<<endl;}对象成员的引用注意:这些成员必须是公有的成员,只有公有成员才能在对象的外面对它进行访问。

访问对象中的成员可以有3种方法:通过对象名和成员运算符访问对象中的成员;

通过指向对象的指针访问对象中的成员;

通过对象的引用变量访问对象中的成员。1.

通过对象名和成员运算符访问对象中的成员访问对象中成员的一般形式:

对象名.成员名classLocation{//例1public:

voidinit(intx,inty){X=x;

Y=y;

}intGetX()

{returnX;}

intGetY()

{returnY;}private:

intX,Y;

};voidmain(){Locationl;

l.init(2,3);cout<<l.GetX()<<""<<l.GetY();}若:l.X=20;l.Y=30;//错误或:cout<<l.X<<“”<<

l.Y;//

错误若:intX=20,Y=30;//错误2.通过指向对象的指针访问对象中的成员classTime{public://数据成员是公用的

inthour;intminute;};voidmain(){Timet,*p;//定义对象t和指针变量pt.hour=100;t.minute=20;p=&t;//使p指向对象tcout<<(*p).hour<<“:”<<(*p).minute;//输出p指向的对象中的成员hour和minute}//或写成:

cout<<p->hour<<":"<<p->minute;3.通过对象的引用变量访问对象中的成员若已声明了Time类,并有以下定义语句:

Timet1;

//定义对象t1Time&t2=t1;

//定义Time类引用变量t2,并使之初始化为t1cout<<t2.hour;

//输出对象t1中的成员hourt2与t1共占同一段存储单元(即t2是t1的别名),则t2.hour就是t1.hour。类声明和成员函数定义的分离

为了减少工作量,节省篇幅,提高编程的效率,c++将类的声明(包含成员函数的声明)放在指定的头文件中,用户如果想用该类,只要把有关的头文件包含进来即可.

为了实现信息隐蔽,对类成员函数的定义一般不放在头文件中,而另外放在一个文件中。//student.h

头文件classStudent{

//类声明

public:voiddisplay();

//公用成员函数原型声明

private:intnum;charname[20];charsex;};//student.cpp(在此文件中进行函数的定义)#include<iostream.h>#include″student.h″

voidStudent∷display(){//在类外定义display类函数

cout<<″num:″<<num<<endl;cout<<″name:″<<name<<endl;cout<<″sex:″<<sex<<endl;}//main.cpp主函数模块#include<iostream.h>#include″student.h″

//将类声明头文件包含进来intmain(){Studentstud;//定义对象

stud.display();//对象stud发出的消息,通知它执行display函数}

建立一个对象后,如果没有对数据成员赋初值则它的值是不可预知的。

对对象的数据成员进行初始化的方法有三种:

1.使用初始化数据列表的方法;

2.通过构造函数实现初始化;

3.通过对象的拷贝初始化函数来实现。对象的初始化4.3 构造函数和析构函数使用初始化数据列表的方法对新产生的对象初始化。classC{public: inti; charname[10]; floatnum[2];};Cc1={25,“张三”,{77.8,99.56}};intmain(){ cout<<c1.i<<'\t'<<<<'\t'; cout<<c1.num[0]<<'\t'<<c1.num[1]<<'\n';return0;}

输出:

25张三

77.8 99.56产生对象c1时,完成对象数据成员的初始化。该初始化方法仅对public数据有效//与结构体变量初始化差不多,//先声明再赋值例:若类中定义的数据成员是私有或保护型的,对其成员变量初始化classTime{public:voidset_time();voidshow_time();private:inthour,minute,sec;};voidmain(){Timet1;t1.set_time();t1.show_time();return0;}voidTime∷set_time(){cin>>hour;cin>>minute;cin>>sec;}voidTime∷show_time(){cout<<hour<<″:″<<minute<<″:″<<sec<<endl;}程序烦琐,效率低

在面向对象程序的设计中,数据成员,从封装的目的出发,应该多为私有的,要对它们进行初始化,必须用一个公有函数来进行。同时这个函数应该在且仅在定义对象时自动执行一次。

C++提供了构造函数来处理对象的初始化。

构造函数是类的成员函数,系统约定:1.构造函数名必须与所在类的类名相同。2.构造函数没有返回值,也不用void。3.在程序运行时,当新的对象被建立,该对象所属的类的构造函数自动被调用,在该对象生存期中也只调用这一次。(构造函数通常声明为public)4.构造函数可以重载,系统在自动调用时按一般函数重载的规则选一个执行。5.构造函数可以在类中定义,也可以在类外定义。构造函数定义:

如:classTime{……}1.在类中定义:

Time(){…….}2.在类外定义:

Time::Time(){…….}若类说明中没有给出构造函数,则C++编译器自动给出一个缺省的构造函数:

类名(){}

//例classRectangle{private:intLeft,Right,Top,Bottom;public:

Rectangle(intL,intR,intT,intB){ Left=L;Right=R;Top=T;Bottom=B; cout<<"调用带参的构造函数!\n";}

Rectangle(){ Left=0; Right=0;Top=0; Bottom=0; cout<<"调用不带参的构造函数!\n";}voidPrint(void){ cout<<Left<<‘\t’<<Right<<‘\t’<<Top<<‘\t’<<Bottom<<'\n';}};重载intmain(void){Rectangler1(100,200,300,400); r1.Print();

Rectangler2;//A r2.Print(); return0; }注:无参的构造函数属默认构造函数。一个类只能有一个默认构造函数。(2)若在建立对象时选用的是无参构造函数,应注意正确书写定义对象的语句。例A行。不能写成:Rectangler2();(3)一个类中可以包含多个构造函数,但对于每一个对象来说,建立对象时只执行其中的一个构造函数。

用参数初始化表对数据成员初始化

使用参数初始化表来实现对数据成员的初始化。写法方便、简练,尤其当需要初始化的数据成员较多时更显其优越性。可以直接在类体中定义构造函数,也可以在类体外定义。在类体中定义:Rectangle(intL,intR,intT,intB): Left(L),Right(R),Top(T),Bottom(B){}上例中定义构造函数可以改用以下形式:在类体外定义:Rectangle::Rectangle(intL,intR,intT,intB): Left(L),Right(R),Top(T),Bottom(B){}

//例classBox{public:Box();//声明一个无参的构造函数

Box(inth,intw,intlen):height(h),width(w),length(len){}intvolume();private:intheight;intwidth;intlength;};Box::Box():height(10),width(10),length(10){}intBox::volume(){return(height*width*length);}Box::Box(){height=10;width=10;length=10;}intmain(){Boxbox1;cout<<"box1is"<<box1.volume()<<endl;Boxbox2(15,30,25);cout<<"box2is"<<box2.volume()<<endl;return0;}运行结果:

box1is1000box2is11250使用默认参数的构造函数

构造函数中也可以采用第3章中介绍过在函数中可以使用有默认值的参数的方法来实现初始化。例:classBox{public:Box(inth=10,intw=10,intlen=10);//在声明时指定默认参数

intvolume();private:intheight;intwidth;intlength;};Box::Box(inth,intw,intlen):height(h),width(w),length(len){}intBox::volume(){return(height*width*length);}intmain(){Boxbox1;//无实参

cout<<″box1is″<<box1.volume()<<endl;Boxbox2(15);//1个实参

cout<<″box2is″<<box2.volume()<<endl;Boxbox3(15,30);//2个实参

cout<<″box3is″<<box3.volume()<<endl;Boxbox4(15,30,20);//3个实参

cout<<″box4is″<<box4.volume()<<endl;return0;}例1:classE{intx,y;public:E(inta,intb){x=a;y=b;}voidP(void){cout<<x<<'\t'<<y<<'\n';}};如果有说明:Ee;

错误,因产生对象e时,没有合适的构造函数可供调用。类中,必须有一个缺省的构造函数供产生对象e时调用。E(){}例2:classQ{intx,y;public:Q(inta=0,intb=0){x=a;y=b;}Q(){}voidP(void){cout<<x<<'\t'<<y<<'\n';}};如果有说明:Qq;

错误。有二个缺省的构造函数,在产生对象q时,不知调用哪一个缺省的构造函数,产生二义性。

在一个类中定义了全部是默认参数的构造函数后,不能再定义重载构造函数。例3:classR{intx,y;public:R(inta,intb){x=a;y=b;}R(){x=y=0;}};如果有说明:

Rr();

编译器认为这不是定义对象r,而是函数原型说明,即说明函数r()的返回值为类R类型。所以,当定义的对象要调用缺省的构造函数时,在对象名后不能有括号。

产生对象时系统要为对象分配存储空间;在对象结束生命期或结束作用域(静态存储类型的对象除外)时,系统要收回对象所占用的存储空间,使这部分内存可以被程序分配给其它对象使用。此项工作是由析构函数来完成的。析构函数也是类的成员函数,定义析构函数的格式为:

~类名(){……}

或:~类名(){}4.3.2析构函数的定义与使用1.

析构函数名与类名相同,但在前面加上字符‘~’。2.

析构函数无函数返回类型,析构函数不带任何参数。

唯一的,不允许重载。

3.一个类只有一个析构函数。析构函数可以缺省。

如果程序中未声明析构函数,编译器将自动产生一个缺省的析构函数。4.对象注销时,系统自动调用析构函数。约定:如果在一个函数中定义了一个对象(自动局部对象),当该函数被调用结束时,对象释放,在对象释放前自动执行析构函数。static局部对象在函数调用结束时对象不释放,也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用static局部对象的析构函数。(4.7节介绍)③如果定义了一个全局对象,在程序的流程离开其作用域时(如main函数结束或调用exit函数)时,调用该全局对象的析构函数。④如果用new运算符动态地建立了一个对象,当用delete运算符释放该对象时,先调用该对象的析构函数。(第7章介绍)以下几种情况,程序会执行析构函数:例:classQ{ intx,y;public:Q(inta=0,intb=0){x=a;y=b;}voidP(){cout<<x<<'\t'<<y<<'\n';}~Q(){cout<<"调用了析构函数!"<<'\n';}};intmain(){Qq(50,100);q.P();Qqq;qq.P();cout<<"退出主函数!\n";return0;}执行程序后的输出为:10000退出主函数!调用了析构函数!调用了析构函数!//qq//q

析构函数的作用并不仅限于释放资源方面,它还可以被用来执行“用户希望在最后一次使用对象之后所执行的任何操作”,例如输出有关的信息。与调用构造函数的次序相反:最先被调用的构造函数,其对应的(同一对象中的)析构函数最后被调用,而最后被调用的构造函数,其对应的析构函数最先被调用。见右图:析构函数的调用次序:【例4.2】矩形类。要确定一个矩形(四边都是水平或垂直方向,不能倾斜),只要确定其左上角和右下角的x和y坐标即可,即左右上下四个边界值。classRectangle{//rect.h intleft,top,right,bottom;public:Rectangle(int=0,int=0,int=0,int=0);//默认构造函数必须在此指定默认实参

~Rectangle(){};

//析构函数,在此函数体为空

voidAssign(int,int,int,int);//赋值

voidSetLeft(intt){left=t;}voidSetRight(intt){right=t;}voidSetTop(intt){top=t;}voidSetBottom(intt){bottom=t;}voidShow();};Rectangle::Rectangle(intl,intt,intr,intb){left=l;top=t;right=r;bottom=b;}voidRectangle::Assign(intl,intt,intr,intb){//赋值

left=l;top=t;right=r;bottom=b;}voidRectangle::Show(){cout<<”left-toppointis(”<<left<<”,”<<top<<”)”<<’\n’;cout<<”right-bottompointis(”<<right<<”,”<<bottom<<”)”<<’\n’;}//rect.cpp#include<iostream>usingnamespacestd;#include“rect.h”intmain(){ Rectanglerect; rect.Show(); rect.Assign(100,200,300,400); rect.Show(); Rectanglerect1(0,0,200,200); rect1.Show(); Rectanglerect2(rect1); rect2.Show();

return0;}//Ex4_2.cpp输出:

left-toppointis(0,0)right-bottompointis(0,0)left-toppointis(100,200)right-bottompointis(300,400)left-toppointis(0,0)right-bottompointis(200,200)left-toppointis(0,0)right-bottompointis(200,200)4.4.2 复制(拷贝)构造函数

同类的对象之间可以互相复制(在内存中有完全相同的结构)。复制过程只需要复制数据成员,而函数成员是共用的(只有一份代码)。在建立对象时可用同一类的另一个对象来初始化该对象,这时所用的构造函数称为复制构造函数。对象复制(复制构造函数)的使用:

1.需要用到多个完全相同的对象;

2.将对象在某一瞬时的状态保留下来用一个已有的对象快速地复制出多个完全相同的对象。一般形式为:

类名对象2(对象1);

用对象1复制出对象2。如:Boxbox1(15,30,25);Boxbox2=box1;Boxbox3(box1);

三个对象的初始化完全一样。复制构造函数特征:复制构造函数的参数必须采用引用。用这种构造函数创建一个对象时,必须用一个已产生的同类对象作为实参

原因:

在C++中按值传递一个参数时,会在函数中重新分配一块内存建立和参数同类型的变量或对象,再把参数的数据成员赋给新的变量或对象。在建立这个对象时,编译器就会自动为这个对象调用复制构造函数。如果形参是对象而不是引用,则又会引入新的一轮调用复制构造函数的过程,出现了无穷递归,…。例使用完成复制功能的构造函数。classTest{ intx,y;public: Test(inta,intb){x=a;y=b; cout<<"调用了构造函数!\n"; }

Test(Test&t){ x=t.x;y=t.y; cout<<"调用了完成拷贝的构造函数!\n"; } voidShow(){ cout<<"x="<<x<<'\t'<<"y="<<y<<'\n'; }};intmain(){Testt1(10,10);Testt2=t1;//ATestt3(t1);//Bt1.Show();t2.Show();t3.Show();return0;}输出:调用了构造函数!调用了完成拷贝的构造函数!调用了完成拷贝的构造函数! x=10 y=10 x=10 y=10 x=10 y=10

执行A行和B行时,编译器自动将A行转换为:

Testt2(t1);

因此,A行和B行都调用了完成拷贝的构造函数,初始化新产生的对象。例使用隐含的完成复制功能的构造函数。classTest{ intx,y;public:Test(inta,intb){ x=a;y=b; cout<<"调用了构造函数!\n";} voidShow(){ cout<<"x="<<x<<'\t'<<"y="<<y<<'\n';}};intmain(void){Testt1(10,10);Testt2=t1;//ATestt3(t1);//Bt1.Show();t2.Show();t3.Show();return0;}输出:调用了构造函数!x=10 y=10x=10 y=10x=10 y=10Test::Test(Test&t){x=t.x;y=t.y;}A行和B行分别调用了默认的拷贝构造函数普通构造函数和复制构造函数的区别:(1)在形式上

类名(形参表列);

//普通构造函数的声明,如Box(inth,intw,intlen);

类名(类名&

对象名);

//复制构造函数的声明,如Box(Box&b);(2)在建立对象时,实参类型不同。系统会根据实参的类型决定调用普通构造函数或复制构造函数。如

Boxbox1(12,15,16);

//实参为整数,调用普通构造函数

Boxbox2(box1);

//实参是对象名,调用复制构造函数(3)在什么情况下被调用普通构造函数在程序中建立对象时被调用。复制构造函数在用已有对象复制一个新对象时被调用,以下3种情况下需要克隆对象:①程序中需要新建立一个对象,并用另一个同类的对象对它初始化。②当函数的参数为类的对象时。系统是通过调用复制构造函数来实现的。如voidfun(Boxb)

{…}//形参是类的对象intmain(){Boxbox1(12,15,18);fun(box1);

//实参是类的对象,调用函数时将复制一个新对象b

return0;}③函数的返回值是类的对象。在函数调用完毕将返回值带回函数调用处时,此时需要将函数中的对象复制一个临时对象并传给该函数的调用处。如

Boxf(){

//函数f的类型为Box类类型

Boxbox1(12,15,18);returnbox1;

//返回值是Box类的对象}intmain(){Boxbox2;

//定义Box类的对象box2box2=f();

//调用f函数,返回Box类的临时对象,并将它赋值给box2

return0;}returnbox1(12,15,18);classPoint{//例1.cpppublic:Point(intxx=0,intyy=0){X=xx;Y=yy;}

Point(Point&p);intGetX(){returnX;}intGetY(){returnY;}private:intX,Y;};Point::Point(Point&p){X=p.X;Y=p.Y;cout<<“复制构造函数被调用"<<endl;}voidfun1(Pointp){cout<<p.GetX()<<endl;}Pointfun2(){PointA(1,2);returnA;

}intmain(void){PointA(4,5);PointB(A);cout<<B.GetX()<<endl;fun1(B);B=fun2();cout<<B.GetX()<<endl;

return0;}输出:复制构造函数被调用

4复制构造函数被调用

4复制构造函数被调用

1

//例2.cppclassPoint{private:intx,y;public:voidsetX(intxCoord){x=xCoord;}voidsetY(intyCoord){y=yCoord;}intgetX(){returnx;}intgetY(){returny;}Point(inthor,intver){setX(hor);setY(ver);}PointmedialPoint(Pointother){intmedX=(x+other.getX())/2;intmedY=(y+other.getY())/2;Pointq(medX,medY);returnq;}};intmain(){Pointp1(2,2);Pointp2(12,12);cout<<p1.getX()<<","<<p1.getY()<<endl;cout<<p2.getX()<<","<<p2.getY()<<endl;Pointp3=p1.medialPoint(p2);cout<<p3.getX()<<","<<p3.getY()<<endl;}输出:

2,212,127,7问题:若在Point类中增加析构函数:~Point(){cout<<“析构”<<endl;}输出?输出:

2,212,12

析构析构

7,7

析构析构析构若:函数PointmedialPoint(Point&other){}输出?输出:

2,212,12

析构

7,7

析构析构析构4.4.3 成员对象与构造函数聚合(aggregation):

类中的成员,除了成员数据和成员函数外,还可以用其它类的对象作为类的成员。使用成员对象的技术称为聚合(组合)。成员对象是实体,系统不仅为它分配内存,而且要进行初始化。含对象成员的构造函数:类名::构造函数名(参数总表):对象成员1(参数名表1),对象成员2(参数名表2),……对象成员n(参数名表n){……}冒号后用逗号隔开的是要初始化的对象成员,其后的参数名表1,…,参数名表n依次为调用相应对象成员所属的构造函数时的实参表。表中的参数通常来自冒号前的参数总表,但没有类型说明

含对象成员的类对象的初始化时,首先依次自动调用各成员对象的构造函数,再执行该类对象自己的构造函数的函数体部分。各成员对象的构造函数调用的次序与类定义中说明的顺序一致,而与它们在构造函数成员初始化列表中的顺序无关。classA{ inta,b;public: A(inti,intj); voidpointA(); ~A(){cout<<"class_A"<<endl;}};classB{ Ac; intn;public: B(inti,intj,intk); voidpointB(); ~B(){cout<<"class_B"<<endl;}};B::B(inti,intj,intk):c(i,j){n=k;}voidB::pointB(){cout<<"n="<<n;c.pointA();}A::A(inti,intj){a=i;b=j;}voidA::pointA(){cout<<"\ta="<<a<<"\tb="<<b<<endl;}intmain(){ Bm(7,8,2); m.pointB();return0;}自学:【例4.7】演示对象创建和撤消的对应关系输出:n=2a=7b=8class_Bclass_A

在非面向对象的程序设计语言中,所有的运算符都已预先定义了它们的用法及意义,并且这种用法是不允许用户改变的。

C++允许程序设计者重新定义已有的运算符,并能按用户规定要求去完成特定的操作,这就是运算符的重载。同一运算符根据不同的运算对象可以完成不同的操作。运算符重载:

对已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据时导致不同的行为。(实质是函数重载)

运算符的重载可以完成两个对象之间的复杂操作,如两个对象间的加法,两个对象间的减法等。4.5 运算符的重载例1通过函数来实现复数相加。#include<iostream>usingnamespacestd;classComplex{//定义复数类public:Complex(){real=0;imag=0;}//定义构造函数

Complex(doubler,doublei){real=r;imag=i;}//构造函数重载

Complexcomplex_add(Complex&c2);//声明复数相加函数

voiddisplay();//声明输出函数

private:doublereal;//实部

doubleimag;//虚部};ComplexComplex∷complex_add(Complex&c2){Complexc;

c.real=real+c2.real;c.imag=imag+c2.imag;returnc;}

voidComplex∷display(){//定义输出函数

cout<<″(″<<real<<″,″<<imag<<″i)″<<endl;}intmain(){Complexc1(3,4),c2(5,-10),c3;c3=plex_add(c2);//调用复数相加函数

cout<<″c1=″;c1.display();//输出c1的值

cout<<″c2=″;c2.display();//输出c2的值

cout<<″c1+c2=″;c3.display();//输出c3的值

return0;}运行结果:c1=(3+4i)c2=(5-10i)c1+c2=(8,-6i)

运算符重载的方法:例,想将“+”用于Complex类(复数)的加法运算,函数原型为:

Complexoperator+(Complex&c1,Complex&c2);

定义一个重载运算符的函数,在需要执行被重载的运算符时,系统自动调用该函数,以实现相应的运算。运算符重载实质上是函数的重载。重载运算符的函数一般格式:

函数类型

operator运算符名称

(形参表列){

对运算符的重载处理

}

函数名如:Complexc1(3,4),c2(5,-10);

c1+c2;编译系统把它解释为

c1.operator+(c2)

通过对象c1调用运算符重载函数,并以表达式中第二个参数(c2)作为函数实参。运算符重载函数的返回值是Complex类型,返回值是复数c1和c2之和

(Complex(c1.real+c2.real,c1.imag+c2.imag))例2改写例1,重载运算符“+”,使之能用于两个复数相加。#include<iostream.h>classComplex{public:Complex(){real=0;imag=0;}Complex(doubler,doublei){real=r;imag=i;}Complexoperator+(Complex&c2);/*声明重载运算符的函数

voiddisplay();取代了complex_add函数*/private:doublereal;doubleimag;};(Complec2)ComplexComplex∷operator+(Complex&c2){//定义重载运算符的函数

Complexc;c.real=real+c2.real;c.imag=imag+c2.imag;returnc;}//(Complex(c1.real+c2.real,c1.imag+c2.imag))voidComplex∷display(){cout<<″(″<<real<<″,″<<imag<<″i)″<<endl;}voidmain(){Complexc1(3,4),c2(5,-10),c3;c3=c1+c2;//运算符+用于复数运算,取代c3=plex_add(c2);cout<<″c1=″;c1.display();cout<<″c2=″;c2.display();cout<<″c1+c2=″;c3.display();}C++编译系统将程序中的表达式c1+c2解释为

c1.operator+(c2)以c2为实参调用c1的运算符重载函数细解运算符重载:复数类+的重载:ComplexComplex::operator+(Complexc){//显式说明局部对象

ComplexTemp(Real+c.Real,Image+c.Image);returnTemp;//注意:上面直接写对象c的私有成员,不用调c的公有函数处理}编译器把表达式c2+c3解释为:

c2.operator+(c3);函数c2.operator创建一个局部的Complex对象Temp,把出现在表达式中的两个Complex类对象c2和c3的实部之和及虚部之和暂存其内,然后把这个局部对象返回,赋给Complex类对象c(注意这里调用了复制构造函数生成一个无名临时对象过渡)。见图4.8。Temp.Real=Real+c2.Real;Temp.Image=Image+c3.Image;c=return(Temp);RealImagec3.Realc3.Image=+局部对象Temp当前对象c2对象c3图4.8显式说明临时对象的“+”运算符执行过程隐式返回计算结果:

省略局部的Complex对象TempComplexComplex::operator+(doubled){returnComplex(Real+d,Image);}//隐式说明局部对象在return后面跟的表达式中调用的是类的构造函数,它为无名对象赋值(初始化),返回值就是该无名对象。

运算符重载优点:程序易于编写、阅读和维护。原因:通常类声明和类的使用是分离的。1.若在complex类声明时,对运算符+,-,*,/都进行了重载,则使用该类时在编程时可不考虑函数如何实现;同一个运算符可代表不同的功能(由运算符两侧的数据类型决定)。

重载运算符的规则C++不允许用户自己定义新的运算符,只能对已有的

C++运算符进行重载。(2)C++大多运算符允

温馨提示

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

评论

0/150

提交评论