继承
引出继承
点击查看代码
class IndexPage {
public:
//网页头部
void Header() {
cout << "网页头部!" << endl;
}
//网页左侧菜单
void LeftNavigation() {
cout << "左侧导航菜单!" << endl;
}
//网页主体部分
void MainBody() {
cout << "首页网页主题内容!" << endl;
}
//网页底部
void Footer() {
cout << "网页底部!" << endl;
}
private:
string mTitle; //网页标题
};
//如果不使用继承,那么定义新闻页类,需要重新写一遍已经有的代码
class NewsPage {
public:
//网页头部
void Header() {
cout << "网页头部!" << endl;
}
//网页左侧菜单
void LeftNavigation() {
cout << "左侧导航菜单!" << endl;
}
//网页主体部分
void MainBody() {
cout << "新闻网页主体内容!" << endl;
}
//网页底部
void Footer() {
cout << "网页底部!" << endl;
}
private:
string mTitle; //网页标题
};
void test() {
NewsPage* newspage = new NewsPage;
newspage->Header();
newspage->MainBody();
newspage->LeftNavigation();
newspage->Footer();
IndexPage* indexpage = new IndexPage;
indexpage->Header();
indexpage->MainBody();
indexpage->LeftNavigation();
indexpage->Footer();
}
#else
//使用继承,可以复用已有的代码,新闻业除了主体部分不一样,其他都是一样的
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//继承写法:
//抽象一个 基类的网页 重复的代码都写到这个网页上
class BasePage
{
public:
void header()
{
cout << "公共头部" << endl;
}
void footer()
{
cout << "公共底部" << endl;
}
void left()
{
cout << "左侧列表" << endl;
}
};
class News :public BasePage//继承:News类 继承于 BasePage类
{
public:
void content()
{
cout << "新闻播放" << endl;
}
};
class Amusement :public BasePage
{
public:
void content()
{
cout << "娱乐播放" << endl;
}
};
class Game :public BasePage
{
public:
void content()
{
cout << "LOL总决赛" << endl;
}
};
void test()
{
cout << "新闻网页内容" << endl;
News news;
news.header();
news.footer();
news.left();
news.content();
cout << endl;
cout << "娱乐网页内容" << endl;
Amusement amusement;
amusement.header();
amusement.footer();
amusement.left();
amusement.content();
cout << endl;
cout << "游戏网页内容" << endl;
Game game;
game.header();
game.footer();
game.left();
game.content();
}
//继承:减少代码冗余
int main()
{
test();
system("pause");
return 0;
}
继承的方式
c++最重要的特征是代码重用,通过继承机制可以利用已有的数据类型来定义新的数据类型,新的类不仅拥有旧类的成员,还拥有新定义的成员。
一个B类继承于A类,或称从类A派生类B。这样的话,类A成为基类(父类), 类B成为派生类(子类)。
派生类中的成员,包含两大部分:
- 一类是从基类继承过来的,一类是自己增加的成员。
- 从基类继承过过来的表现其共性,而新增的成员体现了其个性。
派生类定义格式:
Class 派生类名 : 继承方式 基类名
{
//派生类新增的数据成员和成员函数
}
三种继承方式:
- public : 公有继承
- private : 私有继承
- protected : 保护继承
从继承源上分:
单继承:指每个派生类只直接继承了一个基类的特征
多继承:指多个基类派生出一个派生类的继承关系,多继承的派生类直接继承了不止一个基类的特征
4.7.2 派生类访问控制
- 派生类继承基类,派生类拥有基类中全部成员变量和成员方法(除了构造和析构之外的成员方法),但是在派生类中,继承的成员并不一定能直接访问,不同的继承方式会导致不同的访问权限。
- 派生类的访问权限规则如下:
类的继承方式代码说明
//1.继承的方式:公有继承,私有继承,保护继承
//2.公有继承在类内:可以访问父类的公有成员,保护成员,不可以访问父类的私有成员
//3.公有继承在类外:可以访问父类的公有成员,其他的不行
//4.私有继承在类内:可以访问父类的公有成员,保护成员。不可以访问父类的私有成员
//5.私有继承在类外:都不可以访问父类的成员
//6.保护继承在类内:可以访问父类的公有成员,保护成员,不可以访问父类的私有成员
//7.保护继承在类外:不可以访问父类的成员
//基类
class A{
public:
int mA;
protected:
int mB;
private:
int mC;
};
//1. 公有(public)继承
class B : public A{
public:
void PrintB(){
cout << mA << endl; //可访问基类public属性
cout << mB << endl; //可访问基类protected属性
//cout << mC << endl; //不可访问基类private属性
}
};
class SubB : public B{
void PrintSubB(){
cout << mA << endl; //可访问基类public属性
cout << mB << endl; //可访问基类protected属性
//cout << mC << endl; //不可访问基类private属性
}
};
void test01(){
B b;
cout << b.mA << endl; //可访问基类public属性
//cout << b.mB << endl; //不可访问基类protected属性
//cout << b.mC << endl; //不可访问基类private属性
}
//2. 保护(protected)继承
class D : protected A{
public:
void PrintD(){
cout << mA << endl; //可访问基类public属性
cout << mB << endl; //可访问基类protected属性
//cout << mC << endl; //不可访问基类private属性
}
};
class SubD : public D{
void PrintD(){
cout << mA << endl; //可访问基类public属性
cout << mB << endl; //可访问基类protected属性
//cout << mC << endl; //不可访问基类private属性
}
};
void test03(){
D d;
//cout << d.mA << endl; //不可访问基类public属性
//cout << d.mB << endl; //不可访问基类protected属性
//cout << d.mC << endl; //不可访问基类private属性
}
//3. 私有(private)继承
class C : private A{
public:
void PrintC(){
cout << mA << endl; //可访问基类public属性
cout << mB << endl; //可访问基类protected属性
//cout << mC << endl; //不可访问基类private属性
}
};
class SubC : public C{
void PrintSubC(){
//cout << mA << endl; //不可访问基类public属性
//cout << mB << endl; //不可访问基类protected属性
//cout << mC << endl; //不可访问基类private属性
}
};
void test02(){
C c;
//cout << c.mA << endl; //不可访问基类public属性
//cout << c.mB << endl; //不可访问基类protected属性
//cout << c.mC << endl; //不可访问基类private属性
}
继承中的对象模型
继承中的对象模型
在C++编译器的内部可以理解为结构体,子类是由父类成员叠加子类新成员而成:
class Aclass{
public:
int mA;
int mB;
};
class Bclass : public Aclass{
public:
int mC;
};
class Cclass : public Bclass{
public:
int mD;
};
void test(){
cout << "A size:" << sizeof(Aclass) << endl;
cout << "B size:" << sizeof(Bclass) << endl;
cout << "C size:" << sizeof(Cclass) << endl;
}
子类中,会继承父类的私有成员,只是被编译器给隐藏起来了,访问不到私有成员
查看类继承的内部模型:
- 找到VS2013开发人员命令提示程序(一般在:C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\Tools\Shortcuts),
打开,然后复制你工程路径, - 命令:cd 路径,进入你工程文件夹中(如果工程不在C盘在E盘的话,要再E:下),
- 然后命令:cl /d1 reportSingleClassLayout类名 文件名全称
如:cl /d1 reportSingleClassLayoutSon test.cpp
结构图如下:
点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//基类
class Base
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son :public Base
{
public:
int m_D;
};
void test01()
{
cout << sizeof(Son) << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
继承中的构造与析构顺序
对象构造和析构的调用原则
继承中的构造和析构
- 子类对象在创建时会首先调用父类的构造函数
- 父类构造函数执行完毕后,才会调用子类的构造函数
- 当父类构造函数有参数时,需要在子类初始化列表(参数列表)中显式调用父类构造函数
- 析构函数调用顺序和构造函数相反
继承与组合混搭的构造和析构
class D{
public:
D(){
cout << "D类构造函数!" << endl;
}
~D(){
cout << "D类析构函数!" << endl;
}
};
class A{
public:
A(){
cout << "A类构造函数!" << endl;
}
~A(){
cout << "A类析构函数!" << endl;
}
};
class B : public A{
public:
B(){
cout << "B类构造函数!" << endl;
}
~B(){
cout << "B类析构函数!" << endl;
}
};
class C : public B{
public:
C(){
cout << "C类构造函数!" << endl;
}
~C(){
cout << "C类析构函数!" << endl;
}
public:
D c;
};
void test(){
C c;
}
代码示例1
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//基类
class Base
{
public:
Base(int a)//有参构造
{
this->m_A = a;
cout << "有参构造函数调用" << endl;
}
int m_A;
};
class Son :public Base
{
public:
//子类在调用自己的构造函数的时候,要先调用父类的默认构造,而父类的默认构造被有参构造替代了
//此时,要么父类再写一个无参构造,要么利用初始化列表方式 显式调用 有参构造
//子类继承了父类的构造函数,也不知道如何初始化,只有父类才知道如何去初始化自己的m_A
//所以我们不能把父类的构造函数也继承到子类里。
//如果父类没有合适的默认构造,那么子类可以利用初始话列表的方式显示地调用父类的其它构造。
//Son()
//{
//}
/*
使用有参构造也不行
Son(int a)
{
}
*/
Son(int a):Base(a)//只能利用初始化列表方式 显示调用 有参构造 //这种写法在QT中比较常见
{
}
};
void test02()
{
Son s2(1000);
}
void test01()
{
cout << sizeof(Son) << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
代码示例2
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//基类
class Base
{
public:
Base()//有参构造
{
cout << "Base有参构造函数调用" << endl;
}
~Base()
{
cout << "Base的析构函数调用" << endl;
}
int m_A;
};
//子类会继承父类的成员属性,成员函数
//但是子类不会继承父类的 构造函数 和 析构函数
//只有父类自己知道如果构造和析构自己的属性,而子类不知道
class Son :public Base
{
public:
Son()//有参构造
{
cout << "Son有参构造函数调用" << endl;
}
~Son()
{
cout << "Son的析构函数调用" << endl;
}
};
void test01()
{
//Base b1;
Son s1;
}
int main()
{
test01();
system("pause");
return 0;
}
继承中的同名成员处理方法
- 当子类成员和父类成员同名时,子类依然从父类继承同名成员
- 如果子类有成员和父类同名,子类访问其成员,默认访问子类的成员(本作用域,就近原则)
- 在子类通过作用域::进行同名成员区分(在子类中使用父类的同名成员,显示使用类名限定符)
public:
Base():mParam(0){}
void Print(){ cout << mParam << endl; }
public:
int mParam;
};
class Derived : public Base{
public:
Derived():mParam(10){}
void Print(){
//在子类中使用和父类的同名成员,显示使用类名限定符
cout << Base::mParam << endl;
cout << mParam << endl;
}
//返回基类重名成员
int& getBaseParam(){ return Base::mParam; }
public:
int mParam;
};
int main(){
Derived derived;
//派生类和基类成员属性重名,子类访问成员默认是子类成员
cout << derived.mParam << endl; //10
derived.Print();
//类外如何获得基类重名成员属性
derived.getBaseParam() = 100;
cout << "Base:mParam:" << derived.getBaseParam() << endl;
<details>
<summary>点击查看代码</summary>
class Base{
public:
Base():mParam(0){}
void Print(){ cout << mParam << endl; }
public:
int mParam;
};
class Derived : public Base{
public:
Derived():mParam(10){}
void Print(){
//在子类中使用和父类的同名成员,显示使用类名限定符
cout << Base::mParam << endl;
cout << mParam << endl;
}
//返回基类重名成员
int& getBaseParam(){ return Base::mParam; }
public:
int mParam;
};
int main(){
Derived derived;
//派生类和基类成员属性重名,子类访问成员默认是子类成员
cout << derived.mParam << endl; //10
derived.Print();
//类外如何获得基类重名成员属性
derived.getBaseParam() = 100;
cout << "Base:mParam:" << derived.getBaseParam() << endl;
return EXIT_SUCCESS;
}
</details>
return EXIT_SUCCESS;
}
注意: 如果重新定义了基类中的重载函数,将会发生什么?
class Base{
public:
//重载函数
void func1(){
cout << "Base::void func1()" << endl;
};
void func1(int param){
cout << "Base::void func1(int param)" << endl;
}
//非重载函数
void myfunc(){
cout << "Base::void myfunc()" << endl;
}
};
class Derived1 : public Base{};
class Derived2 : public Base{
public:
void myfunc(){
//基类myfunc被隐藏,可通过类作用域运算符指定调用基类myfunc函数
//Base::myfunc();
cout << "Derived2::void myfunc()" << endl;
}
};
class Derived3 : public Base{
public:
//改变成员函数的参数列表
void func1(int param1, int param2){
//Base::func1(10); //类的内部可通过类作用域运算符访问基类重载版本的函数
cout << "Derived3::void func1(int param1,int param2)" << endl;
};
};
class Derived4 : public Base{
public:
//改变成员函数的返回值
int func1(int param){
Base::func1(10);
cout << "Derived4::int func1(int param)" << endl;
return 0;
}
};
//和基类非重载函数重名
void test01(){
Derived1 derived1;
derived1.myfunc();
//和基类函数重名
Derived2 derived2;
derived2.myfunc();
}
//和基类重载函数重名
void test02(){
Derived3 derived3;
//derived3.func1(); //基类重载版本的函数fun1被全部隐藏,子类外部不可访问
//derived3.func1(10);
derived3.func1(10,20);
Derived4 derived4;
//derived4.func1(); //基类重载版本的函数fun1被全部隐藏,子类外部不可访问
derived4.func1(10);
}
//结论:任何时候重新定义基类中的任何一个函数,子类中这种函数的任何版本都会被隐藏(非覆盖,可通过类作用域运算符调用)
Base():mParam(0){}
void Print(){ cout << mParam << endl; }
public:
int mParam;
};
class Derived : public Base{
public:
Derived():mParam(10){}
void Print(){
//在子类中使用和父类的同名成员,显示使用类名限定符
cout << Base::mParam << endl;
cout << mParam << endl;
}
//返回基类重名成员
int& getBaseParam(){ return Base::mParam; }
public:
int mParam;
};
int main(){
Derived derived;
//派生类和基类成员属性重名,子类访问成员默认是子类成员
cout << derived.mParam << endl; //10
derived.Print();
//类外如何获得基类重名成员属性
derived.getBaseParam() = 100;
cout << "Base:mParam:" << derived.getBaseParam() << endl;
return EXIT_SUCCESS;
}
注意: 如果重新定义了基类中的重载函数,将会发生什么?
class Base{
public:
//重载函数
void func1(){
cout << "Base::void func1()" << endl;
};
void func1(int param){
cout << "Base::void func1(int param)" << endl;
}
//非重载函数
void myfunc(){
cout << "Base::void myfunc()" << endl;
}
};
class Derived1 : public Base{};
class Derived2 : public Base{
public:
void myfunc(){
//基类myfunc被隐藏,可通过类作用域运算符指定调用基类myfunc函数
//Base::myfunc();
cout << "Derived2::void myfunc()" << endl;
}
};
class Derived3 : public Base{
public:
//改变成员函数的参数列表
void func1(int param1, int param2){
//Base::func1(10); //类的内部可通过类作用域运算符访问基类重载版本的函数
cout << "Derived3::void func1(int param1,int param2)" << endl;
};
};
class Derived4 : public Base{
public:
//改变成员函数的返回值
int func1(int param){
Base::func1(10);
cout << "Derived4::int func1(int param)" << endl;
return 0;
}
};
//和基类非重载函数重名
void test01(){
Derived1 derived1;
derived1.myfunc();
//和基类函数重名
Derived2 derived2;
derived2.myfunc();
}
//和基类重载函数重名
void test02(){
Derived3 derived3;
//derived3.func1(); //基类重载版本的函数fun1被全部隐藏,子类外部不可访问
//derived3.func1(10);
derived3.func1(10,20);
Derived4 derived4;
//derived4.func1(); //基类重载版本的函数fun1被全部隐藏,子类外部不可访问
derived4.func1(10);
}
//结论:任何时候重新定义基类中的任何一个函数,子类中这种函数的任何版本都会被隐藏(非覆盖,可通过类作用域运算符调用)
任何时候重新定义基类中的一个重载函数,在新类中所有的其他版本将被自动隐藏
define _CRT_SECURE_NO_WARNINGS
include
using namespace std;
//基类
class Base
{
public:
Base()//有参构造
{
m_A = 100;
}
void func()
{
cout << "调用父类func()" << endl;
}
void func(int a)
{
cout << "调用父类func(int a)" << endl;
}
int m_A;
};
class Son :public Base
{
public:
Son()//有参构造
{
m_A = 200;
}
void func()
{
cout << "调用子类func()" << endl;
}
int m_A;
};
//如果子类和父类拥有同名的函数、属性,子类不会覆盖父类的
//如果子类没有提供,那就只能调用父类的
//如果子类与父类的成员函数名称相同,子类会把父类的所有同名版本都隐藏掉
//想调用父类的方法,必须加作用域
void test01()
{
//Base b1;
Son s1;
cout << s1.m_A << endl;//200
//想调用父类中的m_A
cout << s1.Base::m_A << endl;
//调用子类方法:
s1.func();
//调用父类方法:
s1.Base::func();
//s1.func(1);//error
s1.Base::func(10);
}
点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//基类
class Base
{
public:
Base()//有参构造
{
m_A = 100;
}
void func()
{
cout << "调用父类func()" << endl;
}
void func(int a)
{
cout << "调用父类func(int a)" << endl;
}
int m_A;
};
class Son :public Base
{
public:
Son()//有参构造
{
m_A = 200;
}
void func()
{
cout << "调用子类func()" << endl;
}
int m_A;
};
//如果子类和父类拥有同名的函数、属性,子类不会覆盖父类的
//如果子类没有提供,那就只能调用父类的
//如果子类与父类的成员函数名称相同,子类会把父类的所有同名版本都隐藏掉
//想调用父类的方法,必须加作用域
void test01()
{
//Base b1;
Son s1;
cout << s1.m_A << endl;//200
//想调用父类中的m_A
cout << s1.Base::m_A << endl;
//调用子类方法:
s1.func();
//调用父类方法:
s1.Base::func();
//s1.func(1);//error
s1.Base::func(10);
}
非自动继承的函数
不是所有的函数都能自动从基类继承到派生类中。构造函数和析构函数用来处理对象的创建和析构操作,构造和析构函数只知道对它们的特定层次的对象做什么,也就是说构造函数和析构函数不能被继承,必须为每一个特定的派生类分别创建。
另外operator=也不能被继承,因为它完成类似构造函数的行为。也就是说尽管我们知道如何由=右边的对象如何初始化=左边的对象的所有成员,但是这个并不意味着对其派生类依然有效。
在继承的过程中,如果没有创建这些函数,编译器会自动生成它们。
继承中的静态成员处理
非静态成员,不可以使用“类名::成员名”调用
静态成员函数和非静态成员函数的共同点:
- 他们都可以被继承到派生类中。
- 如果重新定义一个静态成员函数,所有在基类中的其他重载函数会被隐藏。
- 如果我们改变基类中一个函数的特征,所有使用该函数名的基类版本都会被隐藏。
- 类似非静态成员函数处理,子类想访问父类中的成员,加作用域即可
- 静态成员函数不能是虚函数(virtual function).
class Base{
public:
static int getNum(){ return sNum; }
static int getNum(int param){
return sNum + param;
}
public:
static int sNum;
};
int Base::sNum = 10;
class Derived : public Base{
public:
static int sNum; //基类静态成员属性将被隐藏
#if 0
//重定义一个函数,基类中重载的函数被隐藏
static int getNum(int param1, int param2){
return sNum + param1 + param2;
}
#else
//改变基类函数的某个特征,返回值或者参数个数,将会隐藏基类重载的函数
static void getNum(int param1, int param2){
cout << sNum + param1 + param2 << endl;
}
#endif
};
int Derived::sNum = 20;
点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//基类
class Base
{
public:
static void func()
{
cout << "base func()" << endl;
}
static void func(int a)
{
cout << "base func(int a)" << endl;
}
static int m_A;
};
int Base::m_A = 10;
class Son :public Base
{
public:
static void func()
{
cout << "son func()" << endl;
}
static void func(int a)
{
cout << "son func(int a)" << endl;
}
static int m_A;
};
int Son::m_A = 100;
//静态成员属性,子类可以继承
void test01()
{
cout << Son::m_A << endl;
//子类访问父类的m_A
cout << Base::m_A << endl;
Son::func();//son func()
//子类访问父类中同名的函数
Son::Base::func(10);//base func()
Base::func(10);//base func()
}
int main()
{
test01();
system("pause");
return 0;
}
多继承的概念及问题
我们可以从一个类继承,我们也可以能同时从多个类继承,这就是多继承。但是由于多继承是非常受争议的,从多个类继承可能会导致函数、变量等同名导致较多的歧义。
class Base1{
public:
void func1(){ cout << "Base1::func1" << endl; }
};
class Base2{
public:
void func1(){ cout << "Base2::func1" << endl; }
void func2(){ cout << "Base2::func2" << endl; }
};
//派生类继承Base1、Base2
class Derived : public Base1, public Base2{};
int main(){
Derived derived;
//func1是从Base1继承来的还是从Base2继承来的?
//derived.func1();
derived.func2();
//解决歧义:显示指定调用那个基类的func1
derived.Base1::func1();
derived.Base2::func1();
return EXIT_SUCCESS;
}
- 多继承会带来一些二义性的问题, 如果两个基类中有同名的函数或者变量,那么通过派生类对象去访问这个函数或变量时就不能明确到底调用从基类1继承的版本还是从基类2继承的版本?
- 解决方法就是显示指定调用那个基类的版本。
点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//基类
class Base1
{
public:
Base1()
{
cout << "Base1 构造被调用" << endl;
m_A = 10;
}
int m_A;
};
class Base2
{
public:
Base2()
{
cout << "Base2 构造被调用" << endl;
m_A = 20;
}
int m_A;
};
//多继承
//多继承中,很容易引发二义性
class Son : public Base1,public Base2
{
public:
int m_C;
int m_D;
};
void test01()
{
cout << sizeof(Son) << endl;//16
Son s1;
cout << s1.Base1::m_A << endl;
cout << s1.Base2::m_A << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
菱形继承的问题及解决方案
两个派生类继承同一个基类而又有某个类同时继承者两个派生类,这种继承被称为菱形继承,或者钻石型继承。
这种继承所带来的问题:
- 羊继承了动物的数据和函数,鸵同样继承了动物的数据和函数,当草泥马调用函数或者数据时,就会产生二义性。
- 草泥马继承自动物的函数和数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。
点击查看代码
class BigBase{
public:
BigBase(){ mParam = 0; }
void func(){ cout << "BigBase::func" << endl; }
public:
int mParam;
};
class Base1 : public BigBase{};
class Base2 : public BigBase{};
class Derived : public Base1, public Base2{};
int main(){
Derived derived;
//1. 对“func”的访问不明确
//derived.func();
//cout << derived.mParam << endl;
cout << "derived.Base1::mParam:" << derived.Base1::mParam << endl;
cout << "derived.Base2::mParam:" << derived.Base2::mParam << endl;
//2. 重复继承
cout << "Derived size:" << sizeof(Derived) << endl; //8
return EXIT_SUCCESS;
}
- 上述问题如何解决?对于调用二义性,那么可通过指定调用那个基类的方式来解决,那么重复继承怎么解决?
- 对于这种菱形继承所带来的两个问题,c++为我们提供了一种方式,采用虚基类。那么我们采用虚基类方式将代码修改如下:
点击查看代码
class BigBase{
public:
BigBase(){ mParam = 0; }
void func(){ cout << "BigBase::func" << endl; }
public:
int mParam;
};
class Base1 : virtual public BigBase{};
class Base2 : virtual public BigBase{};
class Derived : public Base1, public Base2{};
int main(){
Derived derived;
//二义性问题解决
derived.func();
cout << derived.mParam << endl;
//输出结果:12
cout << "Derived size:" << sizeof(Derived) << endl;
return EXIT_SUCCESS;
}
以上程序Base1 ,Base2采用虚继承方式继承BigBase,那么BigBase被称为虚基类。
通过虚继承解决了菱形继承所带来的二义性问题。
但是虚基类是如何解决二义性的呢?并且derived大小为12字节,这是怎么回事?
vbptr:virtual base pointer:虚基类指针
这个指针指向这个虚基类表:Sheep表和Tuo表
0 和 1是偏向,
0 + 8就跑到了8这个位置
Sheep的虚基类表偏移量是 32
Tuo的虚基类表偏移量是 16
点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//基类
class Animal
{
public:
int m_Age;
};
//虚基类
class Sheep : virtual public Animal
{
public:
int m_Age;
};
//虚基类
class Tuo : virtual public Animal
{
public:
int m_Age;
};
//多继承
//多继承中,很容易引发二义性
class SheepTuo : public Sheep,public Tuo
{
};
//菱形继承的解决方案 利用虚继承表
//操作的是共享的一份数据,
//这里只有animal保存的有一份age,而sheep和tuo保存的只是一个指针,指针 + 偏移量去找m_Age
void test01()
{
SheepTuo st;
st.Sheep::m_Age = 10;
st.Tuo::m_Age = 20;
cout << st.Sheep::m_Age << endl;
cout << st.Tuo::m_Age << endl;
//此时也不用加作用域了
//cout << st.m_Age << endl; 为啥还要加?
}
int main()
{
test01();
system("pause");
return 0;
}
虚基类的内部工作原理解析
class BigBase{
public:
BigBase(){ mParam = 0; }
void func(){ cout << "BigBase::func" << endl; }
public: int mParam;
};
#if 0 //虚继承
class Base1 : virtual public BigBase{};
class Base2 : virtual public BigBase{};
#else //普通继承
class Base1 : public BigBase{};
class Base2 : public BigBase{};
#endif
class Derived : public Base1, public Base2{};
通过对象布局图,我们发现普通继承和虚继承的对象内存图是不一样的。我们也可以猜测到编译器肯定对我们编写的程序做了一些手脚。
-
BigBase 菱形最顶层的类,内存布局图没有发生改变。
-
Base1和Base2通过虚继承的方式派生自BigBase,这两个对象的布局图中可以看出编译器为我们的对象中增加了一个vbptr (virtual base pointer),vbptr指向了一张表,这张表保存了当前的虚指针相对于虚基类的首地址的偏移量。
-
Derived派生于Base1和Base2,继承了两个基类的vbptr指针,并调整了vbptr与虚基类的首地址的偏移量。
由此可知编译器帮我们做了一些幕后工作,使得这种菱形问题在继承时候能只继承一份数据,并且也解决了二义性的问题。现在模型就变成了Base1和 Base2 、Derived三个类对象共享了一份BigBase数据。
当使用虚继承时,虚基类是被共享的,也就是在继承体系中无论被继承多少次,对象内存模型中均只会出现一个虚基类的子对象(这和多继承是完全不同的)。即使共享虚基类,但是必须要有一个类来完成基类的初始化(因为所有的对象都必须被初始化,哪怕是默认的),同时还不能够重复进行初始化,那到底谁应该负责完成初始化呢?C++标准中选择在每一次继承子类中都必须书写初始化语句(因为每一次继承子类可能都会用来定义对象),但是虚基类的初始化是由最后的子类完成,其他的初始化语句都不会调用。
点击查看代码
class BigBase{
public:
BigBase(int x){mParam = x;}
void func(){cout << "BigBase::func" << endl;}
public:
int mParam;
};
class Base1 : virtual public BigBase{
public:
Base1() :BigBase(10){} //不调用BigBase构造
};
class Base2 : virtual public BigBase{
public:
Base2() :BigBase(10){} //不调用BigBase构造
};
class Derived : public Base1, public Base2{
public:
Derived() :BigBase(10)
{cout << "哈哈哈哈" << endl;} //调用BigBase构造
};
//每一次继承子类中都必须书写初始化语句
int main()
{
Derived derived;
derived.func();
cout << "derived Base1:mParam:" << derived.Base1::mParam << endl;
cout << "drived Base2:mParam:" << derived.Base2::mParam << endl;
cout << "Derived size:" << sizeof(Derived) << endl;
system("pause");
return 0;
}
注意:
-
虚继承只能解决具备公共祖先的多继承所带来的二义性问题,不能解决没有公共祖先的多继承的.
-
工程开发中真正意义上的多继承是几乎不被使用,因为多重继承带来的代码复杂性远多于其带来的便利,多重继承对代码维护性上的影响是灾难性的,在设计方法上,任何多继承都可以用单继承代替。
-
Jerry Schwarz,输入输出流(iostream)的作者,曾在个别场合表示如何他重新设计iostream的话,很可能从iostream中去除多重继承。