C++程序员宝典

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++程序员宝典相关的知识,希望对你有一定的参考价值。


文章目录

  • ​​1. C++​​
  • ​​1.1. 说一下 static 关键字的作用​​
  • ​​1.2. 说一下 C++和 C 的区别​​
  • ​​1.3. 指针和引用​​
  • ​​1.4. C++的智能指针? 为何使用智能指针​​
  • ​​1.5. 重写和重载​​
  • ​​1.6. 多态​​
  • ​​1.7. 析构函数为何为虚函数:​​
  • ​​1.8. map和set的实现​​
  • ​​1.9. 指针和数组的区别?​​
  • ​​1.10. 定义字符串的区别​​
  • ​​1.11. 类型转换? cast​​
  • ​​1.12. new/delete 与 malloc/free 的区别是什么​​
  • ​​1.13. allocator 内存分配和释放?​​
  • ​​1.14. malloc 的原理​​
  • ​​1.15. STL迭代器删除元素:​​
  • ​​1.16. vector和list 的区别​​
  • ​​1.17. STL迭代器的作用, 为何不用指针而用迭代器?​​
  • ​​1.18. C++中类成员的访问权限​​
  • ​​1.19. struct和class的区别​​
  • ​​1.20. C++源文从文本到可执行文件经历过程​​
  • ​​1.21. include "" 和include <>的区别​​
  • ​​1.22. fork,wait,exec 函数​​
  • ​​1.23. map 和 set 有什么区别?​​
  • ​​1.24. STL 里 resize 和 reserve 的区别​​
  • ​​1.25. BSS端等六段: C++的内存管理?​​
  • ​​1.26. 内存泄漏​​
  • ​​1.27. 判断内存泄漏:​​
  • ​​1.28. 如何采用单线程的方式处理高并发?​​
  • ​​1.29. 大端小端?​​
  • ​​1.30. 设计一个server, 实现多个客户端请求​​
  • ​​1.31. C++的锁你知道几种?​​
  • ​​2. 算法​​
  • ​​2.1. n个整数无序数组, 找到每个元素后面比他大的一个数,时间复杂度为O(N)​​
  • ​​3. 操作系统​​
  • ​​3.1. 进程与线程?​​
  • ​​3.1.1. 定义​​
  • ​​3.1.2. 区别​​
  • ​​3.1.3. 有了进程为何要线程?​​
  • ​​3.1.4. 进程间通信的方式​​
  • ​​3.1.5. 线程间通信​​
  • ​​3.1.6. 线程间同步方式 互斥锁, 信号量, 条件变量?​​
  • ​​3.1.7. 线程锁:​​
  • ​​3.1.8. 进程的5中基本状态​​
  • ​​3.2. 虚拟内存​​
  • ​​3.2.1. 虚拟内存的好处​​
  • ​​3.2.2. 虚拟内存的代价​​
  • ​​3.3. 操作系统中的程序的内存结构, 或C++的内存管理?​​
  • ​​3.4. 缺页中断​​
  • ​​3.5. fork和vfork的区别:​​
  • ​​3.6. 并发与并行?​​
  • ​​3.7. 死锁​​
  • ​​3.7.1. 死锁的起因:​​
  • ​​3.7.2. 四大必要条件和解决死锁:​​
  • ​​3.8. 内存溢出和内存泄漏​​
  • ​​3.9. 系统调用:​​
  • ​​3.10. IO模型?​​
  • ​​3.11. 死循环+来连接时新建线程的方法效率有点低, 如何改进?​​
  • ​​4. 计算机网络​​
  • ​​4.1. TCP与UDP的区别和各自应用场景?​​
  • ​​4.2. TCP是如何保证可靠性的?​​
  • ​​4.2.1. 序列号, 确认应答, 超时重传​​
  • ​​4.2.2. 窗口控制, 告诉重发控制, 快速重传​​
  • ​​4.2.3. 拥塞控制​​
  • ​​4.3. TCP建立连接和断开连接的过程:​​
  • ​​4.4. 说一下本地主机访问百度网页的整个过程?​​
  • ​​4.4.1. url分析​​
  • ​​4.4.2. 域名解析​​
  • ​​4.4.3. 服务器处理​​
  • ​​4.4.4. 网站处理 MVC​​
  • ​​4.4.5. 浏览器处理​​
  • ​​4.4.6. 分层分析​​
  • ​​4.5. http和https的区别?​​
  • ​​4.6. IP/MAC:​​
  • ​​4.7. 中断:​​
  • ​​4.8. get/post的区别:​​
  • ​​4.9. socket编程:​​
  • ​​5. 数据库相关​​
  • ​​5.1. 请你说一说数据库索引?​​
  • ​​5.2. 请你说一说数据库事务​​
  • ​​5.3. 数据库的事务和四大特性和隔离级别​​
  • ​​5.3.1. 四大特性​​
  • ​​5.3.2. 不同的隔离级别:​​
  • ​​5.4. 索引是什么, 多加索引一定会好吗​​
  • ​​5.5. 数据库的三大范式​​
  • ​​5.6. mysql的四种隔离状态​​
  • ​​5.7. mysql的MVCC机制​​
  • ​​5.8. SQL优化方法有哪些?​​
  • ​​5.9. MySQL引擎和区别?​​
  • ​​5.9.1. MySQL引擎​​
  • ​​5.9.2. InnoDB引擎​​
  • ​​5.9.3. Mylsam引擎​​
  • ​​5.9.4. InnoDB和Mylsam的区别:​​
  • ​​5.10. 请你说一说inner join和left join​​
  • ​​5.11. mongodb和redis的区别?​​
  • ​​5.12. 请你说一下mysql引擎以及其区别?​​
  • ​​5.13. 请你来说一说Redis的定时机制怎么实现的?​​
  • ​​5.14. Redis是单线程的, 但是为什么这么高效呢?​​
  • ​​5.15. 请问Redis的数据类型有哪些, 底层怎么实现?​​
  • ​​5.16. Redis的rehash怎么做的, 为什么要渐进rehash, 渐进rehash又是怎么实现?​​
  • ​​5.17. Redis和memcached的区别?​​
  • ​​5.18. 基础语句​​
  • ​​6. 多表查询​​
  • ​​7. 项目相关​​
  • ​​7.1. 请你回答一下git中Merge和rebase区别​​
  • ​​8. 设计模式​​
  • ​​8.1. 单例模式​​
  • ​​8.2. 工厂模式​​
  • ​​8.3. 观察者模式​​
  • ​​8.4. 装饰器模式​​

1. C++

1.1. 说一下 static 关键字的作用

  1. 全局静态变量: 静态存储区, 整个程序运行期间都在, 未经初始化的静态全局变量自动初始化为0, 作用域定义之处到整个文件结尾
  2. 局部静态变量: 静态存储区, 同上, 函数或者语句块结束的时候,作用域结束, 但是没有被销毁, 驻留内存, 等待该函数被在此调用, 值不变
  3. 静态函数: 函数声明和定义默认都是extern, 声明文件中可见, 其他位置不可见, 防止其他文件中同名冲突
  4. 类的静态成员: 多个对象数据共享, 所有对象的共享成员
  5. 类的静态函数: 与静态成员一样, 公共使用

1.2. 说一下 C++和 C 的区别

思想: 面向对象, 面向过程的结构化编程语言
语法: 重载继承多态三种特性, 更安全:强制类型转化
支持范式, 模板类, 函数模板等


1.3. 指针和引用

  1. 指针有自己的一块空间, 而引用只是一个别名 ;
  2. 使用 sizeof 看一个指针的大小是 4, 而引用则是被引用对象的大小 ;
  3. 指针可以被初始化为 NULL, 而引用必须被初始化且必须是一个已有对象的引用 ;
  4. 作为参数传递时, 指针需要被解引用才可以对对象进行操作, 而直接对引用的修改都会改变引用所指向的对象 ;
  5. 可以有 const 指针, 但是没有 const 引用 ;
  6. 指针在使用中可以指向其它对象, 但是引用只能是一个对象的引用, 不能被改变 ;
  7. 指针可以有多级指针(**p), 而引用只有一级 ;
  8. 指针和引用使用++运算符的意义不一样 ;
  9. 如果返回动态内存分配的对象或者内存, 必须使用指针, 引用可能引起内存泄露。

1.4. C++的智能指针? 为何使用智能指针

C++里面的四个智能指针: auto_ptr, shared_ptr, weak_ptr, unique_ptr 其中后三个是c++11 支持,并且第一个已经被 11 弃用。

  1. 作用是管理一个指针; 申请空间在函数结束时忘记释放, 造成内存泄漏, 而使用智能指针, 一旦智能指针超出类的作用域, 类会自动调用析构函数, 释放资源, 所以智能指针的作用原理在函数结束后, 自动释放内存空间;
  2. auto_ptr p1 (new string ("I reigned lonely as a cloud.”));
    auto_ptr p2;
    p2 = p1; //auto_ptr 不会报错. 此时p2掠夺了p1所有权, 使用p1的时候, 内存崩溃
  3. unique_ptr p3 (new string (“auto”));
    unique_ptr p4;
    p4 = p3;// 报错, 非法, 避免内存崩溃
  4. shared_ptr共享拥有, 多个智能指针可以指向同一个对象, 该对象和其相关资源会在最后一个引用被销毁后释放
  5. weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象, 作为管理指针; 为了解决循环引用导致的内存泄漏, 构造函数不会改变引用计数, 不会对对象内存进行管理, 像是一个普通指针, 但会检测所管理的对象是否被释放, 从而避免内存泄漏;
    **

1.5. 重写和重载

  1. 重写: 子类调用父类的虚函数,
  2. 重载: 函数名相同, 但是参数列表不同, 返回类型没要求, 同一个作用域中

1.6. 多态

多态的实现主要分为静态多态和动态多态,静态多态主要是重载,在编译的时候就已经确定;动
态多态是用虚函数机制实现的,在运行期间动态绑定。
如动物音乐大赛, 乌鸦和狗和猫报名, 但是这三个对象都指向动物类(这是一个基类), 使用动物指针对乌鸦, 狗, 猫进行方法调用, 就是多态

1.7. 析构函数为何为虚函数:

父类设置为虚函数,保证new子类时,使用父类指针指向子类对象,释放父类指针时, 会自动释放子类空间, 防止内存泄漏

1.8. map和set的实现

  1. C++关联容器,红黑树,map是KV对,K索引,V数据, set中K为集合;
  2. map修改V不改K, 因为红黑树底层按照K排列,保证有序,如果可以修改K,首先需要删除K,调节树平衡,在插入修改后的K,调节平衡, 将会破坏map和set的结构;
  3. map支持下标查询,不存在默认值, 因此慎用, 建议find

1.9. 指针和数组的区别?

保存数据的地址; 保存数据
间接访问数据, 获得指针内容, 作为地址, 获取地址中数据; 直接访问数据
动态数据结构; 固定数目, 数据类型相同
malloc分配内存和free释放; 隐式分配和删除
指向匿名数据, 操作匿名函数; 自身作为数据名


1.10. 定义字符串的区别

const char * arr = “123”; char * brr = “123”; const char crr[] = “123”; char drr[] = “123”; 区别
const 常量区
* brr 地址存放

1.11. 类型转换? cast

  1. reinterpret_cast:任意类型的指针之间的转换,对转换的结果不做任何保证
  2. dynamic_cast:只能用于存在虚函数的父子关系的强制类型转换
  3. const_cast:删除变量的const属性方便再次赋值
  4. static_cast:完成基础数据类型;同一个继承体系中类型的转换;任意类型与空指针类型 void* 之间的转换。
int i = 10;
double d2 = static_cast<double>(i); //相当于创建一个static_cast<double>类型的匿名对象赋值给d2
int* p2 = reinterpret_cast<int*>(i); // 任意类型转换
int *p = const_cast<int*>(&i);

1.12. new/delete 与 malloc/free 的区别是什么

new/delete 是 C++的关键字,而 malloc/free 是 C 语言的库函数,后者使用必须指明申请内存空间的大小,对于类类型的对象,后者不会调用构造函数和析构函数


1.13. allocator 内存分配和释放?

  1. STL分配器封装与STL容器在内存管理上的底层细节;
  2. new(调用operate new配置内存,调用对象构造函数构造对象内容)delete(调用析构函数, 释放内存);
  3. allocator将两个阶段操作区分开来,内存配置有alloc::allocate()负责, 释放alloc::deallocate(); 对象构造由construct负责,内存析构由destroy负责;
  4. 为了提升内存管理效率, 减少申请小内存内存碎片问题, STL采用两级配置器, 当分配大小空间超过128B, 使用一级空间配置器(malloc, realloc, free进行内存管理和内存空间分配和释放),大于128B, 二级(内存池技术,通过空闲链表来管理内存)

1.14. malloc 的原理

malloc函数用于动态分配内存; 为了减少内存碎片和系统调用开销, malloc采用内存池的方式, 首先申请大块内存作为堆, 再将堆分成多个内存块, 以块作为内存管理的基础单位; 当用户申请内存时, 直接从堆区分配一块合适的空闲块; malloc采用隐式链表结构将堆区分成连续,大小不一的块, 包含已分配和未分配块; 同时malloc采用显示链表结构管理所有空闲块, 双向链表, 每个空闲块记录一个连续的, 未分配的地址;
当进行内存分配时,Malloc 会通过隐式链表遍历所有的空闲块,选择满足要求的块进行分配;当进行内存合并时,malloc 采用边界标记法,根据每个块的前后块是否已经分配来决定是否进行块合并。
Malloc 在申请内存时,一般会通过 brk 或者 mmap 系统调用进行申请。其中当申请内存小于128K 时,会使用系统函数 brk 在堆区中分配;而当申请内存大于 128K 时,会使用系统函数 mmap在映射区分配。

1.15. STL迭代器删除元素:

  1. 对于序列容器vector,deque来讲,使用erase, 后面元素前移一位,erase返回下一个有效的迭代器;
  2. 对于map,set,使用erase,当前元素迭代器失效,但是因为结构为红黑树,所以删除元素不会影响下一元素迭代器,在调用erase之前,记录下一个元素的迭代器即可,
  3. 对于list,使用不连续分配内存, erase返回下一个有效迭代器

1.16. vector和list 的区别

Vector

List

连续存储的容器,动态数组,在堆上分配空间, 两倍容量增长, 顺序内存

动态双向链表, 堆上空间, 每删除一个元素会释放一个空间

访问:O(1)(随机访问);插入:后插快, 中间需要内存拷贝, 内存申请和释放; 删除: 后删快, 中间需要内存拷贝

访问: 随机访问差, 只能开头和结尾; 插入和删除快, 常数开销

适用场景:经常随机访问,且不经常对非尾节点进行插入删除

适用于经常插入和删除

下面是区别

数组

双向链表

支持随机访问

不支持随机访问

顺序内存

离散内存

中间节点插入删除会导致拷贝

不会

一次性分配好内存, 二倍扩容

list每次在新节点插入会进行内存申请

随机访问性能好,插入性能差

相反

1.17. STL迭代器的作用, 为何不用指针而用迭代器?

  1. 迭代器提供一种方法顺序访问一个聚合对象各个元素, 而又不暴露该对象的内部表示; 或者说运用这种方法, 是的我们可以在不知道对象内部结构情况下, 按照一定顺序规则直接反问聚合对象的各个元素
  2. 与指针的区别: 迭代器不是指针, 而是类模板, 表现像指针,模拟指针功能,重载指针操作符如->, *, ++等, 相当于一种智能指针, 根据不同类型的数据结构实现不同的操作
  3. 迭代器类的访问方式就是把不同集合类的访问逻辑抽象出来, 是的不用暴露集合内部的结构而达到循环遍历的效果;

1.18. C++中类成员的访问权限

C++通过 public、protected、private 三个关键字来控制成员变量和成员函数的访问权限,它们分别表示公有的、受保护的、私有的,被称为成员访问限定符
类内部, 不区分, 无限制
子类, 能访问父类的private以外的属性和方法
其他类, 只能访问public

1.19. struct和class的区别

在 C++中,可以用 struct 和 class 定义类,都可以继承。区别在于:structural 的默认继承权限和默认访问权限是 public,而 class 的默认继承权限和默认访问权限是 private。另外,class 还可以定义模板类形参,比如 template <class T, int i>

1.20. C++源文从文本到可执行文件经历过程

  1. 预处理: 源代码文件包含的头文件, 预编译语句, 分析替换, 生成预编译文件
  2. 编译阶段: 特定编码
  3. 汇编阶段: 转化为机器码, 重定位目标文件
  4. 链接阶段: 多个目标文件及所需要的库链接成为最终可执行文件

1.21. include “” 和include <>的区别

  1. 编译器预处理阶段查找头文件的路径不一样
  2. 双引号查找路径: 当前头文件目录, 编译器设置的头文件路径, 系统变量路径path指定的路径
  3. <>查找路径: 编译器设置的头文件, 系统变量

1.22. fork,wait,exec 函数

父进程产生子进程使用 fork 拷贝出来一个父进程的副本,此时只拷贝了父进程的页表,两个进程都读同一块内存,当有进程写的时候使用写实拷贝机制分配内存,exec 函数可以加载一个 elf文件去替换父进程,从此父进程和子进程就可以运行不同的程序了。fork 从父进程返回子进程的 pid,从子进程返回 0.调用了 wait 的父进程将会发生阻塞,直到有子进程状态改变,执行成功返回 0,错误返回-1。exec 执行成功则子进程从新的程序开始运行,无返回值,执行失败返回-1

1.23. map 和 set 有什么区别?

map和set的实现*3:

  1. C++关联容器,红黑树,map是KV对,K索引,V数据, set中K为集合;
  2. map修改V不改K, 因为红黑树底层按照K排列,保证有序,如果可以修改K,首先需要删除K,调节树平衡,在插入修改后的K,调节平衡, 将会破坏map和set的结构;
  3. map支持下标查询,不存在默认值, 因此慎用, 建议find

1.24. STL 里 resize 和 reserve 的区别

  1. resize(): 改变当前容器内含有元素的数量
    vectorv;
    v.resize(20);
    v.push_back(2); // 此时的2是21位置
  2. reserve(len): 改变当前容器最大容量, 不会生成元素; 如果reserve大于capacity, 重新分配个len的对象空间, 原始对象复制过来

1.25. BSS端等六段: C++的内存管理?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jhYUtGYU-1623204463133)(07_操作系统/代码存储结构.jpg)]
在C++中, 虚拟内存分为代码段,数据段, BSS段, 堆区, 文件映射区, 栈区六个部分

  1. 代码段: 包括只读存储区(字符串常量)和文本区(程序的机器代码), 只读
  2. 数据段: 存储程序中已初始化的全局变量和静态变量; 属于静态内存分配
  3. BSS段: 存储未初始化或初始化为0的全局变量和静态变量(局部+全局); 属于静态分配, 程序结束后静态变量资源由系统自动释放。
  4. 堆区: 调用 new/malloc 函数时在堆区动态分配内存,同时需要调用 delete/free 来手动释放申请的内存。频繁的malloc free造成内存空间不连续, 产生碎片, 因此堆比栈效率低
  5. 映射区:存储动态链接库以及调用 mmap 函数进行的文件映射
  6. 栈区: 存储函数的返回地址,返回值, 参数, 局部变量; 编译器自动释放,

1.26. 内存泄漏

  1. 堆内存泄漏, 如果malloc, new, realloc从堆分配的内存, 由于程序错误造成内存未释放, 产生的
  2. 系统资源泄漏: 程序使用系统资源: bitmap, handle, socket忘记释放, 将导致系统效能和稳定差
  3. 没有将基类析构函数定义为虚函数, 基类指针指向子类对象后, 释放基类时, 子类资源不会被正确释放

1.27. 判断内存泄漏:

  1. 内存泄漏原因: 通常调用malloc/new等内存申请操作, 缺少对应的free/delete
  2. 判断内存是否泄漏, 可以使用Linux环境下的内存泄漏检测工具, 也可以在写代码时添加内存申请和释放统计功能, 统计申请和释放是否一致, 以此判断内存泄漏
    varglind,mtrace 检测

1.28. 如何采用单线程的方式处理高并发?

I/O 复用 异步回调

1.29. 大端小端?

大端是指低字节存储在高地址;小端存储是指低字节存储在低地址。我们可以根据联合体来判断该系统是大端还是小端。因为联合体变量总是从低地址存储。

1.30. 设计一个server, 实现多个客户端请求

多线程, 线程池 ,IO复用

1.31. C++的锁你知道几种?

锁包括互斥锁,条件变量,自旋锁和读写锁
生产者消费者问题利用互斥锁和条件变量可以很容易解决,条件变量这里起到了替代信号量的作用

2. 算法

2.1. n个整数无序数组, 找到每个元素后面比他大的一个数,时间复杂度为O(N)

方法: 注意读题: 是找到每个元素后面比他大的一个数
如[1,3,2,6,9,3]
则[3,6,6,9,-1,-1]
如果栈空, 当前元素不大于栈顶, 元素入栈,
栈不空, 当前元素大于栈顶, 出栈, 元素在比较当前栈顶, 不大于, 入栈

vector<int> res(nums.size(), 0);
stack<int> temp;
for(int i=0; i<nums.size(); i++)
while(!temp.empty() && nums[temp.top()] < nums[i])
res[temp.top()] = i-temp.top();
temp.pop();

temp.push(i);

3. 操作系统

3.1. 进程与线程?

3.1.1. 定义

进程是对运行时程序的封装, 是系统进行资源调度和分配的的基本单位, 实现了操作系统的并发, 线程是进程的子任务, 是 CPU 调度和分派的基本单位, 用于保证程序的实时性, 实现进程内部的并发 ; 线程是操作系统可识别的最小执行和调度单位。每个线程都独自占用一个虚拟处理器:独自的寄存器组, 指令计数器和处理器状态。每个线程完成不同的任务, 但是共享同一地址空间(也就是同样的动态内存, 映射文件, 目标代码等等), 打开的文件队列和其他内核资源。

3.1.2. 区别

  1. 一个线程只能属于一个进程, 而一个进程可以有多个线程, 但至少有一个线程。线程依赖
    于进程而存在。
  2. 进程在执行过程中拥有独立的内存单元, 而多个线程共享进程的内存。(资源分配给进程, 同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量), 数据段(全局变量和静态变量), 扩展段(堆存储)。但是每个线程拥有自己的栈段, 栈段又叫运行时段, 用来存放所有局部变量和临时变量。)
  3. 进程是资源分配的最小单位, 线程是 CPU 调度的最小单位 ;
  4. 系统开销:由于在创建或撤消进程时, 系统都要为之分配或回收资源, 如内存空间, IO设备等。因此, 操作系统所付出的开销将显著地大于在创建或撤消线程时的开销。类似地, 在进行进程切换时, 涉及到整个当前进程 CPU 环境的保存以及新被调度运行的进程的 CPU 环境的设置。而线程切换只须保存和设置少量寄存器的内容, 并不涉及存储器管理方面的操作。可见, 进程切换的开销也远大于线程切换的开销。
  5. 通信:由于同一进程中的多个线程具有相同的地址空间, 致使它们之间的同步和通信的实现, 也变得比较容易。进程间通信 IPC, 线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助, 以保证数据的一致性。在有的系统中, 线程的切换、同步和通信都无须操作系统内核的干预
  6. .进程编程调试简单可靠性高, 但是创建销毁开销大 ; 线程正相反, 开销小, 切换速度快, 但是编程调试相对复杂。
  7. 进程间不会相互影响 ; 线程一个线程挂掉将导致整个进程挂掉
  8. 进程适应于多核、多机分布 ; 线程适用于多核

3.1.3. 有了进程为何要线程?

进程可以多个程序并发执行, 提高资源利用率和系统吞吐量, 但是进程同一时间只能干一件事, 进程阻塞, 整个程序阻塞
线程是并发执行的基本单位,减少程序在并发执行时时空开销, 提高并发性, 同时线程资源消耗小,公用内存
同时CPU更加有效, 当线程数量不大于CPU数目时, 不同线程运行在不同的CPU上
改善程序结构, 对一个复杂又长进程考虑分为多个线程, 独立或者半独立运行, 程序容易被编译器解释

3.1.4. 进程间通信的方式

进程间通信主要包括管道、系统 IPC(包括消息队列、信号量、信号、共享内存等)、以及套接字 socket

  1. 管道: 管道主要包括无名管道和命名管道:管道可用于具有亲缘关系的父子进程间的通信, 有名管道除了具有管道所具有的功能外, 它还允许无亲缘关系进程间的通信
    a. 普通管道, 半双工(数据只能在一个方向流动), 具有固定的读和写
    只能亲缘关系进程间通信
    可以看成一种特殊文件, 读写使用read,write函数, 但是不是普通文件, 只存在在内存中的读写操作
    b. 命名管道:FIFO, 无关进程间通信, 数据交换 特殊形式存在于文件系统中
  2. 消息队列:消息的链接表, 存于内核, 一个消息队列一个标识符, 具有读写权限的进程可以按照规则向消息队列中添加新消息, 对消息队列有读写权限的进程可以从消息队列中读取消息
  3. 信号量: 计数器, 控制过个进程对共享资源的访问,实现进程间互斥和同步, 不是用于存储进程间通信数据, 结合共享内存传递数据, 基于操作系统的PV操作, 程序对信号量进行的是原子操作, 每次PV操作不限于对信号量加一或减一, 而且可以加减任意正整数, 支持信号量组
  4. 信号: 通知接受进程某个事件已经发生
  5. 共享内存: 多个进程访问同一个内存空间, 不同进程及时看到对方进程中对共享内存中数据更新, 这种方式依赖某种同步操作, 如互斥锁和信号量
  6. 套接字:进程间通信, 只不过他可以用于不同主机间

3.1.5. 线程间通信

  1. 临界区: 多线程串行化访问公共资源或一段代码, 速度快, 适合控制数据访问
  2. 互斥量: 互斥对象机制, 只有拥有互斥对象才有访问公共资源权限, 因为互斥对象只有一个, 可以保证公共资源不会被多个线程同时访问
  3. 信号量: 为控制具有优先数量用户资源设计, 允许多线程同一时刻访问同一个资源, 但一般需要限制同一时刻访问此资源的最大线程数目
  4. 事件: 通过通知操作方式来保持多线程同步, 还可以方便的实现多线程优先级比较操作;

3.1.6. 线程间同步方式 互斥锁, 信号量, 条件变量?

  1. 信号量: PV操作, 信号量SV大于0, P则减一, 如果SV等于0, 挂起线程;
    其他线程因为等待信号量SV, 则唤醒, SV+1
  2. 互斥量: 线程互斥, 不保证按序访问, 可以与条件锁实现同步, 线程进入临界区, 先获得互斥锁, 离开后, 解锁, 以唤醒其他等待互斥锁的线程; 保证任何时刻, 只有一个线程访问该对象;
  3. 条件变量: 线程间同步共享数据的值, 共享数据达到某个值, 唤醒等待这个共享数据的另一个线程

3.1.7. 线程锁:

线程锁通常用于实现线程同步和通信, 单核机器上, 多线程程序, 仍然存在线程同步的问题; 因为在抢占式操作系统中, 通常为每一个线程分配时间片, 时间片耗尽, 操作系统会挂起, 进入另一个线程, 若两个线程共享某些数据, 不用线程锁, 可能导致共享数据修改引起冲突

3.1.8. 进程的5中基本状态

  1. 创建状态:进程正在被创建
  2. 就绪状态:进程被加入到就绪队列中等待 CPU 调度运行
  3. 执行状态:进程正在被运行
  4. 等待阻塞状态:进程因为某种原因,比如等待 I/O,等待设备,而暂时不能运行。
  5. 终止状态:进程运行完毕

3.2. 虚拟内存

为了防止不同进程同一时刻在物理内存中运行而对物理内存的争夺和践踏, 采用了虚拟内存。
虚拟内存技术使得不同进程在运行过程中, 它所看到的是自己独自占有了当前系统的 4G 内存。所有进程共享同一物理内存, 每个进程只把自己目前需要的虚拟内存空间映射并存储到物理内存上。 事实上, 在每个进程创建加载时, 内核只是为进程“创建”了虚拟内存的布局, 具体就是初始化进程控制表中内存相关的链表, 实际上并不立即就把虚拟内存对应位置的程序数据和代码(比如.text .data 段)拷贝到物理内存中, 只是建立好虚拟内存和磁盘文件之间的映射就好(叫做存储器映射), 等到运行到对应的程序时, 才会通过缺页异常, 来拷贝数据。还有进程运行过程中, 要动态分配内存, 比如 malloc 时, 也只是分配了虚拟内存, 即为这块虚拟内存对应的页表项做相应设置, 当进程真正访问到此数据时, 才引发缺页异常。
请求分页系统、请求分段系统和请求段页式系统都是针对虚拟内存的, 通过请求实现内存与外存的信息置换。

3.2.1. 虚拟内存的好处

  1. 扩大地址空间 ;
  2. 内存保护:每个进程运行在各自的虚拟内存地址空间, 互相不能干扰对方。虚存还对特定的内存地址提供写保护, 可以防止代码或数据被恶意篡改。
  3. 公平内存分配。采用了虚存之后, 每个进程都相当于有同样大小的虚存空间。
  4. 当进程通信时, 可采用虚存共享的方式实现。
  5. 在程序需要分配连续的内存空间的时候, 只需要在虚拟内存空间分配连续空间, 而不需要实际物理内存的连续空间, 可以利用碎片

3.2.2. 虚拟内存的代价

  1. 虚存的管理需要建立很多数据结构, 这些数据结构要占用额外的内存
  2. 虚拟地址到物理地址的转换, 增加了指令的执行时间
  3. 页面的换入换出需要磁盘IO, 这是很耗时的
  4. 如果一页中只有一部分数据, 会浪费内存

3.3. 操作系统中的程序的内存结构, 或C++的内存管理?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yfIG1Gb1-1623204463135)(07_操作系统/代码存储结构.jpg)]
在C++中, 虚拟内存分为代码段,数据段, BSS段, 堆区, 文件映射区, 栈区六个部分

  1. 代码段: 包括只读存储区(字符串常量)和文本区(程序的机器代码), 只读
  2. 数据段: 存储程序中已初始化的全局变量和静态变量; 属于静态内存分配
  3. BSS段: 存储未初始化或初始化为0的全局变量和静态变量(局部+全局); 属于静态分配, 程序结束后静态变量资源由系统自动释放。
  4. 堆区: 调用 new/malloc 函数时在堆区动态分配内存,同时需要调用 delete/free 来手动释放申请的内存。频繁的malloc free造成内存空间不连续, 产生碎片, 因此堆比栈效率低
  5. 映射区:存储动态链接库以及调用 mmap 函数进行的文件映射
  6. 栈区: 存储函数的返回地址,返回值, 参数, 局部变量; 编译器自动释放,

3.4. 缺页中断

  1. 原因: malloc和mmap等内存分配函数, 在分配时只是建立了进程虚拟地址空间, 并没分配虚拟内存对应的物理内存, 当进程访问这些没有建立映射关系的虚拟内存时, 处理器自动触发缺页异常, 操作系统会根据页表中外存地址在外存中找到所缺的一页, 调入内存;
  2. 步骤:
    保护CPU现场; 分析中断原因; 中断处理程序; 恢复CPU现场, 继续执行

3.5. fork和vfork的区别:

  1. fork:创建一个和当前进程映像一样的进程可以通过 fork( )系统调用:它几乎与调用 fork( )的进程一模一样,这两个进程都会继续运行
  2. vfork( )会挂起父进程直到子进程终止或者运行了一个新的可执行文件的映像。通过这样的方式,vfork( )避免了地址空间的按页复制。在这个过程中,父进程和子进程共享相同的地址空间和页表项。实际上 vfork( )只完成了一件事:复制内部的内核数据结构。因此,子进程也就不能修改地址空间中的任何内存。
  3. fork子拷贝父的数据段和代码段, vfork与父共享数据段
  4. fork的父子执行顺序不确定, v保证先子后父
  5. v调用exec或exit后, 父进程才调度运行

3.6. 并发与并行?

  1. 并发: 宏观上两个程序同时运行, 单核CPU多任务, 微观上两个程序指令交织运行, 单个时钟周期只运行一个指令, 这种并发不能提高PC性能, 只能提高效率
  2. 并行: 物理意义同时运行, 多核CPU, 两个程序两个核上同时运行互补干扰, 这种并行提高PC效率

3.7. 死锁

3.7.1. 死锁的起因:

两个或以上进程执行过程中, 因为争夺资源造成的互相等待现象

3.7.2. 四大必要条件和解决死锁:

  1. 互斥条件: 进程所分配的资源不允许被其他进程所掠夺, 若其他进程需要访问该资源, 只有等待
  2. 请求保持: 进程获得一定资源后, 又对其他资源发出请求, 但是该资源被其他进程占有, 请求阻塞, 该进程不会释放自己的资源
  3. 不可掠夺: 进程获取资源, 未使用, 不可掠夺, 只能使用后才能释放
  4. 环路等待: 进程发生死锁, 必然存在进程-资源之间的环形链路(A有a求b, B有b求a)

破破坏掉任意一个条件即可

  1. 资源一次性分配, 保证没有死锁才分配资源
  2. 可掠夺资源: 进程不满足, 释放已占有的资源, 破坏条件3
  3. 资源有序分配: 每个进程按序号递增请求, 破坏环路等待

3.8. 内存溢出和内存泄漏

  1. 内存溢出: 申请内存时, 没有足够内存给申请者使用
  2. 内存泄漏: 程序错误, 造成未及时释放资源

3.9. 系统调用:

对文件的读写, open, write

3.10. IO模型?

  1. 阻塞IO: 调用某个函数, 等待函数返回, 期间不做任何任务
  2. 非阻塞IO: 每个一段时间检测IO时间是否就绪, 没有就绪就去做别的任务
  3. 信号驱动IO: 信号处理函数, 进程继续执行, 当IO时间到, 进程收到信号, 然后处理IO时间
  4. IO复用: select/poll函数实现, 同时阻塞多个IO操作, 同时多个读写操作进行检测, 检测到有数据可读或者可写, 在调用IO操作函数
  5. 异步IO,调用aio_read函数告诉内核, 需要的东西, 文件, 通知方式, 当内核将数据拷贝到缓冲区, 在通知应用程序

3.11. 死循环+来连接时新建线程的方法效率有点低, 如何改进?

提前创建好一个线程池,用生产者消费者模型,创建一个任务队列,队列作为临界资源,有了新连接,就挂在到任务队列上,队列为空所有线程睡眠。改进死循环:使用 select epoll 这样的技术

4. 计算机网络

4.1. TCP与UDP的区别和各自应用场景?

区别

TCP

UDP

连接

面向连接的传输层控制协议

无连接

对象

点对点, 一条TCP连接只能有两个端点

一对多, 多对一, 多一的交互通信, 总之就是广播

拥塞控制

有拥塞控制和流量控制保证数据传输安全和稳定

没有, 网络拥塞不影响源主机发送效率

可靠性

可靠交付, 无差错, 不丢失, 无重复, 按序到达

尽最大努力交付

报文长度

动态报文长度, 根据接收方的窗口大小和当前网络拥塞决定

8个字节(源端口, 目的端口, 数据长度, 校验和)

适应场景

由于TCP可靠但是传输慢, 因此如通信数据完整性需要, 建议文件传输, 重要状态使用

如通信实时性重要, 则如视频传输, 实时通信

4.2. TCP是如何保证可靠性的?

4.2.1. 序列号, 确认应答, 超时重传

数据达到接收方, 接收方需要发确认应答, 表示该段收到, 确认序列号说明它下一次需要接收的数据序列号, 如果发送方未收到确认应答, 可能发送数据丢失, 也可能是确认应答丢失, 这时发送方等待一定时间后, 超时重传;

4.2.2. 窗口控制, 告诉重发控制, 快速重传

TCP会利用窗口控制提高传输速度, 意思是一个窗口内大小, 不用一定等到应答在传输下一个序列, 窗口大小就是无需等待确认而可以继续发送数据的最大值, 如果不适用窗口控制, 每一个没收到的应答数据都需要重发, 使用窗口控制, 如果序列号为1001-2000丢失导致接收方没有接收到, 后面数据每次传输, 确认应答都会不停地发送序列号为1001的应答, 表示我需要接收1001-2000的数据, 发送方收到3次相同应答, 就会立即重新发送; 另一种情况是接收方收到数据, 但是应答丢失, 这种情况不会重发, 因为发送端知道, 如果数据段丢失, 接收端是会一直请求数据;

4.2.3. 拥塞控制

如果把窗口定的过大, 发送端连续发送大量数据, 可能造成网络拥堵, 甚至网络瘫痪, 所以TCP为了防止这种情况, 设定拥塞控制

  1. 慢启动: 定义拥塞窗口, 一开始设置为1, 之后每次收到确认应答, 窗口*2
  2. 拥塞避免: 设置慢启动阈值, 当拥塞窗口达到这个值, 拥塞窗口不在*2的上升, 而是线性增加, 每次拥塞窗口+1, 避免拥塞
  3. 一旦出现超时, 发送方将阈值设置为当前窗口的一半, 窗口大小设置为1, 进入慢启动过程
  4. 快速重传: 遇到3次重复确认应答, 表示收到了三个报文段, 但是这之前如果丢了一个, 进行立即重传, 然后阈值设置当前窗口一半, 窗口大小设置为慢启动阈值+3的大小
  5. 目的: TCP通信时, 网络吞吐量呈现逐渐上升, 随着拥堵来降低吞吐量, 在慢慢上升, 网络不容易发生瘫痪;

4.3. TCP建立连接和断开连接的过程:

三次握手: 客户端syn, 服务端ack, 客户端syn
第三次握手的原因: 两次握手, 服务端发送完ack后不知道客户端是否已经收到连接确认
四次握手: 可以合并为三次握手, 第二次和第三次可以放到同一个报文中

数据传输: 客户端write socket 服务端 read
四次挥手: C->S; S->C; S->C; C->S; (全双工, 停止接收, 另一方还可以发送)
四次挥手的原因: 因为TCP连接为全双工, 因此每个方向必须单独进行关闭, 这一原则是当一方完成数据发送任务后, 发送一个FIN终止一个方向的连接, 即不会在接受数据, 但是TCP另一方能发送, 只有当另一方也终止连接,

4.4. 说一下本地主机访问百度网页的整个过程?

涉及: 网络协议, 软件, 硬件, 安全, 高并发, 高可用
URL请求目标IP, 本地浏览器DNS缓存, host文件, 本地DNS(电信),根域名服务器->百度服务器, 返回目标IP, 以上查询基于传输层UDP协议, 然后http建立get请求, TCP三次握手, IP路由跳转到目标IP, 打包请求报文, 开辟空间,传递数据, 解析并展示到页面

4.4.1. url分析

https://www.baidu.com 统一资源定位符

  • https确定访问web浏览器
  • www.baidu.com确定百度web服务器名字
  • index.html是资源名称
    上面三个解析生成http请求信息, 发送后得到服务器响应报文, 展示到浏览器网页

4.4.2. 域名解析

  1. 浏览器缓存: 浏览器有百度DNS则会缓存DNS, 没有转下
  2. 系统缓存: 从host文件中查找对应的百度域名和IP, 没有转下
  3. 路由器缓存: 没有转下
  4. ISP DNS缓存, 电信, 联通, 移动
  5. 以上都没有则向根域名服务器查域名对应IP, 根域名请求转下一级, 直到找到IP

4.4.3. 服务器处理

  • web服务器接受用户的请求, 并交给网站代码

4.4.4. 网站处理 MVC

4.4.5. 浏览器处理

4.4.6. 分层分析

应用层: http, DNS
传输层: TCP / UDP
网络层: IP
数据链路: 路由器
物理层: 电缆,光缆

4.5. http和https的区别?

  1. http 明文,端口80,
  2. https, 经过TLS加密, 443端口 安全性高, TCP三次握手后加密; 需要跟服务器端申请证书, 浏览器安装证书才能正常使用, 确保数据发送正确,
  3. https缺点: 握手延时高, 部署成本高, 占用资源CPU高;

4.6. IP/MAC:

互联网每个网络每台主机分配的逻辑地址, 屏蔽物理差异,网络层控制 MAC是硬件, 网络设备位置, 数据链路层控制

4.7. 中断:

对某一事件的响应: 暂停保存当前, 去执行中断, 而后返回还原, 外部(IO,时钟)内部(非法, 溢出, 越界) 系统调用, 硬件响应, 软件处理

4.8. get/post的区别:

get请求将http header和data一起发送, 服务器响应200, post请求, 先发送header, 响应100, 发送data, 响应200
get url请求, post放到request body中请求, get在url中参数长度限制, post没有
get不安全, url明文, 一般通过加密方式
get浏览器主动cache, post多种编码, get完整保存浏览器记录, post请求不会
本质都是tcp, get一个请求包, post两个

4.9. socket编程:

服务端:socket-bind-listen-accept
客户端:socket-connect

5. 数据库相关

5.1. 请你说一说数据库索引?

索引是对数据库表中一列或多列的值进行排序的一种结构, 使用索引可快速访问数据库表中的特定信息。如果想按特定职员的姓来查找他或她, 则与在表中搜索所有的行相比, 索引有助于更快地获取信息。
索引的一个主要目的就是加快检索表中数据的方法, 亦即能协助信息搜索者尽快的找到符合限制条件的记录ID的辅助数据结构。

  1. 索引的分类:
    普通索引: create index index_name on table(column);
    唯一索引: 类似普通索引,索引列的值必须唯一(可以为空,这点和主键索引不同)create unique index index_name on table(column);
    主键索引: 特殊的唯一索引,不允许为空,只能有一个,一般是在建表时指定primary key(column)
    组合索引: 在多个字段上创建索引,遵循最左前缀原则。alter table t add index index_name(a,b,c);
    全文索引: 主要用来查找文本中的关键字,不是直接与索引中的值相比较,像是一个搜索引擎,配合match against使用,现在只有char,varchar,text上可以创建全文索引。
  2. 何时使用索引
    MySQL每次查询只使用一个索引。与其说是“数据库查询只能用到一个索引”,倒不如说,和全表扫描比起来,去分析两个索引B+树更加耗费时间。所以where A=a and B=b这种查询使用(A,B)的组合索引最佳,B+树根据(A,B)来排序。
  3. 何时不适用索引
    表记录太少;
    数据重复且分布平均的字段(只有很少数据值的列);
    经常插入、删除、修改的表要减少索引;
    text,image等类型不应该建立索引,这些列的数据量大(假如text前10个字符唯一,也可以对text前10个字符建立索引);
    MySQL能估计出全表扫描比使用索引更快时,不使用索引;
  4. 索引何时失效
    组合索引未使用最左前缀,例如组合索引(A,B),where B=b不会使用索引;
    like未使用最左前缀,where A like ‘%China’;
    搜索一个索引而在另一个索引上做order by,where A=a order by B,只使用A上的索引,因为查询只使用一个索引 ;
    or会使索引失效。如果查询字段相同,也可以使用索引。例如where A=a1 or A=a2(生效),where A=a or B=b(失效)
    如果列类型是字符串,要使用引号。例如where A=‘China’,否则索引失效(会进行类型转换);
    在索引列上的操作,函数(upper()等)、or、!=(<>)、not in等;

5.2. 请你说一说数据库事务

数据库事务(Database Transaction) , 是指作为单个逻辑工作单元执行的一系列操作, 要么完全地执行, 要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成, 否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元, 可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务, 必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。事务是数据库运行中的逻辑工作单位, 由DBMS中的事务管理子系统负责事务的处理。


5.3. 数据库的事务和四大特性和隔离级别

  1. 事务:单个逻辑工作单元执行的一系列操作, 要么完全工作/完全不执行, 确保所有操作完全成功, 要么不会更新数据资源, 错误恢复机制使得应用程序更加可靠,

5.3.1. 四大特性

  1. 原子性Atomicity:原子性是指事务包含的所有操作要么全部成功, 要么全部失败回滚(bin-log); 转账失败回滚
  2. 一致性Consistency: 一个事务执行之前和执行之后都必须处于一致性状态。两人之间转账的钱永远保持5000
  3. 隔离性Isolation: 隔离性是当多个用户并发访问数据库时, 比如操作同一张表时, 数据库为每一个用户开启的事务, 不能被其他事务的操作所干扰, 多个并发事务之间要相互隔离。 A 取钱的过程结束前,B 不能向这张卡转账
  4. 持久性: 持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的; 见到转账成功必然数据更改完成

5.3.2. 不同的隔离级别:

  1. Read Uncommitted(读取未提交内容):最低的隔离级别, 什么都不需要做, 一个事务可以读到另一个事务未提交的结果。所有的并发事务问题都会发生。
  2. Read Committed(读取提交内容):只有在事务提交后, 其更新结果才会被其他事务看见。
  3. Repeated Read(可重复读):在一个事务中, 对于同一份数据的读取结果总是相同的, 无论是否有其他事务对这份数据进行操作, 以及这个事务是否提交。可以解决脏读、不可重复读。
  4. Serialization(可串行化):事务串行化执行, 隔离级别最高, 牺牲了系统的并发性。可以解决并发事务的所有问题。

5.4. 索引是什么, 多加索引一定会好吗

1、索引
数据库索引是为了增加查询速度而对表字段附加的一种标识, 是对数据库表中一列或多列的值进行排序的一种结构。

DB在执行一条Sql语句的时候, 默认的方式是根据搜索条件进行全表扫描, 遇到匹配条件的就加入搜索结果集合。如果我们对某一字段增加索引, 查询时就会先去索引列表中一次定位到特定值的行数, 大大减少遍历匹配的行数, 所以能明显增加查询的速度。

  1. 优点:
    通过创建唯一性索引, 可以保证数据库表中每一行数据的唯一性。
    可以大大加快数据的检索速度, 这也是创建索引的最主要的原因。
    可以加速表和表之间的连接, 特别是在实现数据的参考完整性方面特别有意义。
    在使用分组和排序子句进行数据检索时, 同样可以显著减少查询中分组和排序的时间。
    通过使用索引, 可以在查询的过程中, 使用优化隐藏器, 提高系统的性能。
  2. 缺点:
    创建索引和维护索引要耗费时间, 这种时间随着数据量的增加而增加。
    索引需要占物理空间, 除了数据表占数据空间之外, 每一个索引还要占一定的物理空间, 如果要建立聚簇索引, 那么需要的空间就会更大。
    当对表中的数据进行增加、删除和修改的时候, 索引也要动态的维护, 这样就降低了数据的维护速度。
  3. 添加索引原则
    在查询中很少使用或者参考的列不应该创建索引。这是因为, 既然这些列很少使用到, 因此有索引或者无索引, 并不能提高查询速度。相反, 由于增加了索引, 反而降低了系统的维护速度和增大了空间需求。
    只有很少数据值的列也不应该增加索引。这是因为, 由于这些列的取值很少, 例如人事表的性别列, 在查询的结果中, 结果集的数据行占了表中数据行的很大比例, 即需要在表中搜索的数据行的比例很大。增加索引, 并不能明显加快检索速度。
    定义为text、image和bit数据类型的列不应该增加索引。这是因为, 这些列的数据量要么相当大, 要么取值很少。
    当修改性能远远大于检索性能时, 不应该创建索引。这是因为, 修改性能和检索性能是互相矛盾的。当增加索引时, 会提高检索性能, 但是会降低修改性能。当减少索引时, 会提高修改性能, 降低检索性能。因此, 当修改性能远远大于检索性能时, 不应该创建索引。

5.5. 数据库的三大范式

第一范式:当关系模式R的所有属性都不能再分解为更基本的数据单位时, 称R是满足第一范式, 即属性不可分
第二范式:如果关系模式R满足第一范式, 并且R得所有非主属性都完全依赖于R的每一个候选关键属性, 称R满足第二范式
第三范式:设R是一个满足第一范式条件的关系模式, X是R的任意属性集, 如果X非传递依赖于R的任意一个候选关键字, 称R满足第三范式, 即非主属性不传递依赖于键码


5.6. mysql的四种隔离状态

事务隔离级别

脏读

不可重复读

幻读

读未提交read-uncommitted




不可重复读

C++面试宝典

《PHP程序员面试笔试宝典》——如果面试问题曾经遇见过,是否要告知面试官?

Qt 设置CPU亲缘性,把进程和线程绑定到CPU核心上(Linux)

2020年中高级iOS大厂面试宝典+答案

进程间通信方式特点

计算机程序员书籍推荐

(c)2006-2024 SYSTEM All Rights Reserved IT常识