C++面向对象程序设计 - 纯虚函数和抽象类

       在C++中,纯虚函数(Pure Virtual Function)是一种特殊的虚函数,它在基类中被声明但没有定义。纯虚函数用于在基类中创建一个接口,要求所有派生类必须提供该函数的具体实现。

        纯虚函数通过在虚函数声明的末尾加 = 0 来标识。如果一个类包含一个或多个纯虚函数,那么这个类就被称为抽象类(Abstract Class)。抽象类不能被实例化(即创建对象),因为至少有一个成员函数(即纯虚函数)没有被实现。

一、纯虚函数

        纯虚函数是在声明虚函数时被“被始化”为0的函数,它的声明一般形式是:

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

        注意的事项:

  1. 纯虚函数没有函数体;
  2. 最后面的“= 0”并不表示函数返回为0,它只是形式上的作用,告诉编译系统这是“纯虚函数”;
  3. 这是一个声明语句,最后应有分号。

示例代码如下:

virtual float area() const = 0;        //纯虚函数

二、抽象类

        抽象类是一种特殊的类,它包含一个或多个纯虚函数。由于抽象类至少包含一个纯虚函数,因此它不能被实例化。抽象类主要用于定义接口,即一组相关操作但不提供这些操作的具体实现,具体的实现细节留给派生类来完成。

        如果在抽象类所派生的新类中对基类的所有纯虚函数进行了定义,那么这些函数就被赋予了功能,可以被调用;这个派生类就不是抽象类,而是可以用来定义对象的具体类。如果在派生类中没有对所有纯虚函数进行定义,则此派生类仍然是抽象类,不能用来定义对象。

        抽象类不能定义对象,但是可以定义指向抽象类数据的指针变量。当派生类成为具体类之后,就可以用这种指针指向派生类对象,然后通过该指针调用虚函数,实现多态性的操作。

三、实例应用

        这里在程序案例中使用虚函数和抽象基类,定义顶层抽象基类Shape(形状),派生出Point(点)、Circle(圆)、Cylinder(圆柱体)与Shape类的直接派生类和间接派生类。

1)抽象类基类Shape

        在抽象基类Shape中定义两个虚函数(包含函数体),一个纯虚函数(无函数体),示例代码如下:

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

2)派生类Point

        在派生类Point中增加其相关属性数据成员和成员函数,并对纯虚函数进行再次定义。示例代码如下:

// 声明派生类Point
class Point: public Shape{
	protected:
		float x, y;
	public:
		Point(float x = 0.0, float y = 0.0): x(x), y(y){}			//定义构造函数
		void setPoint(float x, float y){
			this->x = x;
			this->y = y;
		}
		float getX(){ return x; }
		float getY(){ return y; }
		// 对虚函数再次定义
		virtual void shapeName() const { cout <<"Point:"; }
		friend ostream & operator <<(ostream &, const Point &);
};
// 定义Point类的重载运算符友函数
ostream & operator <<(ostream &output, const Point &p){
	output <<'[' <<p.x <<',' <<p.y <<']' <<endl;
	return output;
}

3)派生类Circle

        在派生类Circle中增加其相关属性数据成员和成员函数,并对纯虚函数进行再次定义。另外,因为圆要重新计算面积,所以要对area()函数重新定义。示例代码如下:

// 声明派生类Circle
class Circle: public Point{
	protected:
		float radius;
	public:
		Circle(float x = 0.0, float y = 0.0, float r = 0.0): Point(x, y), radius(r){}			//定义构造函数
		void setRadius(float r){
			this->radius = r;
		}
		float getRadius(){ return radius; }
		// 因为圆中要计算面积,所以需要对area重新定义
		virtual float area() const{
			return 3.1415926 * radius * radius;
		}
		// 对虚函数再次定义
		virtual void shapeName() const { cout <<"Circle:"; }
		friend ostream & operator <<(ostream &, const Circle &);
};
// 定义Point类的重载运算符友函数
ostream & operator <<(ostream &output, const Circle &c){
	output <<'[' <<c.x <<',' <<c.y <<']' <<", radius=" <<c.radius <<endl;
	return output;
}

4)派生类Cylinder

        在派生类Cylinder中增加其相关属性数据成员和成员函数,并对纯虚函数进行再次定义。另外,圆柱体中表面积除了上下两个圆,还有柱面面积,所以area()需要重新定义;还有圆柱体中,要对体积重新计算,需对volume()函数重新定义。示例代码如下:

// 声明派生类Cylinder
class Cylinder: public Circle{
	protected:
		float height;
	public:
		Cylinder(float x = 0.0, float y = 0.0, float r = 0.0, float h = 0.0): Circle(x, y, r), height(h){}			//定义构造函数
		void setHeight(float h){
			this->height = h;
		}
		float getHeight(){ return height; }
		// 因圆柱体中,表面积除了上下两个圆,还有柱面面积,所以area()需要重新定义
		virtual float area() const{
			return 2 * Circle::area() + 2 * 3.1415926 * radius * height;
		}
		// 因圆柱体中,需要对volume()函数重新定义
		virtual float volume() const{
			return Circle::area() * height;
		}
		// 对虚函数再次定义
		virtual void shapeName() const { cout <<"Cylinder:"; }
		friend ostream & operator <<(ostream &, const Cylinder &);
};
// 定义Point类的重载运算符友函数
ostream & operator <<(ostream &output, const Cylinder &c){
	output <<'[' <<c.x <<',' <<c.y <<']' <<", radius=" <<c.radius <<endl;
	return output;
}

5)静态关联

        使用对象名直接调用进行静态关联输出类Point、Circle、Cylinder的对象信息,示例代码如下:

int main(){
	Point point(3.2, 4.5);					// 建立Point对象
	Circle circle(2.4, 1.2, 5.6);			// 建立Circle对象
	Cylinder cylinder(3.5, 4.5, 5.2, 10.5);		// 建立Cylinder对象
	
	// 静态关联输出
	point.shapeName();
	cout <<point <<endl;
	
	circle.shapeName();
	cout <<circle <<endl;
	
	cylinder.shapeName();
	cout <<cylinder <<endl;
	
	return 0;
}

        运行后输出结果如下图:

6)动态关联

        通过基类Shape指针变量进行动态关联输出类Point、Circle、Cylinder的对象信息。为了方便统一输出,将在类Point中增加一个重载运算符输出,形参的常引用变量更改为常指针变量,代码如下:

friend ostream & operator <<(ostream &, const Point *);

修改后的Point类代码如下:

// 声明派生类Point
class Point: public Shape{
	protected:
		float x, y;
	public:
		Point(float x = 0.0, float y = 0.0): x(x), y(y){}			//定义构造函数
		void setPoint(float x, float y){
			this->x = x;
			this->y = y;
		}
		float getX(){ return x; }
		float getY(){ return y; }
		// 对虚函数再次定义
		virtual void shapeName() const { cout <<"Point:"; }
		friend ostream & operator <<(ostream &, const Point &);
		friend ostream & operator <<(ostream &, const Point *);
};
// 定义Point类的重载运算符友函数(常引用)
ostream & operator <<(ostream &output, const Point &p){
	output <<'[' <<p.x <<',' <<p.y <<']' <<endl;
	return output;
}
//定义Point类的重载运算符友函数(常指针)
ostream & operator <<(ostream &output, Point *p){
	output <<'[' <<p->getX() <<',' <<p->getY() <<']' <<"\narea=" <<p->area() <<"\nvolume=" <<p->volume() <<endl <<endl;
	return output;
}

        因为基类Shape中无数据成员和输出坐标成员函数,点坐标在类Point中,而且Point类继承了Shape基类中的虚函数和纯虚函数,圆Circle和圆柱体直接继承或间接继承了类Point,故重载运算符定义在类Point中,能满足类Point、Circle、Cylinder信息的需求。

main函数代码如下:

int main(){
	Point point(3.2, 4.5);					// 建立Point对象
	Circle circle(2.4, 1.2, 5.6);			// 建立Circle对象
	Cylinder cylinder(3.5, 4.5, 5.2, 10.5);		// 建立Cylinder对象
	
	cout <<"dynamic output:" <<endl;
	Shape *pt;							//定义基类指针
	
	pt = &point;						// 指针指向Point
	pt->shapeName();
	cout <<&point;
	
	pt = &circle;						// 指针指向Circle
	pt->shapeName();
	cout <<&circle;
	
	pt = &cylinder;						// 指针指向Cylinder
	pt->shapeName();
	cout <<&cylinder;
	return 0;
}

        运行后如果如下图:

7)总结

        本例中可以得到以下结论:

  1. 一个基类如果包含一个或一个以上纯虚函数,就是抽象基类,抽象基类不能也不必要定义对象。
  2. 抽象基类与普通基类不同,它一般并不是现实存在的对象的抽象,它可以没有任何物理上的或其他实际意义方面的含义。
  3. 在类的层次结构中,顶层或最上面的几层可以是抽象基类。抽象基类体现了本类族中各类的共性,把各类中共有的成员函数集中在抽象基类中声明。
  4. 抽象基类是本类族的公共接口。
  5. 区别静态关联和动态关联。如果通过对象名调用虚函数,在编译阶段就能确定调用是哪个类的虚函数,属于静态关联。如果是通过基类指针调用虚函数,在编译阶段无法从语句本身确定调用哪个类的虚函数,只能在运行时确认指向某一类对象后,才能确定调用的是哪个类的虚函数,属于动态关联。
  6. 如果在基类声明了虚函数,则在派生类中凡是与该函数有相同的函数名、函数类型、参数个数和参数类型的函数,均为虚函数(不论在派生类中是否用virtual声明)。
  7. 纯虚函数是在抽象基数在中声明的,只是在抽象类中它才称为纯虚函数,在其派生类中虽然继承了该函数,但除非再次用"=0"把它声明为纯虚函数,否则它就不是也不能称为纯虚函数。如示例中Point类的shapeName()函数不能称为纯虚函数,只是虚函数。
  8. 使用虚函数提高了程序的可扩充性。

        利用虚函数和多态性,程序员的注意力集中在处理普遍性,而让执行环境处理特殊性。多态性把操作的细节留给设计者去完成。

        虚函数允许在派生类(derived class)中重写(override)基类(base class)中的函数。当使用基类的指针或引用来引用派生类对象,并调用一个虚函数时,会执行派生类中的重写版本,而不是基类中的原始版本。这就是所谓的“动态绑定”或“运行时绑定”。

        多态性是面向对象编程的四大特征之一(封装、继承、多态、抽象),它允许不同的对象对同一消息做出不同的响应。在C++中,多态性主要通过虚函数来实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值