C++面试题目汇总【持续更新】

面试题目汇总

  • C++基础
    • 1. 面向对象是什么?[GPT]
    • 2. 面向对象的三大特征是什么?[GPT]
    • 3. 重载,重写,隐藏的区别?[GPT]
    • 4. 构造函数的类别有哪些?[GPT]
    • 5. 定义一个空类时,默认生成哪些函数?[GPT]
    • 6. 向下转型和向上转型是什么?[GPT]
    • 7. 深拷贝和浅拷贝是什么?[GPT]
    • 8. 类继承时,不同关键字的访问权限?[GPT]
    • 9. 类内可以定义引用数据成员吗?[GPT]
    • 10. 构造函数为什么不能声明为虚函数?[GPT]
    • 11. 结构体内存对齐规则是什么?
    • 12. 指针和引用的区别?[GPT]
    • 13. 在传递函数参数时,什么时候该使用指针,什么时候该使用引用?[GPT]
    • 14. 堆和栈的区别?[GPT]
    • 15. malloc 和 new 的区别?[GPT]
    • 16. 宏定义和函数的区别?[GPT]
    • 17. 什么是内存泄漏?如何检测和避免?[GPT]
    • 18. 智能指针的原理?常见的智能指针及实现?[GPT]
    • 19. static 的用法和作用?[GPT]
    • 20. unordered_map 和 map,unordered_set 和 set 的区别和应用场景?[GPT]
    • 21. 友元函数和友元类是什么?[GPT]
    • 22. 模版和泛型的区别?[GPT]
    • 23. ++i、i++ 哪个好?
    • 24. 基类的虚函数表存放在内存的什么区?虚表指针的初始化时间?[GPT]
    • 25. 野指针和悬空指针的区别?[GPT]
    • 26. 指针数组和数组指针的区别?[GPT]
    • 27. 常量指针和指针常量区别?[GPT]
    • 28. 全局变量和局部变量的区别?[GPT]
    • 29. push_back 和 emplace_back 的区别?[GPT]
    • 30. 为什么不能把所有的函数写成内联函数?[GPT]
    • 31. 智能指针有什么缺点?[GPT]
    • 32. 纯虚函数和虚函数的区别?[GPT]
    • 33. ET 模式和 LT 模式的区别?[GPT]
    • 34. lambda 函数是什么?[GPT]
    • 35. C++ 11有哪些新特性?[GPT]
    • 36. new 的实现,new 什么时候返回空指针,异常?[GPT]
    • 37. 程序的内存分区是什么样的?[GPT]
    • 38. resize 和 reserve 的区别?[GPT]
    • 39. 四种强制转换?[GPT]
    • 40. 如何计算类的大小?[NO]
    • 41. 析构函数为什么写成虚函数?[GPT]
    • 42. extern"C" 的用法?[GPT]
    • 43. NULL 和 nullptr 区别?[GPT]
    • 44. malloc 申请的存储空间能用 delete 释放吗?[GPT]
    • 45. 结构体和类的区别?
  • 计算机网络
    • 1. 什么是 socket?
    • 2. socket 属于网络的哪一层?
    • 3. socket 通信的过程?
    • 4. 进程,线程的概念?[GPT]
    • 5. 进程和线程的使用场景?
    • 6. 常见的 IO 模型
        • 1. md5 加密是什么?为什么要加密?怎么实现的?
        • 2. jwt 鉴权是什么?为什么要鉴权?怎么实现的?
        • 3. ffmpeg 是什么?原理是什么?
        • 4. ftp服务器是什么?采用的什么协议?
        • 5. ORM框架是什么?三层架构是什么?
        • 6. 如何实现登录?注册?
        • 7. 如何实现发布视频?获取视频?
        • 1. 请介绍下这个项目?
        • 22. epoll如何判断数据已经读取完成?
        • 23. epoll为什么要使用红黑树?
        • 24. 为什么要使用状态机?
        • 25. 主状态机的三种状态?
        • 26. 从状态机的三种状态?
        • 27. 状态机的转移图?
        • 4. TCP三次握手?
        • 5. TCP四次挥手?
    • HTTP 报文格式
    • 锁有哪些

C++基础

1. 面向对象是什么?[GPT]

  • 面向对象是一种计算机编程和设计方法,它将问题和系统概括为相互关联的对象,每个对象都有自己的属性和方法,以及与其他对象的交互。这种编程思想可以提高代码的可读性,维护性和重用性。

2. 面向对象的三大特征是什么?[GPT]

  • 面向对象的三大特征是:继承,封装,多态。
  • 继承:允许派生类基于基类创建,并继承基类的属性和方法。提高了重用性和扩展性。
  • 封装:将属性和操作属性的方法封装在单元内部,对外隐藏单元内部实现细节。提高了安全性和维护性。
  • 多态:允许不同对象使用相同的方法做出不同的响应,根据对象的实际类型来调用方法。提高了灵活性。

3. 重载,重写,隐藏的区别?[GPT]

  • 重载:是指在同一个类中定义多个同名但是参数列表不同的函数,根据传参决定使用哪个函数。
  • 重写:是指在派生类中重新定义基类中的虚函数,使得派生类可以根据实际所传对象对用相应的函数。
  • 隐藏:是指在派生类中重新定义基类中的函数,但不是虚函数,使得基类中的方法被隐藏。

4. 构造函数的类别有哪些?[GPT]

  • 默认构造函数:用于创建对象时不需要传入参数的情况。
  • 初始化构造函数:用于创建对象时需要传入参数的情况。
  • 拷贝构造函数:它接受一个同类对象的引用作为参数,并将该对象的所有属性值拷贝给当前对象。
  • 移动构造函数:它将待销毁对象的资源指针转移到新的对象,而不是复制资源的内容,提高了构造的效率。

5. 定义一个空类时,默认生成哪些函数?[GPT]

  • 默认构造函数:用于创建类的对象。
  • 拷贝构造函数:通过已有的对象来初始化新对象。
  • 拷贝赋值运算符:将一个对象的内容赋值给另一个对象。
  • 析构函数:在对象生命周期结束时,进行清理工作,如动态分配的内存。
  • 移动构造函数:用于从即将销毁对象上移动资源,而不是拷贝资源,从而提升性能。
  • 移动赋值运算符:将一个对象的资源移动到另一个已存在的对象。

6. 向下转型和向上转型是什么?[GPT]

  • 向下转型和向上转型是继承和多态相关的概念,我们可以在基类和派生类之间相互转换。
  • 向上转型:将派生类的指针或引用转换为基类的指针或引用,这是自动,安全,不需要提供类型的转换。
  • 向上转型:将基类的指针或引用转换为派生类的指针或引用,这是非自动,不安全的,需要强制类型转换。

7. 深拷贝和浅拷贝是什么?[GPT]

  • 深拷贝和浅拷贝主要与对象如何管理资源有关。
  • 浅拷贝:对于指针或引用类型的对象,只会复制指针或引用的值,而不会复制指针或引用所指的数据。这可能会导致多个对象共享相同的资源。
  • 深拷贝:不仅复制会指针或引用的值,还会复制指针或引用所指向的数据。避免了对象间的共享资源。

8. 类继承时,不同关键字的访问权限?[GPT]

  • 类的继承可以使用3各关键字:public,protected,private。
  • public 继承:public 和 protected 继承为 public 和 protected,但 private 在派生类中不可访问。
  • protected 继承:public 和 protected 继承为 protected,但 private 在派生类中不可访问。
  • private 继承:public,protected 被继承为 private,但 private 在派生类中不可访问。

9. 类内可以定义引用数据成员吗?[GPT]

  • 类内可以定义引用数据成员。
  • 由于引用必须在声明时初始化,并在之后不能更改指向,因此引用数据成员必须在类的构造函数的初始化列表中初始化。

10. 构造函数为什么不能声明为虚函数?[GPT]

  • 虚函数的调用依赖于对象的类型,而在对象构造过程中,对象的类型尚未完全确定,无法在构造函数中使用虚函数。
  • 虚函数的目的是为了派生类覆盖掉基类的方法,但是构造函数无法在派生类中被覆盖。

11. 结构体内存对齐规则是什么?

  • 结构的内存对齐是为了提高内存的访问效率,减少性能损失。
  • 对齐数:成员变量自身大小和编辑器默认对齐数中的最小值。
  • 第一个成员变量对齐在偏移量为 0 的地址处。
  • 其他成员变量对其在整数倍的自身对齐数的地址处。
  • 结构体的总大小为最大对齐数的整数倍。
  • 可以通过 alignof 和 alignas 计算和指定结构体的对齐方式。pragma back 可以指定对齐数。

12. 指针和引用的区别?[GPT]

  • 指针:是一个变量,存储所指对象的内存地址,可以修改指向,可以为空指针,需要显式的分配和释放内存。
  • 引用:是一个别名,对所指对象绑定,不是独立的变量,引用必须初始化,且不可改变指向,不需要显式的分配和释放内存。

13. 在传递函数参数时,什么时候该使用指针,什么时候该使用引用?[GPT]

  • 需要可选参数时使用指针:因为指针可以为 nullptr,表示没有对象。
  • 需要在函数内部改变参数所指对象时使用指针:指针可以被重新赋值指向不同的对象。
  • 需要动态分配内存时使用指针:比如创建新对象时的动态内存分配。
  • 当不允许空值时使用引用:使用引用可以确保参数一定指向有效的对象。
  • 当减少内存开销时使用引用:传递对象的引用比传递对象的拷贝更加的高效。
  • 需要修改原始对象的值时使用引用:因为引用直接绑定在原始对象上。

14. 堆和栈的区别?[GPT]

  • 内存分配:栈是一种后进先出管理内存的线性数据结构,内存是自动分配和释放的,由编辑器负责。堆是一种按任意顺序管理内存的非线性数据结构,内存是手动分配和释放的,由程序员负责。
  • 生命周期:栈上的数据具有局部生命周期,在函数调用后自动销毁。堆上的数据具有动态声明周期,可以在程序的任意位置创建和销毁。
  • 速度:栈的数据访问比较快,因为其内存连续。堆的访问速度比较慢,因为他们分配在不同的位置,且需要指针两次访问。
  • 大小:栈的大小受操作系统的限制,较小。堆的大小受物理内存的限制,较大。

15. malloc 和 new 的区别?[GPT]

  • 原始数据类型:malloc 可以分配任意大小的内存块,这些内容块存放基本数据类型比如 int。new 主要用于为对象分配内存。
  • 分配的内存类型:malloc 分配的是原始的,未初始化的内存块,需要手动调用构造函数初始化对象。new 分配的内存是通过类的构造函数来进行初始化的,自动调用构造函数。
  • 返回类型:malloc 返回的是 void 类型,需要显式类型转换成特定类型的指针。new 返回特定类型的指针,不需要显式转换。
  • 错误处理:malloc 在分配失败时返回 nullptr。new 在分配失败时会产生 bad_alloc 分配异常。
  • 释放内存:malloc 分配内存需要通过 free 函数来释放。new 分配的内存需要通过 delete 操作符来释放。

16. 宏定义和函数的区别?[GPT]

  • 处理时间:宏定义是在编译时进行处理的,是一种文本替换机制。函数是在运行时被调用的。
  • 类型安全:宏定义不进行类型检查,不能捕获潜在的类型错误。函数具有严格的类型检查,安全性更高。
  • 错误处理:宏定义不提供错误处理机制,编译时才能发现错误。函数提供了更好的错误处理。

17. 什么是内存泄漏?如何检测和避免?[GPT]

  • 内存泄漏:发生在程序分配了内存但未释放的情况,导致程序在运行时持续消耗内存,耗尽系统的可用资源。
  • 使用智能指针:shared_ptr 和 unique_ptr 可以帮助自动管理内存,他们在对象不需要时会自动释放内存。
  • 使用标准库容器:它们可以自动处理内存的分配与释放,减少手动管理内存的需求。
  • 使用 RAII 资源获取即初始化:就是在构造函数中分配内存,在析构函数中释放内存。
  • 记录内存的分配与释放:在代码中用日志来跟踪内存分配和释放操作,确保分配释放相互对应。
  • 内存检测工具:比如 visual stido 上的内存检测器来检测程序中的内存泄漏。

18. 智能指针的原理?常见的智能指针及实现?[GPT]

  • 智能指针:是一种内存管理工具,用于自动管理动态分配的内存,避免内存泄漏和悬空指针的问题。
  • 基本原理:是将一个计数器与分配的内存相关联,跟踪引用该内存的次数。创建智能指针时,计数器增加,销毁智能指针时,计数器减少。当计数器为 0 时,自动释放该内存。
  • shared_ptr:实例维护一个引用计数器,当计数器为 0 时释放内存,允许多个智能指针共享同一块内存,适用于资源共享的情况。
  • unique_ptr:实例拥有唯一的指针,当声明周期结束后自动释放,适用于需要独占资源的情况。
  • weak_ptr:它在内部引入了一个额外的计数器,用于解决 shared_ptr 中循环引用的问题。

19. static 的用法和作用?[GPT]

  • 静态变量:在程序的整个生命周期上都存在,不会因为函数的调用而被影响。
  • 静态局部变量:在函数内部声明的局部变量,其生命周期不受函数的调用和销毁影响。
  • 静态成员变量:在类中定义的静态变量,这些变量属于类,所有的示例共享该静态成员变量。
  • 静态成员函数:这些函数不依赖于类的示例,可以直接通过类名访问。

20. unordered_map 和 map,unordered_set 和 set 的区别和应用场景?[GPT]

  • map:是基于红黑树实现的有序关联容器,按键值对存储并保持元素的有序性。
  • unordered_map:是基于哈希表实现的无序关联容器,按键值对存储但无序。
  • set:是基于红黑树实现的有序集合容器,它存储唯一的元素且有序。
  • unordered_set:是基于哈希表实现的无序集合容器,它存储唯一的元素但无序。
  • map,set 应用:需要按顺序来存储和访问数据,或执行范围查询时。
  • unordered_map,unordered_set 应用:需要快速查找,插入和删除数据,或数据集很大寻需要高校查找时。

21. 友元函数和友元类是什么?[GPT]

  • 友元函数和友元类是用于访问一个类的私有成员的机制。
  • 友元函数:是一个在类外部的函数,但它被声明为某个类的友元。该友元函数可以像成员函数一样访问类内的私有成员。
  • 友元类:是一个类,他被声明为另一个类的友元。该友元类可以访问另一个类中的私有成员。

22. 模版和泛型的区别?[GPT]

  • 模板:是C++的一项特性,用于实现通用编程,以便在不同的类型上执行相同的操作,而无需重复写代码。模板可以作用于函数和类。
  • 泛型:是一种通用的编程理念,许多编程语言都支持泛型。泛型不仅可作用于函数和类,也可以用于数据结构,接口等。

23. ++i、i++ 哪个好?

  • ++i 在加一后直接返回变量进行后续运算,而 i++ 需要临时变量来存储 i,因此 ++i 更加高效。

24. 基类的虚函数表存放在内存的什么区?虚表指针的初始化时间?[GPT]

  • 虚函数表:是一个表格,包含了类的虚函数的指针,允许进行多态函数的调用。
  • 虚函数表通常存放在内存布局中的只读数据区域。
  • 虚表指针是一个指向虚函数表的指针,它的初始化是在对象构造期间执行的。

25. 野指针和悬空指针的区别?[GPT]

  • 野指针:是指向已经释放或无效的内存地址的指针。
  • 悬空指针:是指向空值或未初始化的指针。
  • 野指针的问题是访问无效的内存地址,容易导致程序崩溃或数据损坏。而悬空指针是安全的,因为他们没有指向有效的内存地址。

26. 指针数组和数组指针的区别?[GPT]

  • 指针数组是一个数组,其中每一个元素都是指针。比如 int* ptr[5];
  • 数组指针是一个指针,指向一个数组。比如 int (*ptr)[5];

27. 常量指针和指针常量区别?[GPT]

  • 常量指针是一个指针,它指向的对象是不可修改的,但指针本身可以修改。比如 const int* ptr;int const* ptr;,*ptr 是一个指针,指向 const int,因此指向的值不可改。
  • 指针常量是一个常量,该指针的指向是不可变的,但是所指对象的值可变。比如 int * const ptr;,*const ptr 是一个常量,指向 int,因此指向不可变。

28. 全局变量和局部变量的区别?[GPT]

  • 作用域:全局变量整个作用域都可见,而局部变量仅在代码块中可见。
  • 声明周期:全局变量在程序启动时分配资源,程序结束时释放资源。而局部变量在执行代码块时分配资源,离开代码块时释放资源。
  • 存储位置:全局变量存储在静态数据区,内存分配在程序启动时完成。而局部变量存储在栈上,内存分配在运行时完成。

29. push_back 和 emplace_back 的区别?[GPT]

  • push_back:适用于已经存在的对象,需要通过拷贝构造或移动构造将对象添加到容器中。
  • emplace_back:用于在容器中直接构造新对象,省略了拷贝或移动的过程。

30. 为什么不能把所有的函数写成内联函数?[GPT]

  • 内联函数在某些情况下可以提升程序运行效率,但并不适用于所有的情况。
  • 对于大型函数或频繁调用的函数,内联函数会导致可执行文件过大,编码时间过长。
  • 内联函数将函数的实现暴漏在头文件中,而不是源文件,如何函数实现需要修改,那么所有调用该函数的地方都要重新编译,可维护性降低。
  • 部分函数不适合内联,比如递归,循环等。

31. 智能指针有什么缺点?[GPT]

  • 智能指针需要引入额外的开销来存储管理指针对象。
  • 允许多个指针指向同一块内存,可能会导致循环引用的问题,导致内存泄漏。

32. 纯虚函数和虚函数的区别?[GPT]

  • 虚函数是在基类中使用 virtual 关键字的成员函数,在基类中提供实现。而纯虚函数在基类中使用 virtual 关键字,但并没有提供实现。
  • 虚函数有默认实现,因此可以在基类中创建对象实例。纯虚函数在基类中没有实现,因此不能实例化,只能作用于派生类。
  • 虚函数的目的是为了实现多态。纯虚函数的目的是定义一个接口或抽象类,强制派生类进行实现。

33. ET 模式和 LT 模式的区别?[GPT]

  • ET 模式是边缘出发模式,LT 模式是水平出发模式。
  • ET 中,只有事件状态发生变化,系统才会触发事件处理。LT 中,一但事件就绪,系统会一直触发事件处理。
  • ET 采用非阻塞的事件处理模式,事件就绪时会触发事件处理,没有及时处理则等待下一次状态变化。LT 采用阻塞和轮询的事件处理模式,当事件就绪时,会一直处理直到执行完毕。
  • ET 需要主动检查事件变化,比如使用 epoll 和 select。LT 就不需要主动检查,系统会自动触发事件处理。

34. lambda 函数是什么?[GPT]

  • lambda 是一个匿名函数,可以在需要函数的地方使用,无需重新定义一个函数。
  • lambda 函数可以像普通函数一样被调用,也可以作为函数对象传递给标准库函数。
  • lambda 函数可以捕获外部变量,包括值,引用等。

35. C++ 11有哪些新特性?[GPT]

  • 自动类型推断 auto,减少了类型声明的冗余。
  • 范围 for 循环:用于迭代容器,代码更加简洁可读。
  • lambda 表达式:允许在代码中使用匿名函数,使函数对象的创建和使用更加方便。
  • 智能指针:改善内存和资源管理,避免了内存泄漏。
  • 移动语义 move:引入了移动构造函数和移动赋值操作符,允许对象之间的资源移动,提升了效率。
  • 线程支持:比如线程 thread 和锁 mutex 等。

36. new 的实现,new 什么时候返回空指针,异常?[GPT]

  • 内存分配:new 运算符首先从堆中分配足够大小的内存来存储对象或数组,可以调用 malloc 完成。
  • 构造函数:如果分配成功,new 会调用相应的构造函数来初始化对象或数组的内容。
  • 返回指针:如果分配和构造都成功,new 会返回指向新内存的指针。
  • 当内存不足时,会返回空指针。
  • 当构造函数抛出异常时,通常会抛出 bad_alloc 异常。

37. 程序的内存分区是什么样的?[GPT]

  • 栈:用于存储函数调用的局部变量,函数参数,函数返回地址等信息。栈的分配高效,但是大小有限。
  • 堆:用于动态分配内存呢的区域,存储程序运行时分配的对象和数据。堆需要手动管理,堆的大小较大。
  • 全局/静态存储区:用于存储全局变量和静态变量,程序开始时分配,结束后才释放。
  • 常量区:用于存储字符串常量,全局常量等。
  • 代码区:存储程序的机器码指令,通常是只读的。
  • 堆栈区:堆和栈共享的一块内存区域,根据堆和栈的增长方向进行划分。

38. resize 和 reserve 的区别?[GPT]

  • resize:用于修改 vector 的元素数量,但不影响容量。如果元素量大于当前大小,则会在尾部插入新的元素并调用构造函数,如果元素量小于当前大小,则会删除多余元素。
  • reserve:用于预留 vector 的容量,但不改变元素的数量。如果元素容量大于当前大小,则会扩增容量,如果元素量小于当前大小,则维持当前容量。

39. 四种强制转换?[GPT]

  • static_cast:最常见的转换方式,对基本的类型进行转换。比如 int 转变为 float。
  • dynamic_cast:用于在继承层次结构中进行类型转换,常用于多态。只能用于具有虚函数的类。
  • const_cast:用于添加或去除 const 修饰符。
  • reinterprect_cast:基于底层的位级别的转换,通常将指针或引用转换为一个不相关的类型,且没有类型检查,因此十分的危险。

40. 如何计算类的大小?[NO]

  • 类的大小可以通过 sizef 来计算。
  • 如果是空类,大小为 1,编辑器希望每个对象都至少具有一个唯一的地址。
  • 如果有一个虚函数,那么大小为 8,相当于指针大小。
  • 如果有一个静态函数/变量,那么大小仍然为 1,静态函数/变量在程序中共享,不占用实例空间。

41. 析构函数为什么写成虚函数?[GPT]

  • 这与程序的多态性有关。多态性使得程序在运行时,可以根据具体传入对象来选择调用正确的函数。如果析构函数不是虚函数,那么只会调用基类中的析构函数,而不会调用派生类中的析构函数,造成内存泄漏。

42. extern"C" 的用法?[GPT]

  • 在 C++ 中调用 C 的代码。
  • 链接 C 中的库。
  • 解决函数名重载问题:extern ”C“ 中的函数声明不进行 C++ 风格的名称修饰,保证 C 的链接兼容性。

43. NULL 和 nullptr 区别?[GPT]

  • NULL:是一个宏定义,通常定义为整数 0。
  • NULL:通常表示为空指针,可能在函数重载时出现二义性 (传入指针还是 int)。
  • nullptr:是 C++11 引入的关键字,专门用来表示空指针。
  • nullptr:更加安全,不会产生空指针歧义。

44. malloc 申请的存储空间能用 delete 释放吗?[GPT]

  • 不能,因为 malloc 和 new 使用了不同的分配方式,因此释放方式也不同,应该采用 free 来释放内存。其中 malloc 和 free 是直接分配和释放内存,而 new 和 delete 是通过构造和析构函数来管理内存。

45. 结构体和类的区别?

  • 结构体中的权限默认是 public,而类中的权限默认是 private。
  • 结构体是值类型的,它在赋值或者传参时会进行拷贝。而类是引用类型的,在赋值或传参时只会传递引用。

计算机网络

1. 什么是 socket?

  • 客户端和服务端之间通过一个双向通信连接来实现数据的交换,而这个双向通信连接的端点称为 socket。

2. socket 属于网络的哪一层?

  • socket 属于传输层和网络层之间的一个抽象层。它是一组接口。

3. socket 通信的过程?

  • 首先服务器使用 socket 函数初始化 socket 并监听,接着调用 bind 函数绑定端口,listen 函数设置监听上限,accept 函数阻塞等待客户端的连接。
  • 接着客户端使用 socket 函数初始化 socket,使用 connect 函数请求与服务器进行连接,服务器收到请求后最终建立连接。
  • 客户端发送请求,服务端接收请求并处理,处理完毕后返回给客户端,客户端收到数据,关闭连接。

4. 进程,线程的概念?[GPT]

  • 进程:是计算机中正在运行的程序的实例,每个进程都有自己的内存空间,代码和数据,相互独立互不干扰。进程的创建和销毁开销较大。
  • 线程:是进程内的执行单元,一个进程可以包含多个线程,共享同一进程的资源。线程的创建和销毁开销较小。

5. 进程和线程的使用场景?

  • 当对资源管理维护需求高,不限制开销和效率时,使用多进程。
  • 当要求效率,涉及频繁切换,且堆资源管理和维护需求低时,使用多线程。

6. 常见的 IO 模型

  • 对于一个 IO 模型,它主要会涉及到用户空间和内核空间。
  • 在第一阶段时的准备数据阶段,将网络数据拷贝到内核缓冲区。在第二阶段时的拷贝数据阶段,将数据从内核缓冲区拷贝到用户缓冲区。根据两阶段的不同对 IO 模型进行划分。
  • 阻塞 IO:当用户进程调用函数时,等待这个函数的返回,期间什么也不做,一直处于阻塞状态,直到获取到返回值结束阻塞。在准备数据和拷贝数据这两个阶段都是阻塞的。它的缺点就是同一时刻只能处理一个操作,效率比较低。
  • 非阻塞 IO:当用户进程调用函数时,如果当前没有准备好数据就立马返回-1,并不断的调用函数询问,如果数据准备好了,引用进程就进入阻塞,直到拷贝数据完成后结束阻塞。在准备数据时时非阻塞的,但是在拷贝数据时是阻塞的。他的缺点是需要忙轮询,系统的开销大。
  • IO 多路复用:通常使用 select、poll、epoll 函数实现 IO 多路复用。当用户进程调用函数时,整个进程会被阻塞,函数会监感兴趣的文件描述符。当其中任何一个文件描述符准备就绪,内核会向用户进程返会该文件描述符,并执行后续的IO操作。在准备数据和拷贝数据时都是阻塞的。他的优点就是可以使用单线程来同时监听多个socket。
  • 信号驱动 IO:linux通过socket来实现信号驱动IO,通过sigaction系统调用,会创建一个信号驱动IO的socket,并绑定一个信号处理函数,当数据准备好时,内核是通过signal信号来告知用户进程的。这样的好处就是不需要忙轮询。在准备数据是非阻塞的,拷贝数据是阻塞的。
  • 异步 IO:linux 提供了异步 IO 的接口函数,当用户调用异步接口 aio_read 函数时,aio_read 函数会向内核传递文件描述符,缓冲区指针,缓冲区大小 等数据。同时,用于进程可以干别的事情了,IO操作这里不需要自己参与。内核根据 aio_read 传递的数据进行 IO 操作,当操作完成时,内核会向用户进程返回信号告知处理完成。在数据准备和数据拷贝时都不阻塞。
1. md5 加密是什么?为什么要加密?怎么实现的?
  • md5叫做信息摘要算法,它是一种被广泛使用的密码散列函数,可以产生一个 128 位的哈希值,是一种不可逆的加密。
  • 如果直接将用户的账号密码存入数据库,那么用户的账户信息就不是很安全。在登陆时将用户的密码加密并与数据库中加密后的密码比对,这样的方式更加安全。
  • 在用户注册的时候,不存放用户输入的密码,而是存放使用 md5 加密后的密码,之后登录也是加密后在核实密码。
2. jwt 鉴权是什么?为什么要鉴权?怎么实现的?
  • jwt鉴权是目前主流的跨域认证解决方案,它用来确保客户端和服务端之间可靠的进行消息传递。
  • 如果要登陆一个 APP,那么用户需要输入账号和密码,jwt 就会根据用户的账号和密码生成一个 token。后续如果用户想要就行别的内部操作,那么必须带上这个 token 识别用户的身份,使得操作就更加的安全。
  • 在用户登录的时候,会根据用户的账号和密码生成一个 token,通过在路由中加入 jwt 函数,那么在进行请求的时候会先判断当前的token是否是匹配的,匹配才能进行后续的操作。
3. ffmpeg 是什么?原理是什么?
  • ffmpeg 是一款音视频编解码工具,我主要使用它来获取视频的关键帧来当作视频的封面。
  • 现在主流的视频压缩算法是 H264,它主要由I帧,P帧,B帧,其中关键帧有着更完整的图像,而其余帧根据I帧来生成图像。比如托视频进度条,某一段区域内能显示图片的就是关键帧。
4. ftp服务器是什么?采用的什么协议?
  • ftp 服务器是一种用来提供文件存储的和访问的计算机。创建ftp服务器后,我们输入相应的端口号就可以访问对应的文件了,我们将用户发布的视频和封面都存放在ftp服务器,但是视频数据信息是存放在本地数据库的。
  • 它采用的是FTP文件传输协议,是一种基于TCP的协议,用户可以在FTP服务器上进行文件上传和下载等操作。
5. ORM框架是什么?三层架构是什么?
  • ORM是对象关系映射,它主要是为了解决程序对象与关系数据库的存在不匹配的问题。比如说ORM会将MySQL中的一张表映射为一个类,类的成员变量就是这个表的字段,通过对类进行操作可以映射为对数据库进行操作。
  • 使用三层架构可以是代码中的分工更加明确,使代码的耦合性低,后续可维护性可扩展性也更好些。在这个项目中主要是控制层,服务层和数据层,控制层明确需要完成的任务,并与前端交互,服务层调用数据层实现主要的业务逻辑,数据层直接对数据库进行操作。
6. 如何实现登录?注册?
  • 当用户输入账号密码时,会先对密码进行md5加密,将账号与加密后的密码与数据库中的数据核对,如果有则成功登录,登陆后会根据当前的用户密码生成一个token,用户身份核验的凭证,后续如果需要进行别的操作都需要这个token。
  • 如果数据库中没有相应的账户信息,那么就需要注册,将注册后的信息添加到数据库中。
7. 如何实现发布视频?获取视频?
  • 用户在发布视频的时候,选择的是本地的视频文件,然后为为视频起标题,这种数据会以表单的形式被服务器接收。接收后获取本地视频地址,通过连接ftp服务器将本地视频上传到服务器中,然后在服务器中调用ffmpeg来获取关键帧图片作为封面,并一起保存在服务器中。同时将服务器中的视频,封面路径存储在本地的Mysql数据库中。
  • 后续如果想获取视频和封面,那么直接访问数据库中的路径信息,通过服务器文件路径来获取视频数据。
1. 请介绍下这个项目?
  • 这个项目是一个轻量级的高并发HTTP服务器,它主要分为两个部分,第一部分为socket服务器部分,它主要负责多个客户端的连接,客户端IO请求的处理等。第二个部分是HTTP解析器,他主要是对浏览器的链接请求进行解析,处理完请求后像客户端浏览器返回响应,比如说文字,图片,视频等。
22. epoll如何判断数据已经读取完成?
  • 只有在ET模式下,我们才关注数据有没读取完成。LT模式下不同关注,当检测到有数据时自动会读取。
  • 可以将socket设为非阻塞状态,当socket可读时,使用recv/read函数读取数据,如果返回值是-1并且errno是EAGAIN或EWOULDBLOACK,表示接收完毕。
23. epoll为什么要使用红黑树?
  • epoll在内核中维护了一个事件表,里面存放着所有的文件描述符,并且之后会对这个事件表进行改动。那么就需要一种插入,删除和查找效率都不错的数据结构,红黑树就比较符合。
24. 为什么要使用状态机?
  • 因为传统的控制流程是顺序执行的,使用状态机可以处理任意顺序的事件,并能够提供有意义的响应。
  • 项目中使用主从状态机来解析,从状态机负责读取报文的一行,主状态机负责对该数据进行解析,主状态机内部调用从状态机,从状态机驱动主状态机。
  • 每解析一部分就会将整个请求的m_check_state状态改变,状态机就是根据这个状态进行解析跳转的。
25. 主状态机的三种状态?
  • CHECK_STATE_REQUESTLINE,解析请求行。
  • CHECK_STATE_HEADER,解析请求头。
  • CHECK_STATE_CONTENT,解析消息体
26. 从状态机的三种状态?
  • LINE_OK,完整读取一行,该条件涉及解析请求行和请求头部。
  • LINE_BAD,报文语法有误。
  • LINE_OPEN,读取的行不完整。
27. 状态机的转移图?
  • 从状态机负责读取报文的一行,主状态机负责对改行数据进行解析。主状态机内部调用从状态机,从状态机驱动主状态机。
  • 每解析一部分就会将整个请求的m_check_state改变,状态机就是根据这个状态来跳转的。
  • (1)解析请求行,通过请求行我们可以判断该请求的类型,比如说GET和POST请求。请求行中最重要的部分就是URL,之后会将URL保存用于返回生成的HTTP响应。
  • (2)解析请求头,里面包含着服务器要使用的附加信息。
  • (3)解析请求体,如果是GET,这部分时空的,因为在URL体中明文给出了,如果是POST,这部分是有数据的,比如说是用户和密码。
4. TCP三次握手?
  • 之所以说是三次握手,是因为建立一个连接时,客户端和服务端之间发了三个报文。
  • 开始时,客户端处于close状态,服务端处于listen状态,如果这时候客户端发起请求。
  • 客户端会向服务端发送一个SYN=1和序列号seq=x,此时客户端进入SYN_SEND状态。
  • 服务端收到SYN后,向客户端发送一个SYN=1,ACK=1,Seq=y,ack=x+1,此时服务端处于SYN_RECEIVE阶段。
  • 客户端收到SYN和ACK后,会向服务端发送ACK=1,Seq=x+1,ack=y+1,此时客户端进入ESTABLISH阶段,等待服务端收到ACK后,也进入ESTABLISH连接阶段。
5. TCP四次挥手?
  • 之所以说是四次挥手,是因为断开一个TCP连接时,客户端和服务端之间会发送4个报文。
  • 最开始客户端和服务端都处于ESTABLISH连接状态,加入客户端发起请求。
  • 客户端会向服务端发送一个FIN报文请求关闭,其中FIN=1,seq=u此时客户端停止发送数据,进入FIN_WAIT1状态。
  • 服务端收到客户端的FIN报文后,得知客户端没有数据了,也是发送一个ACK=1,seq=v,ack=u+1,此时服务端进入CLOSE_WAIT阶段。
  • 服务端赶紧把剩下的数据给发完,发完后给客户但发送一个FIN=1,ACK=1,seq=u+1,ack=w,服务端进入LASK_ACK阶段。
  • 服务端收到FIN和ACK报文后,发送ACK=1,seq=u+1,ack=w+1,进入TIME_WAIT阶段,客户端等待2MSL事件后进入CLOSE状态,服务端收到ACK报文后进入CLOSE状态。

HTTP 报文格式

锁有哪些

你可能感兴趣的:(c++,面试,开发语言)