【面试题】封装/继承/多态

2023-11-05

面向对象

  • C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题;
    C++是基于面向对象的,关注的是对象,将一件事情拆分成不同对象,靠对象之间的交互完成
  • 面向对象程序设计(Object-oriented programming,OOP)是种具有对象概念的程序编程典范,同时也是一种程序开发的抽象方针。
  • 面向对象的三大特征:封装、继承、多态

封装

  • 概念:
    将数据和操作数据的方法有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互
    封装的本质是一种管理
  • C++实现封装的方式:
    用类将对象的属性与方法结合在一起,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用,对外隐藏类内部的实现细节
  • 访问限定符:
    public修饰的成员可以在类外直接被访问;
    protected和private修饰的成员在类外不能直接被访问;
    访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现为止;
    class的默认访问权限为private,struct为public(因为struct要兼容C)

C++中struct和class的区别?

  1. C++需要兼容C语言,所以C++中struct可以当成结构体去使用。
  2. C++中struct可以用来定义类,和class定义类是一样的,区别是struct的成员默认访问方式是public,而class是private
  3. class可以作为模板类型,struct不行

C和C++中struct的区别?

  1. C语言中,struct是用户自定义数据类型(UDT);C++中,struct是抽象数据类型(ADT),支持成员函数的定义
  2. C语言中,struct没有访问权限,且struct只能是一些变量的集合体,可以封装数据却不可以隐藏数据,而且成员不能是函数
  3. C++中,struct增加了访问权限,且成员即可以是变量,也可以是函数,成员默认访问方式是public

this指针

C++编译器给每个“非静态的成员函数”增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问的。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成

this指针的特性:

  1. this指针的类型:类类型* const
  2. 只能在成员函数内部使用
  3. this指针本质上是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针
  4. this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递

继承

继承机制是面向对象程序设计使代码可以复用的最重要手段,它在保持原有类特性的基础上进行扩展,增加功能,这样产生的类,称为派生类。继承呈现了面向对象程序设计的层次结构。继承是类设计层次的复用。

继承机制中对象之间如何转换?指针和引用之间如何转换?(基类和派生类对象赋值转换)

  1. 向上类型转换
    将派生类指针或引用转换为基类的指针或引用被称为向上类型转换,向上类型转换会自动进行,而且向上类型转换是安全的
  2. 向下类型转换
    将基类指针或引用转换为派生类指针或引用被称为向下类型转换,向下类型转换不会自动进行,因为一个基类对应几个派生类,所以向下类型转换时不知道对应哪个派生类,所以在向下类型转换时必须加动态类型识别技术。RTTI技术,用dynamic_cast进行向下类型转换
  3. 派生类对象可以赋值给基类的对象 / 基类的指针 / 基类的引用
  4. 基类对象不能赋值给派生类对象
  5. 基类的指针可以通过强制类型转换赋值给派生类的指针。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI的dynamic_cast来进行识别后进行安全转换

什么是菱形继承?如何解决?

菱形继承的问题:
菱形继承有数据冗余和二义性的问题
解决方式:
虚拟继承可以解决菱形继承的数据冗余和二义性的问题。

什么是菱形虚拟继承?如何解决数据冗余和二义性的?

虚继承是用于解决多继承条件下的菱形继承问题
底层实现原理与编译器有关,一般通过虚基类指针虚基类表实现,每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4字节)和虚基表(不占用类对象的存储空间) (需要注意的是,虚基类依旧会在其子类中存在拷贝,但是只有一份);当虚基类的子类被当作父类继承时,虚基类指针也会被继承
实际上,vbptr指的是虚基表指针,该指针指向了一个虚基表,虚基表中记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员,而虚继承也不用像普通多继承那样维持着公共基类(虚基表)的两份同样的拷贝,节省了存储空间

继承和组合的区别?什么时候用继承?什么时候用组合?

  1. 继承
    继承是is a的关系,比如说Student继承Person,则说明Student is a Person
    1)优点:子类可以重写父类的方法来方便实现对父类的扩展
    2)缺点:
    ① 这种通过生成派生类的复用称为白箱复用。术语“白箱”是相对可视性而言:在继承方式中,父类的内部细节对子类是可见的;
    ② 子类从父类继承的方法在编译时就确定下来了,所以无法在运行期间改变从父类继承的方法或行为;
    ③ 如果对父类的方法做了修改的话(比如增加了一个参数),则子类方法必须做出相应的修改。子类与父类是一种高耦合,违背了面向对象思想。

  2. 组合
    组合也就是设计类的时候要把组合的类的对象作为自己的成员变量加入到该类中
    1)优点:
    ① 黑箱复用,因为当前对象只能通过所包含的那个对象去调用其方法,被包含的对象的内部细节对当前对象是不可见的
    ② 当前对象与包含对象是一个低耦合关系,如果修改被包含类中的代码不需要修改当前类的代码
    ③ 当前对象可以在运行时动态的绑定所包含的对象。可以通过set方法给所包含对象赋值
    2)缺点:
    ① 容易产生更多对象
    ② 为了能组合多个对象,必须对接口进行定义

  3. 优先使用组合,组合的耦合度低,代码维护性好


多态

  • 概念:
    多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为,即“一个接口,多种方法”
  • 构成多态的两个条件:
    1)必须通过基类的指针或者引用调用虚函数
    2)被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

重载、重写(覆盖)、隐藏(重定义)

函数重载

C++允许在同一作用域中声明几个功能相似的同名函数,这些同名函数的形参列表(参数个数或类型或顺序)必须不同

为什么C++支持函数重载,而C语言不支持?

在LInux下,采用gcc编译完成后,函数名字的修饰没有发生改变;采用g++编译完成后,函数名字的修饰变成【_Z+函数长度+函数名+类型首字母】
所以C语言没办法支持函数重载,因为同名函数没办法区分;而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载

extern "C"

有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加extern “C”,意思是告诉编译器,将该函数按照C语言规则来编译

重写(覆盖)(虚函数的重写)

  • 派生类中有一个跟基类完全相同的虚函数(即派生类函数与基类函数的返回值类型、函数名、参数列表完全相同),称子类的虚函数重写了基类的虚函数
  • 虚函数重写的两个例外
    ① 协变:基类与派生类函数返回值类型不同,即基类虚函数返回基类对象的指针或引用,派生类返回派生类对象的指针或引用
    ② 析构函数的重写:基类与派生类析构函数的名字不同。如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写。虽然函数名不同,其实编译器对析构函数名做了特殊处理,编译后析构函数的名称同一处理成destructor

隐藏(重定义)

  • 两个函数参数相同,但是基类函数不是虚函数。和重写的区别在于基类是否是虚函数
  • 两个函数参数不同,无论基类是不是虚函数,都会被隐藏。和重载的区别在于两个函数不在同一个作用域

重载、重写(覆盖)、隐藏(重定义)的区别

在这里插入图片描述

override和final

// override的使用
class A
{
    virtual void foo();
}
class B : public A
{
    void foo(); //OK
    virtual void foo(); // OK
    void foo() override; //OK
}

// final的使用
class Base
{
    virtual void foo();
};

class A : public Base
{
    void foo() final; // foo 被override并且是最后一个override,在其子类中不可以重写
};

class B final : A // 指明B是不可以被继承的
{
    void foo() override; // Error: 在A中已经被final了
};

class C : B // Error: B is final
{
};

C++对函数重写的要求很严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在运行时没有得到预期结果才来debug会得不偿失,因此C++11提供了override和final两个关键字,可以帮助用户检测是否重写

  1. final:修饰虚函数,表示该虚函数不能再被继承
  2. overrride:检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错

什么是抽象类?作用?

纯虚函数:在虚函数的后面写上=0。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
抽象类:包含纯虚函数的类叫做抽象类(接口类),抽象类不能实例化出对象。
派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。

什么是接口继承?什么是实现继承?

实现继承:普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。
接口继承:虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态。

对象访问普通函数快还是虚函数快?

  1. 如果是普通对象,是一样快的
  2. 如果是指针对象或引用对象,则调用普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找

inline函数可以是虚函数吗?

不能。因为inline函数没有地址,无法把地址放到虚函数表中。

静态成员可以是虚函数吗?

不能。因为静态成员函数没有this指针,使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。

构造函数可以是虚函数吗?

不能。因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的。

析构函数可以是虚函数吗? 什么场景下析构函数是虚函数?

可以。最好把基类的析构函数定义成虚函数。

虚函数表(虚表)是在什么阶段生成的,存在哪?

虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的。

虚函数存在哪?

虚函数和普通函数一样,都是存在代码段的,只是他的指针存在虚函数表中。对象中存的不是虚表,存的是虚表指针。

动态绑定与静态绑定

  1. 静态绑定:又称为前期绑定(早绑定),在程序编译器期间确定了程序的行为,也称为静态多态,比如:函数重载
  2. 动态绑定:又称为后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态

虚函数表和虚基表

虚函数表虚表本质是一个存虚函数指针的指针数组,这个数组最后面放了一个nullptr
虚基表中存放的是偏移量

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【面试题】封装/继承/多态 的相关文章

  • JAVA中的内存分配

    JAVA中的内存分配 栈 方法运行时使用的内存 比如main方法的运行 进入方法栈中执行 堆 存储对象或数组 new来创建的 都存储在堆内存中 方法区 存储可以运行的class文件 本地方法栈 JVM在使用操作系统功能的时候使用 和我们开发
  • 查询、关闭正在运行的Tomcat端口

    查询正在使用的端口 快捷键win R 输入cmd 回车 打开cmd窗口 查看所有的端口进程 请输入netstat ano 查看某个特定端口 输入netstat ano findstr 8089 关闭某个端口进程 输入taskkill f p

随机推荐

  • Javaweb

    一 创建包和类来编译servlet程序 二 编译和运行
  • 如何在老版本浏览器中丝滑地使用JS新特性(ES6)

    如何在老版本浏览器中丝滑地使用JS新特性呢 如何在老版本浏览器中丝滑地使用JS新特性呢 有两种方法可以帮助我们实现 第一种方法就是我们用JS原有的方法 自己去实现JS的新特性 不是说好的丝滑使用新特性吗 就这 哈哈哈 别急 客官留步 我还有
  • spring boot 数据库层

    项目开启 首先设计数据库以及存储表 表的联系 需要存贮的信息 基本表的性质 基本表与中间表 临时表不同 因为它具有如下四个特性 1 原子性 基本表中的字段是不可再分解的 2 原始性 基本表中的记录是原始数据 基础数据 的记录 3 演绎性 由
  • openid和unionid的区别

    openid和unionid的区别 1 微信openid和unionid长度是不一样的 openid 28 unionid 29 2 openid同一用户同一应用唯一 unionid同一用户不同应用唯一 这里的不同应用是指在同一微信开发平台
  • C++学习6

    堆 是存在于某个作用域的一个内存空间 例如 当你调用函数 函数本身会形成一个栈用来放置它所接收的参数 以及返回地址 栈 由操作系统提供的一个全局的内存空间 程序可动态分配 内存管理 生命周期 栈对象 离开堆的作用域 会调用对象的析构函数 内
  • rabbitmq分布式事务解决方案

    发送消息到mq 流程 用户下订单创建订单信息 且创建一条订单冗余信息 status 为 0 发送订单信息到mq 使用ack 消息确认机制 确认消息发送成功修改订单状态为 1 表示消息已发送 启动一个定时任务 排查 订单状态为 0 的订单 发
  • Win2003搭建网站教程

    1 搭建Win2003虚拟机 此过程略 2 开始 管理您的服务器 添加或删除角色 3 下一步 配置您的服务器向导 选择应用程序服务器 IIS asp NET 下一步 完成安装 4 打开 开始 管理工具 Internet信息服务器 IIS 管
  • 使用certbot 生成 Let‘s Encrypt 泛域名ssl证书

    文章目录 一 更新证书报错 二 Let s Encrypt 泛域名ssl证书申请 一 更新证书报错 问题描述 更新SSL证书时报 too many failed authorizations 错误 原因分析 当前要更新的域名一个小时触发失败
  • 扫码支付终结刷脸支付强势掘起

    手机支付将会終结 新的支付方式掘起 新的支付方式对很多人还是很陌生的 这就要很好的推广和布置 现在推出了二代的蜻蜓刷脸设备 向商户销售出了舒缓的二代蜻蜓刷脸支付设备 超市 快餐厅 自动贩卖机都已经实现商业直播 相信很快在每个城市 都会发现这
  • 快速排序详解

    近些天来 由于需要找工作 特将数据机构与算法中的快速排序温习总结了一下 希望对于大家学习有所帮助 首先 快速排序的基本思想是基于分治的思想 是冒泡排序的改进型 首先在数组中选择一个基准点 该基准点的选取可能影响快速排序的效率 后面讲解选取的
  • Git提交代码的两种方式

    一 Git Bash提交方式 在电脑桌面鼠标右键点击一下 然后点击Git Bash Here 开始输入命令 1 首次提交 先输入github gitlab等的用户名和邮箱 git命令 git config global user name
  • 【Altium Designer21】使用小技巧

    1 如何取消原理图的网格以及表头如下图 在Properties Visible Grid可以显示 隐藏网格 Title Block勾选上即显示表头 取消勾选即隐藏表头 图1 图2 图3 2 翻转的快捷键 空格 为90翻转 X 为水平翻转 Y
  • Eclipse 启动异常 找不到Java环境(A Java Runtime Environment....)

    点击启动Eclipse弹出异常消息 解决步骤 1 打开eclipse所在文件夹 2 用记事本打开配置文件 即下图的文件 3 找到java所在文件夹 4 复制路径并粘贴到记事本文件中 5 保存并重启Eclipse 大功告成
  • [算法]力扣刷题-动态规划 - 不同路径

    目录 题目 思路 编码 分析 修改 优化 题目 一个机器人位于一个 m x n 网格的左上角 起始点在下图中标记为 Start 机器人每次只能向下或者向右移动一步 机器人试图达到网格的右下角 在下图中标记为 Finish 问总共有多少条不同
  • Junit的基本使用(详解)

    什么是Junit Junit是xUnit的一个子集 在c paython java语言中测试框架的名字都不相同 xUnit是一套基于测试驱动开发的测试框架 其中的断言机制 将程序预期的结果与程序运行的最终结果进行比对 确保对结果的可预知性
  • Java代码质量检查工具及使用案例

    前言 在现在的软件开发中 由于软件的复杂度越来越高 业务也覆盖很广 各个业务模块业务错综复杂 这样就需要我们需要团队开发 在我们团队中开发人员的经验 代码风格样式都不一致 以及缺乏统一的标准 从而导致我们的整个项目的的代码难以阅读 不便于后
  • 顺序表实现图书管理系统增删改查

    顺序表学习 这个程序是用于学习数据结构而参考数据结构C语言第二版的教材实现的一个简易的图书管理系统 逻辑结构 顺序表 线性表的顺序存储又称作顺序表 由一组地址连续的存储单元依次存储线性表的数据元素 从而使得逻辑上相邻的两个元素在物理位置上也
  • 浅谈中断挂起与中断标志的区别

    中断挂起 如果中断发生时 正在处理同级或高优先级异常 或者被掩蔽 则中断不能立即得到响应 此时中断被悬起 悬挂意味着等待而不是舍去 当优先级高的或者同等级先发生的中断完成后 被挂起的中断才会执行 中断的悬起状态可以通过 中断设置悬起寄存器
  • STM32学习之ADC(模拟数字转换器)

    目录 ADC的定义及其类型 ADC 单通道独立规则模式 对于该模式的理解 通道及ADC分配 时钟配置 GPIO配置 ADC模式配置 校准 读取ADC 代码 野火的开源代码 由于大二学生一枚 水平有限 文中自己的理解难免出错 恳请道友发现后能
  • 【面试题】封装/继承/多态

    面向对象 C语言是面向过程的 关注的是过程 分析出求解问题的步骤 通过函数调用逐步解决问题 C 是基于面向对象的 关注的是对象 将一件事情拆分成不同对象 靠对象之间的交互完成 面向对象程序设计 Object oriented program