【C++】继承和多态常见的问题

news2024/11/17 23:27:35

一、概念考查

1、下面哪种面向对象的方法可以让你变得富有( A )

A. 继承                B. 封装                C. 多态                D. 抽象

继承机制是面向对象程序设计使代码可以复用的最重要手段,继承是类设计层次的复用。


2、( D )是面向对象程序设计语言中的一种机制。这种机制实现了方法的定义与具体的对象无关,而对方法的调用则可以关联于具体的对象。

A. 继承                B. 模板                C. 对象的自身引用                D. 动态绑定

动态绑定又称后期绑定或晚绑定,就是多态。


3、面向对象设计中的继承和组合,下面说法错误的是?( C

A. 继承允许我们覆盖重写父类的实现细节,父类的实现对于子类是可见的,是一种静态复用,也称为白盒复用。

B. 组合的对象不需要关心各自的实现细节,之间的关系是在运行时候才确定的,是一种动态复用,也称为黑盒复用。

C. 优先使用继承,而不是组合,是面向对象设计的第二原则。

D. 继承可以使子类能自动继承父类的接口,但在设计模式中认为这是一种破坏了父类的封装性的表现。

优先使用组合,而不是继承。


4、以下关于纯虚函数的说法,正确的是( A )

A. 声明纯虚函数的类不能实例化对象                B. 声明纯虚函数的类是虚基类

C. 子类必须实现基类的纯虚函数                       D. 纯虚函数必须是空函数

虽然声明纯虚函数的类不能实例化对象,但声明纯虚函数的类可以定义指针。


5、关于虚函数的描述正确的是( B )

A. 派生类的虚函数与基类的虚函数具有不同的参数个数和类型 B. 内联函数不能是虚函数  

C. 派生类必须重新定义基类的虚函数                                   D. 虚函数可以是一个 static 型的函数

首先排除 A 和 C 选项,其次虚函数的地址是放在对象的虚表中,如果要形成多态,就必需要用对象的指针或引用来调用,而 static 就意味着是静态的,你连 this 指针都没有,那就不合理了。

内联函数不能是虚函数其实是一个存疑的选项,己验证。在 VS2019 下,内联函数加上虚函数后依然能编译通过,但是我们得知道内联函数对编译器而言只是一个建议,实际上一个函数真的成为内联函数,它就不可能是虚函数,因为内联函数是没有地址的,它直接在调用的地方展开,而虚函数是要把地址放到虚函数表中,所以这里一定会把 inline 给忽略掉。


6、关于虚表说法正确的是( D

A. 一个类只能有一张虚表。

B. 基类中有虚函数,如果子类中没有重写基类的虚函数,此时子类与基类共用同一张虚表。

C. 虚表是在运行期间动态生成的。

D. 一个类的不同对象共享该类的虚表。

上面的多继承中就有两张虚表,且严格来说虚表不是在类,而是在对象,所以 A 选项错误;

不管是否完成重写,父子类的对象都是有独立的虚表,所以排除 B 选项;

虚表如果是运行时动态生成,虚表是需要空间的,且运行起来只能在堆上申请,而虚表是在常量区或代码段,所以虚表是在在编译阶段生成的, C 选项也错。

正确答案为 D。


7、假设A类中有虚函数,B 继承自 A,B 重写 A 中的虚函数,也没有定义任何虚函数,则( D 

A. A 类对象的前 4 个字节存储虚表地址,B 类对象前 4 个字节不是虚表地址

B. A 类对象和 B 类对象前 4 个字节存储的都是虚基表的地址

C. A 类对象和 B 类对象前 4 个字节存储的虚表地址相同

D. A 类和 B 类虚表中虚函数个数相同,但 A 类和 B 类使用的不是同一张虚表

A 类有虚函数,A 类对象的前 4 个字节当然是存储虚表地址,只要 B 类继承了 A 类,B 类的前 4 个字节也当然是存储虚表地址,只不过是不同的虚表地址,所以排除 A 选项;

虚基表是用来解决菱形继承问题的,与虚函数表是两个概念。注意区分解决菱形继承的虚继承的虚基表,所以排除 B 选项;

不管是否重写,父子类的对象都是有独立的虚表,所以排除 C 选项;


8、下面程序输出结果是什么? ( ) 

#include<iostream>
using namespace std;
class A{
public:
    A(char *s)
    {
        cout << s << endl;
    }
    ~A(){}
};

class B:virtual public A
{
public:
    B(char *s1,char*s2):A(s1)
    {
        cout << s2 << endl;
    }
};

class C:virtual public A
{
public:
    C(char *s1,char*s2):A(s1)
    {
        cout << s2 << endl;
    }
};

class D:public B,public C
{
public:
    D(char *s1,char *s2,char *s3,char *s4):B(s1,s2),C(s1,s3),A(s1)
    {
        cout << s4 << endl;
    }
};

int main() {
    D *p=new D("class A","class B","class C","class D");
    delete p;
    return 0;
}

A. class A class B class C class D                B. class D class B class C class A

C. class D class C class B class A                D. class A class C class B class D

注意这里的初始化顺序和初始化列表中的顺序无关,这里是与继承的顺序,也就是声明的顺序有关。

这里 D 继承了 B、C,要去调用父类的构造函数,谁先继承谁就先调,按理说先由 D 调用 B 的构造函数,再由 B 调用 A 的构造函数,再由 D 调用 C 的构造函数,再由 C 调用 A 的构造函数 (A ➡ B ➡ A ➡ C ➡ D)。

但是因为 virtual 后,编译器做了处理,不可能让 B 对 A 初始化一次,C 对 A 再初始化一次,所以应该是 (A ➡ B ➡ C ➡ D)。


9、多继承中指针偏移问题?下面说法正确的是( C )

class Base1{
public:
    int _b1;
};

class Base2{
public:
    int _b2;
};

class Derive : public Base1, public Base2{
public:
    int _d;
};

int main(){
    Derive d;
    Base1* p1 = &d;
    Base2* p2 = &d;
    Derive* p3 = &d;
    return 0;
}

A. p1 == p2 == p3        B. p1 < p2 < p3        C. p1 == p3 != p2        D. p1 != p2 != p3

如下图,所以选择 C 选项。注意 p1 和 p3 虽然都指向同一地址,但是它们的类型不一样,p1 是 Base1 的大小,p3 是 Derive 的大小。


10、以下程序输出结果是什么( 

class A
{
public:
    virtual void func(int val = 1)
    {
        std::cout << "A->" << val << std::endl;
    }
    virtual void test()
    {
        func();
    }
};
   
class B : public A
{
public:
    void func(int val=0)
    {
        std::cout << "B->" << val << std::endl;
    }
};
   
int main(int argc ,char* argv[])
{
    B*p = new B;
    p->test();
    return 0;
}

A: A->0        B: B->1        C: A->1        D: B->0        E: 编译出错        F: 以上都不正确

首先,这里不涉及多态,因为 p 的类型是子类的指针,p 再去调用父类继承下来的 test,但是这里父类中 test 函数的参数中有一个 A* this 的指针,所以调用时就是一个父类的指针指向子类对象,满足多态的条件之一,其次子类重写可以不写 virtual,我们需要重写虚函数,并满足三同或三个例外,但是没有说缺省参数也要相同,标准也基本不会提,我们就认为它构成重写。所以这里 this 调用 func 时符合多态,调用的是子类的 func,所以这里就从 B 选项 和 C 选项中选择。

我们又说了普通函数的继承是实现继承,而虚函数的继承是接口继承,接口继承指的是函数的声明,包括函数名、参数、返回值,所以这里把函数的缺省参数也继承下来,而这里重写的是它的实现,跟参数这些无关,所以选择 B 选项。


11、以下两段程序输出结果是什么 ( B C )

class A
{
public:
	virtual void func(int val = 1)
	{}
	void test()
	{}
};

int main()
{
	//1、
	A* p1 = nullptr;
	p1->func();

	//2、
	A* p2 = nullptr;
	p2->test();
	
	return 0;
}

 A. 编译报错                        B. 运行崩溃                        C. 正常运行

成员函数的地址不在对象中存储,而存在于公共代码段。这里调用成员函数,不会去访问 p1 和 p2 指向的空间,也就不存在空指针解引用了,这里把 p1 和 p2 传递给隐含的 this 指针,但是 p1 是一个父类的指针,而 func 是 virtual,这里转换必然要去虚表中找,因为从语法识别的角度,编译器看到 p1->func() 时也不知道指向的是哪个对象,所以这里依然对 p1 进行解引用了,所以选择 B 和 C 选项。


二、问答题

1、什么是多态?

多态是指不同继承关系的类和对象去调用同一函数,产生了不同的行为。

多态又分为静态多态和动态多态。


2、什么是重载、重写(覆盖)、重定义(隐藏)?

  • 重载是指在同一范围中声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同,也就是说用同一个函数完成不同的功能。
  • 重写(覆盖)是指两个函数分别在基类和派生类的作用域,这两个函数的函数名、参数、返回值都必须相同(协变例外),且这两个函数都是虚函数。
  • 重定义(隐藏)是指两个函数分别在基类和派生类的作用域,这两个函数的函数名相同即可。

3、多态的实现原理?

构成多态的父类对象和子类对象的成员当中都包含一个虚表指针,这个虚表指针指向一个虚表,虚表当中存储的是该类对应的虚函数地址。

因此,当父类指针指向父类对象时,通过父类指针找到虚表指针,然后在虚表当中找到的就是父类当中对应的虚函数;当父类指针指向子类对象时,通过父类指针找到虚表指针,然后在虚表当中找到的就是子类当中对应的虚函数。


4、inline函数可以是虚函数吗?

可以,内联函数是会在调用的地方展开的,是没有地址的,但是 inline 只是一个建议,可以定义成虚函数的,当我们把内联函数定义成虚函数后,在多态调用中,编译器就忽略了该函数的内联属性,这个函数就不再是 inline 了,因为虚函数的地址被放到虚表中去。


5、静态成员(static 函数)可以是虚函数吗?

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

虚函数是为了实现多态,多态都是运行时去虚表找决议,而静态成员函数都是在编译时决议,它是virtual 没有价值。


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

不可以,因为对象中的虚函数表指针是在构造函数初始化列表阶段(运行时)才初始化的,如果构造函数是虚函数,那么调用构造函数时对象中的虚表指针都没有初始化。构造函数时虚函数没有意义。


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

可以,并且最好把基类的析构函数定义成虚函数。当我们 new 一个父类对象和一个子类对象,并均用父类指针指向它们,在我们使用 delete 调用析构函数并释放对象空间时,只有当父类的析构函数是虚函数的情况下,才能正确调用父类和子类的析构函数,否则当我们使用父类指针 delete 对象时,只能调用到父类的析构函数。


8、拷贝构造和 operator= 可以是虚函数吗?

不可以,拷贝构造也是构造函数,答案参考上面的构造函数。

operator= 可以,但是没有实际价值。


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

如果虚函数不构成多态,是普通对象,二者是一样快的。

如果虚函数构成多态的调用,是指针对象或者是引用对象,则调用的普通函数快,因为构成多态,在运行时调用虚函数需要到虚函数表中去查找虚函数的地址。


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

构造函数初始化列表阶段初始化的是虚函数表指针,对象中存的也是虚函数表指针。虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的。


11、C++ 菱形继承的问题?虚继承的原理?

注意这里不要把虚函数表和虚基表搞混了。

菱形继承子类对象当中有两份父类的成员,会导致数据冗余和二义性的问题。
虚继承对于相同的虚基类在对象当中只会存储一份,若要访问虚基类的成员需要通过虚基表获取到偏移量,进而找到对应的虚基类成员,从而解决了数据冗余和二义性的问题。


12、什么是抽象类?抽象类的作用?

抽象类体现了虚函数的继承是一种接口继承,强制子类去抽象纯虚函数,如果子类不抽象从父类继承下来的纯虚函数,那么子类也是抽象类也不能实例化出对象。

其次,抽象类可以很好的去表示现实世界中没有示例对象对应的抽象类型,比如:植物、人、动物等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1118528.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【MySQL】数据库数据类型

文章目录 1. 整体概要2. 数值类型(有符号) tinyint 创建表(无符号) tinyint 创建表bit类型float 类型(无符号)floatdecimal 3. 二进制类型char类型varchar类型 4. 日期时间日期时间类型 5. string 类型enum类型和set类型enum类型和set类型的查找在枚举中的查找在set中的查找 1.…

MOSFET(五):DrMos

一、简介 DrMos&#xff08;Driver MOSFET&#xff09;技术是 Intel 于2004年推出的服务器主板节能技术&#xff0c;即把 2 个MOSFET和 1 个MOS驱动器 三合一&#xff0c;集成在一个封装中。集成后的 DrMos 面积是分离MOSFET的 &#xff0c;功率密度是其 倍&#xff0c;通过搭…

华为eNSP配置专题-BGP路由协议的配置

文章目录 华为eNSP配置专题-BGP路由协议的配置0、概要介绍1、前置环境1.1、宿主机1.2、eNSP模拟器 2、基本环境搭建2.1、终端构成和连接2.2、终端的基本配置 3、OSPF配置4、BGP配置4.1、BGP的基本配置4.2、BGP中路由的宣告4.3、BGP的监控 5、让PC1和PC2互通5.1、将BGP的路由引入…

【鸿蒙软件开发】ArkTS常用组件之Button

文章目录 前言一、创建按钮1.1 Button创建接口介绍1.2 创建正常的按钮&#xff0c;不包括子组件1.3 创建正常的按钮&#xff0c;包括子组件1.4 按钮的不同样式胶囊按钮&#xff08;默认类型&#xff09;圆形按钮普通按钮 二、添加事件2.1 .onClick事件添加事件 三、什么时候使用…

【十四】记一次MySQL宕机恢复过程,MySQL INNODB 损坏恢复

记一次MySQL宕机恢复过程 简介&#xff1a;一个业务数据库疏于运维管理&#xff0c;突然在今天崩溃宕机了&#xff0c;真是让人抓狂&#xff0c;上面也不知道积累了多久的数据&#xff0c;平时也没有定期做好备份&#xff0c;这下岂不是瞎了啊&#xff0c;经过不断的收集信息和…

Base 编码家族:Base16 编码

文章目录 参考环境Base 编码Base 的含义计数系统编码系统 为什么需要 Base 编码&#xff1f;ASCII 编码 Base16 编码概念Base16 字符集 Base16 编码原理编码 Base16 编码特点体积膨胀 参考 项目描述搜索引擎Bing、GoogleAI 大模型文心一言、通义千问、讯飞星火认知大模型、Cha…

Git Bash(一)Windows下安装及使用

目录 一、简介1.1 什么是Git&#xff1f;1.2 Git 的主要特点1.3 什么是 Git Bash&#xff1f; 二、下载三、安装3.1 同意协议3.2 选择安装位置3.3 其他配置&#xff08;【Next】 即可&#xff09;3.4 安装完毕3.5 打开 Git Bash 官网地址&#xff1a; https://www.git-scm.com/…

【C++】特殊类实现

一、请设计一个类&#xff0c;不能被拷贝 拷贝只会放生在两个场景中&#xff1a;拷贝构造函数以及赋值运算符重载&#xff0c;因此想要让一个类禁止拷贝&#xff0c; 只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。 C98 将拷贝构造函数与赋值运算符重载只声明不定义…

聚类分析 | Python密度聚类(DBSCAN)

密度聚类是一种无需预先指定聚类数量的聚类方法&#xff0c;它依赖于数据点之间的密度关系来自动识别聚类结构。 本文中&#xff0c;演示如何使用密度聚类算法&#xff0c;具体是DBSCAN&#xff08;Density-Based Spatial Clustering of Applications with Noise&#xff09;来…

磁盘清理 | 已经卸载的软件还出现在应用和功能里怎么办?

一句话总结解决方法&#xff1a; 安装Geek Uninstaller,删除卸载残留。 问题描述&#xff1a; 最近磁盘满了&#xff0c;需要删除一些平时不常用的软件&#xff0c;但是发现一个问题。就是已经删除的软件&#xff0c;仍然会出现在“应用与功能”中。并且显示卸载图标为灰色&am…

基于PHP的线上购物商城,MySQL数据库,PHPstudy,原生PHP,前台用户+后台管理,完美运行,有一万五千字论文。

目录 演示视频 基本介绍 论文截图 功能结构 系统截图 演示视频 基本介绍 基于PHP的线上购物商城&#xff0c;MySQL数据库&#xff0c;PHPstudy&#xff0c;原生PHP&#xff0c;前台用户后台管理&#xff0c;完美运行&#xff0c;有一万五千字论文。 现如今,购物网站是商业…

用Python进行websocket接口测试

这篇文章主要介绍了用Python进行websocket接口测试&#xff0c;帮助大家更好的理解和使用python&#xff0c;感兴趣的朋友可以了解下 我们在做接口测试时&#xff0c;除了常见的http接口&#xff0c;还有一种比较多见&#xff0c;就是socket接口&#xff0c;今天讲解下怎么用P…

(一)docker:建立oracle数据库

前言&#xff0c;整个安装过程主要根据docker-images/OracleDatabase/SingleInstance /README.md &#xff0c;里边对如何制作容器讲的比较清楚&#xff0c;唯一问题就是都是英文&#xff0c;可以使用谷歌浏览器自动翻译成中文&#xff0c;自己再对照英文相互参照来制作提前准备…

P1 缓冲池管理

文章目录 Task1 LRU-K 替换策略Task2 缓冲池管理Task3 读/写页面保护 Task1 LRU-K 替换策略 LRU-K算法&#xff1a;当访问次数达到K次后&#xff0c;将数据索引从历史队列移到缓存队列中&#xff08;缓存队列时间降序&#xff09;&#xff1b;缓存数据队列中被访问后重新排序&…

Python--循环中的两大关键词 break 与 continue

在Python循环中&#xff0c;经常会遇到两个常见的关键词&#xff1a;break 与 continue break&#xff1a;代表终止整个循环结构 continue&#xff1a;代表中止当前本次循环&#xff0c;继续下一次循环 break&#xff1a; 英 /breɪk/ v. 打破&#xff0c;打碎&#xff0c…

c语言练习95:练习使用双向链表(实现增删改查)

练习使用双向链表&#xff08;实现增删改查&#xff09; 是指针指向了一块被释放的空间 解决方案&#xff1a; plistNULL List.h #pragma once #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<assert.h> #include<…

java实现多线程下载器

前言&#xff1a; &#x1f44f;作者简介&#xff1a;我是笑霸final&#xff0c;一名热爱技术的在校学生。 &#x1f4dd;个人主页&#xff1a;个人主页1 || 笑霸final的主页2 &#x1f4d5;系列专栏&#xff1a;项目专栏 &#x1f4e7;如果文章知识点有错误的地方&#xff0c;…

2316. 统计无向图中无法互相到达点对数(leetcode)并查集-------------------Java实现

2316. 统计无向图中无法互相到达点对数&#xff08;leetcode&#xff09;并查集-------------------Java实现 题目表述 给你一个整数 n &#xff0c;表示一张 无向图 中有 n 个节点&#xff0c;编号为 0 到 n - 1 。同时给你一个二维整数数组 edges &#xff0c;其中 edges[i…

使用IDEA时遇到java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver报错的解决方案

目录 一、项目环境二、可能原因解决方案1. 没有导入mysql的jar包2. mysql的jar包版本问题 一、项目环境 二、可能原因解决方案 1. 没有导入mysql的jar包 先检查项目lib文件夹下有没有mysql的jar包&#xff0c;没有就把jar包复制到该目录下 再检查项目结构中有没有导入mysql…

使用vscode搭建虚拟机

首先vscode插件安装 名称: Remote - SSH ID: ms-vscode-remote.remote-ssh 说明: Open any folder on a remote machine using SSH and take advantage of VS Codes full feature set. 版本: 0.51.0 VS Marketplace 链接: https://marketplace.visualstudio.com/items?it…