华为OD面经(C++)
(回答仅供参考)
【2024版C++面试突击训练营,校招/提前批上岸一周刷爆八股文!(C++、数据结构与算法、网络编程、数据库…)】
C++结构体和类的区别,类默认的访问权限,结构体可以定义成员函数吗?
考察对C++基本面向对象编程(OOP)概念的理解,包括封装、类与结构体的使用,以及对默认访问权限的理解。
类:默认的访问权限是私有。
结构体:默认的访问权限是公开。
结构体可以定义成员函数,包括构造函数、析构函数、以及其他成员函数,和类在这方面是相同的。结构体和类都支持面向对象编程的特性,如封装和抽象。
多态的意义?
多态是面向对象编程的核心特性之一,允许不同类的对象对同一消息做出响应,但可以表现出不同的行为。即同一接口,使用不同的实例而执行不同操作。
重载和重写的区别?
考察面向对象编程的理解、多态性实现、方法的签名与继承机制
重载是编译时的多态性实现,侧重于同一个类中的多态;重写是运行时的多态性实现,侧重于子类与父类之间的多态。
重载:是指在同一个类中可以有多个同名方法,只要它们的参数列表不同(参数类型、个数或顺序)。重载属于静态多态,是在编译时决定的。
重写:是指在子类中定义一个方法,其名称、返回类型及参数列表与父类中某个方法完全相同。重写属于动态多态,是在运行时决定的。
TCP/IP 三次握手的过程,为什么要3次?
考察网络通信协议的理解,对TCP/IP协议栈中的TCP协议运作机制的了解。
TCP/IP三次握手的过程是:
- 客户端发送一个SYN包(同步序列编号)给服务器以开始一个新的连接。
- 服务器响应以一个SYN-ACK包,确认收到客户端的SYN包。
- 客户端再发送一个ACK包给服务器,确认收到服务器的SYN-ACK包。
三次握手的目的是确保双方都确切知道彼此已准备好进行数据传输,从而确保连接的可靠性。
进程和线程的区别
考察计算机操作系统的基础知识
进程是操作系统进行资源分配和调度的基本单位,是应用程序的一次执行过程。线程是进程的一部分,是CPU调度和执行的单位,同一进程下的线程共享进程资源。
进程和CPU的关系
考察对操作系统基本概念的理解
进程是操作系统中的一个执行单元,它包含了运行程序所需的代码和数据。CPU是执行进程中指令的硬件。操作系统通过调度算法决定哪个进程在何时使用CPU进行执行。
进程与CPU的关系是:进程是等待执行的任务,而CPU是执行这些任务的硬件。
多进程通讯方法,什么是消息队列
考察操作系统中多进程通讯机制
多进程通讯方法包括管道、命名管道、信号、消息队列、共享内存、信号量、套接字等。
消息队列是一种在进程间传递消息的通信机制,允许一个或多个进程向它写入消息,并由一个或多个进程读取这些消息。
设计模式,什么时候用单例模式
创建型模式
- 单例模式:确保一个类只有一个实例,并提供该实例的全局访问点。
- 工厂方法模式:定义一个接口用于创建对象,但让子类决定要实例化的类是哪一个。
- 抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要指定具体类。
- 建造者模式:将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。
- 原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
结构型模式
- 适配器模式:允许将一个类的接口转换成客户端期望的另一个接口。
- 桥接模式:将抽象部分与实现部分分离,使它们可以独立地变化。
- 组合模式:将对象组合成树形结构以表示“部分-整体”的层次结构。
- 装饰器模式:动态地给一个对象添加一些额外的职责。
- 外观模式:为系统中的一组接口提供一个一致的界面。
- 享元模式:运用共享技术有效地支持大量细粒度对象的复用。
- 代理模式:为另一个对象提供一个替身或占位符以控制对这个对象的访问。
行为型模式
- 策略模式:定义一系列算法,并将每一个算法封装起来,使它们可以互换。
- 模板方法模式:定义算法的骨架,将一些步骤延迟到子类中实现。
- 观察者模式:当一个对象状态发生改变时,依赖它的所有对象都会收到通知并自动更新。
- 迭代器模式:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
- 责任链模式:为请求创建一个接收者对象的链。
- 命令模式:将请求封装为一个对象,从而使你可用不同的请求、队列或日志请求参数化其他对象。
- 备忘录模式:在不破坏封装的前提下,捕获并保存一个对象的内部状态,从而可以在将来恢复对象到这个状态。
- 状态模式:允许一个对象在其内部状态改变时改变它的行为。
- 访问者模式:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
- 中介者模式:用一个中介对象来封装一系列的对象交互。
- 解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
单例模式是一种创建型设计模式,它保证一个类仅有一个实例,并提供一个全局访问点来获取这个实例。单例模式通常在以下情况下使用:
当一个类在任何时刻只能有一个实例,而且客户端只从一个全局访问点访问它时。
当需要严格控制全局变量(如配置类、线程池、缓存、对话框管理器等)时。
Linux常见命令
Linux系统中的常见命令包括:
ls
:列出目录内容。cd
:改变当前目录。pwd
:显示当前目录的路径。mkdir
:创建新目录。rmdir
:删除空目录。rm
:删除文件或目录。cp
:复制文件或目录。mv
:移动或重命名文件或目录。touch
:创建空文件或修改文件时间戳。chmod
:改变文件或目录的权限。chown
:改变文件或目录的所有者。cat
:查看文件内容。more
/less
:分页显示文件内容。grep
:文件内容查找。find
:查找文件。tar
:归档压缩或解压文件。zip
/unzip
:压缩或解压ZIP文件。df
:显示磁盘空间使用情况。du
:显示文件或目录的磁盘使用量。ps
:显示当前进程。kill
:结束进程。top
:实时显示进程状态。sudo
:以其他用户身份执行命令,通常是以root身份。echo
:在终端显示文本。man
:显示命令的手册页。
C++ 指针和引用的区别,为什么需要引用
指针:
- 指针是一个变量,其值为另一个变量的地址。
- 指针可以被重新赋值以指向另一个对象。
- 指针可以指向nullptr或无效内存。
- 需要使用*运算符(解引用)来访问指针所指向的值。
引用:
- 引用作为一个变量的别名存在,它对应一个已存在变量的引用。
- 引用被创建时必须被初始化,并且不能再改变引用其他变量(不可重新赋值)。
- 引用不能为nullptr,总是指向有效的内存。
- 通过引用访问变量时不需要特殊语法,就像直接操作那个变量一样。
为什么需要引用:
- 提供另一种方式访问变量,不会产生空指针。
- 引用在函数参数传递时,通过传递引用可以避免复制整个对象,同时,通过使用常引用(const引用),还可以保证对象内容不被修改。
- 引用使得C++支持操作符重载成为可能
是否可以把一个父类的对象赋给一个子类的指针
不,这是不允许的。基类指针可以指向派生类对象,但是无法直接将基类对象赋值给派生类指针。基类只包含从基类继承而来的成员,不能确保有足够的信息来表示派生类的全部内容。
如果你尝试将基类对象赋给派生类指针,会遇到类型不兼容的问题。
补码计算方法
对于正数和零:
正数和零的补码与其原码相同。即,它们在计算机中的表示方法是一样的。
- 例如,如果我们用8位二进制数表示,+5的补码(也是它的原码)为
00000101
。
对于负数:
负数的补码是其绝对值的二进制表示的反码(每个位取反)加1。
-
写出绝对值的二进制形式:首先将负数的绝对值写成二进制形式。
-
求反码:将上一步得到的二进制数每一位取反(0变1,1变0)。
-
加1:在反码的基础上加上1。
- 例如,计算-5的补码:
- +5的二进制表示为
00000101
。 - 取反得到反码
11111010
。 - 反码基础上加1得到补码
11111011
。
- +5的二进制表示为
注意:
- 补码系统中,最高位也称为符号位,用于表示数的正负。在8位二进制中,符号位为0表示正数,为1表示负数。
- 补码系统使得0有唯一的表示方式。例如,在8位二进制中,0的补码为
00000000
。 - 在补码表示中,最负数(如8位二进制中的-128,表示为
10000000
)没有对应的正数。这是因为补码表示方法的范围是从-2(n-1)到2(n-1)-1,其中n是位数。
二进制补码
对于正数和零:
- 直接写出其原码。例如,3的8位二进制补码就是
00000011
。
对于负数(以-3为例):
-
写出绝对值的二进制表示:3的二进制表示是
00000011
。 -
求反码:
00000011
取反后变成11111100
。 -
加1:反码
11111100
加上1变成11111101
,这就是-3的8位二进制补码。
注:
-
在二进制补码表示法中,-1的补码是二进制下全为1的情况,例如8位的-1表示为
11111111
。这是因为+1的二进制表示为00000001
,其反码是11111110
,再加1后得到11111111
。 -
补码表示法允许在计算机中使用相同的硬件电路处理整数加法和减法运算,包括正数与负数间的运算,极大简化了计算机的硬件设计。
-
在补码表示中,有一个负数没有对应的正值,那就是最小的负数(例如,在8位二进制中是
10000000
),这表示的是-128
,因为在8位二进制系统中可以表示的最大正数为127
。
什么是动态规划,它常用的算法,他和贪心算法的区别
动态规划是一种解决问题的方法,主要用于求解具有重叠子问题和最优子结构特性的复杂问题。通过记忆化或表格化技术存储已解决子问题的结果,避免对同一问题的重复计算,以此来提高效率。
常用的动态规划算法:
-
斐波那契数列:用动态规划来计算斐波那契数列是最简单的例子。通过存储计算过的值,防止重复计算,大幅提升计算效率。
-
0-1背包问题:给定一组物品,每种物品都有自己的重量和价值,在限定的总重量内,选择物品使得总价值最大。
-
最长公共子序列(LCS):给定两个序列,求解两个序列共有的、顺序相同但不必连续的最长子序列的长度。
-
最长递增子序列(LIS):给定一个无序的序列,求解序列中最长的严格递增子序列的长度。
-
最小路径和:在一个由非负整数组成的m x n网格中,找出一条从左上角到右下角的路径,使得路径上的数字总和为最小,每次只能向下或向右移动一步。
-
编辑距离:给定两个字符串,计算由一个字符串转换成另一个字符串所需要的最少操作次数(可以进行的操作包括插入一个字符、删除一个字符、替换一个字符)。
-
硬币找零问题:给定不同面额的硬币和一个总金额,写出函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回-1。
区别:
动态规划通过分解问题为重叠的子问题,对每个子问题只解决一次并存储其解(利用记忆化),然后通过这些子问题的解,逐步构建出最终问题的解。
贪心算法在每一步决策中都采取在当前看来最优的选择,即它希望通过局部最优的选择达到全局最优。贪心算法不保证找到最优解,因为它没有考虑全局的情况。
动态规划适用于问题具有重叠子问题和最优子结构的情况,特别是当问题的解依赖于其子问题的解时。
贪心算法更适用于那些通过局部最优解能够确保全局最优解的问题。
C++ 和 c 的区别?
-
面向对象编程(OOP):
- C++ 是一种支持面向对象编程的语言,它引入了类和对象的概念,允许封装、继承和多态等特性。
- C 语言是一种过程式语言,主要关注函数和过程,而不直接支持面向对象编程。
-
标准模板库(STL):
- C++ 提供了丰富的标准模板库
- C不提供类似的库,但有一些基础的标准库如
<stdio.h>
、<stdlib.h>
等来进行输入输出和基本的内存操作。
-
类型检查:
- 相比于C语言 C++ 提供更严格的类型检查。例如,C++对函数重载和默认参数提供支持,而C语言不支持。
-
模板编程:
- C++ 支持模版编程,允许编写与类型无关的代码。但C语言不支持这一特性。
-
异常处理:
- C++ 提供了异常处理机制,而C语言则不直接支持异常处理。
数据库什么情况使用索引
数据库使用索引主要是为了提高查询效率,减少数据检索所需的时间。
-
频繁进行查询的列: 如果某些列经常用于查询条件,为这些列建立索引可以快速定位到数据行。这是最常见也是最直接的使用索引的情况。
-
作为表的连接键的列: 在执行表连接操作时,使用索引可以显著提升连接操作的性能,因为索引有助于快速匹配来自不同表的行。
-
具有高选择性的列: 列的选择性是指不重复值占总记录数的比例。选择性高的列(即包含许多唯一值的列)更适合建立索引,因为索引可以有效区分每一行数据,降低查询结果集的大小。
-
排序和分组查询的列: 对于经常需要根据某个列进行排序(ORDER BY)或分组(GROUP BY)的查询,建立索引可以加速排序和分组操作的处理,因为索引已经预先对数据进行了排序。
-
用于唯一性约束的列: 唯一性索引除了加速查询外,还保证了列中的数据值唯一,常用于主键或唯一约束的实现。
-
频繁更新或删除数据的列: 索引可以加速更新或删除操作的查找过程,特别是当表很大且更改操作集中在特定列时。