有关C++继承多态的一点想法

  • 前言
  • 一、面向对象, 多态, 继承和你想的可能是相反的
  • 二、面向对象, 遵从简单的原则
  • 总结

前言

最近看了一道题目, C++实现多态的时候父类指针如何调用子类独有的数据成员 , 发现有时候对面向对象, 很多小白误解很深, 答题者的回答, 会加深这种误解, 如果这种误解根深地固, 恐怕会连累很多人, 实在是看不下去, 只能写篇文章, 能解释多少就解释多少.


一、面向对象, 多态, 继承和你想的可能是相反的

对于父类或基类, 通常不是一个具体的类, 也不能产生一个真正的父类对象.

如果你学过中国哲学史, 会知道一个典故, 叫 “白马非马”, 小时候只觉得这是诡辩, 现在想起来, 这是科学, 是分类学, 是逻辑学, 是面向对象的鼻祖.

马是一个抽象分类, 马在动物分类学中分类为:脊椎动物亚门(Vertebrata)、哺乳纲(Mammalia)、奇蹄目(Perissodactyla)、马科(Equidae)、马属(Equus)、马种.

马这个字, 代表世界上所有的曾经生存过, 现在生存着, 以后出生的所有品种, 所有体重, 所有颜色,所有性别的个体的马的总称, 它从来不是一个对象, 一个有血有肉的对象.

所以马是基类是父类, 白马是派生类是子类.

马类中会有一些性质, 比如四条腿, 四个蹄子, 一个尾巴, 吃草, 很能跑, 但不会包含一些性质, 比如颜色, 颜色要在子类, 也就是黑马, 白马, 红马, 花马中设置.

白马可以产生一个对象, 有血有肉的对象, 一匹白色的马, 同时, 这个对象还要有身高, 体重, 年龄, 血统, 名字等等.

一个子类对象, 必须包含父类的所有性质, 并且要有它自己的多出来的父类没有的性质.

基类作为更高层的抽象, 本身就不应该产生基类对象, 就算产生基类对象, 也不能操作子类对象的特有成员, 因为它就没有包含进去, 操作一个没有的东西, 必然是错误的.

以下代码是前言中提出问题的人所写, 就犯了这种错误.

class father{public:int m_a;
};class son:public father{public:int m_b;
};int main(){father*  p1 = new father();father->m_a = 1;   //可行father->m_b = 0;   //失败
}

但被选中的答案是这样, 将父类对象强制转换为子类对象, 然后给变型的子类对象的成员赋值.

son* p2 = static_cast<son*>(p1); // 将 p1 转换成 son 类型的指针
p2->m_b = 0; // 访问子类独有的数据成员

我只能说, 题主和回答者根本不了解自己干了什么.

我用文字解释一下:

  1. 生成一个父类对象, 并用一个父类指针指向它. 此时, 系统为其开辟一块包含一个 int 大小的内存. 内存的类型为 father.
  2. 强行将这块内存当作子类对象, 但子类对象除了包含父类对象的一个 int 大小的内存, 还要包含另一个 int 大小的内存.
  3. 赋值第一个int占用内存, 让其值为1.
  4. 赋值第二个本来没有, 但生生认为它有的第二个int占用的内存, 让其值为0.

问题出现了, 你操作了一个原本不属于你的内存, 在一个小程序中不是问题, 随机崩溃而已, 但在一个大型程序中, 尤其是复杂继承的复杂类中, 没人知道你改的内存究竟是属于谁, 如果是闲着没用的系统内存, 可能不会崩溃, 也不会改变程序中其它对象的内存, 但若不是, 那可能性就是千千万了, 好一点的直接崩溃, 努力排查能挽回损失, 坏一点的, 就是改变了重要对象的值, 导致程序运行在垃圾数据之中, 出现任何状况都有可能, 并且极难排查, 因为C++的理念是信任程序员, 不检查这些内存操作错误, 恐怕整个项目都要完蛋. 这也是C++被人诟病的地方, 太信任程序员.

二、面向对象, 遵从简单的原则

我们用面向对象编程, 使用继承, 无外乎图一个代码复用, 一个多态. 本来是为简化操作, 但理解不深, 可能适得其反.

进行继承设计时, 请用抽象类作为基类, 不要用具体类做基类.

比如人类和大猩猩, 都是具体类, 谁都不是谁的基类, 虽然都是灵长类, 人科, 都有双手双脚, 两只眼睛, 问题是这是两个平行的具体类, 谁也不比谁抽象, 谁继承谁都是一团乱麻. 但人类和大猩猩都是灵长类人科的子类. 灵长类人科, 这个抽象的概念就是基类.

还有正方形和长方形以及梯形和平行四边形, 还有不规则四边形, 究竟谁是基类?

谁都不是基类, 都是具体类而非抽象类, 基类可以是形状, 形状不含点, 边, 角等具体类的成员, 但可以含两个虚函数, 一个叫周长, 一个叫面积.

说白了, 以上这些就是共用这两个接口, 然后可以用一个基类指针或引用抹除类型, 共用一个容器, 或用作形参被函数调用, 通过虚函数实现多态.

永远是基类指针或引用指向一个子类对象, 而不是反过来. 如果要类型转换, 永远是子类转为基类. 如果非要基类转子类, 那么程序何时崩溃, 就看你的幸运值了.

所以对于前言中问题的回答, 我给出了下面这个代码, 也就是正常的面向对象用法.

#include <iostream>class father
{public:father() = default;virtual ~father() = default;virtual auto getNum() -> int = 0;
};class son1 : public father
{int m_a = 0;public:auto getNum() -> int override{return m_a;}
};class son2 : public father
{int m_b = 1;public:auto getNum() -> int override{return m_b;}
};auto main() -> int
{father *ptrFather1 = new son1();father *ptrFather2 = new son2();int testA = ptrFather1->getNum();int testB = ptrFather2->getNum();std::cout << testA << ' ' << testB << std::endl;delete ptrFather1;delete ptrFather2;return 0;
}

另外, 由于真正编程工作的复杂, 和具体需求的巨大变动, 如果没有考虑好如何设置继承体系, 很可能导致一个重写底层基类的需求, 导致整个继承体系的崩溃, 当然, 程序员可能无法避免这个问题, 因为让人崩溃的需求变动, 一般不是程序员本身要求的.

所以, 编程的原则是 kiss, keep it simple and safe. 保持简单和安全.

能简单的不复杂化, 不是每个程序都需要面向对象, 简单的类能完成的就不要复杂类, 单独类能完成的就不要继承. 能单继承的就不要多继承.

总之, 不要复杂化, 乱七八糟的继承体系, 意味着可能只有程序的缔造者, 在编写程序的时候能明白, 如果没有详实而正确的文档, 大概过个一年半载, 将无人可以看懂, 包括编写此程序的那个程序员.


总结

关于面向对象, 我们本身的理解可能千差万别, 有些了解C++对象内存模型的程序员, 甚至可以玩出花儿来, 各种骚操作, 处处埋坑, 让人绝不敢动他的代码, 也是由于对面向对象的理解不同, 每个人的使用偏向有巨大区别, 有的人嗤之以鼻, 用C语言和脚本完成C++的工作, 来避免C++面向对象的问题, 有些人倾向于 “面向组件” 编程, 抽象出各种不同的性质作为基类, 乐高式继承, 玩的不亦乐乎, 还有些沉迷于具体类继承具体类, 搞到自己头大, 也给后来者埋坑.

总之, 一句歌词, 略表心意:

原谅我这一生不羁放纵爱自由, 也会怕有一天会跌倒,背弃了理想, 谁人都可以,哪会怕有一天只你共我.

好自为之.

2023-01-03 有关C++继承多态的一点想法相关推荐

  1. 【2023.01.03】定时执行专家 V6.5 更新日志 - TimingExecutor V6.5 Change Log

    目录 ◆ 最新版下载链接 ◆ 软件更新日志 – TimingExecutor Full Change Log ▼ 2023-01-03  V6.5 ▼ 2022-12-25  V6.4 ▼ 2022- ...

  2. Python的继承多态

    Python的继承多态 文章目录 Python的继承多态 一.私有属性和私有方法 01. 应用场景及定义方式 02. 伪私有属性和私有方法 二.单例 01. 单例设计模式 单例设计模式的应用场景 02 ...

  3. CLR运行时细节 - 继承多态的实现

    关于多态不多解释了,在运行时决定和调用具体的实现,是面向对象的基础 设计模式的基础. 准备把继承多态和接口多态分开,因为从CLR实现的角度继承多态相比于接口多态要简单得多,也更容易理解,本篇只讨论继承 ...

  4. Code::Blocks 2023.01 全中文汉化-优化版

    Code::Blocks 是一款开放源码.功能全面的跨平台集成开发环境(IDE),通过集成相应的编译器,可以支持使用广泛的C和C++程序开发.而且通过集成各种插件,可以实现各种扩展功能. 目前在中文网 ...

  5. 2023年03月编程语言流行度排名

    点击查看最新编程语言流行度排名(每月更新) 2023年03月编程语言流行度排名 编程语言流行度排名是通过分析在谷歌上搜索语言教程的频率而创建的 一门语言教程被搜索的次数越多,大家就会认为该语言越受欢迎 ...

  6. Java继承_Hachi君浅聊Java三大特性之 封装 继承 多态

    Hello,大家好~我是你们的Hachi君,一个来自某学院的资深java小白.最近利用暑假的时间,修得满腔java语言学习心得.今天小宇宙终于要爆发了,决定在知乎上来一场根本停不下来的Hachi君个人 ...

  7. python多态的三种表现形式_python小结----面向对象的三大特征(封装,继承,多态)

    面向对象的三大特征: 封装,继承,多态 面向对象的编程思想核心:高类聚,低耦合–程序的设计模式范畴 封装 什么是封装: 在面向对象编程的思想中,对代码进行高度封装,封装又叫包装 封装就是指将数据或者函 ...

  8. linux c 多态原理,看了所谓的面向对象中靠继承多态实现的所谓重用 哥笑了

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 这种重用不过还是引用别的类的函数或其它成员元素 我老听有些不懂编程却爱喷的人说什么面向对象代码可重用性"强" C写的代码 完全不能重用 ...

  9. 深度探索C++ 对象模型(7)-Data member的布局(无继承、继承无多态、继承多态、多层继承)

    无继承 继承无多态 继承多态 虚表 : 用来存放基类的每一个虚函数,再加上首位的一个slots(支持RTTI). 每个class object导入一个vptr,提供执行期的链接,使得每一个class ...

最新文章

  1. dataframe按某字段排序
  2. 2. getline()和get()
  3. Advanced Installer 9.8打包实录
  4. 时间序列因果关系_分析具有因果关系的时间序列干预:货币波动
  5. 信息学奥赛一本通(1091:求阶乘的和)
  6. asp.net 安装element ui_Vue+Element环境搭建
  7. 创建CocoaPods的Framework Swift组件化之路(上)
  8. 计算机黑龙江省二级c语言题库,计算机二级c语言题库
  9. 更改android模拟器dns,Android模拟器无法联网 模拟器DNS设置
  10. csp202109-1:数组推导 题解
  11. Android 图片文件读取
  12. 对自己狠一点,离成功近一点
  13. ios html调起高德地图,iOS 调用百度地图, 高德地图,苹果自带的地图
  14. 免费的pdf转word工具
  15. 微信视频号直播数据哪里可以看?
  16. android 信号研究(包括信号图标和信号优化)
  17. 谷歌云|机密 GKE 节点可在计算优化的 C2D 虚拟机上使用
  18. ansible D2
  19. Android应用卸载广播监听
  20. 国外域名注册商选择_如何在2020年选择最佳域名注册商(比较)

热门文章

  1. mysql source导入大数据量时效率提升的方法
  2. 基于物理的渲染—更精确的微表面分布函数GGX
  3. CSS文字超出省略号无效
  4. 计算机考试的话语,考试加油鼓励的话 为考试加油的暖心句子
  5. linux建立ss服务器,如何在linux服务器上部署ss服务
  6. 春藤家长学院简易产品分析及用户分析、K12教育市场分析
  7. 【封面】华为解读“生态伙伴”
  8. python实现自动答题详解含代码
  9. APP安装与卸载测试点
  10. Google的野心 Android未来方向分析