4/7 小米 C++ | 小米Su7 面经分享~
- 作者
- Name
- 青玉白露
- Github
- @white0dew
- Modified on
- Reading time
- 7 分钟
阅读:.. 评论:..
下面分享一位同学在小米的面试经历,对于这次面试,他的评价是,阵容强大,你试试呢?
【提醒】通过这次面试经验,你将可以复习到以下知识点(注意汇总,不超过6个)
- 继承与多态的基本概念和实现方式
- C++中的虚函数表和虚函数指针
- 指针在C++中的大小及NULL指针的作用
- STL模板及其常见方法和使用场景
- C++中的内存管理:new/delete与malloc/free
- 宏定义与简单的函数编写
面试官: 同学你好,很高兴你来参加今天的面试。首先,让我们来聊聊继承和多态。你能给我解释一下它们在面向对象编程中的作用和区别吗?
求职者: 当然可以。继承是面向对象编程(OOP)中的一个基本概念,它允许一个类(称为子类)继承另一个类(称为基类)的属性和方法。这有助于代码复用和扩展。而多态则是指我们可以使用一个接口多种实现,它使得我们可以在运行时动态地决定调用的具体实现,提高了代码的灵活性。
面试官: 很好的开始。那么,你提到了多态,它主要分为哪两种类型?能否举例说明它们是如何实现的? 求职者: 多态分为编译时多态和运行时多态。编译时多态主要是通过方法重载和运算符重载实现的。举个例子,我们可以有多个名为add的函数,它们的参数类型或数量不同,编译器根据参数列表决定调用哪个函数。运行时多态则是通过虚函数实现的,比如我们有一个基类指针指向了派生类的对象,当我们通过这个指针调用虚函数时,会根据对象的实际类型来调用相应的函数。
面试官: 好的。现在来谈谈虚拟机制,你能解释一下虚函数表和虚函数指针是如何工作的吗?以及一个类会有几个虚函数表?
求职者: 在C++中,如果一个类有虚函数,编译器会为这个类创建一个虚函数表,这个表中存储了虚函数的地址。每个对象都有一个虚函数指针,指向其类的虚函数表。当我们调用一个虚函数时,程序会通过对象的虚函数指针找到虚函数表,再通过表中的地址找到并执行正确的函数。通常情况下,每个类只有一个虚函数表,除非我们使用了多重继承,那么可能会有多个虚函数表来支持不同基类的虚函数。
面试官: 很详细。接下来,指针大小的问题。你知道char和int占用多少字节吗?在32位和64位系统中是否有差异?
求职者: 在32位系统中,所有类型的指针大小都是4个字节,因为它们都是存储内存地址的。在64位系统中,指针的大小是8个字节。不管是char还是int,它们的大小是相同的,因为它们本质上都是内存地址。
面试官: 正确。现在让我们看看初始化,int *p=null和int *p有什么不同?
求职者: int *p=null意味着指针p被初始化指向空值,这意味着它不指向任何有效的内存地址。相反,如果只是声明int *p而没有初始化,那么p可能指向任意位置,它的值是不确定的。这是不安全的,因为它可能会导致未定义的行为,比如访问无效内存。
面试官: 接下来,告诉我你熟悉的STL模板及其基本方法。
求职者: STL(Standard Template Library)提供了一系列的容器,算法和迭代器。比如std::vector是动态数组,可以用push_back()添加元素,用size()来获取元素数量。std::map是基于红黑树实现的有序关联容器,可以用insert()添加键值对,用find()来查找元素。
面试官: 很好,你能举例说明如何使用STL中的std::sort
对一个int
数组进行排序吗?
求职者: 当然可以。这是一个简单的代码示例,演示如何使用std::sort
:
#include <algorithm> #include <vector> #include <iostream> int main() { std::vector<int> vec = {4, 1, 3, 5, 2}; std::sort(vec.begin(), vec.end()); for (int v : vec) { std::cout << v << " "; } std::cout << std::endl; return 0; }
在这个例子中,我首先包含了头文件以便使用std::sort
,然后创建了一个std::vector<int>
并用一些初始值填充它。std::sort
的第一个和第二个参数分别是指向要排序数组首元素和尾后元素的迭代器。最后,我使用范围基于的for循环打印出排序后的容器。
面试官: 非常好。现在,假设我们有三个结构体,你如何判断它们的大小?
求职者: 结构体的大小取决于其成员的大小和对齐规则。编译器通常会按照最大成员的对齐要求对结构体的成员进行对齐。因此,要计算结构体的大小,我们需要考虑每个成员的大小和可能的填充字节。我们可以使用sizeof
运算符来确定结构体的实际大小。
面试官: 对,那么delete
和delete[]
有什么区别?
求职者: delete
用于释放单个对象的内存,而delete[]
用于释放对象数组的内存。使用delete[]
时,编译器会为数组中的每个对象调用析构函数。如果我们用new[]
分配了一个对象数组,我们必须用delete[]
去释放它,否则可能无法为所有对象调用析构函数,从而导致资源泄漏。
面试官: 好,请解释一下new
、delete
和malloc
、free
之间的区别。
求职者: new
和delete
是C++中的运算符,它们不仅负责内存分配和释放,还会分别调用对象的构造函数和析构函数。malloc
和free
是C语言中的函数,只负责内存分配和释放,不会调用构造函数和析构函数。此外,malloc
返回的是void*
类型的指针,而new
返回的指针类型与对象类型相同。
面试官: 非常清楚。那么,void*
指针有哪些使用场景?
求职者: void*
指针是一种特殊类型的指针,可以指向任何类型的数据。常见的使用场景包括一些泛型的函数,例如malloc
和free
。此外,在需要与C代码兼容的场合,void*
指针也非常有用,因为它可以无缝转换为其他指针类型。
面试官: 接下来,写一个宏定义来实现min()
函数。
求职者: 这是一个使用宏定义实现min
函数的例子:
#define MIN(a, b) ((a) < (b) ? (a) : (b))
面试官: 好的。最后一个算法题目,写一个函数实现二位数各位求和,如果结果还是两位数,那么继续操作。
求职者: 这个问题可以通过递归来解决。这是可能的实现:
#include <iostream> int sumDigits(int num) { int sum = num % 10 + num / 10; if (sum >= 10) { return sumDigits(sum); } else { return sum; } } int main() { int number = 29; std::cout << "The sum of digits: " << sumDigits(number) << std::endl; return 0; }
在这里,函数sumDigits
接收一个整数num
,计算个位和十位上数字的和。如果和是两位数,函数会递归调用自身直到结果成为个位数。
面试官: 很好,你的代码非常清晰。今天的面试到此结束,你有什么问题要问我的吗?
求职者: 目前没有,感谢您的时间和指导。
面试官: 不客气,我们很快会给你答复。再见。
求职者: 再见。