资源描述
单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,*,单击此处编辑母版标题样式,第六章 数据抽象类,基于数据抽象的程序设计,类和对象,对象的初始化和消亡前处理,常(,const,)成员,静态(,static,)成员,友元(,friend,),类作为模块,本章内容,数据抽象是指把数据及其操作作为一个整体(对象)来进行描述,即,数据描述中包含了能对其实施的操作。,数据的使用者只需要知道对数据所能实施的操作,以及这些操作之间的关系,而不必知道数据的具体表示。,数据抽象是面向对象程序设计的基础,数据抽象,栈,是一种由若干个具有线性次序的元素所构成的复合数据。,对栈只能实施两种操作:,进栈,(增加一个元素)和,退栈,(删除一个元素),并且这两个操作必须在栈的同一端(称为,栈顶,,,top,)进行。,后进先出(,Last In First Out,,简称,LIFO,)是栈的一个重要性质。,例:“栈”数据的表示与操作,top,栈:,#include,using namespace std;,/,定义栈数据类型,const int STACK_SIZE=100;,struct Stack,int top;,int bufferSTACK_SIZE;,;,void init(Stack&s),s.top=-1;,非数据抽象的“栈”数据的表示与操作,bool push(Stack&s,int i),if(s.top=STACK_SIZE-1),cout,“,Stack is overflow.n,”,;,return false;,else,s.top+;s.buffers.top=i;,return true;,bool pop(Stack&s,int&i),if(s.top=-1),cout,“,Stack is empty.n,”,;,return false;,else,i=s.buffers.top;s.top-;,return true;,/,使用栈类型数据,Stack st;,int x;,init(st);/,对,st,进行初始化。,push(st,12);/,把,12,放进栈。,pop(st,x);/,把栈顶元素退栈并存入变量,x,。,或,,Stack st;,int x;,/,对,st,进行初始化。,st.top=-1;,/,把,12,放进栈。,st.top+;,st.bufferst.top=12;,/,把栈顶元素退栈并存入变量,x,。,x=st.bufferst.top;,st.top-;,#include,using namespace std;,/,定义栈数据类型,const int STACK_SIZE=100;,class Stack,int top;,int bufferSTACK_SIZE;,public:,Stack()top=-1;,bool push(int i);,bool pop(int,数据抽象的“栈”数据的表示与操作,bool Stack:push(int i),if(top=STACK_SIZE-1),cout,“,Stack is overflow.n,”,;,return false;,else,top+;buffertop=i;,return true;,bool Stack:pop(int&i),if(top=-1),cout,“,Stack is empty.n,”,;,return false;,else,i=buffertop;top-;,return true;,/,使用栈类型数据,Stack st;/,自动地去调用,st.Stack(),对,st,进行初始化。,int x;,st.push(12);/,把,12,放进栈,st,。,st.pop(x);/,把栈顶元素退栈并存入变量,x,。,st.top=-1;/,Error,st.top+;/,Error,st.bufferst.top=12;/,Error,在方案,(1),中,数据的表示对数据的使用者是公开的,对栈的操作可以通过所提供的函数来实现,也可以直接在栈的数据表示上进行;而在方案,(2),中,只能通过提供的函数来操作栈。,在方案,(1),中,数据和对数据的操作相互独立,数据是作为参数传给对数据进行操作的函数;而在方案,(2),中,数据和对数据的操作构成了一个整体,数据操作是数据定义的一部分。,方案,(1),需要显式地对栈进行初始化,方案,(2),则是隐式地(自动)进行初始化。,两种解决方案的几点不同,把程序构造成由若干,对象,组成,每个对象由一些,数据,以及对这些数据所能实施的,操作,构成;,对数据的操作是通过向包含数据的对象发送,消息,(调用对象的操作)来实现;,对象的特征(数据与操作)由相应的,类,来描述;,一个类所描述的对象特征可以从其它的类获得(,继承,)。,什么是面向对象程序设计,提高软件开发效率和保证软件质量的几个基本的程序设计手段,抽象,封装,模块化,软件复用,可维护性,解题方式与问题空间的对应,为什么要面向对象?,以功能为中心,强调过程(功能)抽象。数据与操作分离。,实现了操作的封装,但数据是公开的,数据缺乏保护。,模块边界模糊。,功能往往针对某个程序而设计,这使得程序功能难以复用。,功能易变,程序维护困难。,基于功能分解的解题方式与问题空间缺乏对应。,过程式程序设计的特点,Data,Sub-programs,以数据为中心,强调数据抽象,数据与操作合而为一。,实现了数据的封装,加强了数据的保护。,模块边界清晰。,对象往往具有通用性,使得程序容易复用。,对象相对稳定,有利于程序维护。,基于对象,/,类的解题方式与问题空间有很好的对应。,面,向对象程序设计的特点,Objects,对象,/,类,(Object&Class),对象是由数据(数据成员、成员变量、实例变量、对象的局部变量等)及能对其实施的操作(成员函数、方法、消息处理过程等)所构成的封装体,它,属于值的范畴。,类(对象的类型)描述了对象的特征(数据和操作),它,属于类型的范畴,。,继承,(Inheritance),在定义一个类时,可以利用已有类的一些特征描述。,父类(基类)与子类(派生类),单继承与多继承,代码复用,面向对象程序设计的基本内容,多态性,(Polymorphism),与动态绑定,(Dynamic Binding),某一论域中的一个元素存在多种解释。通常体现为:,一名多用:,函数名重载,操作符重载,类属性:,类属函数:一个函数能对多种类型的数据进行操作。,类属类型:一个类型可以描述多种类型的数据,。,面向对象程序设计特有的多态:,对象类型的多态:子类对象既属于子类,也属于父类。,对象标识的多态:父类的引用或指针可以引用或指向子类对象。,消息的多态:一个消息集有多种解释(父类与子类有不同解释)。,多态带来的好处:,易于实现程序高层(上层)代码的复用。,使得程序扩充变得容易(只要增加底层的具体实现)。,增强,语言的可扩充性(操作符重载等)。,绑定:确定对多态元素使用的过程,即确定对多态元素的某个使用是多态元素的那一种形式。,静态绑定(,Static Binding,,也称前期绑定,,Early Binding,),:,在编译时刻确定,。,动态绑定(,Dynamic Binding,,也称后期绑定或延迟绑定,,Late Binding,):在运行时刻确定。,对象,构成了面向对象程序的基本计算单位,而对象的特征则由相应的,类,来描述。,C+,的类是一种用户自定义类型,定义形式如下:,class,;,其中,类的成员包括:,数据成员,成员函数,类,class Date,public:,void set(int y,int m,int d)/,成员函数,year=y;,month=m;,day=d;,bool is_leap_year()/,成员函数,return(year%4=0&year%100!=0)|,(year%400=0);,void print()/,成员函数,cout year .month .day;,private:,int year,month,day;/,数据成员,;,例:一个日期类的定义,数据成员指类的对象所包含的数据,它们可以是常量和变量。数据成员的说明格式与非成员数据的声明格式相同,例如:,class Date/,类定义,.,private:/,访问控制说明,int year,month,day;/,数据成员说明,;,说明数据成员时不允许进行初始化(某些静态数据成员除外)。例如:,class,A,int x=0;/Error,const double y=0.0;/Error,.,;,数据成员,数据成员的类型可以是任意的,C+,类型(,void,除外)。,在说明一个数据成员的类型时,如果未见到相应的类型定义或相应的类型未定义完,则该数据成员的类型只能是这些类型的指针或引用类型(静态成员除外)。例如:,class A;/A,是在程序其它地方定义的类,这里是声明。,class B,A a;/Error,,未见,A,的定义。,B b;/Error,,,B,还未定义完。,A*p;/OK,B*q;/OK,A /OK,B /OK,;,成员函数描述了对类定义中的数据成员所能实施的操作。,成员函数的定义可以放在类定义中,例如:,class A,.,void f()./,建议编译器按内联函数处理。,;,成员函数的定义也可以放在类定义外,例如:,class A,.,void f();/,声明,;,void,A:,f()./,需要用类名受限,区别于全局函数,。,成员函数,类成员函数名是可以重载的(析构函数除外),它遵循一般函数名的重载规则。例如:,class A,.,public:,void,f,();,int,f,(int i);,double,f,(double d);,.,;,在,C+,的类定义中,,,可以用访问控制修饰符,public,,,private,或,protected,来描述,对类成员的访问限制。例如:,class A,public,:/,访问不受限制。,int x;,void f();,private,:/,只能在本类和友元的代码中访问。,int y;,void g();,protected,:/,只能在本类、派生类和友元的代码中访问。,int z;,void h();,;,类成员的访问控制,在,C+,的,类定义中,,默认访问控制是,private,,并,可以有多个,public,、,private,和,protected,访问控制说明,例如:,class A,int m,n;/,m,n,的访问控制说明为,private,。,public:,int x;,void f(),;,private:,int y;,void g();,protected:,int z;,void h();,public:,void f1();,;,一般来说,类的,数据成员,和,在类的,内部使用,的,成员函数,应该指定为,private,,只有提供给外界使用的成员函数才指定为,public,。,具有,public,访问控制的成员构成了,类与外界的一种,接口,(,interface,)。操作一个类的对象时,只能通过访问对象类中的,public,成员来实现。,protected,类成员访问控制具有特殊的作用(继承)。,#include,#include,using namespace std;,class Stack,public:/,对外的接口,Stack()top=NULL;,bool push(int i);,bool pop(int,private:,struct Node,int content,;,Node*next;,*top;,;,用链表实现“栈”类,Stack,bool Stack:push(int i),Node*p=new Node;,if(p=NULL),cout content=i;,p-next=top;top=p;,return true;,bool Stack:pop(int&i),if(top=NULL),cout next;,i=p-content;,delete p;,return true;,类属于类型范畴的程序实体,它,一般,存在于静态的程序(运行前的程序)中。,而动态的面向对象程序(运行中的程序)则是由对象构成。,对象在程序运行时创建,程序的执行是通过对象之间相互发送消息来实现的。当对象接收到一条消息后,它将调用对象类中定义的某个成员函数来处理这条消息。,对 象,直接方式,通过在程序中定义一个类型为类的变量来实现的,其格式与普通变量的定义相同。例如:,class A,public:,void f();,void g();,private:,int x,y;,.,A a1;/,创建一个,A,类的对象。,A a2100;/,创建由对象数组表示的,100,个,A,类对象。,对象通过对象名来标识和访问。,分为:全局对象、局部对象和成员对象。,对象的创建和标识,间接方式(动态对象),在程序运行时刻,通过,new,操作或,malloc,库函数来创建对象。,所创建的对象称为动态对象,其内存空间在程序的堆区中。,动态对象用,delete,操作或,free,库函数来撤消(使之消亡)。,动态对象通过指针来标识和访问。,A*p;,p=new A;,/,创建一个,A,类的动态对象。,delete p;,/,撤消,p,所指向的动态对象。,或,p=(A*)malloc(sizeof(A);,.,free(p);,单个动态对象的创建与撤消,A*p;,p=new A100;/,创建一个动态对象数组。,.,delete p;/,撤消,p,所指向的动态对象数组。,或,p=(A*)malloc(sizeof(A)*100);,.,free(p);,动态对象数组的创建与撤消,对于动态对象,需注意下面几点:,函数,malloc,不会调用对象类的构造函数;函数,free,也不会调对象类的析构函数。,new,操作自动分配足够的空间、自动返回指定类型的指针、可以重载。,用,new,创建的,动态对象数组,只能用默认构造函数进行初始化。,delete,中的,“,”,不能省,否则,只有第一个对象的析构函数会被调用。,对于创建的一个对象,需要通过向它发送消息来对它进行操作,这里,向对象发送消息就是指调用对象类中定义的某个,public,成员函数。例如:,class A,int x;,public:,void f();,;,int main(),A a;/,创建,A,类的一个局部对象,a,。,a.f();/,调用,A,类的成员函数,f,对对象,a,进行操作。,A*p=,new A;,/,创建,A,类的一个动态对象,,p,指向之。,p-f();/,调用,A,类的成员函数,f,对,p,所指向的对象进行操作。,delete p;,return 0;,对象的操作,通过对象来访问类的成员时要受到类成员访问控制的限制,例如:,class A,public:,void f(),./,允许访问:,x,y,f,g,h,private:,int x;,void g(),./,允许访问:,x,y,f,g,h,protected:,int y;,void h(),./,允许访问:,x,y,f,g,h,;,.,A a;,a.f();/OK,a.x=1;/,Error,a.g();/,Error,a.y=1;/,Error,a.h();/,Error,可以对对象进行赋值,Date,yesterday,today,some_day;,some_day=yesterday;,/,把对象,yesterday,的数据成员分,/,别赋值给对象,some_day,的相应数据成员。,取对象地址,Date,*p_date;,p_date=,/,把对象,today,的地址赋值给对象指针,/p_date,。,把对象作为实参传给函数以及作为函数的返回值等操作。例如:,Date f(Date d);,/,函数,f,的声明,该函数需要一个,Date,类,/,的对象作为参数,返回值为一个,Date,类的对象。,some_day2=f(yesterday);,/,调用函数,f,,把对象,yesterday,/,作为实参。返回值对象赋给对象,some_day2,。,类定义中说明的数据成员(静态数据成员除外)对该类的每个对象都有一个拷贝。例如:,class A,public:,void f();,void g(int i)x=i;f();,private:,int x,y,z;,;,A a,b;,this,指针,a.x,a.y,a.z,a,b.x,b.y,b.z,b,类中的成员函数对该类的所有对象,只有一个拷贝,。,对于类的一个成员函数来讲,它如何知道要对该类的哪一个对象进行操作呢?例如,在函数,g,中,,x,是那一个对象的呢?,每一个成员函数都有一个隐藏的形参,this,,其类型为:,*const,this,;,在成员函数中对类成员的访问是通过,this,来进行的。,例如,前面类,A,的成员函数,g,的实际形式为:,void g(A*const this,int i),this-x=i;this-f();,对于下面的成员函数调用:,a.g(1),;,和,b.g(2);,编译程序将会把它编译成:,A:g(,和,A:g(,一般情况下,类的成员函数中不必显式使用,this,指针来访问对象的成员(编译程序会自动加上)。如果成员函数中要把,this,所指向的对象作为整体来操作(如:取对象的地址),则需要显式地使用,this,指针。例如:,void func(A*p),.,class A,int x;,public:,void f()func(,?,);,/,“,?,”,应写什么?,void q(,int i,)x=i;f();,;,.,A a,b;,a.f();/,要求在,f,中调用,func(&a),b.f();/,要求在,f,中调用,func(&b),func(,this,);,一个,C+,程序,class A,int x,y;,public:,void f();,void g(int i)x=i;f();,;,.,A a,b;,a.f();,a.g(1);,b.f();,b.g(2);,用,C,语言编写面向对象的程序,功能上与前面,C+,程序等价的,C,程序,struct A,int x,y;,;,void f_A(struct A*this);,void g_A(struct A*this,int i),this-x=i;,f_A(this);,.,struct A a,b;,f_A(,g_A(,f_A(,g_A(,当一个对象被创建时,它将获得一块存储空间,该存储空间用于存储对象的数据成员。在使用对象前,需要对对象存储空间中的数据成员进行初始化。,C+,提供了一种对象初始化的机制:,构造函数,(,Constructor,),。构造函数是类的特殊成员函数,它的名字与,类名相同、无返回值类型。,创建对象时,构造函数会自动被调用。,例如:,class A,int x;,public:,A(),x=0;/,构造函数,.,;,.,A a;/,创建对象,a,:为,a,分配内存空间,然后调用,a,的构造函数,A(),。,对象的初始化,构造函数可以重载,其中,,不带参数的(或所有参数都有默认值的)构造函数被称为,默认构造函数,(Default Constructor),。,例如:,class A,int x,y;,public:,A()/,默认构造函数,x=y=0;,A(int x1),x=x1;y=0;,A(int x1,int y1),x=x1;y=y1;,.,;,如果类中未提供任何构造函数,则编译程序在需要时会隐式地为之提供一个默认构造函数。,在创建对象时,可以显式地指定调用对象类的某个构造函数。如果没有指定调用何种构造函数,则调用默认构造函数初始化。,class A,.,public:,A();,A(int i);,A(char*p);,;,A a1;,/,调用默认构造函数。也可写成:,A a1=A();,但不能写成:,A a1();,A a2(1);/,调用,A(int i),。也可写成:,A a2=A(1);,或,A a2=1;,A a3(abcd);/,调,A(char*),。也可写成:,A a3=A(abcd);,/,或,A a3=abcd;,A a4;/,调用对象,a0,、,a1,、,a2,、,a3,的默认构造函数。,A b5=A(),A(1),A(abcd),2,xyz;/,调用,b0,的,A(),、,/b1,的,A(int),、,b2,的,A(char*),、,b3,的,A(int),和,b4,的,A(char*),A*p1=new A;,/,调用默认构造函数。,A*p2=new A(2);,/,调用,A(int i),。,A*p3=new A(xyz);,/,调用,A(char*),。,A*p4=new A20;,/,创建动态对象数组时只能调用各对象的默认构造函数,对于常量数据成员和引用数据成员(某些静态成员除外),不能在说明它们时初始化,也不能采用赋值操作对它们初始化。例如:,class A,int x;,const int y=1;,/Error,int,/Error,public:,A(),x=0;/OK,y=1;,/Error,z=,/Error,;,成员初始化表,对于常量数据成员和引用数据成员,可以在定义构造函数时,在函数头和函数体之间加入一个,成员初始化表,来对它们进行初始化。例如:,class A,int x;,const int y;,int,public:,A():,z(x),y(1),/,成员初始化表,x=0;,;,成员初始化表中成员初始化的书写次序并不决定它们的初始化次序,数据成员的初始化次序由它们在类定义中的说明次序来决定。,在类中可以定义一个特殊的成员函数:,析构函数,(Destructor),,,它的名字为“,”,,,没有返回类型、不带参数、不能被重载。例如:,class String,int len;,char*str;,public:,String(char*s);,String,();,一个对象消亡时,系统在收回它的内存空间之前,将会自动调用析构函数。可以在析构函数中完成对象被删除前的一些清理工作(如:归还对象额外申请的资源等)。,析构函数,(Destructors),class String,int len;,char*str;,public:,String(char*s),len=strlen(s);,str=,new charlen+1;,strcpy(str,s);,String(),delete str;,;,void f(),String s(abcd);/,调用,s,的构造函数,.,/,调用,s,的析构函数,注意,:,系统为对象,s,分配的内存空间只包含,len,和,str,(指针)本身所需的空间,,str,所指向的空间不由系统分配,而是由对象自己处理!,对于类的数据成员,其类型可以是另一个类。也就是说,一个对象可以包含另一个对象(称为成员对象)。例如:,class A,.,;,class B,.,A a;/,成员对象,.,;,B b;/,对象,b,包含一个成员对象:,b.a,成员对象,成员对象由成员对象类的构造函数初始化。,如果在包含成员对象的类中,没有指出用成员对象类的什么构造函数对成员对象初始化,则调用成员对象类的默认构造函数。,可以在类构造函数的成员初始化表中显式指出用成员对象类的某个构造函数对成员对象初始化。,成员对象的初始化,class A,int x;,public:,A()x=0;,A(int i)x=i;,;,class B,A a;,int y;,public:,B(int i)y=i;/,调用,A,的默认构造函数对,a,初始化。,B(int i,int j):,a(j),y=i;/,调用,A(int),对,a,初始化。,;,B b1(1);/b1.y,初始化为,1,,,b1.a.x,初始化为,0,B b2(1,2);/b2.y,初始化为,1,,,b2.a.x,初始化为,2,创建包含成员对象的类的对象时,先执行成员对象类的构造函数,再执行本身类的构造函数。,一个类若包含多个成员对象,这些对象的初始化次序按它们在类中的说明次序(而不是成员初始化表的次序)进行。,析构函数的执行次序与构造函数的执行次序正好相反。,在创建一个对象时,若用一个同类型的对象对其初始化,这时将会调用一个特殊的构造函数:,拷贝构造函数,。例如:,class A,.,public:,A();/,默认构造函数,A(const A,&,a);/,拷贝构造函数,;,拷贝构造函数,在,三种情况,下,会调用类的拷贝构造函数:,定义对象时,例如:,A a1;,A a2(a1);/,也可写成:,A a2=a1;,或:,A a2=A(a1);,/,调用,A,的拷贝构造函数,用对象,a1,初始化对象,a2,,,把对象作为,值参数,传给函数时,例如:,void f(,A,x);,A a;,f(a);/,调用,f,时将创建形参对象,x,,并调用,A,的拷贝构造函数,,/,用对象,a,对其初始化。,把对象作为函数的,返回值,时,例如:,A,f(),A a;,.,return a;/,创建一个,A,类的临时对象,并调用,A,的,/,拷贝构造函数,用对象,a,对其初始化。,如果程序中没有为类提供拷贝构造函数,则编译器将会为其生成一个,隐式拷贝构造函数,。,隐式拷贝构造函数将,逐个成员,拷贝初始化:,对于普通成员:它采用通常的初始化操作;,对于成员对象:则调用成员对象类的拷贝构造函数来实现成员对象的初始化。,默认拷贝构造函数,class A,int x,y;,public:,A()x=y=0;,void inc(),x+;,y+;,./,其中没有定义拷贝构造函数。,;,A a1;,a1.inc();,A a2(a1);/a2.x,初始化为,1,(,a1.x,);,/a2.y,初始化为,1,(,a1.y,),class A,int x,y;,public:,A()x=y=0;,.,;,class B,int z;,A a;,public:,B()z=0;,./,其中没有定义拷贝构造函数,;,.,B b1;/b1.z,、,b1.a.x,以及,b1.a.y,均为,0,。,B b2(b1);,/b2.z,初始化成,b1.z,;调用,A,的拷贝构造函数用,b1.a,对,/b2.a,初始化。如果,A,中没有定义拷贝构造函数,则,/A,的隐式拷贝构造函数把,b2.a.x,和,b2.a.y,分别初始化成,/b1.a.x,和,b1.a.y,;否则,由,A,的自定义拷贝构造函数决定,/,如何对,b2.a.x,和,b2.a.y,进行初始化。,一般情况下,编译程序提供的默认,拷贝构造函数的行为足以满足要求,,类中不需要自定义拷贝构造函数。不过,自定义拷贝构造函数可以对初始化的行为进行控制。例如:,class A,int x,y;,public:,A()x=y=0;,A(const A&a),x=a.x+1;,y=a.y+1;,;,.,A a1;/a1,初始化为:,a1.x=0,,,a1.y=0,A a2(a1);/a2,初始化为:,a2.x=1,,,a2.y=1,自定义拷贝构造函数,自定义的拷贝构造函数将调用成员对象类的,默认构造函数,对成员对象初始化!,class A,int x,y;,public:,A()x=y=0;,void inc()x+;y+;,;,class B,int z;,A a;,public:,B()z=0;,B(const B,void inc()z+;a.inc();,;,.,B b1;/b1.z,、,b1.a.x,以及,b1.a.y,均为,0,。,b1.inc();/b1.a.x,、,b1.a.y,和,b1.z,都增加了,1,。,B b2(b1);,/b2.z,为,1,,,b2.a.x,和,b2.a.y,均为,0,。,如何能让,b2,与,b1,一致呢?,:a(b.a),b2.a.x,和,b2.a.y,均为,1,。,在有些情况下必须要自定义拷贝构造函数,否则,将会产生设计者未意识到的严重的程序错误。,class A,int x,y;,char*p;,public:,A(char*str),x=0;y=0;,p=new charstrlen(str)+1;,strcpy(p,str);,A()delete p;,;,.,A a1(“abcd”);,A a2(a1);,x,y,p,x,y,p,a1,a2,abcd,0,0,0,0,系统提供的隐式拷贝构造函数将会使得,a1,和,a2,的成员指针,p,指向同一块内存区域:,它带来的问题是:,如果对一个对象操作之后修改了这块空间的内容,则另一个对象将会受到影响。如果不是设计者特意所为,这将是一个隐藏的错误。,当对象,a1,和,a2,消亡时,将会分别去调用它们的析构函数,这会使得同一块内存区域将被归还两次,从而导致程序运行异常。,解决上面问题的办法是在类,A,中显式定义一个拷贝构造函数,A:A(const A&a),x=a.x;,y=a.y;,p=new charstrlen(a.p)+1;,strcpy(p,a.p);,在程序运行的不同时刻,一个对象可能会处于不同的状态。对象的状态由对象的数据成员的值来体现。,可以把类中的成员函数分成两类:,获取对象状态,改变对象状态,例如,class Date,public:,void set(int y,int m,int d)/,改变对象状态,int get_day();/,获取对象状态,int get_month();/,获取对象状态,int get_year();/,获取对象状态,.,;,const,成员函数,为了防止在获取对象状态的成员函数中改变对象的状态,可以把它们说明成,const,成员函数。,const,成员函数不能改变对象的状态(数据成员的值)。例如:,class A,int x;,char*p;,public:,.,void f(),const,x=10;,/Error,p=new char20;,/Error,strcpy(p,ABCD);/,没有改变,p,的值,编译程序认为,OK,!,;,给成员函数加上,const,修饰符还有一个作用:描述对常量对象所能进行的操作。例如:,class Date,int d,m,y;,public:,Date();,Date(int year,int month,int day);,int get_day(),const,return d;,int get_month(),const,return m;,int get_year(),const,return y;,int set(int year,int month,int day),y=year;m=month;d=day;,;,int main(),const,Date today(2006,4,3);,cout today.get_day()today.get_month(),today.get_year();,today.set(2005,4,13);,/Error,属于一个类的不同对象有时需要共享数据。,采用全局变量来表示共享数据违背数据抽象与封装原则,数据缺乏保护。,静态数据成员,为同一类对象之间的数据共享提供了一种较好的途径。,静态成员,在类中,可以把成员说明成静态的。例如:,class A,int x,y;,static,int shared;/,静态数据成员说明,public:,A()x=y=0;,void increase_all()x+;y+;shared+;,int sum_all()const return x+y+shared;,static,int get_shared()/,静态数据成员定义,return shared;,.,;,int A:shared=0;/,静态数据成员的定义,类的,静态数据成员,对该类的所有对象只有一个拷贝。例如:,A a1,a2;,shared:0,a1 a2,a1.x:0 a2.x:0,a1.y:0 a2.y:0,a1.increase_all();,cout a2.get_shared(),a2.sum_all()endl;/,输出:,1,1,静态成员函数,只能访问类的静态成员。,静态成员函数可以通过对象来访问外,也可以直接通过类来访问。例如:,cout A:get_shared();,class A,static int obj_count;,.,public:,A()obj_count+;,A()obj_count-;,static int get_num_of_objects(),return obj_count;,.,;,int A:obj_count=0;,.,cout A:get_num_of_objects()endl;,例:实现对某类对象的计数,根据数据保护的要求,通常把类的数据成员说明成,private,。对,private,数据成员的访问通常要通过该类的,public,成员函数来进行,而这有时会降低对数据成员的访问效率。例如:,class Point,public:,Point(double xi,double yi)x=xi;y=yi;,double GetX()const return x;,double GetY()const return y;,private:,double x,y;,;,对数据成员的访问效率问题,double,Distance,(const Point&a,const Point&b),double dx=,a,.GetX(),-b,.GetX();/,效率不高,double dy=,a,.GetY()-,b,.GetY();/,效率不高,return sqrt(dx*dx+dy*dy);,int main(),Point p1(3.0,5.0),p2(4.0,6.0);,double d=Distance(p1,p2);,coutThe distance is d endl;,return 0;,一种解决方案:把,Distance,作为成员函数来定义,class Point,public:,Point(double xi,double yi)x=xi;x=yi;,double,Distance,(const Point&pt),double dx=x,-,pt.x;/,效率高,double dy=y-pt.y;/,效率高,return sqrt(dx*dx+dy*dy);,private:,double x,y;,;,.,A a(1,2),b(3,4);,cout a.Distance(b);,另一种解决方案:把,Distance,作为友元函数来定义,class Point,public:,Point(double xi,double yi)x=xi;x=yi;,private:,double x,y;,friend double,Distance,(const Point,;,double,Distance,(const Point&a,const Point&b),double dx=,a,.x,-b,.x;/,效率高,double dy=,a,.y-,b,.y;/,效率高,return sqrt(dx*dx+dy*dy);,.,A a(1,2),b(3,4);,cout Distance(a,b);,为了提高在类的外部对类的数据成员的访问效率,在,C+,中,可以指定与一个类,密切相关的,、又,不适合作为该类成员,的程序实体(某些全局函数、某些其它类或某些其它类的某些成员函数)可以直接访问该类的,pri
展开阅读全文