假设定义了以下的基类和派生类:
class shape
{
public:
shape(int a){}; //基类构造
int length(){}; //基类中与子类重名函数
void display(){}; //基类中独有
vitual int area(){}; //基类中虚函数
}
class rectangle:public shape
{
public:
rectangle(int);
int length();
int area();
}
1. 派生类的定义
rectangle::rectangle(int a):shape(int a){}; //派生类中使用基类构造函数
void rectangle::length()
{
shape::length(); //派生类中使用重名的父类函数
display(); //派生类中使用父类中独有函数
}
2. 派生类实例的使用
rectangle rect();
shape theshape();
rect.length(); //调用rectangle::length()
rect.shape::length(); //显式指定调用基类函数shape::length()
rect.display(); //display()被继承,调用shape::display()
3. 对象的成员变量与成员函数的不同
成员变量在对象初始化之后,仍然可以进行赋值,更改。事实上,对对象的任何操作,也仅仅是对它的成员变量进行操作。
成员函数在对象初始化那一刻使确定下来,成员函数是根据该对象的类型进行绑定的,是编译期已经确定的,静态绑定。
各个实例的成员变量的地址是不同的,与实例的首地址有关。
各个实例的成员函数其实是共用的,因为只与他们的类型相关。
成员变量的地址(包括虚表变量)是按照各实例的首地址给的,各实例之间不相同。
成员函数的地址是按照实例的类型给的,各实例间相同。
4. 类型的转换
要区分对象的类型转换与对象指针的类型转换:
- 实际上,指针的类型可以任意转换,只是指针指向的地址发生变化而已。
- 对象的类型转换,实际上是将右值的所有成员变量的值赋给左值。注意:右值的虚表变量不会赋值给左值!
- 就地转换,实际上是隐含地调用了一次转换后类型的构造函数,产生了一个临时对象,将这个临时对象作为左值。
rectangle rect;
shape shp;
shp = (shape)rect; //合法,将rect中与shp相同的那部分成员变量赋给shp
//等价于 shp.a = rect.a, shp.b = rect.b
// shp.vtable并没有被rect.vtable覆盖! 仍然是原来的。
rect = (rectangle)shp; //不合法,因为rect的一些成员变量,shp并没有,所以赋值时要出错
//rect.a = shp.a, rect.b = shp.b,rect.c = shp.?
rectangle* prect;
shape* pshp;
pshp = ▭ //pshp指向了rect的首地址
//pshp指向的成员变量是rect的成员变量
//(pshp可以指向一些shape类型没有的成员变量)
// 但pshp绑定的成员函数还是shape类型的成员函数。
//pshp->vtable就是rect.vtable, 但pshp->length()调用的是 shape::length();
prect = &shp; //合法,但是没有实际意义
5. 虚成员函数与普通成员函数
上面已提了,一个实例的普通成员函数在编译期已经根据它的类型确实了。而虚函数不是这样的,它的调用是根椐实例的成员变量虚表查到的。
rectangle rect;
shape shp;
shp.area(); //其实是shp.vtable->area();
rect.area(); //rect.vtable->area();
shape* pshp;
pshp = ▭
pshp->area(); //实际上是pshp->vtable->area(),而pshp->vtable == rect.vtable,所以调用的是 rectangle::area();
pshp->length(); //length是普通的成员函数,根据pshp的类型确定,所以调用的是shape::length();
((shape)rect).area(); //就地转换并调用,相当于下面的的两行代码
shape shp2 = rect;
shp2.area(); //shp2的成员变量被rect的成员变量覆盖,但shp2.vtable并没有变成rect.vtable,仍然调用shape::area();
6. 虚函数的用途:统一形式的调用
shape* pshps = [&triangle(), &rectangel()];
for(auto ipshp: pshps)
ipshp->area(); //分别调用了各个不同类型实例的area();