一.继承的概念和意义

1.组合关系

组合关系:整体与部分的关系
在这里插入图片描述
组合关系示例:

#include <iostream>
#include <string>
using namespace std;

class Memory
{
public:
    Memory()
    {
        cout << "Memory()" << endl;
    }
    ~Memory()
    {
        cout << "~Memory()" << endl;
    }
};

class Disk
{
public:
    Disk()
    {
        cout << "Disk()" << endl;
    }
    ~Disk()
    {
        cout << "~Disk()" << endl;
    }   
};

class CPU
{
public:
    CPU()
    {
        cout << "CPU()" << endl;
    }
    ~CPU()
    {
        cout << "~CPU()" << endl;
    }    
};

class MainBoard
{
public:
    MainBoard()
    {
        cout << "MainBoard()" << endl;
    }
    ~MainBoard()
    {
        cout << "~MainBoard()" << endl;
    }    
};

class Computer
{
    Memory mMem;
    Disk mDisk;
    CPU mCPU;
    MainBoard mMainBoard;
public:
    Computer()
    {
        cout << "Computer()" << endl;
    }
    void power()
    {
        cout << "power()" << endl;
    }
    void reset()
    {
        cout << "reset()" << endl;
    }
    ~Computer()
    {
        cout << "~Computer()" << endl;
    }
};

int main()
{   
    Computer c;
    return 0;
}
编译结果:
Memory()
Disk()
CPU()
MainBoard()
Computer()
~Computer()
~MainBoard()
~CPU()
~Disk()
~Memory()

组合关系的特点:
将其他类的对象作为当前类的成员使用
当前类的对象与成员对象的生命期相同
成员对象在用法上与普通对象完全一致

2.继承关系

继承关系:父子关系

父类(基类) 电脑
子类(派生类) 惠普电脑 苹果电脑 华硕电脑

面向对象中的继承指类之间的父子关系:

  • 子类拥有父类的所有属性和行为
  • 子类就是一种特殊的父类
  • 子类对象可以当做父类对象使用
  • 子类中可以添加父类没有的方法和属性

C++描述继承关系的方式:

class Parent
{
    int mv;
public:
    void method();
};
class child : public Parent //描述继承关系
{
};

继承示例:

#include <iostream>
#include <string>
using namespace std;

class Parent
{
    int mv;
public:
    Parent()
    {
        cout << "Parent()" << endl;
        mv = 100;
    }
    void method()
    {
        cout << "mv = " << mv << endl;
    }
};

class Child : public Parent
{
public:
    void hello()
    {
        cout << "I'm Child calss!" << endl;
    }
};

int main()
{   
    Child c;
    Parent p1 = c;
    Parent p2;
    c.hello();
    c.method();
    p2 = c;
    return 0;
}
编译结果:
Parent()
I'm Child calss!
mv = 100

重要规则:
子类就是一个特殊的父类
子类对象可以直接初始化父类对象
子类对象可以直接赋值给父类对象

继承的意义:
继承是C++中代码复用的重要手段。通过继承,可以获得父类的所有功能,并且可以在子类中重写已有的功能,或者添加新功能。

示例:

#include <iostream>
#include <string>
using namespace std;

class Memory
{
public:
    Memory()
    {
        cout << "Memory()" << endl;
    }
    ~Memory()
    {
        cout << "~Memory()" << endl;
    }
};

class Disk
{
public:
    Disk()
    {
        cout << "Disk()" << endl;
    }
    ~Disk()
    {
        cout << "~Disk()" << endl;
    }   
};

class CPU
{
public:
    CPU()
    {
        cout << "CPU()" << endl;
    }
    ~CPU()
    {
        cout << "~CPU()" << endl;
    }    
};

class MainBoard
{
public:
    MainBoard()
    {
        cout << "MainBoard()" << endl;
    }
    ~MainBoard()
    {
        cout << "~MainBoard()" << endl;
    }    
};

class Computer
{
    Memory mMem;
    Disk mDisk;
    CPU mCPU;
    MainBoard mMainBoard;
public:
    Computer()
    {
        cout << "Computer()" << endl;
    }
    void power()
    {
        cout << "power()" << endl;
    }
    void reset()
    {
        cout << "reset()" << endl;
    }
    ~Computer()
    {
        cout << "~Computer()" << endl;
    }
};

class HPBook : public Computer
{
    string mOS;
public:
    HPBook()
    {
        mOS = "Windows 8"; //预装的操作系统
    }
    void install(string os)
    {
        mOS = os;
    }
    void OS()
    {
        cout << mOS << endl;
    }
};

class MacBook : public Computer
{
public:
    void OS()
    {
        cout << "Mac OS" << endl;
    }
};

int main()
{   
    HPBook hp;
    
    hp.power();
    hp.install("Ubuntu 16.04 LTS");
    hp.OS();
    
    cout << endl;
    
    MacBook mac;
    
    mac.OS();
    
    return 0;
}
Memory()
Disk()
CPU()
MainBoard()
Computer()
power()
Ubuntu 16.04 LTS

Memory()
Disk()
CPU()
MainBoard()
Computer()
Mac OS
~Computer()
~MainBoard()
~CPU()
~Disk()
~Memory()
~Computer()
~MainBoard()
~CPU()
~Disk()
~Memory()

总结:
继承是面向对象中类之间的一种关系
子类拥有父类的所有属性和行为
子类对象可以当作父类对象使用
子类中可以添加父类没有的方法和属性
继承是面向对象中代码复用的重要手段

二. 继承中的访问级别

1. 思考:

子类是否可以直接访问父类的私有成员?
可以,protected关键字替换private即可

思考过程:

根据面向对象理论:
在这里插入图片描述
根据C++语法:
在这里插入图片描述
继承中的访问级别示例:

#include <iostream>
#include <string>
using namespace std;
class Parent
{
private:
    int mv;
public:
    Parent()
    {
        mv = 100;
    }
    int value()
    {
        return mv;
    }
};
class Child : public Parent
{
public:
    int addValue(int v)
    {
        mv = mv + v;    // 如何访问父类中的非公有成员? 使用protected关键字
    }
};
int main()
{   
    return 0;
}
编译报错

面向对象中的访问级别不只是public和private
可以定义protected访问级别

2. 关键字protected的意义:

修饰的成员不能被外界直接访问
修饰的成员可以被子类直接访问

protected示例:

#include <iostream>
#include <string>
using namespace std;
class Parent
{
protected: //可以让子类访问
    int mv;
public:
    Parent()
    {
        mv = 100;
    }
    int value()
    {
        return mv;
    }
};
class Child : public Parent
{
public:
    int addValue(int v)
    {
        mv = mv + v;    
    }
};
int main()
{   
    Parent p;
    cout << "p.mv = " << p.value() << endl;
    // p.mv = 1000;    // error,仅仅只有子类可以访问,外界不能访问
    
    Child c;
    cout << "c.mv = " << c.value() << endl;
    c.addValue(50);
    cout << "c.mv = " << c.value() << endl; 
    // c.mv = 10000;  // error,仅仅只有子类可以访问,外界不能访问
    
    return 0;
}
运行结果
p.mv = 100
c.mv = 100
c.mv = 150

定义类时访问级别的选择:
在这里插入图片描述

3.组合与继承的综合实例

Point类 继承 Object类
Line类 继承 Object类
Point类组合成Line类

示例:

#include <iostream>
#include <string>
#include <sstream>
using namespace std;
class Object
{
protected:
    string mName;
    string mInfo;
public:
    Object()
    {
        mName = "Object";
        mInfo = "";
    }
    string name()
    {
        return mName;
    }
    string info()
    {
        return mInfo;
    }
};
class Point : public Object
{
private:
    int mX;
    int mY;
public:
    Point(int x = 0, int y = 0) //默认值0 0
    {
        ostringstream s;   
        mX = x;
        mY = y;
        mName = "Point";     
        s << "P(" << mX << ", " << mY << ")";
        mInfo = s.str();
    }
    int x()
    {
        return mX;
    }
    int y()
    {
        return mY;
    }
};
class Line : public Object
{
private:
    Point mP1;
    Point mP2;
public:
    Line(Point p1, Point p2)
    {
        ostringstream s;//格式化
        mP1 = p1;
        mP2 = p2;
        mName = "Line";
        s << "Line from " << mP1.info() << " to " << mP2.info();
        mInfo = s.str();
    }
    Point begin()
    {
        return mP1;
    }
    Point end()
    {
        return mP2;
    }
};
int main()
{   
    Object o;
    Point p(1, 2);
    Point pn(5, 6);
    Line l(p, pn);
    
    cout << o.name() << endl;
    cout << o.info() << endl;
    
    cout << endl;
    
    cout << p.name() << endl;
    cout << p.info() << endl;
    
    cout << endl;
    
    cout << l.name() << endl;
    cout << l.info() << endl;
    
    return 0;
}
运行结果
Object

Point
P(1, 2)
Line
Line from P(1, 2) to P(5, 6)

小结:
面向对象中的访问级别不只是public和private
protected修饰的成员不能被外界所访问
protected使得子类能够访问父类的成员
protected关键字是为了继承而专门设计的
没有protected就无法完成真正意义上的代码复用

三.不同的继承方式

细节:
冒号( : )表示继承关系,Parent表示被继承的类,public的意义是什么?

class Parent
{
};
class Child : public Parent  //public可以换成protected或者private吗?
{
};

示例:

#include <iostream>
#include <string>
using namespace std;
class Parent
{
};
class Child_A : public Parent
{
};
class Child_B : protected Parent
{
};
class Child_C : private Parent
{
};
int main()
{   
    return 0;
}

1.C++中支持三种不同的继承方式:

public继承
父类成员在子类中保持原有访问级别
private继承
父类成员在子类中变为私有成员
protected继承
父类中的公有成员变为保护成员,其他成员保持不变
在这里插入图片描述
继承成员的访问属性 = Max{继承方式,父类成员访问属性}
C++中的默认继承方式为private

继承与访问级别深度实践:

#include <iostream>
#include <string>
using namespace std;
class Parent
{
protected:
    int m_a;
protected:
    int m_b;
public:
    int m_c;
    void set(int a, int b, int c)
    {
	    m_a = a;
	    m_b = b;
	    m_c = c;
    }
};
class Child_A : public Parent
{
public:
    void print()
    {
	    cout << "m_a" << m_a << endl;
	    cout << "m_b" << m_b << endl;
	    cout << "m_c" << m_c << endl;
    }
};
class Child_B : protected Parent
{
public:
    void print()
    {
	    cout << "m_a" << m_a << endl;
	    cout << "m_b" << m_b << endl;
	    cout << "m_c" << m_c << endl;
    }
};
class Child_C : private Parent
{
public:
    void print()
    {
	    cout << "m_a" << m_a << endl;
	    cout << "m_b" << m_b << endl;
	    cout << "m_c" << m_c << endl;
    }
};
int main()
{   
    Child_A a;
    Child_B b;
    Child_C c;
    a.m_c = 100;//共有继承
    //b.m_c = 100;
    //Child_B保护继承自Parent,所有的public成员全部变成了protected成员,因此外界无法访问
    //c.m_c = 100;
    //Child_C私有继承自Parent,所有的Parent成员全部变成了private成员,因此外界无法访问
    a.set(1,1,1);
    //b.set(2,2,2);
    //c.set(3,3,3);
    //继承方式影响了父类中的成员函数
    a.print();
    b.print();
    c.print(); 
    //继承方式仅仅影响父类中的成员,对子类自己定义的成员没有影响
    return 0;
}

2.遗憾的事实

一般而言,C++工程项目中只使用public继承
C++的派生语言只支持一种继承方式(public继承)
protected和private继承带来的复杂性远大于实用性

小结
C++中支持3种不同的继承方式
继承方式直接影响父类成员在子类中的访问属性
一般而言,工程中只使用public的继承方式
C++的派生语言中只支持public继承方式

四.继承中的构造与析构

1.子类对象的构造

(1)子类中可以定义构造函数
(2)子类构造函数
必须对继承而来的成员进行初始化:
直接初始化: 1) 直接通过初始化列表或者赋值的方式进行初始化设置
调用初始化: 2) 调用父类构造函数进行初始化
(3)父类构造函数在子类中的调用方式
默认调用: 1) 适用于无参构造函数 2) 使用默认参数的构造函数
显式调用: 1) 通过初始化列表进行调用 2) 适用于所有父类构造函数

父类构造函数的调用示例:

class Child : public Parent
{
public:
    Child()  /*隐式调用*/
    {
        cout << "Child()" << endl;
    }
    Child(string s) : Parent("parameter to Parent")  /*显式调用*/
    {
        cout << "Child() : " << s << endl;
    }
}

子类的构造初探示例:

#include <iostream>
#include <string>
using namespace std;
class Parent 
{
public:
    Parent()
    {
	    cout << "Parent()" << endl;
    }
    Parent(string s)
    {
        cout << "Parent(string s) : " << s << endl;
    }
};
class Child : public Parent
{
public:
    Child()
    {
        cout << "Child()" << endl;
    }
    Child(string s):Parent(s) //显式调用父类的构造函数
    {
        cout << "Child(string s) :" << s << endl;
    }
};
int main()
{       
    Child c; //需要父类中有无参构造函数
    Child cc("cc"); 
    return 0;
}
运行结果
Parent()
Child()
Parent(string s) : cc
Child(string s) : cc

子类对象的构造规则:

  • 子类对象在创建时首先调用父类的构造函数
  • 先执行父类构造函数再执行子类的构造函数
  • 父类构造函数可以被隐式调用或者显式调用

对象创建时构造函数的调用顺序:

  • 调用父类的构造函数
  • 调用成员变量的构造函数
  • 调用类自身的构造函数
先父母,后客人,再自己

子类构造深度解析示例:

#include <iostream>
#include <string>
using namespace std;
class Object
{
public:
     Object(string s)
    {
	    cout << "Object(string s ) : " << s << endl;
    }
};
class Parent : public Object
{
public:
    Parent() : Object("Default")
    {
        cout << "Parent()" << endl;
    }
    Parent(string s) : Object(s)
    {
        cout << "Parent(string s) : " << s << endl;
    }
};
class Child : public Parent 
{ //继承自Parent,组合使用了Object类
    Object m01; //声明成员变量
    Object m02;
public:
    Child() :m01("Default 1"), m02("Default 2")
    {
        cout << "Child()" << endl;
    }
    Child(string s) : Parent(s), m01(s + "1"), m02(s + "2")
    {
        cout << "Child(string s) : " << s << endl;
    }
};
int main()
{       
    Child cc("cc");
    return 0;
}
运行结果
Object(string s ) : cc
Parent(string s) : cc
Object(string s ) : cc1
Object(string s ) : cc2
Child(string s) : cc

2.子类对象的析构

析构函数的调用顺序与构造函数相反

  • 执行自身的析构函数
  • 执行成员变量的析构函数
  • 执行父类的析构函数

代码示例:

#include <iostream>
#include <string>
using namespace std;
class Object
{
    string ms;
public:
    Object(string s)
    {
	    cout << "Object(string s ) : " << s << endl;
	    ms = s;
    }
    ~Object()
    {
	    cout << "~Object() : " << ms <<endl;
    }
};
class Parent : public Object
{
    string ms;
public:
    Parent() :Object("Default")
    {
        cout << "Parent()" << endl;
	    ms = "Default";
    }
    Parent(string s) : Object(s)
    {
        cout << "Parent(string s) : " << s << endl;
	    ms = s;
    }
    ~Parent()
    {
	    cout << "~Parent() : " << ms <<endl;
    }
};
class Child : public Parent
{
    Object m01;
    Object m02;
    string ms;
public:
    Child() :m01("Default 1"), m02("Default 2")
    {
        cout << "Child()" << endl;
	    ms = "Default";
    }
    Child(string s) : Parent(s),m01(s + "1"),m02(s + "2")
    {
        cout << "Child(string s) : " << s << endl;
	    ms = s;
    }
    ~Child()
    {
	    cout << "~Child() : " << ms <<endl;
    }
};
int main()
{       
    Child cc("cc");
    cout << endl;
    return 0;
}
运行结果
Object(string s ) : cc
Parent(string s) : cc
Object(string s ) : cc1
Object(string s ) : cc2
Child(string s) : cc
~Child() : cc
~Object() : cc2
~Object() : cc1
~Parent() : cc
~Object() : cc

小结:
子类对象在创建时需要调用父类构造函数进行初始化
先执行父类构造函数然后执行成员的构造函数
父类构造函数显示调用需要在初始化列表中进行
子类对象在销毁时需要调用父类析构函数进行清理
析构顺序与构造顺序对称相反

五.父子间的冲突

1.成员变量的冲突

子类可以定义父类中的同名成员
子类中的成员将隐藏父类中的同名成员
父类中的同名成员依然存在于子类中
通过作用域分辨符(::)访问父类中的同名成员

 Child c;
 c.mi = 100;         //子类中的mi
 c.Parent::mi = 100; //父类中的mi

同名成员的示例:

#include<iostream>
#include<string>
using namespace std;
class Parent
{
public:
    int mi;
    Parent()
    {
	    cout << "Parent() : " << "&mi = " << &mi<< endl;
    }
};
class Child : public Parent
{
public:
    int mi;
    Child()
    {
	    cout << "Child() : " << "&mi = " << &mi << endl;
    }
};
int main()
{
    Child c;
    c.mi = 100; 
    c.Parent::mi = 1000;
    cout << "&c.mi = " << &c.mi << endl;
    cout << "c.mi = " << c.mi << endl;
    cout << "&c.Parent::mi = " << &c.Parent::mi << endl;
    cout << "c.Parent::mi = " << c.Parent::mi << endl;
    return 0;
}
运行结果
Parent() : &mi = 0x77777777
Child() : &mi = 0x99999999
&c.mi = 0x99999999
c.mi = 100
&c.Parent::mi = 0x77777777
c.Parent::mi = 1000

2.成员函数的冲突

再论重载:
类中的成员函数可以进行重载
(1)重载函数的本质为多个不同的函数
(2)函数名和参数列表是唯一的标识
(3)函数重载必须发生在同一个作用域中

子类中的函数将隐藏父类的同名函数
子类无法重载父类中的成员函数,不在同一个作用域当中
使用作用域分辨符访问父类中的同名函数
子类可以定义父类中完全相同的成员函数

示例:

#include <iostream>
#include <string>
using namespace std;
class Parent
{
public:
    int mi;
    void add(int v)
    {
	    mi += v;
    }
    void add(int a, int b)
    {
	    mi += (a + b);
    }
};
class Child : public Parent
{
public:
	int mi;
	void add(int v)
    {
	    mi += v;
    }
    void add(int a, int b)
    {
	    mi += (a + b);
    }
    void add(int x, int y, int z) //子类只定义这一个,父类的同名函数就会被隐藏
    {
	    mi += (x + y + z);
    }
};
int main()
{
    Child c;
    c.mi = 100; 
    c.Parent::mi = 1000;
    cout << "c.mi = " << c.mi << endl;
    cout << "c.Parent::mi = " << c.Parent::mi << endl;
    c.add(1);
    c.add(2,3);
    c.add(4,5,6);
    cout << "c.mi = " << c.mi << endl;
    cout << "c.Parent::mi = " << c.Parent::mi << endl;
    return 0;
}
运行结果
c.mi = 100
c.Parent::mi = 1000
c.mi = 121
c.Parent::mi = 1000

小结:
子类可以定义父类中的同名成员
子类中的成员将隐藏父类中的同名成员
子类和父类中的函数不能构成重载关系
子类可以定义父类中完全相同的成员函数
使用作用域分辨符访问父类中的同名成员

六.同名覆盖引发的问题

1.父子间的赋值兼容

子类对象可以当作父类对象使用(兼容性)

  • 子类对象可以直接赋值给父类对象
  • 子类对象可以直接初始化父类对象
  • 父类指针可以直接指向子类对象
  • 父类引用可以直接引用子类对象

子类对象的兼容性示例:

#include <iostream>
#include <string>
using namespace std;
class Parent
{
public:
    int mi;
    int add(int i) 
    {
	    mi += i;
    }
    int add(int a, int b)
    {
	    mi += (a + b);
    }
};
class Child : public Parent
{
public:
    int mv;
    int add(int x, int y, int z)
    {
	    mv += (x + y + z);
    }
};
int main()
{
    Parent p;
    Child c;
    p = c; //子类对象可以直接赋值给父类对象
    Parent p1(c); //子类对象可以直接初始化父类对象
    Parent* pp = &c; //父类指针可以直接指向子类对象
    Parent& rp = c; //父类引用可以直接引用子类对象
 
    rp.mi = 100;
    rp.add(5);       //没有发生同名覆盖
    rp.add(10, 10);  //没有发生同名覆盖
 
    //pp->mv = 1000; //编译报错,显示父类中没有此变量
    //pp->add(1, 10, 100); //编译报错,显示父类中没有此函数
    return 0;
}

注意事项: !!!!!!
当使用父类指针(引用)指向子类对象时:

  • 子类对象退化为父类对象
  • 只能访问父类中定义的成员
  • 可以直接访问被子类覆盖的同名成员

2.特殊的同名函数

子类中可以重定义父类中已经存在的成员函数
这种重定义发生在继承中,叫做函数重写
函数重写是同名覆盖的一种特殊情况

class Parent
{
public:
    void print()
    {
        cout << "I'm Parent." << endl;
    }
};
函数重写 -->
class Child : public Parent
{
public:
    void print()
    {
        cout << "I'm Child." << endl;
    }
};

函数重写对赋值兼容示例:

#include <iostream>
#include <string>
using namespace std;
class Parent
{
public:
    int mi;
    
    void add(int i)
    {
        mi += i;
    }
    
    void add(int a, int b)
    {
        mi += (a + b);
    }
    
    void print()
    {
        cout << "I'm Parent." << endl;
    }
};
class Child : public Parent
{
public:
    int mv;
    
    void add(int x, int y, int z)
    {
        mv += (x + y + z);
    }
    
    void print()
    {
        cout << "I'm Child." << endl;
    }
};
//全局函数,
void how_to_print(Parent* p)
{
    p->print();
}
int main()
{
    Parent p;
    Child c;
    //c.print(); //函数重写,子类没有的话就调用父类的
    how_to_print(&p);    // Expected to print: I'm Parent.
    how_to_print(&c);    // Expected to print: I'm Child. 
    return 0;
}
运行结果
I'm Parent.
I'm Parent.

问题分析:
编译期间,编译器只能根据指针的类型判断所指向的对象
根据赋值兼容,编译器认为父类指针指向的是父类对象
因此,编译结果只可能是调用父类中定义的同名函数

void how_to_print(Parent* p)
{
    p->print();
}

在编译这个函数的时候,编译器不可能知道指针p究竟指向了什么。但是编译器没有理由报错。可是,编译器认为最安全的做法是调用父类的print函数,因为父类和子类肯定都有相同的print函数。

小结:
子类对象可以当做父类对象使用(赋值兼容)
父类指针可以正确的指向子类对象
父类引用可以正确的代表子类对象
子类中可以重写父类中的成员函数

七.被遗弃的多重继承

1.C++支持编写多重继承的代码

  • 一个子类可以拥有多个父类
  • 子类拥有所有父类的成员变量
  • 子类继承所有父类的成员函数
  • 子类对象可以当做任意父类对象使用

多重继承的语法规则:

class Derived : public BaseA,
                public BaseB,
                public BaseC
{
    ...
};

多重继承的本质与单继承相同。

示例:

#include <iostream>
#include <string>
using namespace std;
class BaseA
{
    int ma;
public:
    BaseA(int a)
    {
		ma = a;
    }
    int getA()
    {
		return ma;
    }
};
class BaseB
{
    int mb;
public:
    BaseB(int b)
    {
		mb = b;
    }
    int getB()
    {
		return mb;
    }
};
class Derived : public BaseA, public BaseB
{
    int mc;
public:
    Derived(int a, int b, int c) :BaseA(a), BaseB(b)
    {
		mc = c;
    }
    int getC()
    {
		return mc;
    }
    void print()
    {
		cout << "ma =" << getA() << ","
             << "mb =" << getB() << ","
	     	 << "mc =" << mc << endl;
    }
};
int main()
{
   cout << "sizeof(Derived) = " << sizeof(Derived) << endl; //12
   
    Derived d(1,2,3);
    d.print();
    cout << "d.getA() = " << d.getA() << endl;
    cout << "d.getB() = " << d.getB() << endl;
    cout << "d.getC() = " << d.getC() << endl;
    cout << endl;
    BaseA* pa = &d;
    BaseB* pb = &d;
    cout << "pa->getA() = " << pa->getA() << endl;
    cout << "pb->getB() = " << pb->getB() << endl;
    cout << endl;
    void* paa = pa;
    void* pbb = pb;
    if( paa == pbb)
    {
		cout << "Pointer to the same object!" << endl;
    }
    else
    {
		cout << "Error" << endl;
    }
    cout << "pa = " << pa << endl;
    cout << "pb = " << pb << endl;
    cout << "paa = " << paa << endl;
    cout << "pbb = " << pbb << endl;
    return 0;
}
运行结果
sizeof(Derived) = 12
ma =1,mb =2,mc =3
d.getA() = 1
d.getB() = 2
d.getC() = 3

pa->getA() = 1
pb->getB() = 2

Error
pa = 0x7fffe245f4b0
pb = 0x7fffe245f4b4
paa = 0x7fffe245f4b0
pbb = 0x7fffe245f4b4

多重继承问题一:
通过多重继承得到的对象可能拥有“不同的地址”
解决方案:无
实质:指向同一个对象的不同位置(一个指向头,一个指向腿)

Derived d(1, 2, 3);
BaseA* pa = &d;
BaseB* pb = &d;

在这里插入图片描述

多重继承的问题二:
多重继承可能产生冗余的成员
在这里插入图片描述
示例:

#include <iostream>
#include <string>
using namespace std;
class People
{
    string m_name;
    int m_age;
public:
    People(string name, int age)
    {
		m_name = name;
		m_age = age;
    }
    void print()
    {
		cout << "Name = " << m_name << ","
	     	 << "Age = " << m_age << endl;
    }
};
class Teacher : virtual public People  //中间父类加上virtual
{
public:
    Teacher(string name, int age) : People(name, age)
    {
    }
};
class Student : virtual public People  //中间父类加上virtual
{
public:
    Student(string name, int age) : People(name, age)
    {
    }
};
class Doctor : public Teacher, public Student
{
public:
    Doctor(string name, int age) : Teacher(name + "1", age + 1), Student(name + "2", age + 2), People(name, age)
	{
	}
};
int main()
{
    Doctor d("Jenius", 33);
    //d.print(); //有两个print函数
    d.Teacher::print(); //使用作用域分辨符做选择
    d.Student::print();
    
    return 0;
}
运行结果
Name = Jenius1,Age = 34
Name = Jenius2,Age = 35

当多重继承关系出现闭合时将产生数据冗余的问题
解决方案:虚继承

class People();
class Teacher : virtual public People {};
class Student : virtual public People {};
class Doctor : public Teacher, public Student
{
};

虚继承能够解决数据冗余问题
中间层父类不再关心顶层父类的初始化
最终子类必须直接调用顶层父类的构造函数

问题:当架构设计中需要继承时,无法确定使用直接继承还是虚继承.
牺牲效率,牺牲移植性,架构师不喜欢多继承,多重继承仅作学术研究。

小结
C++支持多重继承的编程方式
多重继承容易带来问题
可能出现“同一个对象的地址不同”的情况
虚继承可以解决数据冗余的问题
虚继承的使得架构设计可能出现问题

多重继承的问题三:
多重继承可能产生多个虚函数表
在这里插入图片描述
示例:

#include <iostream>
#include <string>
using namespace std;
class BaseA
{
public:
    virtual void funcA()
    {
	cout << "BaseA::funcA()" << endl;
    }
};
class BaseB
{
public:
    virtual void funcB()
    {
	cout << "BaseB:: funcB()" << endl;
    }
};
class Derived : public BaseA, public BaseB
{
};
int main()
{
    Derived d;
    BaseA* pa = &d;
    BaseB* pb = &d;
    BaseB* pbb = (BaseB*)pa; //oops 不用这种方法
    BaseB* pbc = dynamic_cast<BaseB*>(pa);  //use this
    cout << "sizeof(d) = " << sizeof(d) << endl;
    cout << "Using pa to call funcA()..." << endl;
    pa->funcA();
    cout << "Using pb to call funcB()..." << endl;
    pb->funcB();
    cout << "Using pbb to call funcB()..." << endl;
    pbb->funcB();
    cout << "Using pbc to call funcB()..." << endl;
    pbc->funcB();
    cout << endl;
    cout << "pa = " << pa << endl;
    cout << "pb = " << pb << endl;
    cout << "pbb = " << pbe << endl;
    cout << "pbc = " << pbc << endl;
    return 0;
}
运行结果
sizeof(d) = 8  //同事拥有两个虚函数表指针,占用8个字节
Using pa to call funcA()...
BaseA::funcA()
Using pb to call funcB()...
BaseB:: funcB()
Using pbb to call funcB()...
BaseB:: funcA() //强制类型的使用问题导致
Using pbb to call funcB()...
BaseB:: funcB()

pa = 0x7fff843450d0
pb = 0x7fff843450d8
pbb = 0x7fff843450d0
pbc = 0x7fff843450d8

需要进行强制类型转换时,C++中推荐使用新式类型转换关键字!!
解决方案:dynamic_cast //继承,虚函数的转换

Derived d;
BaseA* pa = &d;
BaseB* pb = &d;
BaseB* pbb = (BaseB*)pa;

pa ---------->   vptr1   <---------- pbb
                         <---------- pb
                 vptr2    

程序运行本质:
通过pa调用funcA的过程:从pa里面拿到pa的地址,通过地址找到虚函数表指针vptr1,进而通过虚函数表指针找对象的函数地址,进而找到funA的地址。
通过pb调用funcB的过程:从pb里面拿到pb的地址,通过地址找到虚函数表指针vptr2,进而通过虚函数表指针所指向的虚函数表里面得到虚函数地址,进而找到funB的地址。
vptr1,vptr2两个虚函数表指针所指向的虚函数表在结构上是一样的,因此通过pbb调用funcB的过程:首先得到地址,得到的地址在pbb所指向的位置,然后找虚函数表指针,找到的虚函数表指针vptr1,就在pbb所指向的虚函数表里面找funcB()的地址,在这里面是找不到funcB()的地址,能够找到的是funcA()的地址,并且这个地址是完全正确没有误差的。

工程开发中的“多重继承”方式:
在这里插入图片描述
正确的多继承方式—单继承、多接口进行面向对象的设计示例:

#include <iostream>
#include <string>
using namespace std;
class Base
{
protected:
    int mi;
public:
    Base(int i)
    {
	    mi = i;
    }
    int getI()
    {
	    return mi;
    }
    bool equal(Base* obj) //判断参数指针指向的是否是当前对象
    {
	    return (this == obj);
    }
};
class Interface1
{
public:
    virtual void add(int i) = 0;
    virtual void minus(int i) = 0;
};
class Interface2
{
public:
    virtual void multiply(int i) = 0;
    virtual void divide(int i) = 0;
};
class Derived : public Base, public Interface1, public Interface2
{
public:
    Derived(int i) : Base(i)
    {
    }
    void add(int i)
    {
	    mi += i;
    }
    void minus(int i)
    {
	    mi -= i;
    }
    void multiply(int i)
    {
	    mi *= i;
    }
    void divide(int i)
    {
	    if( i!= 0)
        {
	        mi /= i;
        }
    }
}; 
int main()
{
    Derived d(100);
    Derived* p = &d;
    Interface1* pInt1 = &d;
    Interface2* pInt2 = &d;
    cout << "p->getI() = " << p->getI() << endl;  //100
    pInt1->add(10);    //110
    pInt2->divide(11); //10
    pInt1->minus(5);   //5
    pInt2->multiply(8);//40
    cout << "p->getI() = " << p->getI() << endl;  //40
    cout << endl;
    cout << "pInt1 == p : " << p->equal(dynamic_cast<Base*>(pInt1)) << endl;
    cout << "pInt2 == p : " << p->equal(dynamic_cast<Base*>(pInt2)) << endl;
    return 0;
}
运行结果
p->getI() = 100
p->getI() = 40
pInt1 == p : 1
pInt2 == p : 1

一些有用的工程建议:
先继承自一个父类,然后实现多个接口
父类中提供equal()成员函数
equal()成员函数用于判断指针是否指向当前对象
与多重继承相关的强制类型转换用dynamic_cast完成

小结:
多继承中可能出现多个虚函数表指针
与多重继承相关的强制类型转换用dynamic_cast完成
工程开发中采用“单继承多接口”的方式使用多继承
父类提供成员函数用于判断指针是否指向当前对象

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐