【C++面向对象程序设计】CH6 多态性与虚函数

news2024/7/3 12:08:41

目录

一、多态性的概念

二、一个典型的例子——【例6.1】

1.声明基类point类

2.声明派生类circle

​编辑

3.声明circle的派生类cylinder

三、虚函数

1.虚函数的作用

(1)派生类的对象可以给基类的对象赋值

(2)派生类的对象可以初始化基类引用

(3)派生类的对象的地址可以赋给指向基类的指针

(4)【例6.2】基类与派生类中有同名函数display

2.静态关联与动态关联

(1)研究虚函数

​(2)虚函数用法1

(3)虚函数用法2

(4)虚函数用法2(改)

(5)虚函数用法3

(6)虚函数用法3(改)

3.在什么情况下应当声明虚函数

4.虚析构函数

【例6.3】派生类对象析构函数的执行演示

四、纯虚函数与抽象类

1.纯虚函数

2.抽象类

3.应用实例

4.【例6.4】虚函数和抽象基类的应用


一、多态性的概念

        在C++程序设计中,多态性是指具有不同功能的函数可以用一个函数名。在面向对象程序设计中这样描述多态性:向不同的对象发送同一个消息,它们接受后会产生不同的行为(即方法)。

        多态性是指同样的消息被不同的对象接收时导致的不同行为。

        消息是指对类的成员函数的调用。

        不同的行为是指不同的实现,也就是调用了不同的函数。

        函数的重载、运算符重载都是多态现象。从系统实现的观点看,多态性分为两类:静态多态和动态多态性。以前学过的函数重载和运算符重载属于静态多态性,在编译程序时系统就可以确定调用哪个函数,因此静态多态性又称编译时的多态性。静态多态性是通过函数重载实现的。动态多态性是在程序运行中才能确定操作所针对的对象。他又称运行时的多态性。动态多态性是通过虚函数实现的。

        这章要研究的问题是:从一个基类派生出不同的派生类时,各派生类可以使用与基类成员相同的成员名,如果在运行时用相同的成员名调用类的成员,会调用哪个类的成员?也就是说,通过继承而产生了相关的不同的派生类,与基类成员同名的成员在各个派生类中有不同的含义,也就是说,多态性是一个接口多种方法。

二、一个典型的例子——【例6.1】

先建立一个点类(point),有数据成员x,y(坐标)。以它为基类派生一个圆类,增加数据成员r(半径),再以圆为直接基类派生出一个圆柱体类,再增加数据成员h(高),要求重载运算符<<和>>使之能输出以上的对象。

1.声明基类point类

#include <iostream>
using namespace std;

class Point 
{
	protected:
		float x, y;
	public:
		Point(float = 0, float = 0);
		void setPoint(float, float);
		float getX() const 
		{
			return x;
		}
		float getY() const 
		{
			return y;
		}
		friend ostream &operator<<(ostream &, const Point &);
};

//    Point的构造函数
Point::Point(float a, float b) 
{
	x = a;
	y = b;
}

//    设置x和y的坐标值
void Point::setPoint(float a, float b) 
{
	x = a;
	y = b;
}

//    输出点的坐标
ostream &operator<<(ostream &output, const Point &p) 
{
	output << "[" << p.x << "," << p.y << "]" << endl;
	return output;
}

int main() 
{
	Point p(3.5, 6.4);
	cout << "x=" << p.getX() << ",y=" << p.getY() << endl;
	p.setPoint(8.5, 6.8);
	cout << "p(new):" << p << endl;
	return 0;
}

【注】

// friend ostream & operator<<(ostream &,const Point &);
//对"<<"运算符的重载。

         一般我们用的“<<”只能输出整型、实型等普通类型。要想输出类类型,则必须对“<<”进行重载,其中一个参数为类类型对象。为了方便对对象内部数据的操作,设置为friend友元函数。为了能达到cout<<对象<<对象<<endl;的连续输出对象的效果,设置返回类型为引用。

        参数:第一个为输出流对象;第二个为要输出的对象(为了防止产生临时对象、提高程序的效率,将参数设置为引用类型,但引用类型又能改变实参的值,所以设置为const)。

2.声明派生类circle

        在1的基础上,再声明派生类circle的部分。

#include <iostream>
using namespace std;

class Point 
{
	protected:
		float x, y;
	public:
		Point(float = 0, float = 0);
		void setPoint(float, float);
		float getX() const 
		{
			return x;
		}
		float getY() const 
		{
			return y;
		}
		friend ostream &operator<<(ostream &, const Point &);
};

//    Point的构造函数
Point::Point(float a, float b) 
{
	x = a;
	y = b;
}

//    设置x和y的坐标值
void Point::setPoint(float a, float b) 
{
	x = a;
	y = b;
}

//    输出点的坐标
ostream &operator<<(ostream &output, const Point &p) 
{
	output << "[" << p.x << "," << p.y << "]" << endl;
	return output;
}

class Circle: public Point 
{
	protected:
		float radius;
	public:
		Circle(float x = 0, float y = 0, float r = 0);
		void setRadius(float);
		float getRadius() const;
		float area () const;
		friend ostream &operator<<(ostream &, const Circle &);
};

Circle::Circle(float a, float b, float r): Point(a, b), radius(r) {}

void Circle::setRadius(float r) 
{
	radius = r;
}

float Circle::getRadius() const 
{
	return radius;
}

float Circle::area() const 
{
	return 3.14159 * radius * radius;
}

ostream &operator<<(ostream &output, const Circle &c) 
{
	output << "Center=[" << c.x << "," << c.y << "],Radius = " << c.radius << ", area = " << c.area() << endl;
	return output;
}

int main() 
{
	Circle c(3.5, 6.4, 5.2);
	cout << "original circle:\nx=" << c.getX() << ",y = " << c.getY() << ", r = " << c.getRadius()  << ",area = " <<
	     c.area() << endl;
	c.setRadius(7.5);
	c.setPoint(5, 5);
	cout << "new circle:\n" << c;
	Point &pRef = c;
	cout << "pRef:" << pRef;
	return 0;
}

3.声明circle的派生类cylinder

        以circle为基础,从circle类派生出cylinder类。

#include <iostream>
using namespace std;
class Point 
{
	protected:
		float x, y;
	public:
		Point(float = 0, float = 0);
		void setPoint(float, float);
		float getX() const 
		{
			return x;
		}
		float getY() const 
		{
			return y;
		}
		friend ostream &operator<<(ostream &, const Point &);
};
//    Point的构造函数
Point::Point(float a, float b) 
{
	x = a;
	y = b;
}
//    设置x和y的坐标值
void Point::setPoint(float a, float b) 
{
	x = a;
	y = b;
}
//    输出点的坐标
ostream &operator<<(ostream &output, const Point &p) 
{
	output << "[" << p.x << "," << p.y << "]" << endl;
	return output;
}
class Circle: public Point 
{
	protected:
		float radius;
	public:
		Circle(float x = 0, float y = 0, float r = 0);
		void setRadius(float);
		float getRadius() const;
		float area () const;
		friend ostream &operator<<(ostream &, const Circle &);
};
Circle::Circle(float a, float b, float r): Point(a, b), radius(r) {}
void Circle::setRadius(float r) 
{
	radius = r;
}
float Circle::getRadius() const 
{
	return radius;
}
float Circle::area() const 
{
	return 3.14159 * radius * radius;
}
ostream &operator<<(ostream &output, const Circle &c) 
{
	output << "Center=[" << c.x << "," << c.y << "],Radius = " << c.radius << ", 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 setHeight(float);
		float getHeight() const;
		float area() const;
		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::setHeight(float h) 
{
	height = h;
}
float Cylinder::getHeight() const 
{
	return height;
}
float Cylinder::area() const 

	return 2 * Circle::area() + 2 * 3.14159 * radius * height;
}
float Cylinder::volume() const 
{
	return Circle::area() * height;
}
ostream &operator<<(ostream &output, const Cylinder &cy) 
{
	output << "Center=[" << cy.x << "," << cy.y << "], r=" << cy.radius << ", h=" << cy.height << " \narea=" << cy.area() <<
	       ", volume=" << cy.volume() << endl;
	return output;
}
int main() 
{
	Cylinder cy1(3.5, 6.4, 5.2, 10);
	cout << "\n original cylinder:\n x=" << cy1.getX() << ", y=" << cy1.getY() << ", r=" << cy1.getRadius() << ", h=" <<
	     cy1.getHeight() << "\narea=" << cy1.area()
	     << ", volume=" << cy1.volume() << endl;
	cy1.setHeight(15);
	cy1.setRadius(7.5);
	cy1.setPoint(5, 5);
	cout << "\nnew cylinder:\n" << cy1;
	Point &pRef = cy1;
	cout << "\npRef as a point:" << pRef;
	Circle &cRef = cy1;
	cout << "\ncRef as a Circle:" << cRef;
	return 0;
}

        可以在一个工程里设计多个头文件“point.h”、“cylinder.h”、“circle.h”分别定义各个类。

        再设计“point.cppp”、“circle.cpp”、“cylinder.h”多个源程序文件分别定义各个类的成员函数。

        设计一个主函数,通过对象访问类的成员函数。

三、虚函数

1.虚函数的作用

        在一个类中不能定义两个名字相同,参数个数和类型都相同的函数。在类家族中,不同层次的类可以出现名字相同,参数个数和类型都相同而功能不同的函数。如在【例6.1】中,circle类中定义了area函数,在circle的派生类cylinder类中也定义了一个area函数,这两个函数名字相同,参数个数也相同,但是功能不同,前者计算圆的面积,后者计算圆柱体的表面积。编译程序按照同名覆盖的原则决定调用哪个函数。

        在【例6.1】中用cy1.area()调用的是派生类cylinder中的成员函数area,如果想调用cy1中直接基类circle的area函数,应当表示为cy1.circle::area()。虽然可以区分两个同名函数,但写起来非常不方便。

        能否用同一个调用形式,即能调用派生类的函数也能调用基类的同名函数?C++中的虚函数就是解决这个问题的。虚函数的工作原理是在派生类中定义与基类函数同名的函数,通过基类指针或引用来访问基类或派生类中的同名函数。

        派生类与基类的转换

        赋值兼容性规则:

  • 含义:在需要基类对象的任何地方都可以使用共有派生类的对象来替代
  • 实质:公有派生类实际上具备了基类的所有功能,凡是基类能解决的问题,派生类都可以解决

(1)派生类的对象可以给基类的对象赋值

derived d;     //   derived类是base类的公有派生类
base b; b=d;

        反之不然,即基类对象不能赋值给派生类的对象,即使使用强制类型转换也不行,如:

d=b;     //error
d= (derived) b;  //error

        原因:基类对象不具有派生类的所有成员。

(2)派生类的对象可以初始化基类引用

derived d;     //   derived类是base类的公有派生类
base &rb=d;

         反之不然,除非通过强制类型转换,用一个基类的对象初始化其派生类的对象。

base b;
derived &rd=(derived) b;

(3)派生类的对象的地址可以赋给指向基类的指针

derived d;                 //       derived类是base类的公有派生类
base *pb=&d;

        反之不然,除非通过强制类型转换,把一个指向基类的指针赋值给一个指向其派生类的指针。

base *pb=new base;
derived *pd=(derived *)pb;

        赋值兼容性规则的作用。

  • 赋值兼容性规则的引入,对于基类及其共有派生类的对象,就可以使用相同的函数统一处理,而没有必要为每个类设计单独的功能模块
  • 当函数的形参为基类对象时,实参可以是派生类的对象
  • 当函数的形参为指向基类对象的指针时,实参可以是派生类对象的地址
  • 当函数的形参为基类对象的引用时,实参可以是派生类的对象

        结论:

  • 赋值兼容性规则是多态性的基础。同一个函数可以统一处理具有公有派生关系的基类的对象和派生类对象

        【例6.1】开始时没有使用虚函数,然后再讨论使用虚函数的情况。

(4)【例6.2】基类与派生类中有同名函数display

#include <iostream>
using namespace std;

class Student 
{
	public:
		Student(int, string, float);
		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\n";
}

class Graduate: public Student {
	public:
		Graduate(int, string, float, float);
		void display();
	private:
		float pay;
};

Graduate::Graduate(int n, string nam, float s, float p): Student(n, nam, s), pay(p) {}

void Graduate::display() 
{
	cout << "num:" << num << "\nname:" << name << "\nscore:" << score << "\npay=" << pay << endl;
}
int main() 
{
	Student stud1(1001, "Li", 87.5);
	Graduate grad1(2001, "Wang", 98.5, 563.5);
	Student *pt = &stud1;
	pt->display();
	pt = &grad1;
	pt->display();
	return 0;
}

        原因是指针是指向基类的指针。用虚函数就能顺利地解决这个问题。

        虚函数的使用方法:

  • 在基类用virtual声明成员函数为虚函数。在派生类中重新定义同名函数,让它具有新的功能
  • 在派生类中重新定义此函数时,要求函数名、函数类型、参数个数和类型与基类的虚函数相同,根据需要重新定义函数体。C++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数自动成为虚函数
  • 定义一个指向基类对象的指针变量,并让他获得同一类族中某个对象的地址
  • 用该指针变量调用虚函数,调用的就是该对象所属类的虚函数

        根据虚函数的要求,程序作一点修改,在student类中声明display函数时,在最左边加一个关键字virtual,即

virtual void display();

        把student类的display函数声明为虚函数。程序其他部分不变,再编译后运行程序,请看程序运行结果。

        现在用同一个指针变量不仅输出了基类对象的数据,而且输出了派生类的所有数据,说明调用了grad1的display函数。 

        用同一种调用形式pt->display(),而且pt是一个基类的指针,可以调用同一类族中不同类的虚函数。这就是多态性,对同一消息,不同对象有不同的响应方式。

        原来,基类指针是用来指向基类对象的,如用它指向派生类对象,几桶要进行指针类型转换,所以基类指针指向的是派生类对象中的基类部分。

        虚函数突破了这个限制,在基类指针指向派生类对象后,就能调用派生类的虚函数。

        以前介绍的函数重载处理的是同一层次中的同名函数问题,而虚函数处理的是不同派生层次上的同名函数问题,前者要求函数名相同,但函数的参数或类型可以不同;后者要求的是不仅函数名要相同,而且函数参数和类型也要相同。 

2.静态关联与动态关联

        从【例6.2】修改后的程序可以看到,同一个成员函数display在不同类中有不同的作用,呈现了多态。

        以前学过的函数重载和用对象名调用虚成员函数,在编译时即可确定其调用的虚函数属于哪个类,其过程称为静态关联。

        从【例6.2】里,在调用虚函数时没有指定对象名,系统怎样确定关联呢?编译系统把它放在运行阶段处理,在运行阶段确定关联关系。

        在运行阶段,指针可以先后指向不同的类对象,从而调用同一类族中不同类的虚函数。

(1)研究虚函数

        先看一个程序

#include <iostream>
using namespace std;

class Novirtual 
{
	private:
		int x;
	public:
		void Foo() {}
};

class Withvirtual 
{
	private:
		int x;
	public:
		virtual void Foo() {}
};

int main() 
{
	cout << "无虚函数的对象长度=" << sizeof(Novirtual ) << endl;
	cout << "有虚函数的对象长度=" << sizeof(Withvirtual ) << endl;
	return 0;
}

【注】程序中两个类的差别是一个不带虚函数,另一个带虚函数。

        当类中任何函数前带virtual时,编译系统将暗自在类中增加一个数据成员,在这称它为VPTR,它是一个指向函数指针表的指针,因此,Withvirtual类对象占用的内存空间比Novirtual类的对象大。C++需要用一个特别的间接引用调用虚函数。下面演示间接引用调用虚函数的工作原理。

struct  dis
{   
    int i ;
    virtual  void f();
    vitrual  void g();
}; 
dis  x;
dis  *xp =&x;
xp->g();

        这段代码中包括了调用虚函数(如果使用对象调用成员函数则是调用非虚函数,因为编译程序知道x的确切类型。)

(2)虚函数用法1

#include <iostream>
using namespace std;

class base 
{
	public:
		virtual void Fn() 
		{
			cout << "在基类内" << endl;
		}
};

class subclass  : public base 
{
	public:
		virtual void Fn() 
		{
			cout << "在派生类内" << endl;
		}
};

void test (base &b) 
{
	b.Fn();
}

int main() 
{
	base  bc;
	subclass  sc;
	cout << "调用test(bc)\n";
	test(bc);
	cout << "调用test(sc)\n";
	test(sc);
	return 0;
//当函数的形参为基类对象的引用时,实参可以是派生类//的对象。
}

(3)虚函数用法2

#include <iostream>
using namespace std;

class base 
{
	public:
		virtual void Fn(int x) 
		{
			cout << "在基类内x=" << x << endl;
		}
};

class subclass  : public base 
{
	public:
		virtual void Fn( float x) 
		{
			cout << "在派生类内x=" << x << endl;
		}
};

void test (base &b) 
{
	int i = 1;
	b.Fn( i );
	float f = 2.0;
	b.Fn( f );
}

int main() 
{
	base  bc;
	subclass  sc;
	cout << "调用test(bc) \n";
	test(bc);
	cout << "调用test(sc) \n";
	test(sc);
	return 0;
}

        上述两个程序唯一的区别是:base中Fn()被声明为Fn(int),而subclass中Fn()被声明为Fn(float)。因为在派生类里,虚函数虽与基类虚函数同名,但是参数的类型不相同,成员函数不能被重载。在第二次调用时,把float类型转换成int类型,仍然调用base的Fn(int)函数。

(4)虚函数用法2(改)

#include <iostream>
using namespace std;

class base 
{
	public:
		virtual void Fn(int x) 
		{
			cout << "在基类内x=" << x << endl;
		}
};

class subclass  : public base 
{
	public:
		virtual void Fn( int x) 
		{
			cout << "在派生类内x=" << x << endl;
		}
};

void test (base &b) 
{
	int i = 1;
	b.Fn( i );
	int f = 2.0;
	b.Fn( f );
}

int main() 
{
	base  bc;
	subclass  sc;
	cout << "调用test(bc)\n";
	test(bc);
	cout << "用test(sc) \n";
	test(sc);
	return 0;
}

(5)虚函数用法3

#include <iostream>
using namespace std;

class base 
{
	public:
		virtual void Fn(int x ) 
		{
			cout << "在基类内x=" << x << endl;
		}
};

class subclass  : public base 
{
	public:
		virtual void Fn(float x ) 
		{
			cout << "在派生类内x=" << x << endl;
		}
};

int main(int argc, char *argv[]) 
{
	base  bc;
	base *p;
	subclass  sc;
	p = &bc;
	cout << "调用基类p->Fn(2) \n";
	p->Fn(2);
	p = &sc;
	cout << "调用派生类p->Fn(2) \n";
	p->Fn(2);
	return 0;
}

(6)虚函数用法3(改)

#include <iostream>
using namespace std;

class base 
{
	public:
		virtual void Fn(int x ) 
		{
			cout << "在基类内x=" << x << endl;
		}
};

class subclass  : public base 
{
	public:
		virtual void Fn(int x ) 
		{
			cout << "在派生类内x=" << x << endl;
		}
};

int main(int argc, char *argv[]) 
{
	base  bc;
	base *p;
	subclass  sc;
	p = &bc;
	cout << "调用基类p->Fn(2) \n";
	p->Fn(2);
	p = &sc;
	cout << "调用派生类p->Fn(2) \n";
	p->Fn(2);
	return 0;
}

3.在什么情况下应当声明虚函数

        注意,只能将类的成员函数声明为虚函数。一个成员函数被声明为虚函数后,在同一类族不能再定义一个与该虚函数相同的非虚函数。

        声明虚函数,主要考虑下面几点:

  • 首先看成员函数的类是否会作为基类。然后看在派生类里它是否会被改变功能,如要改变,一般应该将它声明为虚函数。否则不要将它声明为虚函数
  • 有时在定义虚函数时,函数体是空的。具体功能留给派生类去添加

4.虚析构函数

        当派生类的对象撤销时一般先调用派生类的析构函数,然后调用基类的析构函数。如用new运算符建立一个动态对象,如基类中有析构函数,并且定义了一个指向基类的指针变量。在程序中用带指针参数的delete运算符撤销对象时,系统只会执行基类的析构函数,而不执行派生类的析构函数。

【例6.3】派生类对象析构函数的执行演示

#include <iostream>
using namespace std;

class Point 
{
	public:
		Point() {}
		~Point() 
		{
			cout << "executing Point destructor" << endl;
		}
};

class Circle: public Point 
{
	public:
		Circle() {}
		~Circle() 
		{
			cout << "executing Circle destructor" << endl;
		}
	private:
		int radus;
};

int main() 
{
	Point *p = new Circle;
	delete p;
	return 0;
}

        表示只执行了基类point的析构函数,未执行派生类的析构函数。这时可将基类的析构函数声明为虚函数,如:

virtual ~ Point(){cout<<"executing Point estructor" <<endl;}

        先调用派生类的析构函数,再调用基类的析构函数。当基类的析构函数为虚函数时无论指针指的是同一类族中的哪一类对象,撤销对象时,系统会采用动态关联,调用相应的析构函数。

        构造函数不能声明为虚函数。

        虚析构函数

        何时需要虚析构函数?

  • 当你可能通过基类指针删除派生类对象时
  • 如果你打算允许其他人通过基类指针调用对象的析构函数(通过delete这样做是正常的),并且被析构的对象是有重要的析构函数的派生类的对象,就需要让基类的析构函数称为虚拟的

四、纯虚函数与抽象类

1.纯虚函数

        只声明,不定义的虚函数称为纯虚函数。一般格式为:

virtual  函数类型  函数名(参数表)=0;

        纯虚函数的作用:

        在很多情况下,基类中不能为虚函数给出一个有意义的定义,而将它说明为纯虚函数,起作用是: 为派生类提供一个一致的接口(界面)。它的定义留给派生类来做,派生类根据需要来定义各自的实现。

        注意:一个类可以说明一个或多个纯虚函数;纯虚函数与函数体为空的虚函数的区别:

  • 纯虚函数:根本没有函数体;所在的抽象类,不能直接进行实例化
  • 空的虚函数:函数体为空;所在的类可以实例化
  • 共同的特点:可以派生出新的类

2.抽象类

        抽象类:带有纯虚函数的类是抽象类

        抽象类的作用:通过它为一个类族建立一个公共的接口,使他们能够更有效地发挥多态特性

        抽象类刻画了一组子类的公共操作接口的通用语义,这些接口的语义也传给子类。一般而言,抽象类只描述这组子类共同操作接口,而完整的实现留给子类。

        抽象类的说明:

  • 抽象类是一个特殊的类,是为了抽象和设计的目的而建立的,它处于继承层次的结构的较上层,即只能用作其他类的基类,抽象类是不能定义对象的
  • 从一个抽象类派生的类必须提供纯虚函数的实现代码或在该派生类中仍将它说明为纯虚函数,否则编译错
  • 抽象类不能用作参数类型、函数返回类型或显式转换的类型,但可以说明指向抽象类的指针和引用,此指针可以指向它的派生类,实现多态性
  • 构造函数不能是虚函数,析构函数可以是虚函数

3.应用实例

#include <iostream>
using namespace std;

class B0 
{    //抽象基类B0声明
	public:        //外部接口
		virtual void display( ) = 0;  //纯虚函数成员
};

class B1: public B0 
{  //公有派生
	public:
		void display() 
		{
			cout << "B1::display()" << endl;   //虚成员函数
		}
};

class D1: public B1 
{ //公有派生
	public:
		void display() 
		{
			cout << "D1::display()" << endl;   //虚成员函数
		}
};

void fun(B0 *ptr) 
{	//普通函数
	ptr->display();
}

int main() 
{	//主函数
	B0 *p;	//声明抽象基类指针
	B1 b1;	//声明派生类对象
	D1 d1;	//声明派生类对象
	p = &b1;
	fun(p);	//调用派生类B1函数成员
	p = &d1;
	fun(p);	//调用派生类D1函数成员
	return 0;
}

4.【例6.4】虚函数和抽象基类的应用

#include <iostream>
using namespace std;

class Shape 
{
	public:
		virtual float area() const 
		{
			return 0.0;   //虚函数
		}
		virtual float volume() const 
		{
			return 0.0;   //虚函数
		}
		virtual void shapeName() const = 0; //纯虚函数
};

class Point: public Shape 
{ // Point是Shape的公用派生类
	protected:
		float x, y;
	public:
		Point(float = 0, float = 0);
		void setPoint(float, float);
		float getX() const 
		{
			return x;
		}
		float getY() const 
		{
			return y;
		}
		//    对纯虚函数进行定义
		virtual void shapeName() const 
		{
			cout << "Point:";
		}
		friend ostream &operator<<(ostream &, const Point &);
};

Point::Point(float a, float b) 
{
	x = a;
	y = b;
}

void Point::setPoint(float a, float b) 
{
	x = a;
	y = b;
}

ostream &operator<<(ostream &output, const Point &p) 
{
	output << "[" << p.x << "," << p.y << "]";
	return output;
}

class Circle: public Point 
{ //    声明Circle类
	protected:
		float radius;
	public:
		Circle(float x = 0, float y = 0, float r = 0);
		void setRadius(float);
		float getRadius() const;
		virtual float area() const;
//   对纯虚函数进行再定义
		virtual void shapeName() const 
		{
			cout << "Circle:";
		}
		friend ostream &operator<<(ostream &, const Circle &);
};

Circle::Circle(float a, float b, float r): Point(a, b), radius(r) {}

void Circle::setRadius(float r) 
{
	radius = r;
}

float Circle::getRadius() const 
{
	return radius;
}

float Circle::area() const 
{
	return 3.14159 * radius * radius;
}

ostream &operator<<(ostream &output, const Circle &c) 
{
	output << "[" << c.x << "," << c.y << "], r=" << c.radius;
	return output;
}

//    声明Cylinder类
class Cylinder: public Circle 
{
	public:
		Cylinder (float x = 0, float y = 0, float r = 0, float h = 0);
		void setHeight(float);
		float getHeight() const;
		virtual float area() const;
		virtual float volume() const;
		//    对纯虚函数进行再定义
		virtual void shapeName() const 
		{
			cout << "Cylinder:";
		}
		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::setHeight(float h) 
{
	height = h;
}
float Cylinder::getHeight() const 
{
	return height;
}

float Cylinder::area() const 
{
	return 2 * Circle::area() + 2 * 3.14159 * radius * height;
}

float Cylinder::volume() const 
{
	return Circle::area() * height;
}

ostream &operator<<(ostream &output, const Cylinder &cy) 
{
	output << "[" << cy.x << "," << cy.y << "], r=" << cy.radius << ", h=" << cy.height;
	return output;
}

int main() 
{
	Point point(3.2, 4.5);            //建立Point类对象point
	Circle circle(2.4, 12, 5.6);     //建立Circle类对象circle
	Cylinder cylinder(3.5, 6.4, 5.2, 10.5);
//建立Cylinder类对象cylinder
	point.shapeName();                              //静态关联
	cout << point << endl;
	circle.shapeName();                             //静态关联
	cout << circle << endl;
	cylinder.shapeName();                           //静态关联
	cout << cylinder << endl << endl;
	Shape *pt;                                      //定义基类指针
	pt = &point;                                    //指针指向Point类对象
	pt->shapeName();                                //动态关联
	cout << "x=" << point.getX() << ",y=" << point.getY() << "\narea=" << pt->area() << "\nvolume=" << pt->volume() <<
	     "\n\n";
	pt = &circle;                                   //指针指向Circle类对象
	pt->shapeName();                                //动态关联
	cout << "x=" << circle.getX() << ",y=" << circle.getY() << "\narea=" << pt->area() << "\nvolume=" << pt->volume() <<
	     "\n\n";
	pt = &cylinder;                                 //指针指向Cylinder类对象
	pt->shapeName();                                //动态关联
	cout << "x=" << cylinder.getX() << ",y=" << cylinder.getY() << "\narea=" << pt->area() << "\nvolume=" << pt->volume() <<
	     "\n\n";
	return 0;
}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/47567.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mmdetection3d SUN RGB-D数据集预处理

SUN RGB-D是普林斯顿大学发布的一种关于室内场景理解的数据集&#xff0c;共包含了10335个样本&#xff0c;其中训练样本和验证测试样本数量分别为5285和5050。每个样本包含了彩色图像&#xff08;RGB&#xff09;和深度&#xff08;D&#xff09;信息&#xff0c;并且分别进行…

智能微型断路器在道路照明、园区照明、隧道照明中的应用-安科瑞 时丽花

智能微型断路器应用场景 九小场所 商场、超市等购物场所 养老院、福利院、孤儿院等社会福利机构 图书馆、博物馆、科技馆、文化馆等文化活动中心 车站、轨道交通等交通枢纽 易燃易爆仓库、木材加工、纺织、喷涂、制药等企业 酒吧&#xff0c;网吧等娱乐场所 幼儿园、小…

B站视频弹幕不挡住人脸效果

前言 有天在B站看二舅的视频时&#xff0c;密密麻麻的弹幕居然没有二舅的人脸&#xff0c;很好奇是怎么做到的&#xff0c;于是决定一探究竟。 高端的效果&#xff0c;往往只需要采用最朴素的实现方式&#xff0c;琢磨了好一会儿&#xff0c;打开了F12&#xff0c;豁然开朗。…

Quartz深度实战

概述 Java语言中最正统的任务调度框架&#xff0c;几乎是首选。后来和Spring Schedule平分秋色&#xff1b;再后来会被一些轻量级的分布式任务调度平台&#xff0c;如XXL-Job取代。另外近几年Quartz的维护和发布几乎停滞&#xff0c;但这并不意味着Quartz被淘汰&#xff0c;还…

【干货】微信私域运营打法和案例拆解(附78页pdf下载链接)

省时查报告-专业、及时、全面的行研报告库省时查方案-专业、及时、全面的营销策划方案库【免费下载】2022年10月份热门报告盘点知识图谱在美团推荐场景中的应用实践.pdf清华大学256页PPT元宇宙研究报告.pdf&#xff08;附下载链接&#xff09;机器学习在B站推荐系统中的应用实践…

【吴恩达机器学习笔记】七、神经网络

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 &#x1f4e3;专栏定位&#xff1a;为学习吴恩达机器学习视频的同学提供的随堂笔记。 &#x1f4da;专栏简介&#xff1a;在这个专栏&#xff0c;我将整理吴恩达机器学习视频的所有内容的笔记&…

数据库设计三范式

数据库设计三范式 范式是数据库设计时遵循的一种规范&#xff0c;不同的规范需要遵循不同的范式&#xff0c;只有充分遵循了数据库设计的范式&#xff0c;才能设计开发出冗余较小、高效、结构合理的数据库。 通常&#xff0c;我们在设计数据库的时候会要求遵循三范式。 第一…

《强化学习周刊》第69期:ICLR2023强化学习论文推荐、MIT实现自动调整内在奖励的强化学习...

No.69智源社区强化学习组强化学习研究观点资源活动周刊订阅告诉大家一个好消息&#xff0c;《强化学习周刊》已经开启“订阅功能”&#xff0c;以后我们会向您自动推送最新版的《强化学习周刊》。订阅方法&#xff1a;方式1&#xff1a;扫描下面二维码&#xff0c;进入《强化学…

使用Python PySNMP模块获取设备指标

一、PySNMP模块介绍&#xff1a; PySNMP 是一个跨平台的纯Python SNMP 引擎实现。它具有功能齐全的 SNMP 引擎&#xff0c;能够充当代理/管理器/代理角色&#xff0c;通过 IPv4/IPv6 和其他网络传输传输 SNMP v1/v2c/v3 协议版本。目前&#xff0c;使用较多的是SNMP v3和v2c版…

Robust Document Image Dewarping Method Using Text-Lines and Line Segments论文学习笔记

1 摘要 传统的基于文本行的文档去扭曲方法在处理复杂布局和/或非常少的文本行时会出现问题。当图像中没有对齐的文本行时&#xff0c;这通常意味着照片、图形和/或表格占据了输入的大部分。因此&#xff0c;对于健壮的文档去扭曲变形&#xff0c;我们建议除了对齐的文本行之外…

Python解题 - CSDN周赛第11期 - 圆桌请客(脑筋急转弯)

本来想着没有all pass就不写题解了&#xff0c;但在赛后对最后一题纠结了好久&#xff0c;然后发现是个类似脑筋急转弯的题&#xff0c;自己与正确答案只差一层纸&#xff0c;实在有点不吐不快。另外本期考了经典的背包问题的模板题&#xff0c;也值得记录下来&#xff0c;加深…

全志科技A40i国产开发板——性能参数综合测试

本次测试板卡是创龙科技旗下,一款基于全志科技A40i开发板,其接口资源丰富,可引出双路网口、双路CAN、双路USB、双路RS485等通信接口,板载Bluetooth、WIFI、4G(选配)模块,同时引出MIPI LCD、LVDS LCD、TFT LCD、HDMI OUT、CVBS OUT、CAMERA、LINE IN、H/P OUT等音视频多媒…

宿主机与开发板网络共享

宿主机网络共享 一、关键步骤 11. 网络共享简介 目标&#xff1a;宿主机可以用ssh连接开发板&#xff0c;开发板可以上网。 步骤&#xff1a;宿主机与目标机用网线直连&#xff0c;宿主机采用IP共享的方式连接开发板&#xff1b; 配置项IP开发板IP192.168.0.232宿主机以太网I…

Java 序列化原理

我的网站 | 我的博客 | 序列化解析工具 概念 Java为我们提供了一种默认的对象序列化机制&#xff0c;通过这种机制可以将一个实例对象写入到IO流中&#xff0c;当然这种IO流可以是文件流、网络流或者其他什么流。 代码的写法 ObjectOutputStream 对象输出流&#xff0c;用…

2022新一代设备维修管理系统助力企业降本增效

设备的维修是指企业或者设备密集型单位为了保持、恢复并提升设备使用寿命而定期对设备进行状态的维护&#xff0c;备件的更换&#xff0c;发生故障后的维修和恢复&#xff0c;从而让设备保证良好的运营状态&#xff0c;提升设备的可利用性并保证产能和设备安全。 大型企业在设…

C++11标准模板(STL)- 算法(std::merge)

定义于头文件 <algorithm> 算法库提供大量用途的函数&#xff08;例如查找、排序、计数、操作&#xff09;&#xff0c;它们在元素范围上操作。注意范围定义为 [first, last) &#xff0c;其中 last 指代要查询或修改的最后元素的后一个元素。 归并两个已排序的范围 st…

关于Mysql使用left join写查询语句执行很慢的问题解决

目录 &#xff08;一&#xff09;前言 &#xff08;二&#xff09;正文 1. 表结构/索引展示 &#xff08;1&#xff09;表结构 &#xff08;2&#xff09;各表索引情况 2. 存在性能问题的SQL语句 3. 解决思路 &#xff08;1&#xff09;执行计划思路调优 &#xff08;…

数字图像处理(入门篇)二 颜色空间

在对图像进行处理时&#xff0c;前提图像必须是以数据的形式来描述的&#xff0c;而颜色空间就是用数据来表征图像颜色的一种方法。颜色信息由三个独立的分量来综合表示&#xff0c;这三个独立的分量构成了一个三维的坐标空间&#xff0c;每种颜色信息都在该空间中被唯一地表示…

Java-泛型实验

1.定义一个学生类Student&#xff0c;具有年龄age和姓名name两个属性&#xff0c;并通过实现Comparable接口提供比较规则&#xff08;返回两个学生的年龄差&#xff09;&#xff0c; 定义测试类Test&#xff0c;在测试类中定义测试方法Comparable getMax(Comparable c1, Compar…

基于springboot农机电招平台设计与实现的源码+文档

摘要 随着农机电招行业的不断发展&#xff0c;农机电招在现实生活中的使用和普及&#xff0c;农机电招行业成为近年内出现的一个新行业&#xff0c;并且能够成为大群众广为认可和接受的行为和选择。设计农机电招平台的目的就是借助计算机让复杂的销售操作变简单&#xff0c;变高…