第七章 类与对象 丘志杰 电子科技大学 计算机学院 软件学院
C++与C最大的不同在于C++增加了面向对象的概念。C++允许用户定义新的抽象数据类型—类类型,它将一组数据和与这些数据相关的操作封装在一起,实现了面向对象技术中的核心概念—数据封装。 2018/11/29
抽象 抽象是对具体对象(问题)进行概括,抽出这一类对象的公共性质并加以描述的过程。 先注意问题的本质及描述,其次是实现过程或细节。 数据抽象:描述某类对象共有的属性或状态。 行为抽象:描述某类对象共有的行为特征或具有的功能。 2018/11/29
抽象实例:时钟 数据抽象: 具有表明当前时间的时、分、秒 行为抽象: 具有设置时间和显示时间两个最基本的功能 2018/11/29
抽象实例:人 数据抽象: 行为抽象: 姓名、年龄、性别等 生物属性:吃饭、穿衣、睡觉、行走等行为 社会属性:工作、学习等行为 2018/11/29
在研究问题时,侧重点不同,可能会产生不同的抽象结果;解决同一问题时要求不同,也会产生不同的抽象结果。例如: 如果开发一个人事管理软件,那么关心的是员工的姓名、性别、工龄、工资、工作部门等相关信息。 如果开发学籍管理软件,那么关心的是学生的姓名、性别、年龄、籍贯、所在学院等相关信息。 因此,抽象是相对,而非绝对的。 2018/11/29
如何实现抽象? 抽象定义了一个事物的本质特征,那么从软件设计的角度又如何对抽象出来的结果进行描述呢?封装可以实现抽象。 封装就是将数据和操作这些数据的函数进行有机地结合。 C++的解决方案:用类来实现封装机制 2018/11/29
定义和使用类的基本过程 第一步:进行抽象 第二步:定义类 第三步:实现类 第四步:使用类 2018/11/29
类的定义:类的成员构成 类是一种抽象数据类型,声明形式如下: class ClassName { 数据成员 函数成员 }; 用于表达数据抽象 用于表达行为抽象 2018/11/29
类定义举例:时钟类 class Clock { int Hour, Minute, Second; void SetTime( int h, int m, int s ); void ShowTime( ); }; 2018/11/29
类成员的访问控制 什么是“类内”和“类外” 在类定义之内称为类内 在类定义之外称为类外 数据封装的目的就是信息隐蔽。为了达到信息隐蔽,在C++类中,并非所有的成员都是对外可见的(或者说,是类外可以访问的)。 通过设置成员的访问控制属性来实现对类成员的访问控制。这些控制属性有:public、protected、private 2018/11/29
类的定义:对成员的访问控制 class ClassName { public: 公有成员 protected: 保护成员 private: 私有成员 }; 2018/11/29
公有成员 在关键字public后面声明,它们是类与外部的接口,任何类内、类外函数都可以访问公有数据和函数。 class Clock { void SetTime( int h, int m, int s );//公有成员函数 void ShowTime( ); //公有成员函数 …… }; 2018/11/29
私有成员 在关键字private后面声明,只允许本类中的函数(即类内函数)访问,而类外的任何函数都不能访问。 class Clock { int Hour, Minute, Second;//私有数据成员 …… }; 2018/11/29
保护成员 在关键字protected后面声明的数据成员或成员函数。与private类似,其差别表现在继承与派生时对派生类的影响不同,在第八章中再描述。 2018/11/29
类定义举例:时钟类 class Clock { public: void SetTime( int h, int m, int s ); void ShowTime( ); private: int Hour, Minute, Second; }; 2018/11/29
类的实现 实现一个类,就是按照所设定的功能语义去实现类中的每一个成员函数。 2018/11/29
int Hour, Minute, Second; public: void SetTime( int h, int m, int s ){ class Clock{ private: int Hour, Minute, Second; public: void SetTime( int h, int m, int s ){ Hour=h; Minute=m; Second=s; } void ShowTime( ){ cout<<“Current Time:” cout<<Hour<<“:”<< Minute<<“:”<< Second <<endl; }; 在类内实现成员函数,编译器按内联函数处理 在类内实现成员函数,编译器按内联函数处理 2018/11/29
或者: class Clock{ private: int Hour, Minute, Second; public: void SetTime( int h, int m, int s );//给出函数原型 void ShowTime( ); //给出函数原型 }; void Clock::SetTime( int h, int m, int s ){ Hour=h; Minute=m; Second=s; } void Clock::ShowTime( ){ cout<<“Current Time:” cout<<Hour<<“:”<< Minute<<“:”<< Second <<endl; 在类外实现成员函数 在类外实现成员函数 2018/11/29
类的使用 类是一种数据类型,类的变量称作类的实例,或对象。 每个对象各自包含了类中定义的各个数据成员的存储空间,也就是说,一个类的数据成员将拥有多个拷贝。但它们共享类中定义的成员函数。 定义对象的方式:类名 对象名; 例如:Clock aclock; 2018/11/29
类外访问成员的方法 通过对象来访问成员 对象名.公有成员函数名(形参列表); 通过对象指针来访问成员 对象名.公有数据成员 对象的指针->公有成员函数名(形参列表); 对象的指针->公有数据成员 2018/11/29
举例:一个比较完整的Clock类 //定义Clock类 class Clock{ private: int Hour, Minute, Second; public: void SetTime( int h, int m, int s); void addHour(int h); void addMinute(int m); void addSecond(int s); void ShowTime( ); }; 2018/11/29
void Clock::SetTime( int h, int m, int s ){ Hour=h; Minute=m; Second=s; } void Clock::addHour( int h ){ Hour += h; } void Clock::addMinute( int m ){ Minute += m; } void Clock::addSecond( int s ){ Second += s; } void Clock::ShowTime( ){ cout<<“Current Time:”; cout<<Hour<<“:”<< Minute<<“:”<< Second<<endl; 2018/11/29
//使用Clock类 void main( ) { Clock clock_1, clock_2; clock_1. SetTime( 9 , 5 , 25 ); clock_2. SetTime( 15 , 16 , 45 ); clock_1. addHour( 3 ); clock_2. addMinute( 8 ); clock_1. ShowTime( ); clock_2. ShowTime( ); } 程序输出: Current Time:12:5:25 Current Time:15:24:45 2018/11/29
总结:定义和使用类类型的过程 抽象:对事物进行抽象 定义类:根据抽象的结果定义类的特性 实现类:实现类中成员函数的逻辑 使用类:在程序中定义类的实例,使用类的公有成员。 2018/11/29
在定义类时,把可以被外部访问的成员说明为public属性,作为外部访问类中成员的接口。把不能被外部访问的成员说明为private或protected属性。 私有成员 公有成员 类对外的接口 保护成员 2018/11/29
请思考以下情况 能调用下面语句吗? 如果将ShowTime说明为private属性,还能调用如下语句吗? clock_1.Hour=21; 如果将ShowTime说明为private属性,还能调用如下语句吗? clock_2.ShowTime(); 2018/11/29
练习一 设计一个三角形类CTriangle,该类满足下述要求: a)有一个成员函数SetEdge,用于设置三角形的边。 b)有一个成员函数GetArea,用于获得三角形的面积。 c)有一个成员函数GetCircumference,用于获得三角形的周长。 在main函数中,实例化一个Ctriangle类的对象,将三条边分别为3、4、5,然后将其面积和周长显示在屏幕上。 2018/11/29
a、b、c分别为三条边,S代表面积,p=(a+b+c)/2,那么S2=p(p-a)(p-b)(p-c)。 备注: 1.三角形面积的计算方式如下: a、b、c分别为三条边,S代表面积,p=(a+b+c)/2,那么S2=p(p-a)(p-b)(p-c)。 2.求开平方的库函数是sqrt,需要包含math.h头文件,使用方式的举例如下: int a, b; b=36; a=sqrt(b); 那么a的值为6 2018/11/29
练习二 Need To Do 2018/11/29
几点说明 在声明类时,具有不同访问属性的成员可以按任意顺序出现。 class Clock{ private: int Hour, Minute, Second; public: void SetTime( int h, int m, int s ); void ShowTime( ); protected: …… }; 2018/11/29
void SetTime( int h, int m, int s ); private: 修饰访问属性关键字可以多次出现。 class Clock { public: void SetTime( int h, int m, int s ); private: int Hour, Minute, Second; void ShowTime( ); }; 2018/11/29
一个成员只能具有一种访问属性,否则会出现歧异。 class Clock { public: void SetTime( int h, int m, int s ); void ShowTime( ); private: int Hour, Minute, Second; }; 2018/11/29
例子:简单int数组类 //数组类的定义 const int ARRAY_SIZE=4; class MyArray{ int data[ARRAY_SIZE]; public: int getArraySize(); int setVal(int pos,int val); int getMaxVal(); int getMinVal(); }; 2018/11/29
int MyArray::getArraySize() { return ARRAY_SIZE; } //数组类的实现 int MyArray::getArraySize() { return ARRAY_SIZE; } int MyArray::setVal(int pos,int val){ if((pos<0)||(pos>ARRAY_SIZE)) return -1; data[pos]=val; return 0; 2018/11/29
//数组类的实现(续) int MyArray::getMaxVal(){ int temp=data[0]; for(int i=1;i<ARRAY_SIZE;i++){ if(data[i]>temp) temp=data[i]; } return temp; int MyArray::getMinVal(){ if(data[i]<temp) 2018/11/29
//数组类的使用 int main() { MyArray array; int i,size,v,max,min; size=array.getArraySize(); cout<<"please input "<<size<<" numbers:"<<endl; for(i=0;i<size;i++){ cin>>v; array.setVal(i,v); } max=array.getMaxVal(); min=array.getMinVal(); cout<<"max value is : "<<max<<endl; cout<<"min value is : "<<min<<endl; return 0; 2018/11/29
改造:为简单int数组类增加公用的私有函数 //数组类的定义:增加公用的私有函数 const int ARRAY_SIZE=4; class MyArray{ int data[ARRAY_SIZE]; int getVal(int condition); public: int getArraySize(); int setVal(int pos,int val); int getMaxVal(); int getMinVal(); }; 2018/11/29
int MyArray::getVal(int condition){ int temp=data[0]; //数组类的实现:增加和调用公用的私有函数 int MyArray::getVal(int condition){ int temp=data[0]; for(int i=1;i<ARRAY_SIZE;i++){ if(condition==0){ if(data[i]>temp) temp=data[i]; }else{ if(data[i]<temp) } return temp; int MyArray::getMaxVal(){ int temp=getVal(0); return temp; } int MyArray::getMinVal(){ int temp=getVal(1); 2018/11/29
数据封装是一个相对的概念,只是对于类外而言。而对于类内部,所有的成员都是相互可见的。 2018/11/29
指向对象的指针变量 与C语言类似,可以使用指向对象的指针变量来访问对象以及对象的公共成员。 指向对象的指针变量的定义和用法示例: Clock clock, *p; p = &clock; p-> SetTime( 9 , 5 , 25 ); p-> ShowTime( ); p-> SetTime( 10 , 3 , 28 ); 2018/11/29
关键字this C++为每个非静态成员函数提供一个this指针。 this指针是一个隐含的指针,它指向了正在被成员函数操作的那个对象。 2018/11/29
this指针举例 class Counter{ int value; public: void setValue( int v ){ value=v; } …… }; class Counter{ int value; public: void setValue( int value ){ this->value=value; } …… }; void main(){ Counter obj1,obj2; obj1.setValue(1); obj2.setValue(2); } 2018/11/29
编译器做了特殊处理 class Counter{ int value; public: void setValue( int v ){ value=v; } …… }; class Counter{ int value; public: void setValue(Counter *this, int v ){ this->value=v; } …… }; void main(){ Counter obj1,obj2; obj1.setValue(1); obj2.setValue(2); } void main(){ Counter obj1,obj2; obj1.setValue(&obj1,1); obj2.setValue(&obj2,2); } 2018/11/29
构造函数和析构函数 类的数据成员是不能在定义时直接初始化的,以Clock类为例: class Clock { private: int Hour=0, Minute=0, Second=0;//错误 …… }; 2018/11/29
当Clock类被实例化时,如何初始化对象的状态,即如何初始化Hour、Minute、Second三个成员呢?一种可行的方式就是显式调用初始化函数。 Clock clock1; clock1. SetTime(21, 34, 43); 但是这种方式存在一些不好的情况: 程序员可能会忘记调用SetTime()函数,导致对象没有被初始化。 2018/11/29
构造函数 实际上,我们更希望这个初始化工作能够自动进行。类的构造函数提供了这种自动化功能。 构造函数是类的一种特殊成员,函数名和类名相同,没有返回类型,可以有参数。当创建类的一个新对象时,构造函数被自动调用,完成对象的初始化工作。 2018/11/29
例:为Clock类添加构造函数 class Clock { private: int Hour, Minute, Second; public: Clock(int h, int m, int s); …… }; 2018/11/29
构造函数初始化数据成员的两种方式 赋值语句的方式: 表达式表的方式: Clock(int h, int m, int s) { Hour = h; Minute = m; Second = s; } 表达式表的方式: Clock(int h, int m, int s): Hour(h), Minute(m), Second(s) { } 2018/11/29
传给构造函数实参的两种方式 如果构造函数带有参数,那么在创建对象时必须给出对应的实参。 如果构造函数仅有一个参数: className obj = parameter; 如果构造函数有一个或多个参数: className obj(parameter list); 如果构造函数有参数,但是在创建对象时又不给出对应的实参,此时编译出错。 2018/11/29
举例 class Counter{ private: int value; public: Counter(int v):value(v){ } …… }; class Clock{ private: int Hour, Minute, Second; public: Clock(int h, int m, int s); …… }; void main( ){ Counter counter1(8); Counter counter2=9; Clock clock1(3, 5, 1); Clock clock2( );//错误 } 2018/11/29
重载构造函数 一个类可以提供多个构造函数,但它们的参数表必须不同。重载的目的是为了满足不同的初始化需要。 class Clock { private: int Hour,Minute, Second; public: Clock(int h, int m, int s); Clock( ); Clock(char *timestr); …… }; void main( ) { Clock clock1(23, 12, 0); Clock clock2( ); Clock clock3(“14:45:32” ); } 2018/11/29
具有缺省参数的构造函数 构造函数也可以有缺省参数。如果在类外实现该函数时,就不能再说明缺省值了。 class Clock{ private: int Hour,Minute, Second; public: Clock(int h = 0, int m = 0, int s = 0); Clock(const char *timestr); …… }; 请思考一下,这个类还能再有一个没有任何参数的构造函数吗?即:Clock(); 2018/11/29
缺省的构造函数 对于没有构造函数的类,编译器将会自动为它生成一个没有参数的构造函数,该函数不做任何工作。 class Clock { private: int Hour, Minute, Second; …… }; 2018/11/29
析构函数 与构造函数相对的是析构函数。C++通过析构函数来处理对象被销毁时的清理工作。 析构函数没有返回类型,没有参数,函数名是在类名前加“~”。 析构函数将会在对象的生存期结束后被自动调用。 如果没有进行显式说明,系统将会生成一个不做任何事的缺省的析构函数。 2018/11/29
举例:为Clock类添加析构函数 class Clock { private: int Hour, Minute, Second; public: Clock(int h=12, int m=0, int s=0); ~Clock( ); …… }; Clock::~Clock( ){cout<<“Clock obj destroyed!”<<endl;} 2018/11/29
}//输出: Clock obj destroyed! Clock obj destroyed! void func( ) { Clock obj1,obj2; …… } void main() func(); }//输出: Clock obj destroyed! Clock obj destroyed! 2018/11/29
析构函数的典型用法 void func() { MyString obj( 64 ); obj.copy(“helloworld”); } class MyString{ private: int len; char *buf; public: MyString(int n); void copy(char *src); }; MyString::MyString( int n){ len=n; buf=new char[n]; } void MyString::copy(char *src){ strcpy(buf,src); void func() { MyString obj( 64 ); obj.copy(“helloworld”); } void main() func( ); !此时,obj的buf所指向的内存空间没有释放 2018/11/29
obj.copy(“helloworld”); } void main() func( ); class MyString{ private: int len; char *buf; public: MyString(int n); ~ MyString( ); void copy(char *src); }; MyString::MyString( int n){ len=n; buf=new char[n]; } void MyString::copy(char *src){ strcpy(buf,src); MyString::~ MyString( ){ delete [ ]buf; void func() { MyString obj( 64 ); obj.copy(“helloworld”); } void main() func( ); !当obj对象被释放时,析构函数被自动调用,buf所指向的内存空间被释放 2018/11/29
拷贝构造函数 构造函数的参数可以是任意类型,如果将与自己同类的对象的引用作为参数时,该构造函数就称为拷贝构造函数。 拷贝构造函数的特点: 首先,它是一个构造函数,当创建对象时系统会自动调用它。 其次,它将一个已经创建好的对象作为拷贝构造函数的参数,并根据需要将该对象中的数据成员逐一对应地赋值给新对象。 2018/11/29
示例 class Point{ private: float x, y; public: Point(float a, float b){ x=a; y=b; } Point(Point &obj){ x=obj.x; y=obj.y; }; void main(){ Point obj1( 5, 15);//调用Point(float , float ) Point obj2(obj1);//调用Point(A&) Point obj3=obj2;//调用Point(A&) 2018/11/29
缺省的拷贝构造函数的应用 如果一个类没有定义拷贝构造函数,那么C++编译器会为该类产生一个缺省的拷贝构造函数。缺省的拷贝构造函数使用位拷贝的方法来完成对象到对象的复制。也就是说,将第一个对象中的数据成员的值原封不动拷贝到第二个对象的数据成员中。 Bit Copy a_obj b_obj 2018/11/29
例:缺省的拷贝构造函数的应用 class Point{ private: float x, y; public: Point(float a, float b){ x=a; y=b; } }; void main() { Point obj1( 5, 15); //调用A(float, float) Point obj2(obj1); //调用缺省的拷贝构造函数 Point obj3=obj2; //调用缺省的拷贝构造函数 obj2是obj1的精确拷贝,obj3是obj2的精确拷贝 2018/11/29
在大多数情况下,缺省拷贝构造函数工作得很好,但在一些特殊的场合,它的表现将不尽人意。 缺省的拷贝构造函数的缺点 在大多数情况下,缺省拷贝构造函数工作得很好,但在一些特殊的场合,它的表现将不尽人意。 2018/11/29
obj1.copy(“helloworld”); MyString obj2=obj1; } void main() func( ); class MyString{ private: int len; char *buf; public: MyString(int n); ~MyString( ); void copy(char *src); }; MyString::MyString( int n){ len=n; buf=new char[n]; } void MyString::copy(char *src){ strcpy(buf,src); MyString::~MyString( ){ delete [ ]buf; void func() { MyString obj1( 64 ); obj1.copy(“helloworld”); MyString obj2=obj1; } void main() func( ); ! 由于调用的是默认的拷贝构造函数,因此obj1和obj2中的buf指向同一内存空间,当它们被释放时,该内存空间将被释放两次。 2018/11/29
改进办法:增加拷贝构造函数 class MyString{ public: MyString(int n); MyString(MyString &obj); …… }; MyString::MyString(MyString &obj) { len = obj.len; buf=new char[ len ]; strcpy( buf , obj.buf ); } 通过自定义拷贝构造函数,可以准确地复制数据,以免发生错误。 2018/11/29
拷贝构造函数起作用的地方 拷贝构造函数除了在创建新对象时被调用外,还在以下情况被调用: 对象作为函数参数 函数返回对象 2018/11/29
举例 class Counter{ private: int value; public: Counter(int v){ value=v; } void add(int v){ value+=v; void show( ){ cout<<value<<endl; }; Counter func(Counter obj ) { obj.add(6); return obj; } void main( ) Counter b1=5; Counter b2=func(b1); b1.show(); b2.show(); 输出:5 11 2018/11/29
调用func(b1)时调用默认的拷贝构造函数 调用return obj时调用默认的拷贝构造函数 5 b1 11 temp b2 5(11) obj func(b1) return obj b2=func(b1) 调用func(b1)时调用默认的拷贝构造函数 调用return obj时调用默认的拷贝构造函数 将func()返回值赋给b2时调用默认的拷贝构造函数 2018/11/29
对象数组 与任何其它数据类型一样,可以创建一个类的对象数组。例如: 通过下标访问数组中的对象,进而访问该对象的公有成员,例如: Clock clocks[10]; 通过下标访问数组中的对象,进而访问该对象的公有成员,例如: clocks[3].ShowTime(); 2018/11/29
举例 void main( ) class Point{ { public: Point array[3]; float x,y; array[1].x=5; array[1].y=6; array[1].show(); } class Point{ public: float x,y; Point( ){x=0;y=0;} Point(float a){x=a;y=0;} Point(float a ,float b){ x=a;y=b; } void show( ){ cout<<x<<“:”<<y<<endl; }; 输出:5:6 2018/11/29
对象数组的初始化 对象数组的初始化过程,实际上就是调用构造函数对每一个数组元素进行初始化的过程。 如果在声明数组时给出每一个数组元素的初始值,在初始化过程中就会调用最匹配的构造函数。 2018/11/29
举例:对象数组初始化 class Point{ public: Point( ){x=0;y=0;} Point(float a){x=a;y=0;} Point(float a , float b){x=a;y=b;} …… }; void main( ) { Point array[3]={ Point(3,4), 5 }; } 初始化array[0]时调用的是Point(float ,float)构造函数 初始化array[1]时调用的是Point(float)构造函数 初始化array[2]时调用的是Point( )构造函数 2018/11/29
对象指针的加减操作 class Counter{ private: int value; public: Counter(int v){ value=v; } void show( ){ cout<<value<<endl; }; void main( ) { Counter array[3]={5, 6, 7}; Counter *p=array;//或p=&array[0]; p->show(); p++; } 输出:5 6 7 2018/11/29
类类型作参数类型的三种方式 对象本身作为参数 由于C++采用传值的方式传递参数,因此使用对象本身参数时,形参是实参的一个拷贝。在这种情况下,最好显式地为类定义一个拷贝构造函数,以免出现不容易发现的错误。 2018/11/29
举例:对象本身做函数参数 class Counter{ Counter func(Counter obj ) private: { int value; public: Counter(int v){ value=v; } void add(int v){ value+=v; void show( ){ cout<<value<<endl; }; Counter func(Counter obj ) { obj.add(6); return obj; } void main( ) Counter b1=5; Counter b2=func(b1); b1.show(); b2.show(); 输出:5 11 2018/11/29
对象引用作为参数 这是一种推荐的方式。它比对象本身参数或对象指针参数都要容易理解和使用,同时没有任何的副作用。 void func2( Counter &obj ) { obj.add(6); } void main( ) { Counter b1=5; b1.show(); func2(b1); } 输出:5 11 2018/11/29
对象指针作为参数 对象指针指向实参对象,通过间接方式访问和修改它所指向的对象,实际上就是访问和修改实参对象。 void func3(Counter *pobj ) { pobj->add(6); } void main( ) { Counter b1=5; b1.show(); func3(&b1); } 输出:5 11 2018/11/29
静态成员 当用关键字static说明一个类成员时,该成员称为静态成员。因此可以分为: 静态数据成员 静态成员函数 2018/11/29
静态数据成员 类的所有对象共享静态数据成员,因此无论建立多少个该类的对象,静态数据成员只有一份拷贝。 class ABCD { //声明静态数据成员 static int s_value; int value; }; …… ABCD A ,B,C,D; Object A Value Object D value Object B s_value Object C 类的所有对象共享静态数据成员,因此无论建立多少个该类的对象,静态数据成员只有一份拷贝。 静态数据成员属于类,而不属于具体的对象。 2018/11/29
静态数据成员的定义和初始化 在类内只是静态数据成员的声明,但是静态数据成员必须在类外定义,格式如下: 类型 类名::静态数据成员 类型 类名::静态数据成员 int ABCD::s_value; 在定义静态数据成员的时候可以对其进行初始化,方式如下: int ABCD::s_value=6; 2018/11/29
静态数据成员的使用 静态数据成员也有public和private之分,由于静态数据成员是属于类的,因此: 类名::静态数据成员 ABCD::s_value++; 在类内,可以直接访问所有属性的静态数据成员 特别地,当类对象不存在时,也可以访问类的静态数据成员。 2018/11/29
例1:静态数据成员的基本用法 class counter{ static int count;//声明 public: void setcount(int num){ count=num; } void showcount(){ cout<<count<<“ “; }; //定义 int counter::count=0; void main() { counter a, b; a.showcount(); b.showcount(); a.setcount(34); }//输出:0 0 34 34 2018/11/29
静态成员函数 静态成员函数的定义 静态成员函数只属于一个类,它没有this指针。 静态成员函数也可以声明为public或private属性。 class ABCD{ …… public: static void show_staticvalue(){……} }; 静态成员函数只属于一个类,它没有this指针。 静态成员函数也可以声明为public或private属性。 2018/11/29
请思考 能将构造函数声明为非public属性吗?如果能,将会出现什么情况? class Clock{ private: int Hour, Minute, Second; Clock(int h, int m, int s); …… }; void main(){ Clock aclock(1,2,3); } 编译出错,因为构造函数是private属性 2018/11/29
举例:静态成员的应用 如果有一个类MyClass,如何设计才能保证在程序运行中该类只能有一个实例? 利用静态成员可以解决该问题。 class MyClass{ private: static MyClass *instance; MyClass(){} public: static MyClass *getInstance(){ if(instance==NULL) instance=new MyClass(); return instance; } }; MyClass *MyClass::instance=NULL; void main() { MyClass *obj; obj=MyClass::getInstance(); } 将构造函数声明为非public属性,可以控制对象的生成! 2018/11/29
友元关系 封装的目的就是为了实现信息隐蔽。 一个对象的私有成员只能被自己的成员访问到。当类外的对象或函数要访问这个类的私有成员时,只能通过该类提供的公有成员间接地进行。 出于效率(而非技术上必须)的考虑,C++提供了友元机制来打破私有化的界限,即一个类的友元可以访问到该类的私有成员。 2018/11/29
友元函数 问题的提出 Point类代表“点”,考虑如下需求:需要一个函数来计算任意两点间的距离,那么这个函数该如何设计呢? class Point{ float x, y; public: Point( float xx=0, float yy=0){x=xx;y=yy;} float GetX( ){return x;} float GetY( ){return y;} }; 2018/11/29
解决方案1 将计算两点距离的函数设计为类外的普通函数 float Distance(Point a , Point b) { float x1,x2,y1,y2,dx,dy; x1=a.GetX(); y1=a.GetY(); x2=b.GetX(); y2=b.GetY(); dx=x1-x2; dy=y1-y2; return sqrt(dx*dx+dy*dy); } void main() { Point p1(3.0, 5.0), Point p2(4.0, 6.0); float d=Distance(p1, p2); cout<<"The distance is "; cout<<d<<endl; } //输出: The distance is 1.1421 2018/11/29
另一种方案就是将计算两点距离的函数设计成为Point类的成员函数。虽然从语法的角度来看这不难实现,但是理解起来却有问题: 这种方案的缺点: 因为类外函数是不能直接访问类中的私有成员的,因此该函数必须调用GetX和GetY公有成员函数先获取“点”的坐标,然后再计算两点距离,这种方式是不太方便的。 另一种方案就是将计算两点距离的函数设计成为Point类的成员函数。虽然从语法的角度来看这不难实现,但是理解起来却有问题: 距离反映的是两点之间的关系,它既不属于每一个单独的点,也不属于整个Point类。 2018/11/29
解决方案2:设计为友元函数 将一个函数声明为一个类的友元函数时,该函数就具有以下特点: 该类函数可以直接访问私有成员。 友元函数不属于任何类,因此友元函数没有this指针。 友元函数访问非静态成员的方式与静态成员函数是一样的。 友元函数的声明可以放在类内的任何位置。 和普通函数一样,可直接调用友元函数。 2018/11/29
friend float Distance(Point a, Point b); }; class Point{ …… friend float Distance(Point a, Point b); }; float Distance(Point a , Point b) { float dx,dy; dx=a.x-b.x; dy=a.y-b.y; return sqrt(dx*dx+dy*dy); } void main() { Point p1(3.0, 5.0); Point p2(4.0, 6.0); float d=Distance(p1, p2); cout<<"The distance is "; cout<<d<<endl; } //输出: The distance is 1.1421 2018/11/29
友元类 除了将一个普通函数声明为一个类的友元函数外,也可以将一个类Y声明为另一个类X的友元类,那么,类Y中的所有成员函数都成为类X的友元函数,都能直接访问类X中所有的成员。 2018/11/29
举例 void main() { X xobj; Y yobj; yobj.SetX(xobj,5); xobj.show(); class Y;//向前说明 class X{ int x; friend class Y; public: void show(){cout<<x;} }; class Y{ void SetX( X &obj,int v){ obj.x=v; } void main() { X xobj; Y yobj; yobj.SetX(xobj,5); xobj.show(); }//输出:5 2018/11/29
将成员函数说明为另一个类的友元函数 class X;//向前说明 class Y{ public: void SetX( X &obj,int v); void func(X &obj ){……}; }; class X{ …… friend void Y::SetX( X &obj,int v); void Y::SetX( X &obj,int v){obj.x=v;} 2018/11/29
对友元关系的总结 友元具有如下的性质: 类的友元可以直接访问它的所有成员。 友元的声明必须放在类的内部,但放在哪个段没有区别。 友元关系不具备对称性,即X是Y的友元,但Y不一定是X的友元。 友元关系不具备传递性,即X是Y的友元,Y是Z的友元,但X不一定是Z的友元。 2018/11/29
类的组合 一个类的对象作为另一个类的成员,这体现的是整体和部分的关系,即对象的包含关系,这个作为成员的对象被称为子对象。 2018/11/29
举例:Circle类的实现 class Point{ float x, y; public: Point( float xx, float yy){ x=xx;y=yy; } float GetX( ){return x;} float GetY( ){return y;} void moveto(float xx,float yy) { x=xx; y=yy; }; 2018/11/29
Circle(float x,float y,float r):center(x,y) { radius=r; } class Circle{ Point center; float radius; public: Circle(float x,float y,float r):center(x,y) { radius=r; } void moveto(float xx,float yy){ center.moveto(xx,yy); void main( ){ Circle acircle(0,0,5); acircle.moveto(5,8); …… 若子对象对应的类的构造函数有参数,那么包含该子对象的类必须使用表达式的方式先初始化子对象。 2018/11/29