自我介绍
C++
i++ ,++i
i++是右值,++i是左值- 如果在一条语句内执行
i++, 会先获取i的值,然后执行语句,执行结束后i自增1 - 如果在一条语句内执行
++i, 会先对i自增1,然后执行语句 - 补充:一般来讲,
i++的执行操作为:使用临时对象保存i的值,然后执行i+1,返回临时对象;++i直接对i自增,不涉及临时变量
for-loop
-
for(int i = 0;i<10;i++/++i>): ++i 与i++ 输出i,结果一致吗? 一致 -
for()的使用- for如果使用之前声明的变量,可以直接以
for (;condition;expression)循环,循环开始时会获取变量的初始值,判断本次执行是否满足条件,满足则执行,执行完循环体,循环变量更新,不满足则结束循环 - 也可以使用
for(init;condition;expression)
3. 其他想到的补充: - for如果使用之前声明的变量,可以直接以
对象初始化
回答会根据传递的参数,调用不同的重载。也可以根据成员变成,隐式构造;也可以将构造函数声明为explicit的,禁止隐式构造。
面试官问还有吗?不知道问的什么,然后继续问了虚函数。
虚函数
成员函数,通过virtual修饰,子类继承时可以选择是否重载,也可以通过virtual 成员函数=0的方式声明为纯虚函数,子类必须重载。
子类继承父类时,构造子类时需要先构造父类,析构时通过声明父类析构为virtual,先析构子类,后析构父类。
补充:
- 纯虚函数的父类为虚基类,无法实例化。
- 如果构造函数中包含虚函数,因为子类还没有构造,没有虚函数指针,因此会调用父类的虚函数。
- 虚函数,成员函数会存储在代码段,虚函数表存储在
rodata段 - 在析构函数中调用虚函数时:基类析构函数在执行时,派生类部分的对象已经被销毁,虚函数表的指针可能不再有效,导致虚函数调用的行为不可预测。
inline因为inline会原地展开,没有具体的地址,无法通过指针访问static函数不能是虚函数,static成员函数是类级别的,无法通过多态调用。- 子类可以访问父类的静态资源,只要子类有访问权限
|
|
输出
|
|
虚函数指针
虚函数指针存在对象空间内,只有在调用时,才会通过虚函数指针,访问虚函数表,调用对应的虚函数。
new delete, malloc free
C++可以视作C的超集,C++继承了C的malloc和free
new / delete
- new
- 动态申请内存,返回对应对象类型的指针,
- 调用类的构造函数,并进行初始化,访问了内存,涉及到虚实地址变换
- 申请失败会抛出异常
std::bad_alloc。
- delete
- 释放通过new申请的空间;
- 调用类的析构函数
- 补充:可以通过
new(nothrow)返回空指针;new和delete是C++运算符可以重载。是否访问内存取决于具体的初始化逻辑,虚实地址变换由操作系统完成。
malloc / free
- malloc
- 申请内存,返回
void类型的指针, - 不进行初始化,因此没有访存
- 申请失败,返回
NULL
- 申请内存,返回
- free
- 释放通过malloc申请的空间;
- 补充:
malloc和free是C的函数。不支持重载
内存布局
- 栈: 自动管理,速度比较快,超出作用域自定析构
- 堆:手动管理,通过裸指针申请时,必须手动析构;也可以通过智能指针管理
- 全局区data:初始化的全局变量和静态变量
- 代码区text:代码段,存储函数,和类的静态成员函数
- bss区:未初始化的全局变量和静态变量,默认为0
- rodata区:只读数据,常量
线程 进程区别
- 进程
- 进程享有自己的地址空间,因此多进程之间是隔离的,一个进程的崩溃往往不会导致其他进程崩溃
- 进程通信比较复杂
- 进程是资源分配的基本单位
- 线程
- 多个线程共享一个进程的地址空间,因此一个线程崩溃有可能导致其他线程的崩溃
- 线程通信简单,因此需要频繁交互任务可以使用多线程。
- 补充:进程通信包括:管道,共享内存,消息队列,socket,信号,信号量;线程通信主要包括:互斥锁,条件变量,信号量,原子操作,互斥量,读写锁。
数据库索引
尴尬,答成引擎了。然后又回答一遍
- B+: 多路平衡,高度比较矮,快速查询,叶子节点为链表结构,支持范围查询,非叶子节点存储索引节点,叶子节点存储数据
- B:不支持范围查询,非叶子节点也可以存储数据
- 单列索引:单个字段创建索引
- 联合索引:多个字段创建索引,需要主要最左匹配原则
- 补充:哈希索引:快速查找,无法排序,不支持范围查询,
项目
问到有没有测过并发量。回答没有,但是根据之前测试线程池的经验,应该可以支持1w用户。
网络部分
select, poll, epoll
- select 使用了线性结构,需要遍历,且有最大文件描述符限制啊
- poll 将select的结构修改为链表,依然需要遍历访问,
- epoll 使用了红黑树作为底层结构,
补充:
select
- select 使用的固定大小的数组
fd_set,每一位表示一个文件描述符 - 当有事件发生时,select需要遍历访问,判断哪个描述符触发了事件
- select的最大描述符是固定的,由内核常量
FD_SETSIZE定义,通常为1024 - 调用select时,需要将整个文件描述集合从用户态拷贝到内核,处理后,再拷贝回去
poll
- poll使用动态链表结构,可以支持任意数量的文件描述符
- poll需要遍历访问,判断哪个描述符触发了事件
- 没有最大文件描述符的限制,但是上限会受制于系统或环境
- 每次调用poll,需要将整个文件描述符集合拷贝到内核
epoll
- epoll使用了两种数据结构,红黑树用来管理所有监控的文件描述符,双向链表用来存储就绪(
触发事件)的文件描述符 - 触发类型包括:
水平触发(LT, Level-Triggered)只要事件未处理会持续通知;边缘触发(ET, Edge-Triggered)高效模式,仅在状态发生变化时通知用户 - 使用epoll_ctl注册事件,只有一次注册的开销,之后无需遍历
- 支持大量链接
- epoll的系统调用
epoll_create创建epoll实例,返回文件描述符epoll_ctl添加监控文件描述符,添加时需要指定触发类型,以及事件类型,返回0表示成功epoll_wait等待就绪文件描述符,返回就绪文件描述符列表,返回0表示超时,返回-1表示出错
浏览器访问url到渲染页面的具体过程
- DNS解析
- 有缓存先查缓存
- 没有缓存,查询根域名服务器,根域名服务器通过树状结构查询顶级域名服务器,顶级域名查询负责该域名的权威域名服务器
- 权威域名服务器返回IP地址
- TCP握手
- IP
- 同一个子网,通过arp寻找对应的mac,不同子网通过默认路由转发,通过路由器自动更新下一跳的mac地址寻找目标mac地址
- 封装http消息,请求行,请求头,请求体,
- 通过交换机或路由器将信息转为二进制流,发送到目标主机
- 服务器收到后通过拆包,协议栈自底向上到达应用层,知道是http消息,取出对应的资源,以响应行:status_code等,响应头,响应体,经协议栈自顶向下发送
- 客户端收到消息,通过浏览器渲染得到页面
如何加快http的速度
- 选择压缩算法,压缩消息体
- 服务器负载均衡,返回最近的最快的服务器
- 选择合适的协议,因为TCP很慢,所以http3.0 使用了UDP实现的quic
- 将用户访问过的资源,在本地缓存
- 补充:其他优化方式:合并多个css、js使用一个http连接发送,减少请求数量;异步加载js;使用高效的图像压缩算法,压缩图像
- 补充缓存:
- 浏览器缓存:通过设置适当的缓存策略(如Cache-Control、Expires)在浏览器端缓存常用资源,避免每次请求都重新从服务器加载资源,显著提高页面加载速度。
- CDN缓存:内容分发网络(CDN)将资源缓存到离用户更近的节点,通过地理位置分发减少网络延迟,提升响应速度。
- 服务器端缓存:对于常用的动态资源(如数据库查询结果),可以使用 Redis 或 Memcached 等缓存技术,减少数据库的压力,快速返回常见请求的结果。
- 服务工作者缓存:现代浏览器提供服务工作者(Service Workers)来缓存资源并提供离线支持,使得即使没有网络,用户也可以继续使用应用程序。
对如何成为一个优秀的开发者有什么看法
主观题。。。
手撕 二叉树最近公共祖先
ACM模式,写出函数讲解思路
|
|