前言

前面讲了封装,但封装只是隐藏了类内部实现。如果使用多态隐藏类本身的话,只有封装是不够的,还需要继承。

继承

通过封装。我们把一些相关的函数和变量包裹在了一起,这些函数和变量就叫做类的成员函数成员变量。继承就是一种获取这个类的成员函数和成员变量的方式。通常,继承了某个类的类叫做该类的派生类或者子类。

根据封装的意义,当父类将部分成员函数和成员变量的访问权限设置为private时,即使被继承了,子类仍然无法访问。

下面通过例子来说明一下,子类是如何保存这些继承来的成员函数和成员变量的。

class A
{
public:
    A() : a( 1 )
    {
    }
    void foo()
    {
        std::cout << "A::foo()"  << std::endl;
        return;
    }
private:
    int a;
};
class B : public A
{
public:
    B() : b( 2 )
    {
    }
private:
    int b;
};
int main()
{
    B x;
    x.foo();
    return 0;
}

继承可以是public、protected或private,不同关键字为父类设置了不同访问权限。

  • public继承意味着父类所有成员成员变量的访问权限在子类中维持不变
  • protected继承意味着父类中public的成员函数和成员变量在子类中变为protected
  • private继承意味着父类中public和protected的成员函数和成员变量在子类中变为private

成员函数

因为类成员函数可以通过隐式参数this区分具体的调用对象,所以类成员函数只需要存在一份就可以。当子类继承父类的成员函数时,子类只是得到了通过子类对象访问父类成员函数的权利。

隐式参数意味着你没有写,但是编译器帮你写了。

当我们gdb调试上面的代码时,会发现x.foo()实际调用的就是A::foo(),而不是A::foo()的一份拷贝。

   0x00000000004004fe <+8>:     lea    -0x10(%rbp),%rax   # -0x10(%rbp)就是x的地址
   0x0000000000400502 <+12>:    mov    %rax,%rdi # 将x的地址作为A::foo()的参数,也就是this
   0x0000000000400505 <+15>:    callq  0x400512 <A::foo()>

其实任何函数都只需要存在一份,成员函数只是一个稍微特殊的函数。

虚函数又是一个稍微特殊的成员函数,它也只存在一份,只不过是在调用上可能要多些操作,细节在讲多态的时候再说。

成员变量

每个类的实例对象都要变更自己的成员变量,因此其空间肯定都是独立的。当子类继承父类的成员变量时,实际只是继承了父类的数据结构。
当通过gdb打印x的值时,我们会发现它的结构如下

(gdb) p x
$1 = {<A> = {a = 1}, b = 2}

当我们直接查看x的地址的内容时,会发现A::aB::b就是连续排布的。

(gdb) x /2x &x
0x7fffffffe540: 0x00000001  0x00000002

也就是说当子类继承父类时,子类的数据结构就是父类的成员变量加上子类自身的成员变量。

之所以父类的成员变量放在前,是因为父类指针可以直接指向子类,当以父类指针操作父类成员变量时就不需要额外进行地址偏移了。如果子类成员变量在前,那么父类指针操作时还需要跳过子类成员变量。

我们大胆推测当子类继承多个父类时,子类的数据结构就是写在前的父类的成员变量加上写在后的父类的成员变量再加上子类自身的成员变量,事实也确实如此。

多继承时,因为存在多个父类数据结构,所以当不同的父类指针指向子类时,会进行一定偏移,保证该父类指针刚好指向自己的数据结构的起始位置。
多继承会复杂化类关系图,而且在一些场景下会带来歧义,因此都不建议使用多继承。反正我自己到现在为止都没在实际项目中用过,只是在一些开源代码中看到过。

结语

继承除了是多态的基础外,还是一种复用代码的方式。但是谨记只有存在父子关系时才使用继承,如果只是为了复用代码的话,应当使用组合。

C++系列总结——继承的更多相关文章

  1. C++ 系列:继承

    Copyright © 1900-2016, NORYES, All Rights Reserved. http://www.cnblogs.com/noryes/ 欢迎转载,请保留此版权声明. -- ...

  2. Python基础系列讲解——继承派生和组合的概念剖析

    Python作为一门面向对象的语言,它的面向对象体系中主要存在这么两种关系,一个是“类”和“实例”的关系,另一个是“父类”和“子类”的关系. 所谓“类”是从一堆对象中以抽象的方式把相同的特征归类得到的 ...

  3. Java入门系列之类继承、抽象类、接口(五)

    前言 C#和Java关于类.抽象类.接口使用方式基本相似,只是对应关键字使用不同罢了,本节呢,我们只是对照C#和Java中关于这三个概念在具体使用时,看看有哪些不一样的地方. 类继承 C#和Java在 ...

  4. Java基础系列 - 抽象类继承和接口实现

    package com.inter; /** * 继承和接口的关系,单继承,多接口 * java不支持多继承,但可通过接口实现多重继承 */ public class test2 { public s ...

  5. Java基础系列 - 子类继承父类,调用父类的构造函数

    package com.test7; public class test7 { public static void main(String[] args) { Son son = new Son(1 ...

  6. JavaScript总结3—对象

    对象是JavaScript的基本类型,他可以从一个称为原型的对象继承属性,这种原型式继承是JavaScript的核心特征.对对象比较常见的操作有:创建,设置,查找,删除,检测和枚举他的属性.每个对象都 ...

  7. 简学Python第二章__巧学数据结构文件操作

    #cnblogs_post_body h2 { background: linear-gradient(to bottom, #18c0ff 0%,#0c7eff 100%); color: #fff ...

  8. 从零开始学 Web 之 CSS(二)文本、标签、特性

    大家好,这里是「 Daotin的梦呓 」从零开始学 Web 系列教程.此文首发于「 Daotin的梦呓 」公众号,欢迎大家订阅关注.在这里我会从 Web 前端零基础开始,一步步学习 Web 相关的知识 ...

  9. Css学习(2)

    1 标签分类(显示方式) 块元素 典型代表,Div,h1-h6,p,ul,li 特点: ★独占一行 ★可以设置宽高 ★ 嵌套(包含)下,子块元素宽度(没有定义情况下)和父块元素宽度默认一致. 行内元素 ...

随机推荐

  1. 带着新人看java虚拟机07(多线程篇)

    这一篇说一下比较枯燥的东西,为什么说枯燥呢,因为我写这都感觉很无聊,无非就是几个阻塞线程的方法和唤醒线程的方法... 1.线程中断 首先我们说一说怎么使得一个正在运行中的线程进入阻塞状态,这也叫做线程 ...

  2. Java进阶篇设计模式之六 ----- 组合模式和过滤器模式

    前言 在上一篇中我们学习了结构型模式的外观模式和装饰器模式.本篇则来学习下组合模式和过滤器模式. 组合模式 简介 组合模式是用于把一组相似的对象当作一个单一的对象.组合模式依据树形结构来组合对象,用来 ...

  3. 解决 Vue 动态生成 el-checkbox 点击无法赋值问题

    博客地址:https://ainyi.com/68 最近遇到一个问题,在一个页面需要动态渲染页面内的表单,其中包括 checkbox 表单类型,并且使用 Element 组件 UI 时,此时 v-mo ...

  4. C# .NET Web API 如何自訂 ModelBinder

    各位好!這次要來替大家介紹的是如何在 .NET  Web API 中自訂一個 ModelBinder 透過自定義的 ModelBinder 我們可以很簡單的將 QueryString 傳過來的參數綁定 ...

  5. css常见的各种布局上(两列布局)

    常见的布局上(两列布局) 1.常见的两列布局 1.1左边固定,右边自适应,左边宽度已知,右边默认占满整行,使用left 左浮动,右边不浮动,设置margin-left:左侧宽度 <style&g ...

  6. html的<h>标签

    <h>标签:标题标签. <h>标签只有六个:<h1>........<h6>

  7. v4v7升级到androidx过程

    因为原项目应用的都是v4v7包,谷歌改成androidx后就升级了一番,首先在properties文件 然后在菜单里点击升级,studio会帮你把报名什么的都改掉 打开项目,发现都自动改掉了,完美,然 ...

  8. ContentProvider和ContentResolver的使用

    ContentProvider ContentProvider 在android中的作用是对外共享数据,也就是说你可以通过ContentProvider把应用中的数据共享给其他应用访问,其他应用可以通 ...

  9. Math类中round、ceil和floor方法的功能

    Java中的Math工具类用来完成除+.-.*./.%等基本运算以外的复杂运算,位于java.lang包下,Math类的构造器全是私有的(private),因此无法创建Math类的对象,Math类的方 ...

  10. SpringBoot之GZip压缩,HTTP/2,文件上传,缓存配置

    1 设置应用端口以及context # HTTP Server port server.port=8080 # Make the application accessible on the given ...