腾讯后台开发面经

请带着批判的眼光看这篇文章。或许我的一些答案是不对的。如果您发现了错误,烦请指出。
1. C++对象模型

这个要重点掌握, 要明白类里有什么,每个类的对象里又有什么。尤其是对虚函数表的了解。这块知识较多,不在这篇文章中详述。网上有着很多优秀的博客来介绍。

https://coolshell.cn/articles/12176.html

https://www.jianshu.com/p/9fb37bb3094f

  1. C++ 多态机制的实现原理

    主要就是上面问题中的虚函数表。

  2. 如果一个类的指针为 nullptr ,那么调用他的成员函数会不会出错。出错的话是在哪一阶段出错。

    也是对 C++ 内存模型的一个考察以及动态绑定静态绑定。每个类的成员函数都在.text 代码段,静态绑定的时候如果成员函数不访问成员变量的话那么这个函数不会出问题。如果说是虚函数或这个函数访问了成员变量,那么就会出错。虚函数出错是因为其动态绑定,因为是 nullptr, 无法确定其虚函数表。

  3. 引用和指针的区别

    也是一个很常问的问题吧,哎,当时春招时候看的,感觉每一条都是理所当然,但让我说有什么区别,只记得最重要的吧,并不能说全

    (1) 指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。

    (2) 可以有 const 指针,但是没有 const 引用;

    (3) 指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a 是不合法的)

    (4) 指针的值可以为空,但是引用的值不能为 NULL,并且引用在定义的时候必须初始化;

    (5) 指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了

    (6)”sizeof 引用” 得到的是所指向的变量 (对象) 的大小,而”sizeof 指针” 得到的是指针本身的大小;

    (7) 指针和引用的自增 (++) 运算意义不一样;

    怎么说呢,感觉这些都是理所当然的东西,但面试的时候没有回答完全,给面试官一种其实我不会的感觉吧,挺难受的。

  4. STL, 尤其是其中的 vector

    感觉面试时候 vectorSTL 重中之重吧。春招时候的阿里以及这次的腾讯都有着重问到。这次大概问了 vector 是什么, vector 添加元素时会发生什么(vector 的增长)。

    对 vector 中的元素进行引用可以吗?对 vector 中首元素引用可以吗?

    我的回答是不可以,因为当 vector 增长时, vector 可能会进行内存地址的移动,此时这个引用便会无效而埋下隐患。

  5. STL 中经常使用的容器、算法, 几种容器插入删除的时间复杂度(送分题)

  6. 让你删除一个 vector 中所有大于 5 的元素,你代码怎么写

    这题我知道坑在哪,是用迭代器删除时,我们不可以这么写

    1
    2
    3
    4
    5
    for(vector<int>::iterator it = a.begin(); it != a.end(); it++)
    {
    if(*it > 5)
    a.erase(it);
    }

    原因是如果有两个或多个连续的大于 5 的元素,那么当我们删除一个后,因为 vector 数组元素的移动而造成 iterator 会变更。

    但我不知道怎么变的,其实是你删除后迭代器会指向下一个。(更新,评论指导,这里说的是错误的,现更正。)

    在该情境中,使用 erase 后迭代器就失效了,对失效的迭代器解引用是未定义行为,具体要看编译器的实现。而非之前说的迭代器指向下一个,指向下一个只是我的编译器这样实现的罢了。

    这题也有点可惜。我对 STL 还不是想像中的那么熟悉。

    更新,正确写法

    erase 返回指向序列的下一个元素。

    1
    2
    3
    4
    5
    6
    7
    for(auto it = a.begin(); it != a.end(); )
    {
    if(*it > 5)
    it = a.erase(it);
    else
    it++;
    }
  7. 智能指针的原理

    shared_pt : 引用计数,类成员有两个指针,一个是被管理的原始指针,一个则是指向计数控制块的指针。
    unique_ptr:禁止拷贝。

    weak_ptr:

    面试官问了我如何实现一个 unique_ptr,我的回答大概就是用面向对象的思想,对原生指针封装到一个类中,当其生命域结束时析构它。
    使用 unique_ptr 管理指针时,如果我们手动析构了其原生指针,会造成 double free 吗?这个问题把我问懵了,因为我不太懂 unique_ptr 的原理。其实是会造成 double free 的。而我蒙了一波什么 unique_ptr 析构时会检测,balabala, 狗屁。unique_ptr 根本不知道你析构了。难受。

  8. 算法题目:如果一个链表里有环,寻找环的入口

    经典题目吧。快慢指针法,可笑的是我只知道快慢指针法,后面忘记了。

  9. 下面是网络方面的,我答的更是一团糟。也不知道是紧张还是啥,握手挥手都分不清了,把挥手当成了见面????

TIME_WAIT 发生在什么时候,多久,有什么作用。这是很基础的知识了,当时春招的时候很用功地去学习过 TIME_WAIT 和 CLOSED_WAIT,而现在却忘了发生在什么时候。

CLOSED_WAIT 发生在什么时候

  1. UDP 报文最大长度

    不知道。 今天查了下,在实际应用中是 65535 (64K)。用 sendto 函数最大能发送数据的长度为:65535- IP 头 (20) - UDP 头 (8)=65507 字节。用 sendto 函数发送数据时,如果发送数据长度大于该值,则函数会返回错误。

  2. Linux 命令

    • 删除 10 天前的文件 参考链接

      命令格式:find 对应目录 -mtime +天数 -name "文件名" -exec rm -rf {} \;

      如:find /opt/soft/log/ -mtime +30 -name "*.log" -exec rm -rf {} \;

    • 删除大小大于 m 字节的文件

      find 目录 -size +40b -exec rm {} \;

    • 文件中每行一个数字,输出它们的和

      cat 文件名 | awk 'BEGIN {sum=0;} {sum=sum+$1;} END{print sum}'

      awk 命令的使用

  3. top 命令可以查看进程吗

    这个当时不知道,懵了个不可以,因为当时自己用 top 的时候只看到了进程。唉,今天查了下是可以用 -H 来看的。

  4. selectepoll 的原理,及什么场景适合 select

    戳中盲区。自己补了下这一块的知识。

  5. epoll 中 LT 和 ET 两种模式的区别

  6. select 监听数量的限制

  7. 锁、信号量的区别

    互斥量用于线程的互斥,信号量用于线程的同步。

    互斥量值只能为 0/1,信号量值可以为非负整数。

    互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。

  8. 我的项目性能瓶颈在哪?

    QAQ,这个没有分析过。

  9. 线程进程的区别及通信方式

  10. strcpy 使用时要注意什么,以及 strncpy, strcpy_s, memmove, memcpy。(这个问题在几次面试中有多次问到)

    strcpy 使用时注意数组越界以及源字符串,目的字符串有没有重叠等。

    memcpy 是用于拷贝内存,同样要注意源和目的内存间有没有重叠。

    memmove 解决了源和目的内存间重叠问题

  11. 设计一个哈希表

  12. C++ 中萃取是用来做什么的

  13. 四次挥手什么时候会变成三次挥手 (延迟确认)

  14. OSI 七层 和 IP 五层

  15. TCP 特点以及 TCP、UDP 的区别

  16. 如果你的服务器出问题了,那么如何定位问题

  17. C++ 代码编译过程的几个步骤

  18. 动态链接和静态链接区别

  19. constdefine 的区别

  20. inlinedefine 的区别

  21. 引用会占用内存空间吗

    不会

  22. malloc/freenew/delete 的区别

    https://www.cnblogs.com/qg-whz/p/5140930.html

  23. extern “C” 作用

  24. fork () 会从父进程中继承哪些东西

  25. lsof 命令

  26. 在构造函数中可以调用虚函数吗,析构函数中呢

  27. C++ 对象成员初始化顺序(初始化列表中根据声明顺序)

  28. 在项目中有没有具体分析过哪些语句耗时比较大。

  29. 如果有两个类 AB,下面一段代码

    1
    2
    3
    void f(A a) {}
    B b;
    f(b)

    编译通过,请根据你对 C++ 的理解解释下。

    这个当时我懵了下,只想到了 B 继承于 A,一下想不起来其他的可能。后面冷静了下,突然想起前一天在高铁上的《More Effective C++》 上的内容,刚好可以回答这个问题(一定要抓住时间学习,可能你上一分钟学 到的知识下一分钟就会用上)。

    最终我总共回答了三点,看面试官的反应应该挺满意的。

    1. B 继承于 A(不过会有一定的继承条件,比如 public 继承,单继承)
    2. B 可以隐式转换为 A,即 B 中有 operator A()
    3. A 可以由 B 构造。

    后面面试官问如何防范这种情况。于是我回答了在类 B 中不隐式转换为 A, 以及使用 explict 显式构造或根据 C++ 规则不可以进行二次隐式构造的原理。