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的类型怎么表示?
可以为ArrayStack
和LinkedStack
提供一个抽象基类:
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所指向的对象有哪些数据成员,
//因此,无法访问它的数据成员
}