大家好,我是白露。
今天在牛客上看到一条帖子,让我感叹万分:实习两三个月,竟然已经存下了20多万的存款。
这也太夸张了吧?不太真实啊……
很多网友表示疑问,“两三个月实习顶多存两三万吧?武理奖学金十几万?”
楼主回复说:“还有学校补贴和导师横向,实习了半年多,存了五六万吧。”
所以真相了,其实是算上了之前的存款和其他收入。
但是这也很厉害了啊!优秀的人总是能带给我们无限的力量和激励。
把上面这篇帖子的内容分享给大家,是希望给我的粉丝们一些动力和鼓舞。
现在的互联网充斥着各种负面情绪,很多人一刷就很容易陷入emo状态,甚至自我怀疑,失去了学习的热情和动力。
但无论大环境如何,进化论永远不过时:“优胜劣汰,适者生存。”
如果我有躺平的资本,自然可以选择不努力。然而,事实是我没有这个条件,所以努力是无法规避的选择。不过,努力不等于盲目地卷,周末的休息也是必不可少的。
真正的优秀,是那些默默努力并最终脱颖而出的,因为他们懂得如何抓住每一个机会,如何在逆境中找寻希望。如果有任何困惑或分享,欢迎在评论区留言讨论。还没有拿到offer的同学,可以看看一次腾讯面试题目了,已经稳定的同学,可以直接跳转最后,有惊喜哦~
开始面试
面试官: 你好,欢迎来到搜狗输入法PC客户端的面试。请先进行一下自我介绍。
求职者: 您好,我是一名计算机科学与技术专业的毕业生,对C开发有着浓厚的兴趣和实践经验。在校期间,我参与了多个使用C的项目,包括桌面应用程序和游戏开发。我熟悉C++的面向对象编程,理解内存管理,并且对性能优化有一定的研究。
面试官: 很好,请手撕快速排序。
求职者: 快速排序的基本思想是选择一个基准值,通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。
#include <iostream>
#include <vector>
void quickSort(std::vector<int>& nums, int left, int right) {
if (left >= right) return;
int i = left, j = right;
int pivot = nums[left]; // 选择基准值
while (i < j) {
while (i < j && nums[j] >= pivot) j--;
if (i < j) nums[i++] = nums[j];
while (i < j && nums[i] <= pivot) i++;
if (i < j) nums[j--] = nums[i];
}
nums[i] = pivot;
quickSort(nums, left, i - 1);
quickSort(nums, i + 1, right);
}
int main() {
std::vector<int> nums = {3, 1, 4, 1, 5, 9, 2, 6, 5};
quickSort(nums, 0, nums.size() - 1);
for (int num : nums) {
std::cout << num << ' ';
}
std::cout << std::endl;
return 0;
}
面试官: C++重载是怎么实现的?
求职者: C++中的函数重载通过在同一作用域内给多个函数赋予相同的名字但不同的参数列表来实现。编译器根据函数的参数类型、个数以及顺序来区分它们。这一过程在编译时完成,称为静态绑定。
面试官: C++多态是怎么实现的?
求职者: C++的多态主要是通过虚函数实现的,它允许使用基类的指针或引用来调用派生类的函数。编译器使用虚函数表来实现多态,每个拥有虚函数的类都有一个虚函数表,每个对象都有一个指向虚函数表的指针。当我们通过基类的指针调用虚函数时,运行时会根据对象的实际类型来决定调用哪个函数,实现了运行时多态。
面试官: C++代码是如何转化为二进制文件的?
求职者: C++代码转化为二进制文件的过程包括预处理、编译、汇编和链接几个步骤。预处理器处理所有的预处理指令,如宏定义和文件包含。编译器将预处理后的代码转换为汇编语言。汇编器将汇编代码转换为机器码,生成目标文件。最后,链接器将一个或多个目标文件与库文件链接在一起,生成可执行的二进制文件。
面试官: DLL加载文件的顺序是怎样的?
求职者: DLL(动态链接库)文件的加载顺序通常是:首先查找进程内存中是否已加载;如果没有,按照系统目录、系统环境变量指定的路径、应用程序目录来查找DLL。在Windows系统中,还可以通过修改注册表或者使用manifest文件来改变DLL的加载顺序。
面试官: DLL调用的内存分区是怎样的?
求职者: DLL调用时,代码段通常是共享的,每个进程共享同一份代码,但拥有自己的数据段,例如全局变量和静态变量。这意味着DLL中的函数是所有调用者共享的,但是数据是各自独立的。
面试官: PE文件结构是什么样的?
求职者: PE(Portable Executable)文件格式是Windows下的可执行文件格式。PE文件结构主要包括DOS头、NT头(包含File Header和Optional Header)、Section Headers和Section Bodies。NT头部分包含了重要的信息,如入口点地址、各种表的地址(如导入表、导出表和资源表)。
面试官: 说说delete []
。
求职者: 在C++中,delete []
运算符用于释放通过new[]
运算符分配的数组内存。它不仅会释放内存,还会调用数组中每个对象的析构函数。如果使用delete
而不是delete []
来释放数组,可能会导致只调用第一个对象的析构函数,从而引发内存泄漏。
面试官: 内存泄露如何定位?
求职者: 定位内存泄露通常可以借助一些专门的工具,比如在Windows平台上可以使用Visual Studio自带的诊断工具或第三方工具如Valgrind(适用于Linux)、LeakSanitizer等。这些工具可以帮助追踪内存分配和释放,从而找到未被正确释放的内存。此外,代码审查也是一个有效的手段,特别是关注动态内存分配和释放的逻辑。
面试官: 基类、派生类、成员对象构造函数调用顺序?
求职者: 在C++中,构造函数的调用顺序首先是基类的构造函数,然后是成员对象的构造函数,最后是派生类的构造函数。析构函数的调用顺序与此相反,即先调用派生类的析构函数,然后是成员对象的析构函数,最后是基类的析构函数。这种顺序确保了对象在构造和析构过程中的正确性。
面试官: unordered_map和map的区别?
求职者: unordered_map是基于哈希表实现的,它提供了平均时间复杂度为O(1)的查找、插入和删除操作,但是元素是无序的。而map是基于红黑树实现的,它提供了O(log n)的查找、插入和删除操作,元素按照键值自动排序。选择哪个主要取决于对元素顺序的需求和操作的性能考虑。
面试官: 哈希冲突,如何解决?
求职者: 解决哈希冲突的常见策略有开放寻址法、链地址法等。开放寻址法通过探测一个空闲位置来解决冲突,如线性探测、二次探测和双重散列。链地址法将具有相同哈希值的所有元素存储在同一个链表中。此外,还可以通过使用更好的哈希函数减少冲突的可能性。
面试官: HTTPS密钥协商过程?
求职者: HTTPS中的密钥协商过程是基于TLS/SSL协议。首先,客户端发送支持的加密方法列表给服务器。服务器选择一个加密方法,并发送其证书(包含公钥)给客户端。客户端验证证书的有效性,然后生成一个预主密钥,并使用服务器的公钥加密发送给服务器。服务器使用私钥解密得到预主密钥。之后,双方使用预主密钥生成会话密钥,用于后续通信的加密。
面试官: 非对称加密、对称加密?
求职者: 对称加密使用相同的密钥进行数据的加密和解密,加密和解密速度快,适用于大量数据的加密,如AES。非对称加密使用一对密钥,一个公钥用于加密,一个私钥用于解密,如RSA。非对称加密解决了密钥分发的问题,常用于密钥交换和数字签名,但加解密效率低于对称加密。
面试官: 函数调用压栈顺序?
求职者: 在大多数的C++实现中,当函数被调用时,参数按照从右到左的顺序压入栈中,然后是返回地址和旧的基指针。这样保证了在函数体内可以正确通过偏移量访问到每个参数。不过,具体的调用约定可能因编译器和平台而异。
面试官: C++主函数运行前的操作?
求职者: 在C++程序的主函数main运行之前,编译器会安排一些初始化操作,包括全局对象和静态对象的构造,初始化C++运行库环境,设置堆和栈等。对于全局和静态对象,它们的构造函数会在main函数之前调用,析构函数则在main函数执行完毕后调用。
面试官: 谈谈野指针和悬空指针。
求职者: 野指针是指未被初始化的指针,其值是不确定的,可能指向任何内存地址。使用野指针可能导致不可预料的行为,甚至程序崩溃。悬空指针,或称为悬挂指针,是指指向已经释放的内存的指针。内存释放后,指针并没有清空或指向NULL,如果再次使用这样的指针,可能会破坏程序的内存安全。
面试官: 如果发生内存泄露,你会如何定位问题?
求职者: 定位内存泄露可以使用一些专业的内存分析工具,比如在Windows上的Visual Studio内置的诊断工具、Linux上的Valgrind或者使用内存分析库如Google的Sanitizers。这些工具可以帮助开发者监测程序运行时的内存分配和释放,从而找到没有正确释放内存的位置。除此之外,代码审查和遵循良好的内存管理实践也是防止内存泄露的重要手段。
面试官: 描述一下DLL加载文件的顺序。
求职者: DLL文件在Windows系统中的加载顺序大致如下:首先,会在程序的内存空间中查找是否已经加载了目标DLL;如果没有,会查找系统目录如System32;接着是环境变量指定的路径;最后是应用程序的目录。如果在这些位置都未找到DLL,加载会失败。此外,可以通过修改注册表或使用清单文件来更改默认的搜索顺序。
面试官: DLL调用时占用的内存分区是什么?
求职者: DLL调用时,其代码段通常是共享的,被加载到调用进程的地址空间中的一个特定区域,所有使用该DLL的进程都可以使用这些共享代码。每个进程都有自己的数据段,用于存储全局变量和静态变量的副本。这样,虽然代码是共享的,但数据是每个进程独立的。
面试官: PE文件的结构包括哪些部分?
求职者: 一个PE文件包括DOS头部、NT头部、节表和各个节区。DOS头部主要是为了兼容旧DOS系统。NT头部包含了File Header和Optional Header,提供了关于可执行文件的重要信息,如入口点地址、所需版本和段对齐等。节表包含了对应各个节的信息,如.text、.data、.rdata等,并描述了它们在内存和磁盘上的位置和大小。
面试官: delete []的用法是什么?
求职者: 在C++中,delete []用于释放通过new[]操作符分配的数组内存。它不仅负责释放内存,还会依次调用数组中每个对象的析构函数。如果使用单个delete来释放由new[]分配的内存,将会导致未调用除第一个元素之外的其他元素的析构函数,可能造成内存泄露。
面试官: 基类、派生类、成员对象的构造函数调用顺序是怎样的?
求职者: 在C++中,对象构造的顺序首先是基类构造函数的调用,然后是成员对象的构造函数调用,最后是派生类构造函数的调用。相应的,析构的顺序是相反的,即先调用派生类的析构函数,然后是成员对象的析构函数,最后是基类的析构函数。这种顺序确保了对象在其生命周期内的正确构建和销毁。
面试官: 解释一下unordered_map和map。
求职者: unordered_map是基于哈希表实现的关联容器,它提供了平均常数时间复杂度的元素访问。unordered_map内部元素是无序的。而map是基于红黑树实现的,它提供了对元素的有序序列,并且访问的时间复杂度是对数时间。map适合有序数据的操作,而unordered_map适合快速访问的场景。
面试官: HTTP/2和HTTP/3的主要区别是什么?
求职者: HTTP/2引入了诸如头部压缩、单一连接多路复用等特性,显著提高了Web性能。HTTP/3进一步优化,基于QUIC协议运行,它使用UDP而非TCP,减少了连接建立和握手的延迟,提供了更好的错误恢复和拥塞控制机制。