FP-16 抽象类

Lecture 16 抽象类

纯虚函数

纯虚函数是没给出实现的虚函数,函数体用“=0”表示, 例如:

class A
{	......
	public:
		virtual int f()=0; //纯虚函数
	......
};

纯虚函数需要在派生类中给出实现。

抽象类

包含纯虚函数的类称为抽象类。

抽象类不能用于创建对象。 例如:

class A //抽象类
{	......
	public:
		virtual int f()=0; //纯虚函数
	......
};
......
A a;  //Error,A是抽象类

抽象类的作用是为派生类提供一个基本框架和一个公共的对外接口。

例:用抽象类为各种图形类提供一个基本框架

class Figure //抽象基类
{	public:
		virtual void draw() const=0;
		virtual void input_data()=0;
};
class Rectangle: public Figure
{		double left,top,right,bottom;
	public:
		void draw() const
		{	...... //画矩形
		}
		void input_data()
		{	cout << "请输入矩形的左上角和右下角坐标 (x1,y1,x2,y2) :";
			cin >> left >> top >> right >> bottom;
		}
		double area() const 
	 { return (bottom-top)*(right-left); }
};

const double PI=3.1416;
class Circle: public Figure
{		double x,y,r;
	public:
		void draw() const
		{	...... //画圆
		}
		void input_data()
		{	cout << "请输入圆的圆心坐标和半径 (x,y,r) :";
			cin >> x >> y >> r;
		}
		double area() const { return r*r*PI; }
};

class Line: public Figure
{		double x1,y1,x2,y2;
	public:
		void draw() const
		{	...... //画线
		}
		void input_data()
		{	cout << "请输入线段的起点和终点坐标 (x1,y1,x2,y2) :";
			cin >> x1 >> y1 >> x2 >> y2;
		}
};
......
const int MAX_NUM_OF_FIGURES=100;
Figure *figures[MAX_NUM_OF_FIGURES];
int count=0;

图形数据的输入:

for (count=0; count<MAX_NUM_OF_FIGURES;	count++)
{	int shape;
	do
	{	cout << "请输入图形的种类(0:线段,1:矩形,2:圆,-1:结束):";
		cin >> shape;
	} while (shape < -1 || shape > 2);
	if (shape == -1) break;
	switch (shape)
	{	case 0: //线
			figures[count] = new Line;	break;
		case 1: //矩形
			figures[count] = new Rectangle; break;
		case 2: //圆
			figures[count] = new Circle; break;
 	}
	figures[count]->input_data(); //动态绑定到相应类的input_data
}

图形的输出:

for (int i=0; i<count; i++) 
	figures[i]->draw(); 
	//通过动态绑定调用相应类的draw

即使今后增加了图形的种类,

  • 上述代码(属于高层代码)不需要改动!

  • 只需要增加相应的类(属于低层代码)就行。

  • 这体现了多态带来的好处:实现高层代码的复用

用联合类型实现

struct Line { double x1,y1,x2,y2; };
struct Rectangle { double left,top,right,bottom; };
struct Circle { double x,y,r; };
union Figure
{	Line line;
	Rectangle rect;
	Circle circle; 
};
struct TaggedFigure
{	int shape;
	Figure figure; 
};
const int MAX_NUM_OF_FIGURES=100;
TaggedFigure *figures[MAX_NUM_OF_FIGURES];
void input_data(Line &line)
{ cout << "请输入线段的起点和终点坐标 (x1,y1,x2,y2) :";
   cin >> line.x1 >> line.y1 >> line.x2 >> line.y2;
}
void input_data(Rectangle &rect)
{ cout << "请输入矩形的左上角和右下角坐标 (x1,y1,x2,y2) :";
   cin >> rect.left >> rect.top >> rect.right >> rect.bottom;
} 
void input_data(Circle &circle)
{ cout << "请输入圆的圆心坐标和半径 (x,y,r) :";
   cin >> circle.x >> circle.y >> circle.r;
}
void draw(Line &line) { ...... } 
void draw(Rectangle &rect) { ...... }
void draw(Circle &circle) { ...... }
double area(Rectangle &rect) 
{ return (rect.bottom- rect.top)*(rect.right- rect.left); 
}
double area(Circle &circle)
{ return circle.r*circle.r*PI;
}

图形数据的输入:

int count;
for (count=0; count<MAX_NUM_OF_FIGURES; count++)
{	int shape;
	do
	{	cout << "请输入图形的种类(0:线段,1:矩形,2:圆,-1:结束):";
		cin >> shape;
	} while (shape < -1 || shape > 2);
	if (shape == -1) break;
	figures[count] = new TaggedFigure; //空间利用效率不高!
	switch (shape)
	{ 	case 0: //线
			figures[count]->shape = 0;
			input_data(figures[count]->figure.line);
  			break;
	  	case 1: //矩形
			figures[count]->shape = 1;
			input_data(figures[count]->figure.rect);
 			break;
 		case 2: //圆形
			figures[count]->shape = 2;
			input_data(figures[count]->figure.circle);
  	 		break;
	 	} //end of switch
	} //end of for

图形的输出:

for (int i=0; i<count; i++)
{	switch (figures[i]->shape)
		{ case 0:
					draw(figures[i]->figure.line);
					break;
			case 1:
					draw(figures[i]->figure.rect);
					break;
			case 2:
					draw(figures[i]->figure.circle);
 					break;
 		}
}

增加新的图形种类时,需要修改上述代码(增加分支)。

例:用抽象类实现抽象数据类型Stack

抽象数据类型(abstract data type)是指只考虑类型的抽象性质,而不考虑该类型的实现。

例如,从抽象数据类型的角度来讲,栈类型是由若干具有线性关系的元素构成,它包含两个操作push和pop:

  • s.push(a).pop(x)操作之后,条件x == a成立。

  • 上面就是栈这个抽象数据类型所包含的全部内容,它与栈的内部是如何实现的无关。

C++把抽象数据类型与抽象数据类型的实现两者合二为一了,它们都用类来表示。这样就带来了问题:

  • 对于一个用数组实现的栈类ArrayStack和一个用链表实现的栈类LinkedStack,虽然从抽象数据类型的角度看,它们是相同的类型,但在C++中它们却是不同的类型。

  • 对于函数:void f(T& st);,如果要求它既可以接受ArrayStack类的对象,也可以接受LinkedStack类的对象,那么,它的参数T的类型怎么表示?

可以为ArrayStackLinkedStack提供一个抽象基类:

class Stack
{	public:
		virtual void push(int i)=0;
		virtual void pop(int& i)=0;
};
class ArrayStack: public Stack
{		int elements[100],top;
  public:
   		ArrayStack() { top = -1; }
		void push(int i) { ......}
		void pop(int& i) { ...... }
};
class LinkedStack: public Stack
{		struct Node
		{	int content;
			Node *next;
		} *first;
	public:
		LinkedStack() { first = NULL; }
		void push(int i) { ......}
		void pop(int& i) { ...... }
};
void f(Stack *p)
{	......
	p->push(...);  //将根据p所指向的对象类来确定
			       //push的归属。
	......
	p->pop(...);  //将根据p所指向的对象类来确定
			      //pop的归属。
	......
}
int main()
{	ArrayStack st1;
	LinkedStack st2;
	f(&st1);  //OK
	f(&st2);  //OK
	......
}

用抽象类实现类的真正抽象作用

在C++中把抽象数据类型与抽象数据类型的实现合而为一都用类来表示。

由于在C++中使用某个类时必须要见到该类的定义,因此,使用者能够见到该类的非public成员,这样就有手段绕过类的访问控制而使用类的非public成员。

例如:

//A.h (类A的对外接口)
class A
{	int i,j;
  public:
	A();
	A(int x,int y);
	void f(int x);
};

//A.cpp (类A的实现,不公开)
#include "A.h"
void A::A() { ...... }
void A::A(int x,int y) { ...... }
void A::f(int x) { ...... }
......

//B.cpp (A类对象的某个使用者)
#include "A.h"
void func(A *p)  //绕过对象类的访问控制!
{	p->f(2); //Ok
	p->i = 1; //Error
	p->j = 2; //Error
	*((int *)p) = 1; //Ok,访问p所指向的对象的成员i
	*((int *)p+1) = 2; //Ok,访问p所指向的对象的成员j
}

如何防止上面的情况?

用抽象类I_A给类A提供一个抽象接口

//I_A.h (类A的对外接口)
class I_A
{ public:
   	  virtual void f(int)=0;
};

//A.cpp (类A的实现,不公开)
#include "I_A.h"
class A: public I_A
{	int i,j;
   public:
	A();
	A(int x,int y);
	void f(int x);
};
void A::A() { ...... }
void A::A(int x,int y) { ...... }
void A::f(int x) { ...... }
......

//B.cpp (A类对象的某个使用者)
#include "I_A.h"
void func(I_A *p)
{	p->f(2);  //Ok

	......  //这里不知道p所指向的对象有哪些数据成员,
		   //因此,无法访问它的数据成员
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值