http和https的区别是什么
HTTP 和 HTTPS 的核心区别是安全性。HTTP 是明文传输;HTTPS 是 HTTP 加上 TLS,主要有以下区别:
- 安全性:HTTPS 提供数据加密、完整性校验和服务器身份认证,防止数据被窃听、篡改以及服务器被冒充。
- 默认端口:HTTP 使用 80,HTTPS 使用 443。
- 连接过程:HTTPS 需要CA机构的数字证书,并进行 TLS 握手,因此会增加一定的连接开销。
.http也要保证安全的话要怎么设计
只能仿照https,在应用层做加密和签名,并解决安全的密钥分发问题,但这仍不能保护全部 HTTP 元信息。"这只能加密业务层数据,但 URL 路径、请求方法,以及未加密的 Cookie、Token 等请求头仍然可见。仍然可能被他们冒充身份。
消息队列了解吗,有哪些
"了解。消息队列让生产者发送消息、消费者异步处理 ,主要用于异步处理、系统解耦、削峰填谷。比如下单后,把发短信的任务放入队列,避免阻塞下单流程。
常见的消息队列中间件有:
- 业务需要灵活的路由分发和低延迟投递 ,消息量不算特别巨大 → RabbitMQ
- 消息量大,追求极致吞吐 Kafka
- 核心是交易/订单类业务 ,需要保证可靠性 → RocketMQ
如果面试官问的是 Linux 进程间通信的消息队列 ,则主要有 System V 消息队列和 POSIX 消息队列。
mysql的like模糊查询能不能走索引
"**能,但要看有没有固定前缀。**对于普通 B+ 树索引,LIKE 'abc%' 有固定前缀,可以利用索引做范围扫描;LIKE '%abc' 或 LIKE '%abc%' 没有固定前缀,无法通过这个条件确定索引范围。"
"不过,后两种也不能说绝对不走索引 ,覆盖索引时可能进行全索引扫描。最终要用 EXPLAIN 看执行计划:range 表示范围扫描,index 表示全索引扫描,两者效率不同。
除了mysql,其他的数据库有没有了解
"有一些了解,主要是 Redis 和 SQLite。
Redis 是内存型键值数据库,支持多种数据结构,也支持持久化,常用于缓存、计数器等场景。
SQLite 是轻量级的嵌入式关系型数据库,不需要单独部署数据库服务,比较适合 C++ 客户端的本地数据存储。
目前我主要了解它们的基本用法和适用场景,底层实现还没有深入研究。"
mysql的隔离级别了解吗,有哪些,可能出现的问题
MySQL 事务隔离级别有 4 种,从低到高分别是:
- Read Uncommitted,读未提交 :可能出现脏读、不可重复读、幻读。
- Read Committed,读已提交 :解决脏读,但可能出现不可重复读、幻读。
- Repeatable Read,可重复读 :MySQL InnoDB 的默认隔离级别。解决脏读和不可重复读;InnoDB 大部分场景下也能解决幻读问题。
- Serializable,串行化 :隔离级别最高,事务基本串行执行,可以避免前面这些并发问题,但并发性能最差。
这里几个问题的含义是:
- 脏读:读到了其他事务还没提交的数据。
- 不可重复读:同一个事务里,两次查询同一条记录,结果不一样。
- 幻读:同一个事务里,相同条件查询,第二次发现多了或少了几条记录。
内存级缓存有了解吗
了解,以 CPU 的多级缓存为例,主要有 L1、L2、L3,越靠近 CPU,速度越快、容量越小,主要用来减少访问主内存的开销。
它利用时间局部性和空间局部性,以缓存行为单位加载数据,常见缓存行大小是 64 字节。
在 C++ 中,连续存储、顺序访问通常更有利于缓存命中,比如遍历 vector。多线程还要注意伪共享:不同核上线程修改同一缓存行里的不同变量,也会触发缓存一致性开销,影响性能。
AI写的代码和你自己写的代码的比例是多少
按代码量粗略估计,第一个项目AI生成的占30%,自己写的占70%。AI主要帮我写测试用例和一提供一些项目优化的想法,第二个项目除了前端网页全是我自己实现的。AI生成的代码也必须经过我的理解、审查最终由我负责。
对于AI的上下文问题,你在使用AI的时候,是怎么让AI知道你们之前的聊天的
同一个会话里,我一般接着原来的对话聊。如果聊天比较长,或者要换新会话,我会把项目背景、已确定的方案、当前进度和待解决的问题整理成简短摘要,连同必要的代码一起提供给AI。摘要可以让AI先生成,但我会核对;后续有新结论就更新,关键约束也会重新强调,减少上下文丢失和理解偏差。
AI的查找方向有误你是怎么解决的
我会先核对AI的判断依据,一般结合报错、日志,就能知道它哪个部分出现了问题。然后跟ai说你上一步的结果怎么出错,我认为咋样让ai重新思考,如果仍然分析出错,我会自己对应报错审查代码进一步缩小到模块范围,让AI辅助检查。
简单讲讲线程池
线程池就是创建一组可以复用的工作线程,统一执行提交的任务,避免每来一个任务就创建、销毁线程,降低开销,同时控制并发数量。
核心是任务队列和工作线程 :提交方把任务放入队列,工作线程取出任务执行;没有任务时就阻塞等待,有新任务再被唤醒。C++ 中通常用 std::thread 管理线程,用互斥锁保护队列,用条件变量实现等待和唤醒。
实现时还要注意任务异常处理,以及关闭线程池时,是执行完剩余任务还是直接取消。
. 多线程访问遇到过什么问题,是怎么解决的
我在服务器项目里遇到过连接对象被多个线程同时操作的问题,比如业务线程发送数据时,另一个 线程正在关闭连接,容易产生数据竞争,甚至访问已经释放的对象。
我的解决办法是:每个连接固定I/O 线程 ,其他线程把发送、关闭等操作投递到任务队列,由所属线程串行执行。定时器回调使用 weak_ptr,因为定时器不应该拖延整个连接的生命周期所以使用weakptr,执行前通过 lock() 获取对象并检查连接状态,避免任务线程释放了我这个链接造成悬空访问。
在使用这个锁的过程中,还有什么需要注意的问题
使用 std::mutex 时,我主要注意四点:
- 自动释放锁 :用
std::lock_guard管理,避免提前返回或抛异常时忘记解锁。 - 缩小临界区:只锁必要的共享数据操作,避免持锁做耗时操作,减少线程等待。
- 避免死锁:锁内部不要再次调用锁。
- 同步规则一致:同一共享数据的并发读写都要遵守同一套锁保护规则,不能有的地方加锁、有的地方漏锁。
从C++代码到一个可执行文件,会经历什么过程
从 C++ 源代码到可执行文件,主要经历四个阶段:预处理、编译、汇编、链接。
- 预处理:展开头文件和宏,处理条件编译,删除注释,生成预处理后的代码。
- 编译:进行语法分析,检查类型等错误,并进行优化,生成汇编代码。
- 汇编:把汇编代码转换成机器指令,生成目标.o文件。此时外部符号的地址可能还没确定。
- 链接:把目标文件和所需的库组合起来,完成符号解析和地址重定位,最终生成可执行文件。
define 和 const 的区别
- 处理阶段 :
#define在预处理阶段进行文本替换;const由编译器处理。 - 类型检查 :宏没有类型,不做类型检查;
const定义的对象有类型,编译器会进行类型检查。 - 作用域 :宏不遵守 C++ 的作用域规则,从定义处生效直到
#undef或文件结束;const遵守正常的作用域规则。
因此,定义常量时通常优先使用 const,更安全、便于调试。
指针和引用有什么区别
指针是指向变量的地址,引用是对象的别名。 主要有四个区别:
- 初始化和空值:指针可以为空;引用定义时必须绑定对象。
- 能否重新绑定:指针可以改变指向;引用绑定后不能再绑定其他对象。
- 访问方式 :指针通过
*解引用访问对象;引用可以像普通变量一样直接使用。 sizeof的含义 :对指针使用sizeof,得到指针本身的大小;对引用使用,得到被引用对象类型的大小。
new 和 malloc 有什么区别
new 是 C++ 运算符,malloc 是 C 标准库函数。 主要有四个区别:
- 对象初始化 :
new分配内存后会初始化对象,对类对象会调用构造函数;malloc只分配内存,不调用构造函数。 - 大小和类型 :
new根据类型计算所需大小,返回对应类型的指针;malloc需要指定字节数,返回void*,在 C++ 中需要显式转换。 - 分配失败 :普通
new失败时抛出std::bad_alloc;malloc失败时返回空指针。 - 释放方式 :
new配合delete,new[]配合delete[],会执行相应的析构;malloc配合free,不会调用析构函数。
对智能指针的了解
能指针是对裸指针的封装,利用 RAII 自动管理资源,在合适的时机释放对象,减少手动 delete 和内存泄漏的风险。 现代 C++ 常用三种:
unique_ptr:独占所有权,不能复制,但可以通过移动转移所有权。shared_ptr:共享所有权,通过引用计数管理生命周期,最后一个拥有者释放时销毁对象。但相互持有shared_ptr可能造成循环引用,导致内存泄漏。weak_ptr:不拥有对象,不增加强引用计数,常用于打破循环引用。使用时通过lock()获取shared_ptr;对象已经销毁则返回空指针。
weak_ptr 的实现的了解,它是怎么解决循环引用的
weak_ptr 解决循环引用的关键是:它观察对象,但不拥有对象,不增加强引用计数。
实现上,它通常与 shared_ptr 共用一个控制块,里面维护强、弱引用计数。强引用计数归零时销毁对象;控制块会继续保留,直到弱引用也全部释放。
比如,A 和 B 相互用 shared_ptr 持有,即使外部指针都释放了,两者仍互相维持引用计数,无法析构。把 B 对 A 的引用改成 weak_ptr,就不会阻止 A 销毁;A 销毁时释放对 B 的持有,B 也就能正常销毁。
访问对象时调用 lock():对象还存活,就得到一个共享所有权的 shared_ptr;对象已销毁,就得到空指针。
讲一下你知道的类型转换
C++ 的类型转换分为隐式转换和显式转换 。隐式转换由编译器自动完成,比如 int 转 double。显式转换主要有四种:
static_cast:常用于数值转换、继承关系中的转换。向下转型时不做运行时类型检查,需要自己保证对象类型正确。dynamic_cast:用于多态继承体系中的安全向下转型或交叉转换,需要基类有虚函数。失败时,指针转换返回nullptr,引用转换抛出std::bad_cast。const_cast:用于添加或去除const、volatile属性。如果对象本来就是const,去掉属性后修改它属于未定义行为。reinterpret_cast:用于底层类型重解释,比如不同指针类型之间的转换,但不保证转换后可以安全访问对象。动态转换(dynamic_cast)是怎么判断这个类型能不能转换为派生类的
动态转换(dynamic_cast)是怎么判断这个类型能不能转换为派生类的
对于多态类,也就是基类里至少有一个 virtual 函数,对象会关联运行时类型信息。执行 dynamic_cast 时,会根据对象的实际类型以及继承关系,判断目标派生类是不是这个对象合法的派生类路径。
多态具体表现在哪里
C++ 的多态主要体现在:同一个接口,不同对象有不同的行为。
主要分两种:
- 编译时多态:函数重载、运算符重载、模板,编译阶段就确定调用哪个函数。
- 运行时多态 :通过继承 + 虚函数 + 父类指针/引用指向子类对象实现,运行时根据对象的实际类型决定调用哪个函数。
对虚函数的了解
虚函数主要是用来实现 C++ 运行时多态的。
每 一个含虚函数的类 ,编译器会为它生成一张静态的函数指针数组 虚函数表 vtable 和 虚表指针 vptr 实现。同类所有对象共享同一张表;派生类会复制基类的表,并把自己重写的函数地址替换进去,虚表指针指向虚函数表,多态调用就是根据虚表指针找到虚函数表,调用相应的函数指针
纯虚函数有什么好处
纯虚函数主要有几个好处:
- 定义统一接口:基类规定"必须有哪些功能",但不关心具体怎么实现。
- 强制子类实现:派生类必须实现纯虚函数,否则它本身也是抽象类。
- 支持多态:可以通过基类指针或引用调用不同子类的实现,实现运行时多态。
- 降低耦合:调用方依赖抽象接口,不依赖具体实现,方便后续扩展和替换。
- 防止基类被直接实例化:含纯虚函数的类是抽象类,更适合作为接口或基类。对纯虚接口类的了解
对纯虚接口类的了解
虚接口类就是一个主要由纯虚函数组成的抽象类,用来定义统一接口,不提供具体业务实现。
它的特点是:
- 不能直接实例化,只能作为基类使用。
- 派生类必须实现纯虚函数,否则派生类仍然是抽象类。
- 主要用于实现多态和接口隔离,调用方只依赖接口,不依赖具体实现。
- 一般会提供一个虚析构函数,防止通过基类指针删除派生类对象时出现资源释放问题。
使用这种类的过程有没有什么需要注意的地方
析构函数必须是虚函数否则通过基类指针释放派生类对象可能出问题。派生类实现接口时建议加 override,检查是否是多态, 尽量通过指针或引用 使用接口对象,避免对象切片。不要在基类构造/析构函数里调用纯虚函数,因为这时派生类部分可能还没构造好或已经销毁。
内存对齐的理解
内存对齐 就是数据在内存中的起始地址,要满足一定的整数倍规则。主要目的是提高 CPU 访问内存的效率 ,因为 CPU 访问内存时,不一定是"想读几个字节就精确读几个字节",底层通常会按一定粒度去访问内存。如果数据是对齐的,比如一个 4 字节 int 正好放在合适的地址上,CPU 往往一次内存访问就能拿到。
如果没有对齐,这个 int 可能跨两个内存访问边界,CPU 可能需要访问两次,再把结果拼起来,所以效率更低。
我理解内存对齐主要有三点:
- 成员自身要对齐:取数据对齐数和编译器最大对齐数的最小值的倍数的位置。
- 结构体整体也要对齐:结构体大小通常是其最大对齐要求的整数倍。
- 成员顺序会影响结构体大小,合理调整成员顺序可以减少 padding、节省内存。