C/C++面试
TCP 为什么要三次握手?两次握手会有什么问题?、
三次握手可以让服务器和客户端确定序列号seq,并且因为是全双工通信,如果两次握手客户端没办法确认和服务器的连接,因为客户端也要发送信息。
Redis 缓存穿透、击穿、雪崩分别是什么?怎么解决?
1.缓存穿透:查了不存在的数据。用户请求一个数据库里根本没有的数据。
缓存空值:查不到就把null存入缓存,设置短过期时间
布隆过滤器:把所有存在的key放入布隆过滤器,不存在的直接返回,不查数据库。
2.缓存击穿:是查热点Key过期,一个访问量极高的 Key,刚好过期了。一瞬间大量请求同时打到数据库。
1.设置热点key永不过期。
2.设置互斥锁,只让一个线程去查数据库,其他等待。
3.缓存雪崩:大量key同时过期大量缓存同一时间全部失效,所有请求一起打到数据库。
1.过期时间加随机值
2.搭建redis集群
3.读物降级/熔断机制
4.缓存永不过期
进程和线程的核心区别是什么?什么时候用多进程、什么时候用多线程?
进程是程序执行的最小单位,线程是cpu调度的最小单位,线程间资源可以共享,并且一个线程出现异常,其他线程会收到影响,进程是独立资源,一个进程出现问题其他进程不会受影响。进程切换开销大,线程切换小。
多进程:需要稳定性高,一个崩了不能拖垮整体,需要强隔离性,安全需求高,程序之间基本不通信。Nginx,浏览器多标签等。
多线程:需要频繁通信,追求高性能,低开小,并发处理等。
MySQL 索引什么时候会失效?至少说 3 种情况。
违反了最左前缀原则,对索引列进行了计算,采用了!=,<>,IS NOT NULL等运算符,使用了模糊查询,隐式类型转换,采用or连接非索引。
死锁的 4 个必要条件是什么?如何预防死锁?
互斥条件,循环等待,持有并保持,不可剥夺。
破环其中一个条件就行,采用银行家算法,采用鸵鸟思想认为不会发生死锁,
-
破坏请求与保持一次性申请所有需要的资源,要么全分配,要么全不分配。
-
破坏不可剥夺进程申请新资源得不到时,主动释放已持有资源;或系统强行剥夺占用的资源。
-
破坏环路等待统一资源编号,按固定顺序申请资源,所有进程都按相同顺序获取锁 / 资源,杜绝循环等待。
-
破坏互斥尽量减少独占互斥使用,能共享的资源设计为共享访问。
HTTP 和 HTTPS 的区别?HTTPS 是怎么保证安全的?
| HTTP | HTTPS | |
| 端口 | 80端口 | 443端口 |
| 安全性 | 明文传输,无加密 | 加密传输安全 |
| 协议层级 | 应用层协议 | HTTP+SLS/TLS组合,在http和tcp中间 |
| 证书 | 无需证书 | 需要配置CA数字证书 |
| 性能 | 快 | 慢 |
加密,完整性校验,身份验证。保证安全
1.身份验证通过CA数字证书验证服务器身份
2.混合加密防止窃听(对称和非对称加密)
3. 摘要校验(防篡改)
用 MD5/SHA 哈希摘要,
Linux 下查看内存、CPU、端口、进程分别用什么命令?
top free -h ps -ef netstat -tulnp
epoll 的水平触发 LT 和边缘触发 ET 有什么区别?
水平触发:只要缓冲区有数据未读完,epoll 每次调用都会持续通知该文件描述符就绪,支持阻塞、非阻塞,编程简单、不易出错
边缘触发:只有数据第一次到达的瞬间(状态从无到有)只触发一次;如果没把缓冲区数据读完,后续不会再重复通知。只支持非阻塞,性能高但是容易出错。
数据库事务的四大特性 ACID 分别是什么意思?
A:原子性:要么全成功要么全不成功
C:一致性:事务前后数据完整性约束不变
I:隔离性:事务间互相隔离互不干扰
D:一致性:事务一旦提交,永久生效。
智能指针 shared_ptr 和 weak_ptr 的区别?weak_ptr 解决什么问题?
shared_ptr 是共享指针,里面存储了一个引用计数器,每当调用赋值构造或者拷贝构造都会+1,直到他为0时才会释放。
weak_ptr 是弱指针,为了解决共享指针的循环引用问题,每当调用赋值构造或者拷贝构造引用计数不会增加,weak_ptr 不能直接访问,必须调用 .lock() 提升为 shared_ptr 再使用
C++:虚函数和纯虚函数的区别是什么?
虚函数是实现C++动态多态的,纯虚函数是的类是一个抽象类,继承的子类必须重写纯虚函数,
虚函数:基类必须有实现,子类可以重写也可以不重写。
纯虚函数:基类没有实现(或类外实现),子类必须重写,否则子类也成抽象类。
拷贝构造函数为什么必须传引用
如果是普通的值传递,当进行拷贝构造的调用时,会再次调用拷贝构造,会无限循环下去。
指针和引用的区别?
指针:可以不初始化,占用内存,可以指向别的变量。* 解引用->,存在多级指针,支持++--
引用:变量的别名,不占用内存,必须初始化,一旦绑定,终身不变。直接用,& 仅定义时用,不存在多级引用,不支持++--
new/delete 与 malloc/free 的区别?
new/delete:关键字,开辟内存不用给出开辟的大小,自动调用构造函数和析构函数,开辟失败返回异常,返回类型是对象的指针类型,可以重载,类型安全。
malloc/free:库函数,开辟内存需要指出内存大小,释放开辟不调用构造析构,开辟失败返回null,返回对象是void*,不能重载
什么是虚表、虚指针?多态怎么实现?
虚表是一个类中存放虚函数地址的表,每个类只有一个,虚指针是每个对象一个指向所属类的虚表,通过虚指针找到虚表中对应的虚函数地址,进行调用,实现多态。
- 虚表在编译阶段就构建好,存放在只读数据段,不属于对象内存;
- 虚指针是对象隐含成员,创建对象时由编译器自动初始化指向本类虚表。
构造函数为什么不能是虚函数?
虚表是在编译阶段就构建好的,构造函数需要初始化时才确定,两者之间矛盾,所以不能是。
并且虚函数是为了让子类重写,运行时选版本,但是构造函数不能被继承,不能被重写,是每个类独有的,两者机制不兼容。
深拷贝和浅拷贝区别?
深拷贝是重新开辟出一块空间,复制进去,修改内容不影响原来的,浅拷贝是让两个指针指向同一片内存,修改任意一个都会影响另一个。
static 和 const 用法与作用?
static修饰全局变量:作用域整个文件内部,生命周期是整个程序运行期间,存放在全局区
修饰局部变量:生生命周期是整个程序运行期间,但是只能在函数内部进行访问,存放在全局区
修饰类成员变量:不属于类,类外定义,生命周期是整个程序运行期间。存放在全局区
修饰类的成员函数:单例,没有this指针,只能访问类的静态成员变量函数,类名:: 直接调用,不用创建对象。存放在全局区
const修饰变量:变量变为只读常量,初始化后不能修改,存在常量区。
修饰函数形参:形参在函数内部不能进行修改。函数内部不能修改该参数,防止误改,const string& s 避免拷贝 + 禁止修改
是修饰 this 指针:这个成员函数里,不能修改类的任何普通成员变量
移动语义、右值引用解决什么问题?
解决临时对象产生的无谓拷贝开销,把 “拷贝对象” 变成 “偷资源所有权”,提升性能。
互斥锁、条件变量作用?
保证多线程下临界资源同一时刻只被一个线程访问,解决线程竞争、数据乱序问题。
条件变量:让线程在不满足条件时主动阻塞等待,条件满足后再唤醒,替代轮询,节省 CPU。需要配合互斥锁使用
僵尸进程、孤儿进程是什么?
僵尸进程:子进程先退出,父进程还在运行,但父进程没有调用回收子进程退出状态,子进程进程资源没完全释放,变成僵尸进程。
孤儿进程:父进程先退出,子进程还在运行,丢失了父进程,被init进程领养。
线程同步有哪些方式?
mutex互斥锁,条件变量,信号量,原子操作,读写锁,自旋锁
并发和并行区别?
并行:交替执行,看起来一起跑,一个 CPU 核心上时间片轮转交替执行,看起来像是一起执行。
并发:需要多核 CPU,同一时刻多个任务真正同时在不同核心上一起执行。
并发是逻辑上同时,并行是物理上同时。
线程池的好处与原理?
提前创建一批线程放入队列中,任务来了可以取出一个线程使用,任务完成后线程重新放入线程池中,可以避免频繁的销毁和创建带来的开销。
-
降低资源消耗避免频繁创建、销毁线程的系统开销,复用线程。
-
提高响应速度任务到达不用等新建线程,直接拿池中空闲线程立刻执行。
-
方便统一管理可以控制最大并发数、队列排队、拒绝策略,防止无限创建线程把系统拖垮。
-
可定时 / 延时执行
select/poll/epoll 区别?
selete:最大连接数是1024,每次调用都全量遍历所有 fd,还要拷贝用户态到内核态O (n) ,select只支持水平触发 LT
poll:无固定上限,不用位集,结构优化,但还是全量轮询遍历O (n) 、poll只支持水平触发 LT
epoll:最大连接数无限制,事件驱动,内核只返回已就绪 fd,不用遍历全部O (1),epoll 支持 LT 水平触发 + ET 边缘触发,ET 更高效,适合高并发 Reactor 模型
软链接和硬链接区别?
软连接:软链接是存路径的独立文件,可跨分区、可链目录,原文件删除就失效。
硬链接:同一 inode 多个文件名,不能跨分区、不能链目录,删原文件仍可用
僵尸进程如何避免?
父进程主动调用 wait /waitpid
子进程退出后,父进程主动调用 wait() 或 waitpid(),及时回收子进程退出状态,PCB 资源立刻释放,不会产生僵尸进程。
捕获 SIGCHLD 信号
子进程退出会给父进程发 SIGCHLD 信号,父进程注册信号处理函数,在信号函数里循环 waitpid 批量回收所有已退出子进程,高效解决僵尸。
让父进程先退出(托管给 init)
父进程提前退出,子进程变成孤儿进程,被 init 进程(PID=1) 收养,init 会自动回收子进程,不会产生僵尸。
二次 fork(最优雅方案)
父进程 fork 出子进程,子进程再 fork 出孙子进程;
更多推荐

所有评论(0)