IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 「地表最强」C++核心编程(四)类和对象--继承 -> 正文阅读

[C++知识库]「地表最强」C++核心编程(四)类和对象--继承

环境:
编译器:CLion2021.3;操作系统:macOS Ventura 13.0.1


地表最强C++系列传送门:
「地表最强」C++核心编程(一)内存分区模型
「地表最强」C++核心编程(二)引用
「地表最强」C++核心编程(三)函数提高
「地表最强」C++核心编程(四)类和对象----封装
「地表最强」C++核心编程(五)文件操作——暂未更新

继承是C++的三大基本特性之一。

一、继承的基本语法

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

其中子类也称为派生类,父类也称为基类。派生类中的成员,包含两大部分:一类是从基类继承过来的,一类是自己增加的成员。从基类继承过过来的表现其共性,而新增的成员体现了其个性。

//公共页面
class BasePage {
public:
    void header() {
        cout << "首页、公开课、登录、注册...(公共头部)" << endl;
    }

    void footer() {
        cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
    }

    void left() {
        cout << "Java,Python,C++...(公共分类列表)" << endl;
    }

};

//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;
    }
};

二、继承方式

继承方式一共有三种:公共继承、保护继承、私有继承。

2.1 public继承

class Base1 {
public:
    int m_A;
protected:
    int m_B;
private:
    int m_C;
};

//公共继承
class Son1 : public Base1 {
public:
    void func() {
        m_A = 10; //可访问 public权限
        m_B = 10; //可访问 protected权限
//        m_C; //不可访问
    }
};

void myClass() {
    Son1 s1;
    s1.m_A; //ok,其他类只能访问到公共权限
//    s1.m_B; //err,其他类只能访问到公共权限,m_B是protected,类外不可以访问
}

2.2 protected继承

//保护继承
class Base2 {
public:
    int m_A;
protected:
    int m_B;
private:
    int m_C;
};

class Son2 : protected Base2 {
public:
    void func() {
        m_A; //可访问 protected权限
        m_B; //可访问 protected权限
        //m_C; //不可访问
    }
};

void myClass2() {
    Son2 s;
    //s.m_A; //不可访问,此时m_A变成了protected
}

2.3 private继承

//私有继承
class Base3 {
public:
    int m_A;
protected:
    int m_B;
private:
    int m_C;
};

class Son3 : private Base3 {
public:
    void func() {
        m_A; //可访问 private权限
        m_B; //可访问 private权限
        //m_C; //不可访问
    }
};

class GrandSon3 : public Son3 {
public:
    void func() {
        //Son3是私有继承,所以继承Son3的属性在GrandSon3中都无法访问到
//        m_A;//err
//        m_B;//err
//        m_C;//err
    }
};

2.4 继承规则

在这里插入图片描述

三、继承中的对象模型

父类中所有非静态成员属性都会被继承下去,私有成员属性只是访问不到,因为被编译器隐藏了,但是也被继承下去了。

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 = " << sizeof(Son) << endl;//16,说明private也被继承了
}

四、继承中的构造和析构顺序

子类继承父类后,当创建子类对象,也会调用父类的构造函数,此时先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反。

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;
}

五、继承同名成员处理方式

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

  • 访问子类同名成员,直接访问即可,也就是对象.属性
  • 访问父类同名成员,需要加作用域,也就是对象.父类::属性

需要注意的一点是:当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数,否则无法访问。强调一下,只要函数名相同,其他的不管一不一样,都会被隐藏

class Base {
public:
    int m_A;

public:
    Base() {
        m_A = 100;
    }

    void func() {
        cout << "Base - func()调用" << endl;
    }

    void func(int a) {
        cout << "Base - func(int a)调用" << endl;
    }
};


class Son : public Base {
public:
    int m_A;

public:
    Son() {
        m_A = 200;
    }

    //当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数
    //如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域
    void func() {
        cout << "Son - func()调用" << endl;
    }
};

void test01() {
    Son s;
    cout << "Son下的m_A = " << s.m_A << endl;
    cout << "Base下的m_A = " << s.Base::m_A << endl;//加个作用域就可以访问父类的

    s.func();
    s.Base::func();
    //子类中的同名成员函数会隐藏掉父类有所有同名成员函数,包括重载的,要想访问父类中被隐藏的同名成员函数的需要加作用域
//    s.func(10);//err,父类中的有参函数被隐藏,而子类又没有有参函数,需要加作用域
    s.Base::func(10);//ok

}

六、继承同名静态成员处理方式

静态成员和非静态成员出现同名,处理方式一致,只不过有两种访问的方式:通过对象通过类名。比非静态多了一个通过类名访问,这是静态成员本身的特性所导致的。
关于静态成员,可以参考「地表最强」C++核心编程(四)类和对象—对象初始化和清理第八点。

class Base {
public:
    static int m_A;

    static void func() {
        cout << "Base - static void func()" << endl;
    }

    static void func(int a) {
        cout << "Base - static void func(int a)" << 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() {
    //通过对象访问
    cout << "通过对象访问: " << endl;
    Son s;
    cout << "Son  下 m_A = " << s.m_A << endl;
    cout << "Base 下 m_A = " << s.Base::m_A << endl;

    //通过类名访问
    cout << "通过类名访问: " << endl;
    cout << "Son  下 m_A = " << Son::m_A << endl;
    
    //第一个::代表通过类名访问,第二个::代表访问父类作用域下
    cout << "Base 下 m_A = " << Son::Base::m_A << endl;//这样通过子类
//    cout << "Base 下 m_A = " << Base::m_A << endl;//这是直接通过父类访问,也是对的
}

//同名成员函数
void test02() {
    //通过对象访问
    cout << "通过对象访问: " << endl;
    Son s;
    s.func();
    s.Base::func();

	//通过类名访问
    cout << "通过类名访问: " << endl;
    Son::func();
    Son::Base::func();
    //出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问
    Son::Base::func(100);
//    Son::func(100);//err,父类中的同名函数被隐藏了
}

七、多继承语法

C++允许一个类继承多个类,就是多继承。但多继承可能会引发从父类中继承多个同名成员,需要加作用域区分。C++实际开发中不建议用多继承。

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

class Base1 {
public:
    int m_A;

public:
    Base1() {
        m_A = 100;
    }
};

class Base2 {
public:
    int m_A;

public:
    Base2() {
        m_A = 200;  //若是m_B则不会出问题,但是改为mA就会出现不明确
    }
};

//语法:class 子类:继承方式 父类1 ,继承方式 父类2
class Son : public Base2, public Base1 {
public:
    int m_C;
    int m_D;

public:
    Son() {
        m_C = 300;
        m_D = 400;
    }
};


//多继承容易产生成员同名的情况,这是就要通过类名作用域来区分调用的是哪一个基类的成员
void test01() {
    Son s;
    cout << "sizeof Son = " << sizeof(s) << endl;
    cout << s.Base1::m_A << endl;
    cout << s.Base2::m_A << endl;
}

八、菱形继承和虚继承

两个派生类继承同一个基类,又有某个类同时继承这两个派生类,这种继承被称为菱形继承,或者钻石继承。
菱形继承的问题:
1.羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
2.草泥马继承自动物的数据继承了两份,但这个据我们只需要一份,这导致资源浪费而且毫无意义。
通过虚继承就可以解决菱形继承带来的问题。

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

virtual关键字使得继承方式变成了虚继承,此时两个子类从同一父类那里继承来的是虚基类指针vbptr(virtual base pointer),这个指针指向了各自的vbtable(虚基类表),这个表中记录了一个偏移量,通过这个偏移量就可以找到需要的数据,这个数据只有一份。
在这里插入图片描述

class Animal {
public:
    int m_Age;
};

//虚继承,此时公共的父类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 = 100;
    cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;//100
    
    st.Tuo::m_Age = 200;//由于虚继承数据只有一份,操作这个会更改之前的赋值

    cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;//200,确实更改了
    cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;//200
    cout << "st.m_Age = " << st.m_Age << endl;//由于virtual,这种访问方式可以了,否则是错误的
}
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-12-25 10:47:31  更:2022-12-25 10:51:38 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/25 2:46:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计