C++ 06 多态

/*
一、多态性的概念polymorphism
	多态性是面向对象程序设计的一个重要特征。一种语言如果只支持类而不支持多态,只能说是基于对象的,不能被称为面向对象的。
	1、多态,指一个事物有多种形态。C++中,多态性是指具有不同功能的函数可以用同一个函数名来调用。
	2、表述:向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为(方法),用各自的方式去响应共同的消息。
	消息,指调用函数;不同的行为是指不同的实现。
#如函数的重载,运算符的重载都是多态现象。如学校校长向社会发布一个消息——开学,在得到同一个消息时,各种人都知道自己应该怎么做,这就是多态性。
	3、C++中,在不同的类型中定义了其响应消息的方法,使用这些类时不必考虑其是什么类型,只要发布消息即可。如使用+不必考虑相加的数值的类型。
	不论对象千变万化,用户都是用同一形式的信息去调用它们,根据事先的安排做出反应。
	4、分类:静态多态性和动态多态性。
	【1】函数和运算符的重载实现的多态性属于静态多态性,在程序编译时系统就能决定调用的是哪个函数,又称为编译时的多态性。静态多态性是通过函数的重载实现的。
	【2】动态多态性是在程序运行过程中才动态地确定操作所针对的对象,又称为运行时的多态性。通过虚函数virtual function实现的。
	5、通过继承而产生了相关的不同派生类,与基类成员同名的成员在不同的派生类中有不同的含义。
	@多态性是“一个接口,多种方法”。——区别于封装性(数据安全和使用方便)。

	二、虚函数
	(一)虚函数的作用  //区别虚基类--多个直接基类有共同基类,会在最终派生类中有多份基类的数据成员。
	1、同一类中不能定义两个同名、同参(个数和类型)的函数,否则就是“重复定义”。但在类的继承层次结构中,不同的层次中可以出现。函数功能不同,函数体不同,但它们不在同一个类中,是合法的。
	编译系统按照同名覆盖的原则决定调用的对象——编译时的多态性。
	2、由来:能否用同一个调用形式,既能调用派生类又能调用基类的同名函数。在程序中不是通过不同的对象名去调用不同层次中的同名函数,而是通过同一个指针来调用它们。*搭乘公交和出租车。
	3、引入:虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。
	4、例子:student *p=&stud1; p->display(); p=&gard1; p->display(); 后部分只是输出了grad1中的继承自基类这部分的数据成员(另外的值,初始化不同)。
	也可以采用对象名调用函数,不然只能是另外再定义一个指向grad1的指针,若有多个派生类则很麻烦。
	@派生类中继承了基类的部分,这部分的数据成员在派生类中进行不同的初始化,它们的值是不一样的。
	@在基类中声明display函数的时候:virtual void display(); 则上面用法中可以输出grad1中的全部数据,说明调用了grad1中的函数。
@用同一种形式p->display(),调用同一个类族中的不同类的虚函数。这就是多态性,对同一个消息,不同对象有不同的响应方式。
	5、总结:当把基类的某个成员函数声明为虚函数后,允许在其派生类中对该函数重新定义,赋予它新的功能,并且可以通过指向基类的指针指向同一族类中不同类的对象,从而调用其中同名的函数。
	由虚函数实现的动态多态性就是:同一类族中不同类的对象,对同一函数调用作出不同的响应。
	6、使用方法:
	【1】在基类中用virtual声明成员函数为虚函数。类外定义时不用再加virtual.
	【2】派生类中重定义此函数,要求函名、函类、函参等要一样,根据需要重新定义函数体。 如未重定则是简单的继承。
	*当一个成员函数被定义为虚函数以后,其派生类中的同名函数都自动成为虚函数。派生类中重新声明该函数时,可加也可不加virtual。
	【3】定义一个指向基类对象的指变,并使它指向同一类族中(其派生类)的某一个对象。
	【4】通过该指变调用此虚函数,此时调用的就是指变指向的对象的同名函数。
	*通过虚函数与指向基类对象的指变的配合使用,能方便调用同一族类中不同类的同名函数,只要先用基类指针指向即可。
	7、区别:函数重载处理的是同一层次上(同一类中)的同名函数问题,而虚函数处理的是不同派生层次上的同名函数问题,前者是横向重载,后者是纵向重载。

	(二)静态关联与动态关联
	1、概念:确定调用的具体对象的过程称为关联binding。一般是指把一个标识符和一个存储地址联系起来。
	【1】静态关联:函数重载和通过对象名调用虚函数,在编译时即可确定其调用的虚函数属于哪一类。又称为早期关联。
	【2】动态关联:在运行阶段把虚函数和类对象绑定在一起的,称为动态关联。又称为滞后关联。dynamic binding,late binding.
	*使用虚函数的时候,编译只作静态的语法检查,光从语句形式是无法确定应调用哪一个类对象的。
	*只有在运行时,p指向某一个对象后,才能确定调用的是哪一个类的虚函数,故为动态关联。

	(三)使用虚函数的情况
	1、注意事项
	【1】只能用virtual声明类的成员函数,而不能将类外的普通函数声明为虚函数。
	【2】一个成员函数被声明为虚函数以后,就不能在同一个类中定义一个同名同参的非virtual的重载函数了。
	2、需要使用虚函数情况:
	【1】首先看成员函数所在的类是否会作为基类。然后看成员函数在类的继承后有无被修改的可能。若希望更改其功能的话,一般应该声明成虚函数。若不需修改或派生类用不到,则不用。
	【2】应考虑对成员函数的调用是通过对象名还是通过基类指针或引用去访问的,如果是通过基类的指针或引用去访问的,则应当声明为虚函数。
	【3】有时,在定义虚函数时,并不定义其函数体,即函数体为空。作用只是定义了一个虚函数名,具体功能留给派生类去添加。
	3、虚函数表:使用虚函数时,系统要有一定的时间开销。当一个类带有虚函数时,编译会为该类构造一个虚函数表,是一个指针数组,存放每个虚函数的入口地址。
	——系统进行动态关联的时间开销很少,多态性是高效的。

	(四)虚析构函数
#派生类自己定义的析构函数,用来对派生类所增加的成员进行清理工作;其基类部分的清理工作仍然留给基类的析构函数负责。在执行派生类的析构函数时,系统会自动调用基类的析构函数和子对象的析构函数,对基类的子对象进行清理。
	1、由来:当派生类对象从内存中撤销时一般先调用派生类对象的析构函数,然后再调用基类的析构函数。但是如果利用上面的方法,用New运算符建立一个派生类的临时对象,并让指向基类的指针指向它;
	此时,若基类中有析构函数,在程序中用delete运算符撤销对象时,系统只执行基类的析构函数,而不执行派生类的析构函数。
如: point *p=new circle;  delete p ;  
*原因:p所指向的只是派生类中的从基类继承过来的那部分的地址。
	2、解决方法:把基类的析构函数声明为虚函数。 此时,无论基类指针指向的是同一个类族中的哪一个类对象,当对象撤销时,系统会采用动态关联,调用的相应的虚构函数,对该对象进行清理工作。
	【1】最好把基类的析构函数声明为虚函数,此时其所有派生类的析构函数自动成为虚函数。

	三、纯虚函数与抽象类
	(一)纯虚函数
	1、由来(作用):有时在基类中将某一成员函数定位虚函数,并不是基类本身的要求,而是考虑到派生类的需求,在基类中预留了一个函数名,具体功能留给派生类根据需要去定义。
	若派生类没有对其定义,则该虚函数在派生类中依然为纯虚函数。
	如: virtual float area() const{return 0;}
可以简化成: virtual float area()const=0;    //纯虚函数 pure virtual function
2、概念:纯虚函数是在声明虚函数时被初始化为0的函数。
	声明的一般形式: virtual 函数类型 函数名 (参数表列)=0;
	【1】没有函数体。【2】后面的0不表示返回值,只起到形式的作用,告诉编译系统这是纯虚函数。
	【3】只有函数名而具备函数的功能,不能被调用,只在派生类中对此函数定义之后,才具备函数的功能才能被调用。
	(二)抽象类
	1、由来:声明一些类,不用来生成对象。定义这些类唯一的目的就是用它作为基类去建立派生类。它们作为一种基本类型提供给用户,用户在这个基础上根据自己的需要定义出功能各异的派生类。用这些派生类去建立对象。
	2、概念:不用来定义对象而只作为一种基本类型用作继承的类,称为抽象类。由于其常用作基类,又称为抽象基类。
	【1】凡是包含纯虚函数(不管是一个还是多个)的类都是抽象类。  因为纯虚函数是不能被调用的,包含纯虚函数的类是无法建立对象的。抽象类的作用是作为一个类族的公共基类,为一个类族提供一个公共接口。
	【2】派生类中必须对基类的所有纯虚函数进行定义,这些函数才可以被调用。如果没有对所有的纯虚函数进行定义,则此派生类仍然是抽象类不是具体类,不能被用来定义对象。   
	【3】虽然抽象类不能定义对象,但可以定义指向抽象类数据的指针变量。当派生类成为具体类之后,就可以用这种指针指向派生类对象,然后通过该指针调用虚函数,实现多态性的操作。
	3、总结
	【1】一个基类如果包含一个或一个以上纯虚函数,就是抽象基类。抽象基类不能也没必要定义对象。
	【2】抽象基类和普通基类不同,它一般并不是现实存在的对象的抽象,可以没有任何物理上的或其他实际意义方面的含义。
	【3】类的层次结构中,顶层和最上面的几层可以是抽象基类。抽象基类体现了本类族中各类的共性,把各类中共有的成员函数集中在抽象类中声明(为虚函数)。
	【4】区别静态和动态关联:通过基类指针调用虚函数,编译阶段无法从语句本身确定调用哪一个类的虚函数,只有在运行时,p指向某一个对象后,才能确定调用的是哪一个类的虚函数,故为动态关联。
	【5】抽象基类是本类族的公共接口。或说,从同一个基类中派生出来的多个类有同一个接口。因此能响应同一形式的消息,响应方式各不同。在通过虚函数实现动态多态性时,可以不考虑对象属于哪一个派生类的,都用同一种方式调用。
	(基类指针可以指向同一类族中的所有类,因而可通过基类指针调用不同类中的虚函数。)
	【6】基类中声明了虚函数,则在派生类中凡是与该函数同名同类、同参的函数,均为虚函数。但是同名虚函数可以有不同的定义。
#纯虚函数是在抽象基类中声明的,只是在抽象基类中才称为纯虚函数。派生类中虽然继承了其,但除非再次用=0,把它声明,否则它就不是纯虚函数。
	【7】使用虚函数提高了程序的可扩充性。
	如果主函数中,需要把circle类更换成globe类,只需把出现circle类对象的地方改成globe即可.其中有出现p->的地方都不必修改,十分方便。
	即使要增加一个新的类,也可以现在程序中写出对它的操作语句(知道类名),无需修改基本系统即可把一个新类加到系统中。只要在程序编译前定义好,在运行时保证动态关联即可。
	*对软件开发意义很大,把类的声明和使用分离。对于设计类库的软件开发商很重要,用户可以不知道类是怎么声明的,但是可以使用这些类来派生出自己的类。
	(需要类所在的文件和类成员函数定义所在的目标文件的路径和文件名——类的接口,以及类的使用说明-虚函数的作用等。)
	【8】虚函数和多态性的另一意义:使得程序员的注意力集中在处理普遍性,而让环境处理特殊性。如程序员只要进行宏观的操作,让程序调用各对象的draw函数即可,画出需要的图形。
	使用基类指针来控制有关对象,不管对象在继承中的哪一个层次。每一个对象中的draw函数的工作是在类中指定的,这就是执行环境。——类似于封装的一样,封装指每个对象的接口。
	多态性把操作的细节留给类的设计者去完成,而让编程人员(类的使用者)只需做一些宏观性的工作,告诉系统做什么,而不必考虑做什么。——系统结构
*/

//先建立一个point点 类,包含数据成员x,y(坐标点)。以它为基类,派生出circle(圆)类,增加数据成员r (半径),再以circle类为
//直接基类,派生出一个cylinder(圆柱体)类,再增加数据成员h(高)。要求编写程序,重载运算符<<和>>,使之能用于输出以上的类对象。
/*对于一个比较大的程序,应当分成若干个步骤进行。先声明基类,再声明派生类,逐级进行,分步调试。*/

//#define BASIC_CLASS            //声明基类
//#define DERIVED_CLASS          //声明派生类
//#define DERIVED_DERIVED_CLASS  //派生类的派生
//#define VIRTUAL_FUNCTION       //虚函数
//#define VIRTUAL_DESTRUCTOT     //虚虚构函数
#define   ABSTRACT_CLASS         //抽象类


///虚基类
#ifdef ABSTRACT_CLASS

#include <iostream>
#include <string>
using namespace std;  

class person
{
public:
	person(string nam, char s, int a)
	{
		name=nam;
		sex=s;
		age=a;
	}

protected:
	string name;
	char sex;
	int age;
};

//声明person的直接派生类teacher
class teacher:virtual public person							           //声明person为公用继承的虚基类
{
public:
	teacher(string nam,char s,int a,string t):person(nam,s,a)          //构造函数
	{title=t;}

protected:
	string title;
};

class student:virtual public person					//声明直接派生类,并把person声明为公用继承的虚基类      
{
public:
	student(string nam,char s,int a, float sco):person(nam,s,a),score(sco){} 

protected:
	float score;
};

//声明多重继承的派生类
class graduate:public teacher, public student      //直接基类
{
public:
	graduate(string nam,char s,int a,string t,float sco,float w)
		:person(nam,s,a),teacher(nam,s,a,t),student(nam,s,a,sco),wage(w){}        //在最后的派生类中,不只是要对其直接基类初始化,还要负责对虚基类初始化

void show()
{
	cout<<"name:"<<name<<endl;
	cout<<"age:"<<age<<endl;
	cout<<"sex:"<<sex<<endl;
	cout<<"score:"<<score<<endl;
	cout<<"title:"<<title<<endl;
	cout<<"wages:"<<wage<<endl;
}

private:
	float wage;
};

int main(int argc, char *argv[])
{
	graduate grad1("zhang", 'f', 25, "assistant", 90, 123);
	grad1.show();

	system("pause");
	return 0;
}

/虚析构函数
#elif defined VIRTUAL_DESTRUCTOT

#include<iostream>
using namespace std;

class point				     //定义基类pinnt
{
public:
	point(){}                //point类构造函数
	virtual ~point(){cout<<"executing point destructor"<<endl;}         //point类析构函数
}; 
//利用基类指针释放派生类时,必须把基类虚构函数定义成虚析构函数。这样在main中用delete释放的是指向派生类对象的基类指针时,系统会调用相应的派生类析构函数,而不是只调用基类的析构函数。
//此时基类的派生类中的析构函数也是析构函数,析构函数名虽然不一样。

class circle:public point  //定义公用派生类
{
public: 
	circle(){}
	virtual ~circle(){cout<<"executing circle destructor"<<endl;}
};


int main()
{
	point *p=new circle;    //定义了一个指向基类的指针p。 用new开辟了circle类对象的动态存储空间,并返回一个指针。
	delete p;               //根据“基类与派生类的转换”,派生类对象的地址可赋给指向基类对象的指变。指向基类对象的指变也可以指向派生类对象。

	system("pause");
	return 0;
}



///虚函数
#elif defined VIRTUAL_FUNCTION

#include <iostream>
#include <string>
using namespace std;

class student
{
public: 
	student(int ,string,float);       //声明构造函数
	virtual void display();           //声明输出函数, 声明成虚函数

protected:                            //受保护成员,派生类可以访问。
	int num;
	string name;
	float score;
};

student::student(int n,string nam,float s)  //定义构造函数
{
	num=n;
	name=nam;
	score=s;
} 

void student::display()					  //定义输出函数
{
	cout<<"num:"<<num<<"\nname:"<<name<<"\nscore"<<score<<"\n";
}

class graduate:public student           //声明公用派生类
{
public:
	graduate(int ,string, float, float);  // 声明构造函数,形参里面只要有类型即可。
	virtual void display();				//声明成虚函数

private:
	float pay;
};

void graduate::display()
{
	cout<<"num:"<<num<<"\nname:"<<name<<"\nscore"<<score<<"\npay="<<pay<<endl;
}

graduate::graduate(int n,string nam,float s,float p):student(n,nam,s),pay(p){}     //基类的构造函数首部,因其只有调用没有传递参数,所以不用类型。


int main(int argc, char* argv[])
{
	student stud1(130,"zhang",90);
	graduate grad1(131,"huang",90,123);  
	student *p=&stud1;         //定义指向基类对象的指变p

	p->display();          
	p=&grad1;            //用同一个指向基类的指针拿去指向派生类(基类与派生类之间的转换)。本来如果没有虚函数的话只会输出派生类的基类部分。   
	p->display();        //体现了多态性

	system("pause");
	return 0;
}
//当把基类的某个成员函数声明为虚函数后,允许在其派生类中对该函数重新定义,赋予它新的功能,并且可以通过指向基类的指针指向同一族类中不同类的对象,从而调用其中同名的函数。
//由虚函数实现的动态多态性就是:同一类族中不同类的对象,对同一函数调用作出不同的响应。


#elif defined  DERIVED_DERIVED_CLASS

#include <iostream>
using namespace  std;

class point                              //声明类point,点的类
{
public:
	point (float x=0,float y=0);          //有默认参数的构造函数
	void setpoint(float,float);             //设置坐标值-函数
	float getx() const {return x;}              //读x,y的坐标        //区别常成员const和静态数据成员static。前者不可改变,只有在定义时前面加multable,初始化也必须在构造函数的初始化表.
	float gety() const {return y;}              //常成员函数,为了处理本类中的静态成员。 //后者可以改变,只可在类外面赋值,只是其值对各对象。作用不同!
	friend ostream &operator <<(ostream &,const point &);     //重载输出运算符<<,形参里面有一个静态形参,形参为常对象,使得实参传值时也具有同样特性。
		
protected:                                           
	float x,y;						//点类数据成员坐标点
};

//定义point类的成员函数
point::point(float a,float b)     //point的构造函数
{
	x=a,y=b;
}                                //对数据成员初始化——设置x和y的坐标值。

void point::setpoint(float a,float b)     //函数的形参名,a,b可以在另一个函数中重复使用
{
	x=a;					    //为x,y赋新值
	y=b;
}      

ostream &operator <<(ostream &output, const point &p) //普通函数作友元
{
	output<<"["<<p.x<<","<<p.y<<"]"<<endl;          //重载运算符<<,使之能输出点的坐标。
	return output;
}    

class circle:public point       //声明此类是point类的公用派生类
{
public:
	circle(float x=0,float y=0,float r=0);  //带默认参数的构造函数,只是声明。
	void setr(float);           //设置半径值
	float getr() const;         //读取半径值
	float area() const;         //计算圆面积
	friend ostream &operator<<(ostream &,const circle &); //重载运算符<<,两次重载运算符的内容是不同的,函数参数不同,编译系统会根据输出项的类型选择调用。

protected :
	float r;
};

void circle::setr(float r1)
{
	r=r1;
}

circle::circle(float a,float b,float r1):point(a,b),r(r1){}    //定义派生类circle的构造函数,对圆心坐标和半径进行初始化

float circle::getr()const  {return r;}        //读取半径值。

float circle::area()const				     //计算圆的面积
{
	return 3.14159*r*r;
}

ostream &operator<<(ostream &output,const circle &c)          //重载<<,使之可以输出派生类对象的信息。
{
	output<<"center=["<<c.x<<","<<c.y<<"],r="<<c.r<<",area="<<c.area()<<endl;
	return output;
}

class cylinder:public circle				//在派生类的基础上,再进行公用派生
{
public:
	cylinder(float x=0, float y=0, float r=0,float h=0); //有默认参数的构造函数,
	void seth(float);          //设置圆柱的高
	float geth() const;        //读取圆柱的高
	float area() const;        //计算圆表面积           //定义了和circle类中的同名函数,cy.area()调用的是cy类中的函数而不是circle中的函数。两个函数不是重载函数,而是同名、同类、同参的,属于同名覆盖
	float volume() const;      //计算圆柱体积
	friend ostream& operator<<(ostream&,const cylinder&);  //重载运算符<<

protected:
	float height;
};

cylinder::cylinder(float a, float b, float r, float h):circle(a,b,r),height(h){} //定义构造函数,后面的直接派生类里面的参数类型可不用写。

void cylinder::seth(float h){height=h;}   //设置圆柱的高

float cylinder::geth()const {return height;}  //读取圆柱的高,受保护成员外界访问,只能通过本类中的成员函数进行访问了。

float cylinder::area()const {return 2*circle::area()+2*3.14159*r*height;}//计算圆柱表面积

float cylinder::volume()const {return circle::area()*height;}   //计算圆柱的体积

ostream& operator<<(ostream &output,const cylinder &cy)  //重载运算符<<,输出类对象cylinder
{
	output<<"center=["<<cy.x<<","<<cy.y<<"],r="<<cy.r<<",h="<<cy.height<<"\narea="<<cy.area()<<",volume="<<cy.volume()<<endl;
	return output;
}  //注意里面的数据成员和成员函数的 对象限定,其对象是派生类的,形参的cy


int main(int argc, char* argv[])
{
	cylinder cy(3.2, 4.5, 5.9, 12);    //建立cylinder派生类对象cy
	cout<<"original circle:x="<<cy.getx()<<",y="<<cy.gety()<<",r="<<cy.getr()<<",area="<<cy.area()<<",h="<<cy.geth();      //输出cy的值
	cout<<",columr="<<cy.volume()<<endl;         //输出的一些数据成员,是受保护的,不能直接通过对象名访问,要通过其类中的成员函数访问。

	cy.seth(13);                      //重新设定p的坐标值。
	cy.setr(2.7);
	cy.setpoint(5,9);
	cout<<"new circle:\n"<<cy;

	point &pref=cy;                   //用派生类对基类的引用进行赋值
	cout<<"\n new pref as a point:"<<pref;   //ref作为一个点输出
	circle &cref=cy;
	cout<<"\ncref as a circle:"<<cref;  //输出pref的信息,此时输出的是派生类circle中基类point的信息。它是c中基类部分的别名,与其共享同一个单元。

	system("pause");
	return 0;
}
//存在静态多态性,是运算符重载引起的。在编译时编译系统即可以判定应该调用哪个重载函数。


/
#elif defined DERIVED_CLASS
/*
1、派生类对象可以替代基类对象为基类对象的引用初始化或者赋值。
*/

#include <iostream>
using namespace std;

//const float gc_fpai = 3.14159;
#define	GC_FPAI  3.14159

class point                              //声明类point,点的类
{
public:
	point(float x=0, float y=0);         //有默认参数的构造函数
	void setpoint(float,float);             //设置坐标值-函数
	float getx() const {return x;}              //读x,y的坐标        //区别常成员const和静态数据成员static。前者不可改变,只有在定义时前面加multable,初始化也必须在构造函数的初始化表.
	float gety() const {return y;}              //常成员函数,为了处理本类中的静态成员。 //后者可以改变,只可在类外面赋值,只是其值对各对象。作用不同!
	friend ostream &operator <<(ostream &,const point &);     //重载输出运算符<<,形参里面有一个静态形参,形参为常对象,使得实参传值时也具有同样特性。

protected:                                           
	float x,y;							 //点类数据成员坐标点
};

//定义point类的成员函数
point::point(float a,float b)     //point的构造函数
{
	x=a;
	y=b;
}								//对数据成员初始化——设置x和y的坐标值。

void point::setpoint(float a,float b)     //函数的形参名,a,b可以在另一个函数中重复使用
{
	x=a;		
	y=b;
}						//为x,y赋新值 

ostream &operator <<(ostream &output,const point &p) //普通函数作友元
{
	output<<"["<<p.x<<","<<p.y<<"]"<<endl;          //重载运算符<<,使之能输出点的坐标。
	return output;
}

class circle:public point			//声明此类是point类的公用派生类
{
public:
	circle(float x=0,float y=0,float r=0);  //带默认参数的构造函数,只是声明。
	void setr(float);           //设置半径值
	float getr() const;         //读取半径值
	float area() const;         //计算圆面积
	friend ostream &operator<<(ostream &,const circle &); //重载运算符<<,两次重载运算符的内容是不同的,编译系统会根据输出项的类型选择调用。

private :
	float r;
};

void circle::setr(float r1)
{
	r=r1;
}

circle::circle(float a,float b,float r1):point(a,b),r(r1){}         //定义派生类circle的构造函数,对圆心坐标和半径进行初始化
//因为前面声明的时候,在参数里面已经对默认参数赋值了,此处就不用了

float circle::getr()const  {return r;}        //读取半径值。


float circle::area()const  //计算圆的面积
{
	return GC_FPAI*r*r;
}

ostream &operator<<(ostream &output,const circle &c)          //重载<<,使之可以输出派生类对象的信息。
{
	output<<"center=["<<c.x<<","<<c.y<<"],r="<<c.r<<",area="<<c.area()<<endl;
	return output;
}

int main(int argc, char* argv[])
{
	circle c(3.5, 4.6, 5.2);		 //建立point派生类对象c
	cout<<"original circle:x="<<c.getx()<<",y="<<c.gety()<<",r="<<c.getr()<<",area="<<c.area()<<endl;      //输出c的值
	c.setr(4);                  //重新设定p的坐标值和半径。
	c.setpoint(3,5);
	cout<<"new circle:\n"<<c;
	point &pref=c;				//pref 是point类的引用变量,用派生类对象c对它进行初始化。——派生类对象可以替代基类对象为基类对象的引用初始化或者赋值。
	cout<<"pref:"<<pref;        //输出pref的信息,此时输出的是基类point的信息。它是c中基类部分的别名,与其共享同一个单元。

	system("pause");
	return 0;
}

///声明基类,点坐标
#elif defined BASIC_CLASS

#include <iostream>
using namespace std;

class point									 //声明类point,点的类
{
public:
	point(float x=0, float y=0);             //有默认参数的构造函数
	void setpoint(float, float);             //设置坐标值-函数
	float getx() const {return x;}              //读x,y的坐标        //区别常成员const和静态数据成员static。前者不可改变,只有在定义时前面加multable,初始化也必须在构造函数的初始化表.
	float gety() const {return y;}              //常成员函数,为了处理本类中的静态成员。 //后者可以改变,只可在类外面赋值,只是其值对各对象。作用不同!
	friend ostream &operator <<(ostream &,const point &);     //重载输出运算符<<,形参里面有一个静态形参,形参为常对象,使得实参传值时也具有同样特性。

protected:                                           
	float x,y;   //点类数据成员坐标点
};

//定义point类的成员函数
point::point(float a,float b)		 //point的构造函数
{
	x=a;
	y=b;
}									//对数据成员初始化——设置x和y的坐标值。

void point::setpoint(float a,float b)   //为x,y赋新值 
{
	x=a;
	y=b;
}    

ostream &operator <<(ostream &output, const point &p) //普通函数作友元
{
	output<<"["<<p.x<<","<<p.y<<"]"<<endl;          //重载运算符<<,使之能输出点的坐标。
	return output;
}    

int main(int argc, char* argv[])
{
	point p(3.5, 4.6);								 //建立point类对象p
	cout<<"x="<<p.getx()<<"y="<<p.gety()<<endl;      //输出p的坐标值
	p.setpoint(12,23);								 //重新设定p的坐标值。
	cout<<"p(new):"<<p<<endl;						 //用重载运算符<<输出P的坐标
	
	system("pause");
	return 0;
}

#endif

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值