第十篇 继承,虚函数和多态
本章中我们将讨论和面向对象编程直接相关的三个C++特性:继承,虚函数和多态。
继承是允许一个类继承另外一个类特征的特性。使用继承,我们通过一个类来定义一组相关对象的通用特征。这个类可以被另外一些更具体的类继承,每个类都可以增加其特有的属性。而虚函数正是以类的继承为基础的。虚函数支撑了多态性,也就是“同一个接口,多种实现方式”的面向对象的思想。
必备技能10.1:继承的基础知识
在C++中,被继承的类称为基类。继承别的类的类叫做派生类。因此可以说派生类是基类的特殊化。派生类继承了基类中的所有成员,并增加了自己所特有的成员。继承是由基类和派生类共同作用实现的。这是通过在声明派生类的时候指明其基类来完成的。下面我们通过一个简短的示例程序来讨论和继承相关的几个特性。
下面的程序创建了一个基类叫做TwoDShape,用于存储一个二维对象的宽带和高度,还定义了一个派生类叫做Triangle。特别注意下面类Triangle的声明方式。
// 一个简单的类的继承示例
#include <iostream>
#include <csting>
using namespace std;
//二维形状类
class TwoDShape
{
public:
double width;
double height;
void showDim()
{
cout << "Width and height are " << width << " and " << height << "\n";
}
};
//Triangle类是从TwoDShape类中继承而来的
class Triangle : public TwoDShape
{
public:
char style[20];
double area()
{
return width * height / 2;
}
void showStyle()
{
cout << "Triangle is " << style << "\n";
}
};
int main()
{
Triangle t1;
Triangle t2;
t1.width = 4.0;
t1.height = 4.0;
strcpy(t1.style,"isosceles");
t2.width = 8.0;
t2.height = 12.0;
strcpy(t2.style,"right");
cout << "Info for t1:\n";
t1.showStyle();
t1.showDim();
cout << "\n";
cout << "Info for t2:\n";
t2.showStyle();
t2.showDim();
cout << "Area is " << t2.area() << "\n";
return 0;
}
上面程序的输出结果如下:
Info for t1:Triangle is isosceles
Width and height are 4 and 4
Info for t2:Triangle is right
Width and height are 8 and 12Area is 48
其中,TwoDShape类定义了二维形状的通用属性,比如正方形,长方形,三角形等等。而Triangle(三角形)类则是TwoDShape类的具体的例子。三角形类包含了TwoDShape这个类的所有成员,并且还增加了字段style,函数area()和showStyle()。关于三角形类型的描述存储在style字段中,area()函数用于计算并返回三角形的面积,showStyle()函数用于显示三角形的类型。
class Triangle : public TwoDShape
其中,TwoDShape是Triangle类所继承的类,也就是基类。正如这个示例程序演示的那样,类继承的语法是很简单的,也是很方便使用的。
由于Triangle类包含了它的基类TwoDShape的所有成员,因此它就可以在area()函数中访问width和height。同样,在main()函数中,t1,t2都可以直接访问width和height,就像它们是Triangle的一部分一样。图10-1从概念上刻画了TwoDShape和Triangle类的直接关系。
┌────────────┐
┌─│ width │─┐
│ ├────────────┤ │
TwoDShap │ │ height │ │
│ ├────────────┤ │
└─│ showDim() │ │
├────────────┤ │
│ style │ │Triangle
├────────────┤ │
│ area() │ │
├────────────┤ │
│showStyle() │─┘
└────────────┘
图10-1 Triangle类的构成
另外的一点:尽管TwoDShape是Triangle类的基类,它仍然是一个完全对立的类。作为一个派生类的基类并不是说这个类就不能单独使用了。
声明继承关系的通用形式如下:
class 派生类: 访问限定符 基类
{
// 派生类的定义体
}
其中的访问限定符是可选的。然而,如果需要明确指定,只能是public, private或者是protected。我们将在本章的后面对它们进行更多的讨论。到目前为止,我们所有的继承关系都将使用public。使用public的继承关系意味着基类中所有的公有的成员在派生类中依然是公有的。
继承的一个主要好处就是一旦我们创建了一组对象所共有特性的基类后,我们就可以创建任意数量的具体特殊性质的派生类。每个派生类都可以精确地根据分类来进行剪裁。例如,下面就是另外一个从TwoDShape类继承的类Rectangle,它封装了对矩形的操作:
class Rectangle:public TwoDShape
{
public:
bool isSquare()
{
if ( width == height )
{
return true;
}
else
{
return false;
}
}
double area()
{
return width * height;
}
};
这里的Rectangle类包含了TwoDShape类的全部成员,并增加了函数isSquare(),用来判断该矩形是不是正方形。同时,增加了函数area(),用来计算矩形的面积。
继承与成员的访问权限
正如我们在第八篇中学习到的那样,类的成员经常会被声明为是私有的,以避免未经授权的使用或者篡改。继承机制并没有推翻私有访问权限的限制。然而,尽管派生类中包含了基类的所有成员,但是它不能访问基类中的那些私有成员。例如,如果在TwoDShape类中width和height都是私有的,如下代码所示,那么Triangle类就是不能访问它们的。
//二维形状类
class TwoDShape
{
// width 和 height是私有的
double width;
double height;
public: void showDim()
{
cout << "Width and height are " << width << " and " << height << "\n";
}
};
//Triangle类是从TwoDShape类中继承而来的
class Triangle : public TwoDShape
{
public:
char style[20];
double area()
{
return width * height / 2; //错误,Triangle类不能访问基类的私有成员
}
void showStyle()
{
cout << "Triangle is " << style << "\n";
}
};
上面中Triangle类将不能被成功编译。这是因为其中的area()函数中引用了基类的私有数据成员width和height,这是违反规则的。由于width和height成员现在是私有的,只有TwoDShape类本身可以访问它们,其派生类是无权访问它们的。
乍看起来,我们可能会认为派生类不能访问基类的私有数据成员貌似是一种过分严格的限制,因为在很多情况下,这将使得我们无法访问私有的成员。幸运的是,实际情况并非如此,C++提供了几种不同的方法来解决这个问题。其中之一就是使用protected(保护)成员,这点我们将在下面的小节中进行讨论。还有一种方法就是提供公有的函数来实现对私有数据的访问。正如我们在前面篇章中看到的那样,C++程序员通常会通过公有的函数来实现对私有数据成员的访问。提供访问私有数据成员的函数被称为是访问函数。下面代码中,我们就采用了访问函数的方式来提供对TwoDShape类的私有数据成员的访问:
// 一个简单的类的继承示例
#include <iostream>
#include <cstring>
using namespace std;
//二维形状类
class TwoDShape
{
double width;
double height;
public: void showDim()
{
cout << "Width and height are " << width << " and " << height << "\n";
}
double getWidth()
{
return width;
};
double getHeight()
{
return height;
};
void setWidth(double w )
{
width = w ;
};
void setHeight(double h )
{
height = h;
};
};
//Triangle类是从TwoDShape类中继承而来的
class Triangle : public TwoDShape
{
public:
char style[20];
double area()
{
//return width * height / 2;
return getWidth() * getHeight() / 2;
}
void showStyle()
{
cout << "Triangle is " << style << "\n";
}
};
int main()
{
Triangle t1;
Triangle t2;
//t1.width = 4.0;
//t1.height = 4.0;
t1.setWidth(4.0);
t1.setHeight(4.0);
strcpy(t1.style,"isosceles");
//t2.width = 8.0;
//t2.height = 12.0;
t2.setWidth(8.0);
t2.setHeight(12.0);
strcpy(t2.style,"right");
cout << "Info for t1:\n"; t1.showStyle(); t1.showDim();
cout << "\n";
cout << "Info for t2:\n"; t2.showStyle(); t2.showDim();
cout << "Area is " << t2.area() << "\n";
return 0;
}
练习
1. 基类是通过怎样的方式被派生类继承的?
2. 派生类中是否包含了基类中的成员?
3. 派生类是否可以访问基类中私有的成员?