C++中的继承

继承是面向对象的三大特性之一

有些类与类之间存在特殊关系,例如下图:

我们发现,定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。

这个时候我们就可以考虑利用继承的技术,减少重复代码

1. 继承的基本语法

我们看到的很多网站中,都有公共的头部,公共的底部,甚至公共的左侧列表,只有中心内容不同

接下来我分别利用普通的写法和继承写法来实现网页中的内容,看一下继承存在的意义和好处

普通实现:

普通实现页面

Java页面
class Java
{
public:
    void header()
    {
        cout << "首页、公共课、登陆、注册...(公共头部)" << endl;
    }   
    void footer()
    {
        cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
    }
    void left()
    {
        cout << "Java、Python、C++...(公共分类列表)" << endl;
    }
    void content()
    {
        cout << "Java学科视频" << endl;
    }
};


//Python页面
class Python
{
public:
    void header()
    {
        cout << "首页、公共课、登陆、注册...(公共头部)" << endl;
    }   
    void footer()
    {
        cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
    }
    void left()
    {
        cout << "Java、Python、C++...(公共分类列表)" << endl;
    }
    void content()
    {
        cout << "Python学科视频" << endl;
    }
};


//C++页面
class Cpp
{
public:
    void header()
    {
        cout << "首页、公共课、登陆、注册...(公共头部)" << endl;
    }   
    void footer()
    {
        cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
    }
    void left()
    {
        cout << "Java、Python、C++...(公共分类列表)" << endl;
    }
    void content()
    {
        cout << "C++学科视频" << endl;
    }
};

继承实现:

//继承实现页面

//公共页面类
class BasePage
{
public:
    void header()
    {
        cout << "首页、公共课、登陆、注册...(公共头部)" << endl;
    }   
    void footer()
    {
        cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
    }
    void left()
    {
        cout << "Java、Python、C++...(公共分类列表)" << endl;
    }
};


//继承的好处:减少重复的代码
//语法:class 子类名 : 继承方式 父类名
//子类 也称为 派生类
//父类 也称为 基类


//Java页面
class Java : public BasePage
{
public:
    void content()
    {
        cout << "Java学科视频" << endl;
    }
};

//Python页面
class Python : public BasePage
{
public:
    void content()
    {
        cout << "Python学科视频" << endl;
    }
};

//C++页面
class Cpp : public BasePage
{
public:
    void content()
    {
        cout << "C++学科视频" << endl;
    }
};

总结:

继承的好处:可以减少重复的代码

class A : public B;

A 类称为 子类 或 派生类

B 类称为 父类 或 基类

派生类中的成员,包含两大部分:

一类是从基类继承过来的,一类是自己增加的成员。

从基类继承过来的表现其共性,而新增的成员体现了其个性。

2. 继承方式

继承的语法:class 子类 : 继承方式 父类

继承的方式一共有三种:

  • 公共继承(无法访问父类的私有内容,其余内容正常访问且不改变权限)
  • 保护继承(无法访问父类的私有内容,其余都变为保护权限)
  • 私有继承(无法访问父类的私有内容,其余都变为私有权限)

#include<iostream>
using namespace std;


//继承方式

//公共继承
class Base_1
{
public:
    int m_a;
protected:
    int m_b;
private:
    int m_c;
};


class Son_1 : public Base_1
{
public:
    void func()
    {
        m_a = 10;  //父类中的公共权限成员,到子类中依然是公共权限
        m_b = 20;  //父类中的保护权限成员,到子类中依然是保护权限
        // m_c = 30;  //父类中的私有权限成员,子类访问不到
    }
};


//保护继承
class Base_2
{
public:
    int m_a;
protected:
    int m_b;
private:
    int m_c;
};


class Son_2 : protected Base_2
{
public:
    void func()
    {
        m_a = 50;  //父类中的公共权限成员,到子类变为保护权限
        m_b = 60;  //父类中的保护权限成员,到子类中依然是保护权限
        // m_c = 30;  //父类中的私有权限成员,子类访问不到
    }
};


//私有继承
class Base_3
{
public:
    int m_a;
protected:
    int m_b;
private:
    int m_c;
};


class Son_3 : private Base_3
{
public:
    void func()
    {
        m_a = 50;  //父类中的公共权限成员,到子类变为私有权限
        m_b = 60;  //父类中的保护权限成员,到子类变为私有权限
        // m_c = 30;  //父类中的私有权限成员,子类访问不到
    }
};



void test01()
{
    Son_1 s1;

    s1.m_a = 100;
    // s1.m_b = 200;  //Son_1中,m_b是保护权限,类外访问不到
}


void test02()
{
    Son_2 s2;

    // s2.m_a = 500;  //Son_2中,m_a变为保护权限,类外访问不到
    // s2.m_b = 200;  //Son_2中,m_b是保护权限,类外访问不到
}

void test03()
{
    Son_3 s3;

    // s3.m_a = 500;  //Son_3中,m_a变为私有权限,类外访问不到
    // s3.m_b = 200;  //Son_3中,m_b变为私有权限,类外访问不到
}


class GrandSon_3 : public Son_3
{
public:
    void func()
    {
        // m_a = 1000;  //Son_3中,m_a为私有权限,子类访问不到
        // m_b = 2000;  //Son_3中,m_b为私有权限,子类访问不到
    }
};


int main()
{
    test01();
    test02();
    test03();

    return 0;
}

3.继承中的对象模型

问题:从父类继承过来的成员,哪些属于子类对象?

示例:

#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()
{
    //16
    //父类中的所有非静态成员属性都会被子类继承
    //父类中的私有成员属性 被编译器 隐藏了,虽然访问不到,但是仍然继承
    cout << "size of Son = " << sizeof(Son) << endl;
    //12
    cout << "size of Base = " << sizeof(Base) << endl;
}


int main()
{
    test01();

    return 0;
}


输出:
---------------------------------------------------------------------------------
size of Son = 16
size of Base = 12

在windows系统下,可以利用开发人员命令提示工具查看对象模型,步骤如下:

  • 打开vscode的开发人员命令提示符
  • 跳转到项目文件所在的盘符F:
  • 跳转文件路径cd 具体路径
  • cl /d1 reportSingleClassLayout类名 文件名

效果如下图所示:

4. 继承中构造和析构的顺序

子类继承父类后,当创建子类对象时,也会调用父类的构造函数

问题:父类和子类的构造函数和析构函数的顺序是谁先谁后?

示例:

#include<iostream>
using namespace std;


//继承中构造和析构的顺序
class Base
{
public:

    Base()
    {
        cout << "Base的构造函数" << endl;
    }

    ~Base()
    {
        cout << "Base的析构函数" << endl;
    }
};


class Son : public Base
{
public:
    Son()
    {
        cout << "Son的构造函数" << endl;
    }
    ~Son()
    {
        cout << "Son的析构函数" << endl;
    }
};


void test01()
{
    //继承中的构造和析构顺序如下:
    //先构造父类,再构造子类,析构的顺序与构造的顺序相反
    Son s;
}


int main()
{
    test01();

    return 0;
}


输出:
---------------------------------------------------------------------------------
Base的构造函数
Son的构造函数
Son的析构函数
Base的析构函数

总结:继承中,先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反

5. 继承时同名成员处理方式

问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?

  • 访问子类同名成员,直接访问即可
  • 访问父类同名成员,需要加作用域

示例:

#include<iostream>
using namespace std;


//继承中同名成员处理
class Base
{
public:
    Base()
    {
        m_a = 100;
    }

    void func()
    {
        cout << "Base类的func()函数调用" << endl;
    }

    void func(int a)
    {
        cout << "Base类的func(int a)函数调用" << endl;
    }

    int m_a;
};


class Son : public Base
{
public:
    Son()
    {
        m_a = 200;
    }

    void func()
    {
        cout << "Son类的func()函数调用" << endl;
    }

    int m_a;
};


//成员同名属性的处理方式
void test01()
{
    Son s;

    //默认访问子类中的同名成员
    cout << "Son 类中的 m_a = " << s.m_a << endl;
    //如果想通过子类对象访问到父类中的同名成员,需要加作用域
    cout << "Base 类中的 m_a = " << s.Base::m_a << endl;
}


//成员同名函数的处理方式
void test02()
{
    Son s;

    //默认调用子类中的同名成员
    s.func();  
    //如果想通过子类对象访问到父类中的同名成员,需要加作用域
    s.Base::func();

    s.Base::func(100);
}


int main()
{
    test01();
    test02();

    return 0;
}


输出:
---------------------------------------------------------------------------------
Son 类中的 m_a = 200
Base 类中的 m_a = 100
Son类的func()函数调用
Base类的func()函数调用
Base类的func(int a)函数调用

总结:

  1. 子类对象可以直接访问到子类中的同名成员
  2. 子类对象加作用域可以访问到父类同名成员
  3. 当子类对象与父类拥有同名的成员函数,子类会隐藏父类中的同名成员函数,加作用域可以访问到父类中的同名函数

6. 继承中同名静态成员的处理方式

问题:继承中同名的静态成员在子类对象上如何进行访问?

静态成员对象的特点:

  • 所有对象共享同一份数据
  • 编译阶段就分配内存
  • 类内声明,类外初始化

静态成员函数的特点:

  • 只能访问静态成员变量,不能访问非静态成员变量
  • 所有函数共享同一份函数实例

静态成员和非静态成员出现同名,处理方式一致

  • 访问子类同名成员,直接访问即可
  • 访问父类同名成员,需要加作用欲

示例:

#include<iostream>
using namespace std;


//继承中同名静态成员的处理方式
class Base
{
public:

    static int m_a;

    static void func()
    {
        cout << "Base::static void func()" << endl;
    }
};
int Base::m_a = 100;


class Son : public Base
{
public:

    static int m_a;

    static void func()
    {
        cout << "Son::static void func()" << endl;
    }
};
int Son::m_a = 200;


//同名静态成员属性
void test01()
{
    //1、通过对象访问
    cout << "通过对象访问:" << endl;
    Son s;

    cout << "Son 类中的 m_a = " << s.m_a << endl;
    cout << "Base 类中的 m_a = " << s.Base::m_a << endl;

    //2、通过类名访问
    cout << "通过类名访问:" << endl;
    cout << "Son 类中的 m_a = " << Son::m_a << endl;
    //第一个::代表通过类名方式访问,第二个::代表访问父类作用域
    cout << "Base 类中的 m_a = " << Son::Base::m_a << endl;
}


// 同名静态成员函数
void test02()
{
    //1、通过对象访问
    cout << "通过对象访问:" << endl;
    Son s;

    //默认调用子类中的同名成员
    s.func();  
    //如果想通过子类对象访问到父类中的同名成员,需要加作用域
    s.Base::func();

    //2、通过类名访问
    cout << "通过类名访问:" << endl;
    Son::func();
    Son::Base::func();
}


int main()
{
    test01();
    cout << "-----------------------------"  << endl;
    test02();

    return 0;
}


输出:
---------------------------------------------------------------------------------
通过对象访问:
Son 类中的 m_a = 200
Base 类中的 m_a = 100
通过类名访问:
Son 类中的 m_a = 200
Base 类中的 m_a = 100
-----------------------------
通过对象访问:
Son::static void func()
Base::static void func()
通过类名访问:
Son::static void func()
Base::static void func()

总结:同名静态成员变量和非静态成员变量的处理方式一样,只不过有两种访问方式(通过对象和通过类名)

7. 多继承语法

C++允许一个类继承多个类

语法:class 子类 : 继承方式 父类1, 继承方式 父类2, ...

多继承可能会遇到不同父类中出现同名成员的情况,此时需要加作用域区分

C++实际开发当中,不建议用多继承

示例:

#include<iostream>
using namespace std;


//多继承语法
class Base1
{
public:

    Base1()
    {
        m_a = 100;
    }

    int m_a;
};


class Base2
{
public:

    Base2()
    {
        m_a = 200;
    }

    int m_a;
};


class Son : public Base1, public Base2
{
public:

        Son()
        {
            m_c = 300;
            m_d = 400;
        }

        int m_c;
        int m_d;
};


//同名静态成员属性
void test01()
{
    Son s1;

    //16
    cout << "size of Son = " << sizeof(Son) << endl;

    //当父类中有同名成员时,需要加作用域加以区分
    cout << "Base1::m_a = " << s1.Base1::m_a << endl;
    cout << "Base2::m_a = " << s1.Base2::m_a << endl;
}


int main()
{
    test01();

    return 0;
}


输出:
---------------------------------------------------------------------------------
size of Son = 16
Base1::m_a = 100
Base2::m_a = 200

总结:多继承中如果出现父类中成员同名的情况,子类使用时要加作用域

8. 菱形继承

菱形继承概念:

  • 两个派生类继承同一个基类
  • 又有某个类同时继承这两个派生类
  • 这种继承方式被称为菱形继承,也称为钻石继承

菱形继承存在的问题:

  1. 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性
  2. 草泥马继承了两次动物的数据,这是一种内存浪费,实际上只需要保留一份就可以

示例:

#include<iostream>
using namespace std;


//动物类
class Animal
{
public:
    int m_age;
};

//利用虚继承,解决菱形继承的问题
//在继承之前,加上关键字 virtual 变为虚继承,此时 Animal 类称为虚基类

//羊类
class Sheep : virtual public Animal{};

//驼类
class Tuo : virtual public Animal{};

//羊驼类
class SheepTuo : public Sheep, public Tuo{};


void test01()
{
    SheepTuo st;

    st.Sheep::m_age = 18;
    st.Tuo::m_age = 28;

    //菱形继承时,如果访问同一父类的成员变量,需要指出访问哪一个父类的成员变量
    cout << "st.Sheep::m_age = " << st.Sheep::m_age << endl;
    cout << "st.Tuo::m_age = " << st.Tuo::m_age << endl;

    //菱形继承同样的数据保存了两份,导致资源浪费
    cout << "st.m_age = " << st.m_age << endl;
}


int main()
{
    test01();

    return 0;
}


输出:
---------------------------------------------------------------------------------
st.Sheep::m_age = 28
st.Tuo::m_age = 28
st.m_age = 28

普通继承(保留两份相同的m_Age数据)

虚继承(只保留一份m_Age数据)

vbptr:即虚基类指针,指向虚基类表vbtable

v - virtual

b - base

ptr - pointer

  • 13
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值