写在前面:

大家脑海里是否还能浮现以下的场景:
刘海柱:我,从小父母双亡,家徒四壁,除了成功,别无选择
你呢?
你回了老家能干啥,除了继承你家那个养猪场,你爸的几套房子几辆车,和五十亩地之外,你说你还有啥,你还是个啥!!!!
今那么今天咱就讲讲继承的那些事
目录:

  1. 继承的概念与定义
  2. 继承方式
  3. 基类和派生类对象赋值转换
  4. 继承中的作用域
  5. 派生类的默认成员函数
  6. 继承与友元
  7. 继承中的static成员变量
  8. 继承的形式(单继承,多继承,菱形继承,虚拟继承)

一、继承的概念与定义

先抛开继承在语言里的概念,看过这么多年的电视的我们应该也熟悉“继承”这个词,
大多是,子承父业继承遗产之类的,那么在继承之后,儿子理所应当的拥有了父亲的所有东西,而儿子还应该还拥有一些自己的东西。

这是与继承在编程语言中的含义是大致相同的:

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用

名词解释:

定义:
示例:

class A
{};
class B:public A//这就是类的继承的定义  class B继承了 A 拥有了A的东西
{};

原来的类被称为基类或者父类
继承基类或者父类的类被称为子类或者派生类

那么具体有什么特性呢?
看示例的代码:

#include<iostream>
using namespace std;
class Father//基类 或者派生类
{public:Father()//构造函数{cout << "Father()" << endl;}~Father()//析构函数{cout << "~Father()" << endl;}void Print()//类的成员函数{cout << "money:" << money << "car:" << car << endl;}
protected://成员变量int money=100;int car=2;
};
class Son:public Father//子类或者派生类
{public:Son()//构造函数{cout << "Son()" << endl;}~Son()//析构函数{cout << "~Son()" << endl;}
protected://成员变量int age;
};
int main()
{Father f;Son s;s.Print();return 0;
}

从下面的打印结果,可以看到我们实例化了一个 类Son 的对象 s; s 中有默认的构造和析构函数

但是通过打印结果我们发现,先调用了基类(父类) 的构造函数,再调用了子类的构造函数,而子类也可以调用父类的成员函数,说明了成员函数的复用,与此同时我们还发现,析构的时候是先调用子类的析构函数再调用父类的析构函数

而父类的成员变量也理所应当的是可以被子类复用
例如:我们在上述的代码中父类拥有两个成员变量 money=100和car=2 而在子类中能否访问呢?
当然是可以的!

大家可能会注意到一个问题:
上面的成员变量是 protected
继承的方式是 : class Son : public Father
那么这些继承的方式又有什么异同呢?

二、继承方式

类成员/继承方式 public继承 protected继承 private继承
基类的public成员 派生类的public成员 派生类的protected成员 派生类的private成员
基类的protected成员 派生类的protected成员 派生类的protected成员 派生类的private成员
基类的private成员 在派生类中不可见 在派生类中不可见 在派生类中不可见


总结起来就是:

  1. 基类private成员派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是 被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面不能去访问它。
    例如:
    无论什么继承方式,基类的私有成员是不可见的


    但是子类可以通过调用基类的成员函数,来对基类的私有成员进行访问
  1. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
  1. 基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式就是比较访问范围小的那个,例如基类的public程序员被protected继承时,就成为了子类的protected成员,
    访问限定符的范围 public > protected > private。
  2. 使用关键字class默认的继承方式是private,使用struct默认的继承方式是public,不过最好显示的 写出继承方式。
    例如:
    class Student : Person 这里默认是private继承,将基类的public、private成员都继承为私有的
  3. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用 protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中 扩展维护性不强。

三、基类和派生类对象赋值转换

定义两个类:

class Father
{};
class Son:public Father
{};

在子类对象与父类对象的赋值过程中有如下需要注意的事项

int main()
{Son s;Father f;f = s;//子类对象可以赋值给父类对象Father* p = &s;//也可以赋值给父类的指针Father& p1 = s;//也可以赋值给父类的引用//s = f;//但是父类不能赋值给子类对象return 0;
}

注意:

派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。 基类对象不能赋值给派生类对象 基类的指针可以通过强制类型转换赋值给派生类的指针。但是必须是基类的指针是指向派生类对象时才 是安全的。这里基类如果是多态类型,可以使用RTTI(Run-Time Type Information)的dynamic_cast 来 进行识别后进行安全转换。

ps:之所以这里的子类可以给父类对象赋值,而父类不能赋值给子类对象是因为,一般子类的成员变量除了继承父类的还有他自己的,而赋值这一操作类似于切片的操作,是一个多向少的过程,就像是我可以给你你拥有的,但是我不能给你我没有拥有的这个意思。

四、继承中的作用域

这里有这样一个问题,我在子类中如何调用父类的成员函数或者成员变量呢?
其实,即使子类继承了父类的成员函数和成员变量,他们之间还是会有明确而独立的作用域的。
例如:

class Father
{public:void Print(){cout <<"Father:"<< "money:" << money << "car:" << car << endl;}int money=10;int car=1;
};
class Son: public Father
{public:void PrintBaseElement(){Father::Print();}void Print(){cout << "Father: money:" << Father::money << "car:" << Father::car << endl;cout <<"Son:"<<"money:"<< money << "car: " << car << endl;}protected:int age;int money = 100;//int car = 2;
};
int main()
{Son s;s.Print();s.PrintBaseElement();return 0;
}

打印结果:

我们这里会发现 父类 Father 的成员变量有 : money 和 car 这两个变量了,当子类中再定义相同的变量名会如何呢?

这时当我们在子类中访问 money 和 car 这变量时访问的就会是子类自己定义的 money 和car ,同名成员变量将会将父类的成员变量隐藏起来,也可以算作是子类重写了父类的成员变量,而父类的成员变量还是保持原来的值,但是不建议这样定义,容易造成混淆,要想访问父类的变量需要显示访问 如:

 void Print1(){cout << "Father: money:" << Father::money << "car:" << Father::car << endl;//作用域展开,进行显示的访问cout <<"Son:"<<"money:"<< money << "car: " << car << endl;//同名的成员变量对父类的成员变量造成隐藏}

同理:当子类定义了与父类同名的成员函数,也同样构成函数隐藏,同样也可以对其进行显示的调用

class Son: public Father
{public:void PrintBaseElement(){Father::Print();//显示的调用父类的成员函数}
};

以上的定义以及显示访问都是被允许的操作,但是在继承类里面还是不建议这样定义,容易发生混乱

五、派生类的默认成员函数

类有六个默认成员函数分别是:

1.构造函数
2.拷贝构造函数
3.赋值函数
4.析构函数
5.取地址
6.重载

那么在子类的默认成员函数会如何调用呢?
先看一段代码:

#include<iostream>
using namespace std;
class Father
{public:Father(const int _money = 10, const int _car = 2):money(_money),car(_car){cout << "Father() 构造函数" << endl;}Father(const Father& f):money(f.money),car(f.car){cout << "Father(const Father& f) 拷贝构造函数" << endl;}Father& operator=(const Father&f){cout << "Father& operator=(const Father&f) " << endl;;if (this != &f){money = f.money;car = f.car;}return *this;}~Father(){cout << "~Father()析构函数" << endl;}
protected:  int money;int car;
};
class Son :public Father
{public:Son(const int _money,const int _car,int _book):Father(_money,_car),book(_book){cout << "Son() 构造函数" << endl;}Son(const Son&f):Father(f),book(f.book){cout << "Son(const Son&f) 拷贝构造函数" << endl;}Son& operator=(const Son&s){if (this != &s)//防止自己给自己赋值{Father::operator=(s);book = s.book;}return *this;}~Son(){cout << "~Son 析构函数" << endl;}
protected:int book;
};
int main()
{Son s1(100, 20, 15);Son s2(s1);Son s3(10, 35, 5);s1 = s3;return 0;
}

打印结果:

这里我们可以总结出如下的规律:

1.子类会先调用父类的构造函数再调用子类自己的构造函数,如果父类没有的话,必须在子类的构造函数初始化列表显示调用

当子类的构造函数不显示调用时会自动调用父类的构造函数,否则应该显示调用

2.子类必须调用父类的拷贝构造函数完成父类的拷贝构造初始化,并完成自己的拷贝构造初始化
3.子类调用operator =函数的时候必须调用父类的operator =
4.子类先调用自己的析构函数,再调用父类的析构函数

构造结果测试:

根据上面的知识这里举一个,类不能被继承的情况
示例:
当父类的构造函数为私有的时候,即使子类怎么继承都无法调用父类的构造函数这样就无法继承该类了

class Father
{private:Father(){}
};
class Son :public Father
{public:Son(){}//报错  即使这里不写下面实例化对象也会报错protected:int age;
};
int main()
{Son s;//不写子类的构造函数这里还是会报错:显示无法引用Son的构造函数,因为它时已经被删除的函数return 0;
}

六、继承与友元

父类的友元函数是不能被继承的,因此父类的友元函数无法访问子类的私有成员和保护成员,但是可以访问公有成员例如:

class A
{public:friend void Print(const A& a, const B&b);
public://protected 和private 不可访问int car = 10;
};
class B : public A
{public://protected 和private 不可访问int book = 5;
};void Print(const A& a,const B& b)
{cout << a.car << " " << b.book;
}
int main()
{A a;B b;Print(a, b);return 0;
}

七、继承与静态成员

关于静态成员 被 static 修饰的成员在继承中是唯一的实例,也就是说:基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一 个static成员实例 。
关于 关键字 static 这里不再过多的阐述,作为存储在静态区的变量,即使这个变量出了函数的作用域,依然会保持它的值,这样的特质也使其在类的继承中也因此只有这一个static 成员的实例。
例如:
class A 中有一个 static 变量,在类外进行第一次初始化,A 里面的构造函数是对 这个变量++,
class B、C分别继承了A 那么会出现什么结果:

class A
{public:static int count;A() { ++count; }
};
int A::count = 0;
class B :public A {};
class C : public A {};
int main()
{A a;B b;C c;cout << c.count << endl;return 0;
}


而当我们不使用 static 对其修饰会怎么样:

八、继承的多种形式

1.单继承:
像我们上面介绍的大多都是单继承,也就是:

class A{};
class B:public A{};

像是这样就是单继承
2.多继承:
就是一个类继承了多个类(两个或者两个以上)如:

class A{};
class B{];
class C:public A,public B{};//这是C++支持的方法,C就同时继承了A和B的成员变量和成员函数

3.菱形继承

像是这样,因为他们之间的继承关系像一个菱形因而得名,这使多继承的一种比较特殊的情况,这时我们可以发现,这样的继承:
当 class B和class C没有自定义自己的成员变量和成员函数的时候还不如之间 使 class D :public A ,这样的菱形会造成数据的冗余,多继承了一份A中的成员变量和成员函数,而且这样的继承还容易造成二义性

上图表示数据的二义性

这样就会有两份A类的数据了,而继承的数据string 也存在着二义性,到底是B类的还是C类的,这个问题使可以通过显示访问解决的,但是数据的冗余却无法解决
因此在设计类的继承的时候要避免设计出这样的菱形继承

那么这样的如何去解决呢?
这里给出了虚拟继承的概念:

虚拟继承

通过 virtual 关键字,使继承称为虚拟继承 如:

class A{};
class B:virtual public A{];
class C:virtual public B{};
class D:public B,public C{};

而虚拟继承的原理就是:
这里是通过了B和C的两个指针,指向的一张表。这两个指 针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A

这就是虚拟继承的原理,但是值得注意的是,当需要解决菱形继承的数据冗余和二义性的时候可以使用虚拟继承,但是不要在其他的地使用虚拟继承

C++之继承详细介绍(你除了继承你爸几亿的资产,你还是个啥??)相关推荐

  1. php 接口继承,详细对比php中类继承和接口继承

    PHP类继承: 1.PHP类不支持多继承,也就是子类只能继承一个父类,但是支持多层次继承 比如:class frist{ public function __construct(){ echo &qu ...

  2. 继承的详细介绍与理解,看了就懂

    继承的介绍 继承的概念及定义 定义格式 继承基类成员访问方式的变化 基类和派生类对象赋值转换 继承中的作用域 派生类的默认成员函数 继承与友元 继承与静态成员 ⭐复杂的菱形继承及菱形虚拟继承 总结 继 ...

  3. java方法的继承 ppt,Java学习之继承基本介绍和实例方法,java学习继承实例

    Java学习之继承基本介绍和实例方法,java学习继承实例 继承基本介绍 继承概念不做过多介绍.这里只介绍继承使用过程中需要注意的地方.继承的基本语法格式如下,用关键字extends来表示继承关系. ...

  4. java里类得继承详细讲解_java中类的继承详解。

    前言 继承是面向对象的三大特征之一. 也是实现软件复用的重要手段. Java继承具有单继承的特点, 每个子类只有一个直接父类. 继承的特点 Java的继承通过extends关键字实现. 实现继承的类被 ...

  5. 王者服务器维护段位掉了,王者荣耀更新掉段机制是什么 S22赛季段位继承规则介绍...

    王者荣耀这款游戏马上就要开启s22赛季了,段位继承一直都是玩家们的关心的东西,不少玩家们都想知道自己新赛季的起点在哪里,感兴趣的话可以来看看40407小编的攻略哦! S22赛季段位继承规则介绍 王者荣 ...

  6. oc语言学习之基础知识点介绍(三):类方法、封装以及继承的介绍

    一.类方法的使用 /*像我们之前学的方法,必须先实例化这个类的对象才能调用这个方法,类方法不用实例化对象,直接调用这个方法.之前学的方法调用的语法:[对象名 方法名]; //对象方法类方法:[类名 方 ...

  7. php类的继承和,详细对比php中类继承和接口继承

    PHP类继承: 1.PHP类不支持多继承,也就是子类只能继承一个父类,但是支持多层次继承 比如: class frist{ public function __construct(){ echo &q ...

  8. java中继承的介绍

    继承: 生活中的继承:继承财产,钱不用自己挣,自己也能花! 皇位继承:继承江山,江山不用自己打,继承过来也能坐. 软件中的继承:继承代码,代码不用自己写,继承过来也能用. 继承适用性:当多个类之间存在 ...

  9. java的annotation_Java Annotation认知(包括框架图、详细介绍、示例说明)

    摘要 Java Annotation是JDK5.0引入的一种注释机制. 网上很多关于Java Annotation的文章,看得人眼花缭乱.Java Annotation本来很简单的,结果说的人没说清楚 ...

  10. Hadoop中Namenode单点故障的解决方案及详细介绍

    正如大家所知,NameNode在Hadoop系统中存在单点故障问题,这个对于标榜高可用性的Hadoop来说一直是个软肋.本文讨论一下为了解决这个问题而存在的几个solution. 1. Seconda ...

最新文章

  1. 求求你别再写上千行的类了,试试这些牛逼的重构技巧吧
  2. P5431 【模板】乘法逆元2(小学数学题,毒瘤鱼,卡常之王yyds)
  3. CF1060D Social Circles
  4. AGI:走向通用人工智能的【哲学】之现实世界的虚拟与真实——带你回看1998年的经典影片《The Truman Show》感悟“什么是真实”
  5. 定义跳转插件_虚幻插件Review:Logic Driver Pro 终极状态机插件
  6. string’ does not name a type 错误解析
  7. 【转】详细解析Java中抽象类和接口的区别
  8. lambda表达式java_Lambda表达式Java教程
  9. appium===安卓SDK下载很慢的解决办法
  10. 安装nodejs插件并在sublime text 3上使用
  11. LeetCode 127. 单词接龙(广度优先遍历)
  12. Android-【报错】java.lang.ClassCastException: .MainActivity cannot be cast to java.lang.Runnable
  13. 计算机网络是几级学科,教育部更新学科目录 “网络空间安全”增设为一级学科...
  14. diskgenius克隆硬盘无法启动_用diskgenius成功拷出故障硬盘数据
  15. oracle9i12535错误,11gr2 alert日志中报TNS-12535 TNS-00505原因及解决方法
  16. 谈谈String.intern方法
  17. 百度AI的2020:新基建铺路,硬实力出圈
  18. 梦中情人sbl新变种snow.exe,snowfall.exe的分析
  19. 华夏银行签约金融壹账通 借助金融科技转型升级
  20. 111、Flutter 实现动画颜色渐变效果

热门文章

  1. 我的世界手机java版下载_我的世界java版下载手机版-我的世界java版手机版v1.16 - 手机迷...
  2. 双目标定(三)标定流程(含矫正)
  3. jpa原生query_SpringDataJpa使用原生sql的小坑
  4. 服务器 分辨率问题 显示器不显示不出来,遇到显示器分辨率调不了这个问题怎么办?...
  5. html写樱花树,写樱花树的作文
  6. std::decay 类型萃取
  7. 计算机房电器设备功率密度,WP155_R0_数据中心空间和功率密度需求的计算.pdf
  8. 以太坊2.0协议核心Beacon链详解
  9. 霜降已至,你妈喊你加裤添衣!感恩老妈,就送她智慧云谷新风机
  10. 一种人机友好的视频压缩方案(HMFVC)