《面向对象程序设计第6章类与对象.ppt》由会员分享,可在线阅读,更多相关《面向对象程序设计第6章类与对象.ppt(72页珍藏版)》请在三一办公上搜索。
1、面向对象程序设计(C+)第6章 类与对象,6.1 类的定义,类构成了实现C+面向对象程序设计的基础,在C+语言面向对象程序设计中占据着核心地位。它把数据和作用在这些数据上的操作组合在一起,是封装的基本单元。对象是类的实例,类定义了属于该类的所有对象的共同特性。,一个类是对一种概念的描述,或者说是对某一类具体事物的抽象。C语言中对“点”的描述:struct Point double x;double y;上述描述说明了点由两个坐标组成这样的概念。不过,它没有说明所描述概念的操作特性。,6.1.1 类的含义与表述,C+丰富了C语言的结构并称之为类,使其能够描述出完整的概念。例如:struct Po
2、int double x;double y;void display()cout x,y;void move(double rx,double ry)x+=rx;y+=y;类中封装的数据和函数称为类的成员,可进一步称为数据成员和函数成员。C+中定义类时更多地使用class而不是struct作为关键字。,6.1.2 类定义的语法规则,类定义包含类头和类体两部分,形式如下:,class 类名/类头 访问限定符:数据成员声明/类体 访问限定符:函数成员声明或定义;/注意分号是必需的,类有三种访问控制:private访问控制符 protected public,class stack private:
3、char data100;char*top;public:void push(char c)/char pop()/;,可以省略掉,使用说明:类的三个访问控制符可以任意顺序出现任意次。不能在类声明中给数据成员赋初值。(C+规定只有在类对象定义之后才能给数据成员赋初值)protected用于继承(详见第9章)数据成员不能用auto、register、extern修饰。,类的作用域类的作用域指在类的声明中的一对花括号所形成的作用域。在类的作用域内部或外部访问同一个名字(变量或函数)的效果是不同的。,6.2 类对象,从语法上讲,类定义使我们得到了一种新的数据类型。它们比C+的内置类型更复杂,但有着一
4、般类型的效果,如可以定义Point类型的对象(变量)、指针变量和数组等。,6.2.1 对象定义,一个自定义的类类型与内置类型在语法上并没有什么差异。例如:Date d1,d2,*pd,a10;对象和指针之间的运算与简单变量相同,对象间可以直接赋值:pd=,对象赋值语句当一个对象赋值给另一个对象时,所有的数据成员都会逐位复制。如:A a1,a2;a1.x=a2.x;a1.y=a2.y;说明:两个对象类型必须相同;两个对象间仅数据相同,而两个对象仍是分离的;当类中存在指针时,可能产生错误。,6.2.2 成员访问,1.在类的内部在类的内部(主要是类的方法中)访问类的成员时只要直接使用成员名即可,无论
5、属性还是方法都如此。,2.在类的外部一般形式:对象名.数据成员名对象名.成员函数名(实参表)若定义的是指向对象的指针,则访问此对象的成员时,要使用“”操作符。,#include using namespace std;class Date public:int year,month,day;void print()cout d.yeard.monthd.day;/输入数据 d.print();/显示日期 cout the dates year/判断是否闰年(d.isLeapYear()?is:is not)a leap year.;,例:下面的程序从键盘输入一个日期并判断其年份是否为闰年。,6
6、.2.3 对象存储,在生成对象时,每个对象都占用包括所有数据成员在内的存储空间(静态属性除外,参见8.1),即每个对象保存着一份所有数据成员的“拷贝”,但所有对象的成员函数只有一份公用的拷贝,单独存储。因此,一个对象占用的存储空间是所有数据成员占用的存储空间之和,不包括成员函数。cout sizeof(Date),sizeof(d);/d是上述代码中的Date型变量上述语句的输出结果为“12,12”,对应于3个int类型数据成员的存储空间之和(有时会因内部调整而多占用一些空间)。,6.3 类的方法,通常,类定义中需要提供相当数量的方法,可以分为两类:其一是处理属性的方法其二是反映对象行为的方法
7、,6.3.1 为类提供必要的方法,首先,由于类对成员的隐藏特点,使得类外常常不能直接访问对象的属性。如果希望外界能够得到或修改一个属性,需要提供相应的公开方法。因此,每个与外界发生联系的属性常常伴随着两个公开的方法。其次,提供允许外界“调用”的公开方法,使对象产生应有的行为,如display和move。此外,还可能需要定义一些类内使用的private方法以及为其派生类准备的protected方法。,class Point int x,y;public:int getx()return x;/与属性相关的方法 int gety()return y;int setx(int x1)x=x1;int
8、 sety(int y1)y=y1;void display()cout x,y;/与行为相关的方法 void move(double rx,double ry)x+=rx;y+=ry;,6.3.2 inline方法,除了极简单的方法之外,大多数的类方法都在类内声明而在类外实现,以使类的定义与实现分离。如果一个类的方法直接在类定义内实现,称为内联(inline)方法。C+将以内联函数的方式处理这种类方法,即内联方法将在程序中每个调用点上被内联地展开。,内联方法也可以通过inline关键字在类定义外实现。例如:class Point int x,y;public:.inline int setx
9、(int);/方法声明.;inline int Point:setx(int x1)/方法实现 x=x1;与定义普通内联函数一样,最好在内联方法的声明和定义处都以inline标明。,6.3.3 方法重载与缺省参数,一个类中也可以定义参数不同的同名函数,构成方法重载,或为方法提供默认的参数值,这与普通函数的重载与参数缺省值没有任何区别,例如:,class Date int year,month,day;public:void setDate(int y=2007,int m=1,int d=30)/缺省参数 year=y;month=m;day=d;void setDate(Date,6.3.4
10、 常成员函数,如果一个类的方法仅读取对象的属性,可以将其定义成const类型的函数。这样做的优点是让使用者明确地知道该方法不修改对象的值,也起到了对对象的保护作用。简单地说,const方法与普通方法的唯一区别是const方法不会直接或间接地修改类的属性。,例如,由于Point类的所有getx、gety和display都不应该修改对象的属性,可以将类定义修改成如下形式:class Point int x,y;public:int getx()const;/在函数头之后注明const int gety()const;void display()const;.;int Point:getx()con
11、st/此处的const也是必要的 return x;,与inline不同的是,const必须在方法声明和实现时都指明,即const属于函数原型的一部分而不仅是修饰词。程序中可以定义常量对象(与普通常量具有相同含义),如:const Point pt;通过常量对象pt只能调用类的const方法。,6.3.5 this指针,问题的提出:当定义了一个类的若干对象后,每个对象都有属于自己的数据成员,但是,所有对象的成员函数代码却合用一份。那么成员函数是怎样辨别出当前调用自己的是哪个对象,从而对该对象的数据成员而不是对其他对象的数据成员进行处理呢?,C+语言为成员函数提供了一个称为this的指针,因此,
12、常常称成员函数拥有this指针。this是一个隐含的指针,不能被显示声明,它只是一个形参,一个局部变量,它在任何一个非静态成员函数里都存在,作用域仅在一个对象内部。this指针是一个常指针,可以表示为:X*const this这里X是类名。因此,this指针不能被修改和赋值。,某个对象obj调用某个成员函数fun,则fun函数的this指针就指向对象obj,而且在该成员函数fun中,this指针始终指向对象obj.实际上,不管是在类外访问类的成员,还是在类内访问类的成员,都需要使用 对象.成员 或 指向对象的指针-成员 的方式,只不过在类内,若是直接访问成员的方 式,实际就是 this-成员
13、的方式。,访问成员函数的形式为:对象.成员函数();或 指向对象的指针成员函数();实际上,还需要另一个参数,作为this的实参,格式为:对象.成员函数(&对象,参数表);或 指向对象的指针成员函数(指向对象的指针,参数表);总之,成员函数通过this指针,“知道”访问的数据是哪个对象的。,例,class INTEGER int anint;public:void set_int(int intnum)anint=intnum;int get_int()return anint;,void main()INTEGER Anint,*Pint;Pint=,实际上,系统将上述程序改造为:,clas
14、s INTEGER int anint;public:void set_int(INTEGER*const this,int intnum)this-anint=intnum;int get_int(INTEGER*const this)return this-anint;,void main()INTEGER Anint,*Pint;Pint=,this指针是一个常指针,还可以使用const说明符将this声明为指向常量的常指针。const成员函数中this的类型是:const X*const thisconst成员函数的特点是它不能修改this所指的对象成员。,class INTEGER
15、int anint;public:int crease()return+anint;int decrease()const return-anint;/错误;,例,在类INTEDER中,成员函数decrease()的原型后跟一个const,它说明此时该函数的this的类型是:const INTEGER*const this由于const成员函数不能修改this所指的对象的成员,因此在decrease()函数体中的语句 anint 试图改变this指向的对象的成员anint,这是不允许的。但在crease()中,修改anint是完全许可的。this指针主要用在运算苻重载、自引用等场合。,6.3.
16、6 类的模板函数方法,类方法也可以是一个函数模板。例如:class X public:template void print(T x)cout(10);a.print才是一个真正的方法名。,6.4 构造函数与对象初始化,通常,一个对象在生成时应该被初始化,它的属性不应是0或随机值。尝试下面的两种初始化方式:1 Date d=2007,1,2;2 Date d;d.setDate(2007,1,2);,6.4.1 构造函数,解决对象初始化问题的方法是为类定义一种特殊的方法:构造函数,语法形式为:类名(参数列表);例如:,class Date int year,month,day;public:D
17、ate(int y=2007,int m=1,int d=30)year=y;month=m;day=d;.;,1.构造函数的特殊性语法上,构造函数名与类名必须相同,且不能有返回类型(也意味着无返回值),除此之外与普通方法相同,如可以有参数列表、可以重载和设置参数的缺省值等。构造函数在生成对象时由系统自动调用。构造函数必须是公有的,否则系统也无法在构造对象时调用它。,2.缺省的构造函数 在C+中,每个类必须有构造函数,否则不能生成类对象。如果用户没有定义,则系统自动生成一个构造函数,称为缺省构造函数。缺省构造函数没有任何参数,形式为:类名();需要特别注意的是,只要用户自己定义了一个构造函数,
18、无论有无参数,系统将不再提供缺省构造函数。,3.构造函数的重载与对象定义 创建一个对象意味着一次构造函数调用,因此,对象后面的实参数列表必须与某一个构造函数的形参列表相对应,使得系统能找到对应的构造函数并调用它,否则将产生找不到适当匹配的错误。见例P101特殊情况说明:构造函数只有一个参数,则下面两个对象定义语句的作用相同Student s2(Tom);Student s2=“Tom”;/初始化而非赋值无参构造函数定义对象时不能带有括号 Student s1();,4.单参数构造函数的类型转换作用 一个类的单参数构造函数为由类类型到其它类型的转换提供了依据。,void print(Studen
19、t s)cout(J).getname();/调用Student(char),注意:只有包含一个参数的构造函数具有类型转换的功能,且在有二义性时会产生错误。,6.4.2 用构造函数生成对象,除了直接定义一个对象会导致系统调用构造函数外,还存在着其它一些需要调用构造函数的情况。,1.无名对象 可以用直接指定一个构造函数的形式来定义无名对象,如:Student(Tom);Student();无名对象主要有如下二种作用:作函数参数,void fn(string s);fn(string(Mary);,初始化对象string s=string(Mary);string s=Mary;string s(
20、Mary);系统将根据无名对象的参数值直接调用构造函数构造对象s,而不是先构造临时对象再拷贝给s。,2.临时对象 在一些特殊情况下,系统可能生成临时对象。例如,有如下函数原型:,Student getStudent(.).return s;若以下述方式调用函数:Student sx=getStudent();系统在函数返回时要创建临时对象保存getStudent的返回值,再用临时对象拷贝构造sx。在没有为类提供适当的拷贝构造函数时应注意这些问题。,3.对象数组定义对象数组时,系统为每个数组元素对象调用一次无参构造函数以构造这些元素。Student ss4;以不同的构造方式初始化对象数组,利用临
21、时对象Student ss4=Student(),Student(hello),Student(J);,在提供了单参数的构造函数时,也可以依赖构造函数的类型转换作用实现数组初始化,如:Student sx6=C+,Basic,C;Student sx6=Student(C+),Student(Basic),Student(C);,传统的结构方式如果类没有任何构造函数,C+仍允许用C语言的结构方式为数组赋初值,如:class Point int x,y;public:void print()cout x,y;Point ps10=10,20,23,45;定义单个对象时不能以这种初始化方式。先定义
22、一个指针数组,再利用循环动态生成对象的方式构建。,4.动态生成对象 使用new运算符生成动态对象时,导致一次构造函数调用。Student*sp1=new Student;/注意后面不能有圆括号Student*sp2=new Student(Jacson);与普通类对象定义相同,不同的实参数列表使系统调用不同的构造函数。,class X public:X(int x,double y)coutconstructor:x,y;X(char*s)cout constructor:s;,X*p1=new X(1,2.5);X*p2=new X(string);,6.4.3 成员初始化列表与特殊成员的初
23、始化,1.对象成员的缺省构造 如果一个类含有其它类的对象为成员,在生成类对象时,系统会调用成员对象类的无参构造函数来构造成员对象。,class Group Student s1,s2,s3;.;Group g1;,生成对象g1时,系统会为每个Student类的对象调用一次构造函数,但由于没有指定实际参数,也只能调用Student类的无参构造函数。,6.4.3 成员初始化列表与特殊成员的初始化,2.构造函数的成员初始化列表 构造函数的简单参数可以被直接列在函数声明之后,函数体之前,称为构造函数的成员初始化列表,形式为:类名(行参说明表):成员初始化列表;,Date(int y=2007,int
24、m=1,int d=30):year(y),month(m),day(d),3.特殊成员采用成员初始化列表进行初始化一个类可以包含其它类对象成员、const成员和引用成员,称它们为特殊成员是因为其处理上的特殊性const成员、引用成员和只能调用有参数构造函数的对象成员都必须采用成员初始化列表实现初始化,否则无法建立这些特殊成员。成员初始化列表中的初始化项目次序总是按先对象、后简单成员的顺序进行,且同类的成员按其在定义中的顺序处理。,class Date int year,month,day;public:Date(int y,int m,int d):year(y),month(m),day(
25、d);class X int x;const int y;char/构造X类的对象,4.常量数据成员 类中定义的常量成员包括const和enum两类,它们不占用对象的存储空间。,6.5 拷贝构造函数,构造函数的参数可以是任何类型参数,甚至可以将自己类对象的引用作为参数,称它为拷贝构造函数。拷贝构造函数有两个含义:首先,它是一个构造函数,当创建一个新对象时,系统自动调用它;其次,它将一个已经定义过 的对象的数据成员逐一对应地复制给新对象。如果一个类没有定义拷贝构造函数,C+可以为该类产生一个缺省的拷贝构造函数。,拷贝构造函数的作用:1 创建一个新对象,并将一个已存在的对象复制到这个新对象。2 对
26、象本身做参数。3 函数返回一个对象。下面的例子说明了拷贝构造函数的应用。,6.5.1 用已有类对象构建新的类对象,class A int x,y;public:A(int intx,int inty)/一般的构造函数 x=intx;y=inty A(const A,其中:A a(10,20);创建了一个A类的对象a,具有初始参数为10和20,它调用的是一般构造函数A(int,int)。A b(a);创建一个A类对象b,调用拷贝构造函数A(const A 创建了一个A类对象c,调用拷贝构造函数将对象b的数据成员逐域的复制到对象c。这是拷贝构造函数的典型使用,它表明当我们创建一个新对象,并希望将一
27、个已存在的对象复制到这个新对象时,系统自动调用拷贝构造函数。,6.5.2 改变缺省的拷贝行为,类对象的复制还发生在下面两种情况下:1、对象本身作参数 2、函数返回对象,class Studentint age;char*name;public:Student(int ag=18,char*str=0)age=ag;name=new charstrlen(str)+1;strcpy(name,str);Student(const Student,Student Student:getname(Student arg)return arg;void main()Student s1(20,“五四青
28、年”);Student s2(s1);Student s3=s1.getname(s2);,在main()函数调用getname(s2)发生后,拷贝构造函数将参数s2(是Student的对象)复制到栈上,即在栈上创建一个临时对象arg。当从函数getname(s2)返回时,要从栈上将arg对象传送出来,也要创建一个临时对象,这时又调用一次拷贝构造函数。这个临时对象紧接着赋值给string对象t,上述过程都是系统自动完成的。,一般来说,如果一个对象需要独占资源时必须定义拷贝构造函数,这里的资源指动态内存、窗口句柄和文件句柄等。利用用户自定义拷贝构造函数实现的拷贝构造称为“深拷贝”,而采用系统缺省
29、的拷贝构造函数实现的拷贝称为“浅拷贝”。,6.6 析构函数与对象拆除,在一个对象生命期结束时就会被拆除。例如,一个局部定义的对象在程序流程离开此局部区域时被拆除,而一个动态建立的对象在用delete释放时被拆除。拆除对象时系统要自动调用一个类的特殊方法析构函数。如果一个类没定义析构函数,系统会自动生成一个,它的主要工作是将为对象分配的内存资源归还给系统。如果在拆除类的对象时需要加入一些特殊的行为则需要自己定义析构函数。,class A.public:A(.);/构造函数声明 A();/析构函数声明.;1、析构函数不能有返回类型,也没有任何参数(因为无法指定实参数),自然也就不能重载。2、对于一
30、个简单的类,系统提供的缺省析构函数可以很好地工作,但如果在一个对象工作期间占用了系统资源,则必须定义析构函数释放这种资源,,Student:Student()delete name;/释放内存,一般地讲,如果一个类需要定义拷贝构造函数,也就需要定义一个析构函数。此外,利用delete运算符删除对象时自动调用析构函数也是delete运算符与free函数的主要区别。,6.7 字符串类string,1.string类的属性与对象构造 C+标准库定义了一个string类,用于取代C语言的以0结尾的字符串。string类的对象不用0结束,且提供了大量的类方法和运算以支持常规操作。string定义于头文件
31、,string类的主要属性是一个记录字符序列存储区的指针,可称为str,类似Student类中的name。常用的构造函数包括:,string(const char*s=0);/用0结束的字符串s初始化string(int n,char c);/用n个字符c初始化string(string/拷贝构造,string s1(a string),s2;/s2为不含任何字符的字符串string s2(10,a);/s2的值是aaaaaaaaaa,2.string类支持的主要运算 赋值运算=。关系运算。字符串加法(+和+=),也称字符串连接。下标运算。输入输出运算和。,3.string类的主要方法,#in
32、clude using namespace std;#include using namespace std;int main()string s1(a string),s2;int pos;cin s2;/输入s2 if(s2s1)s1+=s2;/字符串连接 else s1+=no link;pos=s1.find(ing,0);/查找s1中有无字符串”ing”if(pos!=-1)s1.erase(pos,3);/如果有将其删除 cout s1.insert(0,modified:);/在s1开头插入一个串并输出,6.8 其它类型构造技术,1.用union定义共用体类型,union X c
33、har c2;int i;,X a;a.i=257;/二进制形式0000000100000001cout(int)a.c0,(int)a.c1;,虽然代码中只是为a.i赋值,但a.i的存储空间也是数组a.c的存储空间,故代码的输出是1,1。可见,使用共用体的好处是可以很容易地将一个数据“拆开”成部分来使用,同时也可以节约存储空间。人们称共用体是一种节约空间的类。,2.用struct或class定义位域类型 位域是一种可以按位使用存储空间的类。,如果某些属性是占用二进制位数很少的整数,可以在类定义中指定这些属性占用的二进制位数,如:class Mixed unsigned a:1,b:1,c:2
34、;/a、b、c分别占用1、1、2个二进制位 int x;public:Mixed(unsigned a1,unsigned b1,unsigned c1,int x1):a(a1),b(b1),c(c1),x(x1);,3.用typedef描述类型的别名 在处理一些难以描述的类型时,typedef几乎是唯一的方法。例如,考虑下述变量定义语句:,int array20;/定义数组arraychar*cpoint;/定义指针变量cpointdouble(*fpx)(double);/定义指向函数的指针变量fpx可以将这些变量定义改造成类型定义:typedef int array20;/定义数组类型arraytypedef char*cpoint;/定义指针变量类型cpointtypedef double(*fpx)(double);/定义指向函数的指针变量类型fpx于是,可以按下述形式定义相应的变量:array a,b;/定义2个长度为20的整型数组a和bcpoint p1,p2;/定义2个char*类型的指针变量p1和p2fpx fp1,fp2;/定义2个指向函数的指针变量fp1和fp2,