上一篇中(https://blog.youkuaiyun.com/wwt_lb1314/article/details/80915986),我们讲了继承与派生的概念、关系、继承权限等一些基础的东西,那么在这一篇中,我们再研究以下继承体系下的作用域以及派生类的对象模型。
继承体系下的作用域
- 在继承体系中基类和派生类都有独立的作用域。
- 基类(父类)和派生类(子类)中有同名成员,子类成员将屏蔽父类对成员的直接访问,即同名隐藏或重定义(发生同名隐藏后,在子类成员函数中,可使用 基类::基类成员 访问基类)
- 注意在实际的继承体系中最好不要定义同名的成员。
上面的第二条很重要,但是只看文字可能不是很好理解,下面我来举一个例子:
class Fruit//父类
{
public:
int price;
};
class Apple :public Fruit//派生类公有继承自基类
{
public:
int price;
};
int main()
{
Apple a;//派生类对象
Fruit f;//基类对象
a.price = 15;
return 0;
}
在上面的代码中,基类和派生类中都定义了 price 成员,所以就会发生同名隐藏,如此一来,在代码的倒数第三行,访问的就是派生类的 price 成员,基类的就被屏蔽了。这就是同名隐藏(重定义),只要成员名或成员函数名相同都会发生同名隐藏。
我们再来看一个同名成员函数的例子:
class AA
{
public:
void f()
{
cout << "AA::f()" << endl;
}
};
class BB :public AA
{
void f()
{
cout << "BB::f()" << endl;
}
};
int mian()
{
AA aa;
BB bb;
aa.f();
bb.f();
//A、BB里面有两个f函数,并且构成重载
//B、BB里面有两个f函数,并且构成隐藏
//C、上面代码编译不过
//D、上面代码可以编译通过,输出AA::f()
return 0;
}
可以看到,在程序中有4个选项,好像并不好选,这隐藏怎么又和重载联系上了呢?
下面我就来依次分析一下:
A选项:构成函数重载的两个函数要求在同一个作用域,很明显,基类和派生类是两个不同的作用域,错误。
B选项:BB既继承了父类的 f() 函数,又有自己的 f() 函数,所以共有2个 f() 函数,同名函数构成隐藏,正确。
C选项:既然发生了同名隐藏,那么就无法访问到基类的成员函数,程序肯定编译不过。
D选项:由C可知,错误。
不同继承体系下的对象模型
对象模型:对象中的非静态成员变量在内存中的布局形式。
我们直到,继承权限共有3种(公有、保护、私有),但是继承体系却是有5种,分别为单继承、多继承、菱形继承、虚拟继承、菱形虚拟继承。
我们来分别看一下每种继承体系下派生类的大小以及基类和派生类在内存中的布局形式。
单继承:一个类是从一个基类派生而来的
例如:
class B1
{
public:
int _b1;
};
class D:public B1
{
public:
int _d;
};
int main()
{
cout << sizeof(D) << endl;
D d;
d._b1 = 0;
d._d = 2;
return 0;
}
派生类大小为:
内存布局为:
由此可见,单继承体系下,派生类的大小就是基类的大小+派生类自己的大小,并且在内存中,基类在上,派生类在下,图示为:
多继承:一个派生类有两个或多个基类。
在声明多继承时,每一个基类前都要加上继承权限,否则就会使用默认的权限。
注意:一个基类不能被继承两次。
例如:
class B1
{
public:
int _b1;
};
class B2
{
public:
int _b2;
};
class D:public B1,public B2
{
public:
int _d;
};
int main()
{
cout << sizeof(D) << endl;
D d;
d._b1 = 0;
d._b2 = 1;
d._d = 2;
return 0;
}
派生类大小为:
内存布局为:
由此可见,在多继承体系下,派生类的的大小为所有继承的基类大小之和+派生类自己的大小,在内存布局中,先继承的基类在最上面,派生类自己在最下面。图示为:
菱形继承:也称为钻石继承。即假设有基类B,声明派生类C1、C2分别单继承自B,再声明派生类D多继承自C1、C2。
可以用下图来表示:
例如:
class B
{
public:
int _b;
};
class C1:public B
{
public:
int _c1;
};
class C2 :public B
{
public:
int _c2;
};
class D :public C1, public C2
{
public:
int _d;
};
int main()
{
cout << sizeof(D) << endl;
D d;
d.C1::_b = 1;
d._c1 = 2;
d.C2::_b = 3;
d._c2 = 4;
d._d = 5;
return 0;
}
派生类大小为:
内存布局为:
为什么是这样,我们来具体分析一下:
在菱形继承下,派生类的大小的大小=单继承自基类的派生类1的大小+单继承自基类的派生类1的大小+派生类自己的大小。
虚拟继承:在继承方式前加 virtual 关键字。
例如:
class B
{
public:
int _b;
};
class D :virtual public B//虚拟继承必须在继承方式前加 virtual 关键字
{
public:
int _d;
};
int main()
{
cout << sizeof(D) << endl;
D d;
d._b = 1;//给基类对象赋值
d._d = 2;//给派生类对象赋值
return 0;
}
派生类大小为:
内存布局为:
可以看到,在内存中,多了4个字节,那么这4个字节是用来干什么的呢?
虚拟继承派生类的大小=基类大小+派生类大小+4。
菱形虚拟继承:为了解决菱形继承的二义性问题,引入了菱形虚拟继承。
具体声明方式如下:
例如:
class B
{
public:
void TestFunc()
{}
public:
int _b;
};
class C1:virtual public B
{
public:
int _c1;
};
class C2 :virtual public B
{
public:
int _c2;
};
class D :public C1, public C2
{
public:
int _d;
};
int main()
{
cout << sizeof(D) << endl;
D d;
d._b = 1;
d._c1 = 2;
d._c2 = 3;
d._d = 4;
return 0;
}
派生类大小为:
内存布局为:
依然画个图分析一下:
在菱形虚拟继承中,派生类的大小=单继承自基类的派生类1的大小+单继承自基类的派生类1的大小+派生类自己的大小+4。