在深入学习本章之前,您应该对指针和类继承有一个正确的理解。 如果你不是很确定以下任何一个表达的意思,你应该查看指定的部分:
声明 | 场景 |
int A::b(int c) { } | class |
a->b | Data structures |
class A: public B {}; | Friendship and inheritance |
类继承的一个关键特性是,指向派生类的指针与指向基类的指针类型兼容。 多态性是利用这个简单但功能强大且通用的特性的艺术。
对于这个特性,关于矩形和三角形类的例子可以使用指针来重写: // pointers to base class #include <iostream> using namespace std; class Polygon { protected: int width, height; public: void set_values (int a, int b) { width=a; height=b; } }; class Rectangle: public Polygon { public: int area() { return width*height; } }; class Triangle: public Polygon { public: int area() { return width*height/2; } }; int main () { Rectangle rect; Triangle trgl; Polygon * ppoly1 = ▭ Polygon * ppoly2 = &trgl; ppoly1->set_values (4,5); ppoly2->set_values (4,5); cout << rect.area() << '\n'; cout << trgl.area() << '\n'; return 0; } |
20 10 |
函数main声明了两个指向Polygon的指针(命名为ppoly1和ppoly2)。 它们分别被分配了rect和trgl的地址,它们是Rectangle和Triangle类型的对象。 这样的赋值是有效的,因为Rectangle 和Triangle 都是从Polygon派生出来的类。
解引用ppoly1和ppoly2(使用ppoly1->和ppoly2->)是有效的,并允许我们访问它们的指向对象的成员。 例如,下面两个语句在前面的例子中是等价的:
ppoly1->set_values (4,5); rect.set_values (4,5); |
但是因为ppoly1和ppoly2的类型都是指向Polygon的指针(不是指向Rectangle的指针, 也不是指向Triangle的指针),所以只能访问从Polygon继承的成员, 而不能访问派生类Rectangle和Triangle的成员。 这就是为什么上面的程序直接使用rect和trgl访问两个对象的area成员, 而不是使用指针;指向基类的指针不能访问area 成员。
如果area是Polygon的成员而不是其派生类的成员,则可以使用Polygon的指针访问成员area, 但问题是,Rectangle 和Triangle 实现了不同版本的area,因此没有一个共同的版本,可以在基类中实现。
虚成员函数是可以在派生类中重新定义的成员函数,同时通过引用保留其调用属性。 函数变为虚函数的语法是在声明函数之前加上virtual关键字:
// virtual members #include <iostream> using namespace std; class Polygon { protected: int width, height; public: void set_values (int a, int b) { width=a; height=b; } virtual int area () { return 0; } }; class Rectangle: public Polygon { public: int area () { return width * height; } }; class Triangle: public Polygon { public: int area () { return (width * height / 2); } }; int main () { Rectangle rect; Triangle trgl; Polygon poly; Polygon * ppoly1 = ▭ Polygon * ppoly2 = &trgl; Polygon * ppoly3 = &poly; ppoly1->set_values (4,5); ppoly2->set_values (4,5); ppoly3->set_values (4,5); cout << ppoly1->area() << '\n'; cout << ppoly2->area() << '\n'; cout << ppoly3->area() << '\n'; return 0; } |
20 10 0 |
在这个例子中,所有三个类(Polygon, Rectangle和Triangle) 都有相同的成员:width, height,函数set_values和area。
成员函数area 在基类中声明为虚函数,因为它稍后会在每个派生类中重新定义。 非虚成员也可以在派生类中重新定义,但不能通过基类的引用访问派生类的非虚成员: 也就是说,如果在上面的例子中,virtual从area的声明中删除, 那么对area的所有三个调用都将返回0,因为在任何情况下,基类的版本都将被调用。
因此,从本质上讲,virtual关键字所做的就是允许从指针调用与基类同名的派生类成员, 更准确地说,指针的类型是指向基类的指针,基类指向派生类的对象,如上面的例子所示。
声明或继承虚函数的类称为多态类。
请注意,尽管它的一个成员是虚拟的,Polygon是一个常规类, 甚至一个对象也被实例化(poly),它自己定义的成员area 总是返回0。
抽象基类与前面例子中的Polygon类非常相似。它们是只能作为基类使用的类, 因此允许具有没有定义的虚成员函数(称为纯虚函数)。语法是用=0(等号和零)替换它们的定义:
一个抽象基类Polygon可以是这样的:
// abstract class CPolygon class Polygon { protected: int width, height; public: void set_values (int a, int b) { width=a; height=b; } virtual int area () =0; }; |
注意这个area 没有定义;这个被=0代替了,这使它成为一个纯虚函数。 包含至少一个纯虚函数的类称为抽象基类。
抽象基类不能用于实例化对象。因此,最后一个抽象基类版本的Polygon不能用于声明如下对象:
Polygon mypolygon; // not working if Polygon is abstract base class |
但是抽象基类并不是无用的。它可以用来创建指向它的指针,并利用它的多态能力。 例如,下面的指针声明是有效的:
Polygon * ppoly1; Polygon * ppoly2; |
当指向派生(非抽象)类的对象时,实际上可以解引用。下面是整个例子:
// abstract base class #include <iostream> using namespace std; class Polygon { protected: int width, height; public: void set_values (int a, int b) { width=a; height=b; } virtual int area (void) =0; }; class Rectangle: public Polygon { public: int area (void) { return (width * height); } }; class Triangle: public Polygon { public: int area (void) { return (width * height / 2); } }; int main () { Rectangle rect; Triangle trgl; Polygon * ppoly1 = ▭ Polygon * ppoly2 = &trgl; ppoly1->set_values (4,5); ppoly2->set_values (4,5); cout << ppoly1->area() << '\n'; cout << ppoly2->area() << '\n'; return 0; } |
20 10 |
在本例中,不同但相关类型的对象使用唯一类型的指针(Polygon*)引用, 并且每次都调用相应的成员函数,因为它们是虚的。在某些情况下,这非常有用。 例如,抽象基类Polygon的成员甚至可以使用特殊指针this来访问相应的虚成员, 即使Polygon本身没有此函数的实现:
// pure virtual members can be called // from the abstract base class #include <iostream> using namespace std; class Polygon { protected: int width, height; public: void set_values (int a, int b) { width=a; height=b; } virtual int area() =0; void printarea() { cout << this->area() << '\n'; } }; class Rectangle: public Polygon { public: int area (void) { return (width * height); } }; class Triangle: public Polygon { public: int area (void) { return (width * height / 2); } }; int main () { Rectangle rect; Triangle trgl; Polygon * ppoly1 = ▭ Polygon * ppoly2 = &trgl; ppoly1->set_values (4,5); ppoly2->set_values (4,5); ppoly1->printarea(); ppoly2->printarea(); return 0; } |
20 10 |
虚成员和抽象类赋予c++多态特征,这对面向对象的项目最有用。 当然,上面的例子是非常简单的用例,但是这些特性可以应用于对象数组或动态分配的对象。
下面是一个结合了最新章节中的一些特性的例子,比如动态内存、构造函数初始化器和多态性:
// dynamic allocation and polymorphism #include <iostream> using namespace std; class Polygon { protected: int width, height; public: Polygon (int a, int b) : width(a), height(b) {} virtual int area (void) =0; void printarea() { cout << this->area() << '\n'; } }; class Rectangle: public Polygon { public: Rectangle(int a,int b) : Polygon(a,b) {} int area() { return width*height; } }; class Triangle: public Polygon { public: Triangle(int a,int b) : Polygon(a,b) {} int area() { return width*height/2; } }; int main () { Polygon * ppoly1 = new Rectangle (4,5); Polygon * ppoly2 = new Triangle (4,5); ppoly1->printarea(); ppoly2->printarea(); delete ppoly1; delete ppoly2; return 0; } |
20 10 |
注意ppoly指针:
Polygon * ppoly1 = new Rectangle (4,5); Polygon * ppoly2 = new Triangle (4,5); |
被声明为“指向 Polygon的指针”类型,但是被分配的对象被直接声明为派生类类型(Rectangle 和 Triangle)。