C++ | QQ后端暑期实习面试

avatar
作者
筋斗云
阅读量:0

tcp三次握手,四次挥手

断点续传

文件断点续传是一种机制,允许在网络传输中的文件传输过程中出现断开连接或传输中断的情况下,能够恢复传输并继续传输未完成的部分。其原理如下:

  • 检测支持:首先,服务器端和客户端需要支持文件断点续传功能。服务器必须能够接收并处理客户端请求的文件的部分内容,并且客户端必须能够发送和接收文件的部分内容。
  • 断点记录:当文件传输开始时,客户端和服务器都会记录已成功传输的字节数或文件的偏移量。这些记录通常保存在服务器端的文件或数据库中。
  • 传输请求:如果传输过程中发生中断,客户端可以发送一个特殊的请求到服务器,请求从断点处继续传输。这个请求通常包含之前传输的字节数或文件的偏移量,以便服务器知道从哪里开始继续传输。
  • 服务器响应:服务器接收到客户端的断点续传请求后,根据请求中的偏移量,定位到文件的相应位置,并从该位置开始继续传输文件。服务器将继续传输的部分内容作为响应发送给客户端。
  • 客户端接收:客户端接收到服务器的响应后,将继续传输的部分内容写入本地文件的相应位置。
  • 继续传输:客户端和服务器通过以上步骤的循环,直到整个文件传输完成。在每次传输中断后,客户端发送断点续传请求,服务器根据请求继续传输文件的未完成部分,直到整个文件成功传输完成。
  • 通过文件断点续传的机制,即使在网络不稳定或传输过程中断的情况下,文件传输可以被恢复和继续,提高了传输的可靠性和效率。这在大文件传输、网络不稳定的环境或需要长时间传输的场景中非常有用。

多态的实现,怎么知道调用的是哪个虚函数,虚函数的工作原理

多态分为静态多态和动态多态。静态多态是指在编译期实现的多态,具体可以分为函数重载和模版。函数重载是通过name mangling实现。动态多态是在运行期实现的多态,主要是通过重写虚函数实现,需要使用虚函数表和虚函数指针。

每个包含虚函数的类都有一个虚函数表(vtable)。虚函数表是一个指向虚函数指针的数组,这些指针指向类的虚函数实现。每个对象包含一个指向虚函数表的指针,称为虚指针(vptr)。当对象被创建时,构造函数会初始化这个虚指针,指向对象所属类的虚函数表。当通过基类指针或引用调用虚函数时,实际上是通过虚指针找到虚函数表,然后在表中找到对应的函数指针并调用它。这个过程确保调用的是实际对象的函数实现,而不是基类的实现。

虚函数表(vtable)存储在静态内存区域中,每个类有一个虚函数表。虚指针(vptr)是每个对象的隐式成员,存储在对象的内存布局中,指向该对象所属类的虚函数表。

Http2.0和1.0的差别

  1. 头部压缩

    HTTP/2 会压缩头(Header)如果你同时发出多个请求,他们的头是⼀样的或是相似的,那么,协议会帮你消除重复的部分。
    这就是所谓的 HPACK 算法:在客户端和服务器同时维护一张头信息表,所有字段都会存如这个表,生成⼀个索引号,以后就不发送同样字段了,只发送索引号,这样就提高速度了。

  2. 二进制格式

    HTTP/2 不再像 HTTP/1.1 里的纯文本形式的报文,而是全面采用了二进制格式,头信息和数据体都是二进制,并且统称为帧(frame):头信息帧和数据帧。增加了数据传输的效率

  3. 数据流

    HTTP/2 的数据包不是按顺序发送的,同⼀个连接里面连续的数据包,可能属于不同的回应。因此,必须要对数据包做标记,指出它属于哪个回应。
    每个请求或回应的所有数据包,称为⼀个数据流( Stream )。每个数据流都标记着⼀个独一无二的编号,其中规定客户端发出的数据流编号为奇数, 服务器发出的数据流编号为偶数。
    客户端还可以指定数据流的优先级。优先级⾼的请求,服务器就先响应该请求。

  4. 多路复用

    HTTP/2 是可以在⼀个连接中并发多个请求或回应,而不用按照顺序⼀⼀对应。
    移除了 HTTP/1.1 中的串行请求,不需要排队等待,也就不会再出现「队头阻塞」问题,降低了延迟,⼤幅度提高了连接的利用率
    举例来说,在⼀个 TCP 连接里,服务器收到了客户端 A 和 B 的两个请求,如果发现 A 处理过程非常耗时,于是就回应 A 请求已经处理好的部分,接着回应 B 请求,完成后,再回应 A 请求剩下的部分。

  5. 服务器推送

    HTTP/2 还在⼀定程度上改善了传统的「请求 - 应答」工作模式,服务不再是被动地响应,也可以主动向客户端发送消息。
    举例来说,在浏览器刚请求 HTML 的时候,就提前把可能会用到的 JS、CSS ⽂件等静态资源主动发给客户端,减少延时的等待,也就是服务器推送(Server Push,也叫 Cache Push)。

InnoDB和MyISAM索引的区别

1. 索引类型

  • InnoDB:
    • 主键索引(Clustered Index):InnoDB 的主键索引也是聚集索引。数据表中的数据按照主键索引的顺序存储。这意味着数据行的物理顺序与主键的顺序一致。
    • 非主键索引(Secondary Index):非主键索引存储的是主键值和索引字段的值,实际上是通过主键来查找具体的行。
    • 全文索引(Full-text Index):从 MySQL 5.6 开始,InnoDB 也支持全文索引。
  • MyISAM:
    • 普通索引(Non-clustered Index):MyISAM 的所有索引都是非聚集的,即索引的顺序与数据的存储顺序无关。数据行的位置和索引条目是分开的。
    • 全文索引(Full-text Index):MyISAM 在 MySQL 5.6 之前支持全文索引,但从 MySQL 5.6 起,InnoDB 也支持了全文索引。

2. 索引存储结构

  • InnoDB:
    • 聚集索引(Clustered Index):InnoDB 使用聚集索引来组织数据,主键决定了数据的存储顺序。聚集索引的叶子节点存储的是实际的表数据。
    • 非聚集索引(Secondary Index):非聚集索引的叶子节点存储的是主键值以及索引字段的值。因此,进行非主键索引的查找时,需要先通过主键索引查找数据行。
  • MyISAM:
    • 非聚集索引(Non-clustered Index):MyISAM 的所有索引都存储了指向数据的指针,索引的叶子节点存储的是数据行的物理位置。数据表中的数据行是按照文件的顺序存储的,而不是按照索引的顺序。

3. 索引维护和性能

  • InnoDB:
    • 事务支持:InnoDB 支持事务(ACID),因此在执行插入、更新或删除操作时,索引维护是原子的。
    • 性能:由于聚集索引的数据和索引存储在一起,对于主键查找非常高效。但由于非主键索引需要二次查找(通过主键查找),在一些场景下可能会导致额外的开销。
    • 锁机制:InnoDB 使用行级锁,可以提高并发性。
  • MyISAM:
    • 没有事务支持:MyISAM 不支持事务,这可能影响数据一致性和并发处理。
    • 性能:MyISAM 的索引维护比 InnoDB 更简单,因为它的索引和数据是分开的。对于简单的读操作,性能可能会更高,但对于复杂的更新操作,可能会稍逊。
    • 锁机制:MyISAM 使用表级锁,这可能影响并发性和性能。

4. 索引的其他特性

  • InnoDB:
    • 自增主键:InnoDB 支持自增主键,使用聚集索引时,自增主键的插入效率很高。
    • 外键支持:InnoDB 支持外键约束,确保数据的完整性和关系的约束。
  • MyISAM:
    • 表级锁:MyISAM 使用表级锁,在高并发的写操作中可能会成为瓶颈。
    • 外键不支持:MyISAM 不支持外键约束,数据的完整性需要通过应用程序逻辑来维护。

C++20新特性

1. Modules(模块)

模块是C++20引入的一种新特性,用于替代传统的头文件机制,旨在改善代码的模块化和编译时间。

主要特性
  • 模块化:将代码分割成独立的模块,可以显著减少编译时间和依赖管理的复杂性。
  • 更快的编译速度:通过模块化,可以减少重复编译,提高编译速度。
  • 避免宏污染:模块可以避免宏带来的命名冲突问题。
  • 明确的接口:模块显式地定义了导出和导入的接口,提高了代码的可读性和安全性。
示例代码
// my_module.cpp export module my_module;  // 定义并导出模块 export void greet() {     std::cout << "Hello from module!\n"; }  // main.cpp import my_module;  // 导入模块  int main() {     greet(); } 

2. Coroutines(协程)

协程是一种用于简化异步编程的新特性,使得可以通过更直观的方式来编写异步代码。

主要特性
  • 异步操作:协程允许函数在需要等待某些操作完成时挂起并返回调用者,然后在条件满足时恢复执行,即实现一个hook完成yield和resume。
  • 状态保持:协程可以在挂起时保持其状态,当恢复执行时可以从中断的地方继续。
  • 高效的资源管理:协程相比于传统的线程更轻量级,具有更低的上下文切换开销。
示例代码
#include <iostream> #include <coroutine>  struct Generator {     struct promise_type;     using handle_type = std::coroutine_handle<promise_type>;      struct promise_type {         int value;         auto get_return_object() { return Generator{handle_type::from_promise(*this)}; }         auto initial_suspend() { return std::suspend_always{}; }         auto final_suspend() noexcept { return std::suspend_always{}; }         void return_void() {}         void unhandled_exception() { std::exit(1); }         auto yield_value(int val) {             value = val;             return std::suspend_always{};         }     };      handle_type h;     Generator(handle_type h) : h(h) {}     ~Generator() { h.destroy(); }     bool next() { return h.resume(), !h.done(); }     int value() const { return h.promise().value; } };  Generator sequence() {     for (int i = 0; i < 3; ++i)         co_yield i; }  int main() {     auto gen = sequence();     while (gen.next()) {         std::cout << gen.value() << "\n";     } } 

3. Ranges(范围库)

范围库提供了一组更强大和灵活的方式来操作集合,使得代码更简洁和易读。

主要特性
  • 视图和适配器:范围库通过视图和适配器提供了对集合的惰性求值操作,使得可以在不复制数据的情况下进行链式操作。
  • 更好的与算法结合:范围库与标准库算法结合得更加紧密,使得代码更具表达力和简洁性。
  • Pipeline风格:支持通过管道操作符进行链式调用,极大地简化了代码。
示例代码
#include <iostream> #include <vector> #include <ranges> #include <algorithm>  int main() { 	std::vector<int> v = { 5, 2, 1, 3, 0, 4, 6 };  	// 过滤和变换范围 	auto result = v | std::views::filter([](int n) { return n % 2 == 0; }) 		| std::views::transform([](int n) { return n * n; });  	for (int n : result) { 		std::cout << n << " "; 	}     std::cout << "\n";      	std::ranges::sort(v); 	for (int x : v) { 		std::cout << x << " "; 	} } 

4. Concepts(概念)

概念用于为模板定义约束条件,提高模板的可读性和错误信息的清晰度。

主要特性
  • 约束模板参数:通过概念可以为模板参数定义约束条件,从而限制模板实例化时的类型。
  • 提高可读性:概念使得模板代码更具可读性,因为可以明确指出模板参数的预期行为和性质。
  • 更好的错误信息:在不满足概念约束时,编译器可以提供更清晰的错误信息,便于调试。
示例代码
c#include <concepts> #include <iostream>  // 定义一个概念,约束类型必须支持加法操作 template<typename T> concept Addable = requires(T a, T b) {     { a + b } -> std::convertible_to<T>; };  template<Addable T> T add(T a, T b) {     return a + b; }  int main() {     std::cout << add(1, 2) << std::endl;  // 合法     // std::cout << add("Hello, ", "world!");  // 非法,不满足Addable概念 } 

进程,线程,协程

关系
  • 进程包含线程:一个进程可以包含一个或多个线程,线程是进程的一部分,共享进程的资源。
  • 线程和协程都是实现并发的手段:线程由操作系统调度,而协程由程序自身调度。
区别
  • 内存和资源
    • 进程:拥有独立的内存空间和资源。
    • 线程:共享进程的内存和资源。
    • 协程:在用户态下运行,通常在单个线程内实现。
  • 调度和上下文切换
    • 进程:由操作系统调度,上下文切换开销大。
    • 线程:由操作系统调度,上下文切换开销相对小。
    • 协程:由程序自身调度,上下文切换开销最小。
  • 并行和并发
    • 进程:可以在多核CPU上实现真正的并行执行。
    • 线程:可以在多核CPU上实现并行执行,但需要处理同步和竞争问题。
    • 协程:在单线程内实现并发处理,适用于I/O密集型任务,不适用于CPU密集型任务的并行处理。
进程线程协程
线程1协程1函数调用1
协程2函数调用2
线程2协程3函数调用3
协程4函数调用4
线程3协程5函数调用5
协程6函数调用6
资源资源资源

智能指针

1. std::unique_ptr

概述
  • 独占所有权std::unique_ptr表示唯一所有权的智能指针,即一个对象只能有一个std::unique_ptr实例管理。
  • 不可复制std::unique_ptr不可复制,但可以移动(使用std::move)。
主要特性
  • 自动释放:当std::unique_ptr超出作用域时,它所管理的对象会自动被删除。
  • 轻量级:由于没有引用计数,std::unique_ptr的开销较小。

2. std::shared_ptr

概述
  • 共享所有权std::shared_ptr表示共享所有权的智能指针,即多个std::shared_ptr实例可以管理同一个对象。
  • 引用计数std::shared_ptr使用引用计数来跟踪对象的使用情况,当引用计数为零时,自动删除对象。
主要特性
  • 自动释放:当最后一个std::shared_ptr超出作用域时,它所管理的对象会被删除。
  • 线程安全:引用计数的增减是线程安全的。

3. std::weak_ptr

概述
  • 弱引用std::weak_ptr是与std::shared_ptr配合使用的智能指针,用于解决循环引用问题。
  • 不影响引用计数std::weak_ptr不会影响对象的引用计数,因此不会阻止对象的删除。
主要特性
  • 观察者模式std::weak_ptr可以观察std::shared_ptr管理的对象,但不能直接访问对象,必须通过lock方法获取std::shared_ptr

编程题,给定一个字符串 s ,请将 s 分割成一些子串,使每个子串都是 回文串 ,返回 s 所有可能的分割方案。

LeetCode 131原题,同时最好也把132,1278,1745几个加强版也做了。

class Solution { public:     std::vector<std::vector<std::string>> partition(std::string s) {         std::ios::sync_with_stdio(false);         std::cin.tie(nullptr);          std::vector<std::vector<std::string>> ans;         std::vector<std::string> path;         int n = s.length();          std::function<void(int)> dfs = [&] (int idx) {             if (idx == n) {                 ans.push_back(path);                 return;             }              std::string tmp;             for (int i = idx; i < n; ++i) {                 tmp += s[i];                 bool f = true;                 // 可以提前使用dp预处理,或者记忆化结果                 for (int i = 0; i * 2 + 1 < tmp.length(); ++i) {                     if (tmp[i] != tmp[tmp.size()-1-i]) {                         f = false;                         break;                     }                 }                  if (f) {                     path.push_back(tmp);                     dfs(i + 1);                     path.pop_back();                 }             }         };         dfs(0);          return ans;     } }; 

最后给大家推荐一个LinuxC/C++高级架构系统教程的学习资源与课程,可以帮助你有方向、更细致地学习C/C++后端开发,具体内容请见 https://xxetb.xetslk.com/s/1o04uB

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!