基础知识(2022 春)

【线程阻塞的三种状态】

阻塞:阻塞状态是指线程因为某种原因放弃了cpu使用权,暂时停止运行,直到线程进入可运行状态,才有机会再次获得cpu 使用权转到运行状态;
等待阻塞:尝试获取锁,但是没有获得,就进入了等待锁的阻塞队列中,notify不会唤醒该队列中的线程;
同步阻塞:正在占用锁的线程,调用了wait方法,就进入了wait阻塞队列中,只有notify才可以唤醒该队列中的线程;
其他阻塞:正在占用锁的线程,调用sleep方法或者IO(等待键盘输入),就进入了另一个阻塞队列中。睡眠时间到或者IO阻塞结束,线程才可以进入可运行状态(就绪状态)。


【进程和线程、协程的区别】

参考资料

既然有了线程,为什么还要用协程?
而且由于抢占式调度执行顺序无法确定的特点,使用线程时需要非常小心地处理同步问题,而协程完全不存在这个问题(事件驱动和异步程序也有同样的优点)。
因为协程是用户自己来编写调度逻辑的,对于我们的CPU来说,协程其实是单线程,所以CPU不用去考虑怎么调度、切换上下文,这就省去了CPU的切换开销,所以协程在一定程度上又好于多线程

协程相对于多线程的优点:
多线程编程是比较困难的, 因为调度程序任何时候都能中断线程, 必须记住保留锁, 去保护程序中重要部分, 防止多线程在执行的过程中断。

而协程默认会做好全方位保护, 以防止中断。我们必须显示产出才能让程序的余下部分运行。对协程来说, 无需保留锁, 而在多个线程之间同步操作, 协程自身就会同步, 因为在任意时刻, 只有一个协程运行。总结下大概下面几点:

无需系统内核的上下文切换,减小开销;
无需原子操作锁定及同步的开销,不用担心资源共享的问题;
单线程即可实现高并发,单核 CPU 即便支持上万的协程都不是问题,所以很适合用于高并发处理,尤其是在应用在网络爬虫中。


【C++ string和C中的Char*】

string是c++里的,不是c里的。用string存储字符串时,不用设定字符串的长度,而char要设定,string也不必担心内存是否足够等
还有就是,string有很强很方便的功能,比如可以方便的赋值,方便的比较大小。
字符串指针 :char*ch=“hello” ch保存的是hello这个字符串常量的首地址;
字符串数组,在C中,字符串是以字符数组的形式保存的;
char ch[]="hello" :字符串数组的特点是以”\0”结尾,因此上述的ch数组长度实际上是6,但是字符串的长度仍为5。
strlen()//求字符串的长度,遇到’\0’停止,因此是不包含’\0’空字符结尾的。


【栈和队列都不支持迭代器】

因为栈和队列不支持迭代器,不能对任意元素进行访问;


【线程安全相关】

线程安全(Thread safety)是指当多个线程同时访问一个共享资源时,不会产生不正确的结果或导致不确定的行为。
在并发编程中,如果多个线程同时访问共享的数据结构、变量或资源,可能会引发一些问题,如竞态条件(race condition)、死锁(deadlock)、数据不一致等。 线程安全的设计目标是确保在并发环境下,共享资源可以被多个线程正确、可靠地访问和操作,而不会出现意外的错误或破坏数据的完整性。

要实现线程安全,通常需要考虑以下几个方面:
互斥访问:使用互斥机制(如锁、信号量) 来保证在同一时间只有一个线程可以访问共享资源,其他线程需要等待。互斥访问可以避免多个线程同时修改共享资源导致的问题。
原子操作:将一系列操作作为不可分割的整体,确保这些操作在执行期间不会被其他线程中断。原子操作可以避免竞态条件,确保数据的一致性。
同步机制:使用同步机制(如条件变量、信号量) 来协调线程之间的执行顺序,保证线程按照预期的顺序进行操作,防止数据的不一致性。
数据封装:通过封装共享资源和访问方法,将对共享资源的访问限制在特定的范围内,避免直接访问共享资源导致的错误。

比如在以下情况下会产生线程安全的问题
你不知道在即将析构一个函数时,是不是有别的进程正在执行该对象的成员函数?
无法保证在执行成员函数期间,对象是否会被另一个线程析构?
也无法保证在调用某个对象的成员函数之前,你是否知道这个对象是不是还活着?也不知道它的析构函数是不是也正在对它进行操作?

解决线程安全的核心思路是避免共享数据结构,避免共享状态;但是一般情况下是无法避免的,需要通过条件(mutex 同步)来加以保护


【递归本质–栈实现】

递归是一种通过重复将问题分解为同类的子问题而解决问题的方法,核心思想是分治策略
递归调用在底层实际上是对线程栈的压栈和出栈操作,每调用一次就会压栈一次,并记录相关的局部变量信息,线程栈的内存是非常有限的,而递归调用是无限的,那么很快就会消耗完所有的内存资源,最终导致栈溢出因此递归调用一定要有递归终止的条件;

递归实现
第一步:递推分解任务
在这里插入图片描述
第二步:回归分治任务
在这里插入图片描述

递归一定是在分解任务不能再分解的时候,才开始执行,在不能分解的时候,意味着该出栈了,然后开始将递归函数的结果不断向上回报并出栈,直到解决顶层的大问题


【decltype 关键字】

decltype被称为类型说明符,主要是用来获取表达式的数据类型,从而定义变量的类型。
decltype不会实际计算表达式的值,编译器分析表达式并得到它的类型;

几种用法;
1、使用decltype(var)的形式,decltype会直接返回变量的类型(包括顶层const和引用),不会返回变量作为表达式的类型;
2、使用decltype(exptr)的形式,decltype会返回表达式结果对应的类型;decltype(expr)的结果根据expr的结果不同而不同:expr返回左值,得到该类型的左值引用;expr返回右值,得到该类型


【C++11 信号量底层实现(锁机制)】

参考
信号量(Semaphore)由一个值和一个指针组成,指针指向等待该信号量的进程。
信号量S是一个整数count,这个整数的值表示相应资源的使用情况,S表示可用资源的数量。它提供两个原子操作 : P操作(wait操作)和V操作(signal操作)
p操作(请求分配一个单位资源):count减一 如果count<0 那么挂起执行线程;
v操作(释放一个单位资源):count加一 如果count>=0 那么唤醒一个执行线程;

信号量本质上就是锁mutex,把信号量理解为多个锁,count就是锁的数量,可以理解为用信号量对多个线程进行管理;
mutex是一把锁,lock 上锁,那么可用的锁的数量为0,count=0;unlock释放锁,可用的锁的数量为1,count=1;我们可以理解为mutex是对一个线程进行管理;
比如 当count = 4时表示有4个空闲的线程可以使用;当count = -5时 有5个线程正在阻塞等待;


【C++初始化的方式】

C++有以下几种初始化方式:
零初始化 值初始化 默认初始化 直接初始化 列表初始化 拷贝初始化
1、默认初始化是定义对象时,没有使用初始化器,也没有做任何初始化说明时的行为;
2、值初始化是定义对象时,要求初始化,但没有给出初始值的行为;
3、直接初始化和拷贝初始化主要是相对于我们自定义的对象的初始化而言的,对于内置类型,二者没有区别;
对于自定义对象,直接初始化和拷贝初始化的区别是直接调用构造函数还是用 = 来进行初始化

vector<int>      v1(10);    //直接初始化,匹配某一构造函数
vector<string>   v2(10);    //直接初始化,匹配某一构造函数
vector<int>      v3=v1;     //拷贝初始化,使用=进行初始化

发生拷贝初始化的几种情景:
1.将一个对象作为实参传递给一个非引用的形参
2.从一个返回类型为非引用的函数返回一个对象
3.用花括号列表初始化一个数组中的元素或一个聚合类中的成员

构造函数初始化发生在拷贝和赋值构造函数里

4、列表初始化是C++11新给出的一种初始化方式,可用于内置类型,也可以用于自定义对象,前者如数组,后者如vector
可以分为在构造函数体内赋值初始化列表初始化这两种初始化的方式;
相比于在构造函数体内进行赋值初始化的方式(先初始化调用默认构造函数,再赋值调用拷贝构造函数),列表初始化的方式是在定义的时候初始化,只需调用一次构造函数即可,减少了一次函数调用,从而降低内存消耗;

int array[5]={
    
    1,2,3,4,5};
vector<int>v={
    
    1,2,3,4,5};

几种优先使用列表初始化的几种方式
1)const成员变量只能用成员初始化列表来完成初始化,而不能在构造函数内赋值;
2)初始化的数据成员是对象;
3)需要初始化引用成员变量;


小注:
1、类里面的任何成员变量在类定义时是不能初始化的,即不能进行类内初始化
2、一般的数据成员可以在构造函数中初始化。(构造初始化列表初始化和构造函数体内赋值初始化)
3、const数据成员必须在构造函数的初始化列表中初始化。(道理很简单,const成员第一次数据初始化就是发生在类构造数据初始化时,一旦过了此时就会出现问题)。
4、static数据成员在类外初始化。 为什么?
静态成员变量隶属于类,不是某个对象,所以静态成员变量不可能占用某一个对象的存储空间(换言之,如果在类内进行初始化,那么静态成员变量会占用类对象的存储空间),所以静态成员需要在类外部定义,以便静态成员变量在全局数据区分配其存储空间

5、数组成员是不能在初始化列表里初始化的。
6、不能给数组指定显式的初始化。


【HTPP报文 TCP报文 IP报文】

参考

HTTP报文可以分为HTTP请求消息HTTP响应消息两部分

HTTP请求消息

在这里插入图片描述
HTTP响应消息(由状态行 消息报头 空行和响应正文组成):

在这里插入图片描述


IP报文格式

在这里插入图片描述

版本(Version, 4bit):为4代表ipv4, 为6代表ipv6
报头长度(Header Length, 4bit):一般为5, 代表IP首部一共占用20个字节. (4*5)
服务类别(Type Of Service, 8bit):
报文长度(Length, 16bit): IP首部+数据部分的总长度, 一般再加上以太头长度即为数据包的长度.
标识(Identification, 16bit): 用于在IP层对数据包进行分段的时候,标识数据包.
标志(IP Flags, 3bit): 0x80—保留字段, 0x40—不分段, 0x20—还有更多的分段
分段偏移(Fragment Offset, 13bit): 本数据包的数据在分段中的偏移(需要再乘以8).
生存时间(Time To Live, 8bit): 本数据包的TTL.
用户协议(Protocol, 8bit): 1—icmp, 2—igmp, 6—tcp, 17—udp, 89—ospf.
报头校验和(Header Checksum, 16bit): IP首部的校验和.
源IP地址(Source IP, 32bit): 源IP地址.
目的IP地址(Destination IP, 32bit): 目的IP地址.
选项 填充
数据


TCP头部
可以分为:源端口号 目的端口号 序号 确认号 状态位 窗口大小 校验和 紧急指针 选项 填充

在这里插入图片描述

在这里插入图片描述


常见的问题点:
1、IP层的分片和重组分别发生在什么时候?
IP分片发生在IP层(网络层),不仅源端主机会进行分片,中间的路由器也有可能分片,因为不同的网络的MTU是不一样的,如果传输路径上的某个网络的MTU比源端网络的MTU要小,路由器就可能对IP数据报再次进行分片。而分片数据的重组只会发生在目的端的IP层
2、IP分片和TCP分段的区别?
IP分片是因为MTU ;TCP分段是因为MSS
MTU:最大传输单元,限制了发送数据的最大尺寸;MSS:最大报文段长度
3、为什么要避免IP分片?
因为IP层没有超时重传机制,一旦分片中有一个数据报的丢失就只能由上层协议重传整个数据报,效率低下;


【C++ set用法详解】

C++ STL 之所以得到广泛的赞誉,也被很多人使用,不只是提供了像vector, string, list等方便的容器,更重要的是STL封装了许多复杂的数据结构算法和大量常用数据结构操作。vector封装数组,list封装了链表,map和set封装了二叉树等,在封装这些数据结构的时候,STL按照程序员的使用习惯,以成员函数方式提供的常用操作,如:插入、排序、删除、查找等;
关于set,必须说明的是set关联式容器(不含重复元素、有序) set作为一个容器也是用来存储同一数据类型的数据类型,并且能从一个数据集合中取出数据,在set中每个元素的值都唯一,而且系统能根据元素的值自动进行排序。应该注意的是set中数元素的值不能直接被改变。
C++ STL中标准关联容器set, multiset, map, multimap内部采用的就是一种非常高效的平衡检索二叉树:红黑树,也成为RB树(Red-Black Tree)。RB树的统计性能要好于一般平衡二叉树,所以被STL选择作为了关联容器的内部结构。
set中的常用方法:

*s.begin()        ,返回set容器的第一个元素
end()      ,返回set容器的最后一个元素
clear()          ,删除set容器中的所有的元素
empty()    ,判断set容器是否为空
max_size()   ,返回set容器可能包含的元素最大个数
size()      ,返回当前set容器中的元素个数
*s.rbegin()  *s.end()    ,返回的值和end()相同
rend()     ,返回的值和rbegin()相同

【C++基本数据类型】

三种基本数据类型
在这里插入图片描述
int的取值范围是-2^31 - 2^31-1 ,也就是-2147483648 - 2147483647


【设计模式的分类】

亚信面试有问道

总体分为三类

创建型模式:分为五种 工厂方法模式 抽象工厂模式 单例模式 建造者模式 原型模式
结构性模式: 分为七种 适配器模式 装饰者模式 代理模式 外观模式 桥接模式 组合模式 享元模式
行为型模式:十一种 策略模式 模板方法模式 观察者模式 迭代子模式 责任链模式 命令模式 备忘录模式 状态模式 访问者模式 中介者模式 解释器模式

设计模式的六大原则
总原则—开闭原则(对扩展开放,对修改封闭)
1、单一职责原则
2、里氏替换原则
3、依赖倒转原则
4、接口隔离原则
5、迪米特法则(最少知道原则)
6、合成复用原则


【Linux系统的应用场景】

1)Android手机的底层内核都是linux,大量智能硬件、嵌入式系统底层也是linux
2)在超算领域,90%以上的超级计算机所运行的操作系统,都是linux二次开发的,甚至在2017年,世界前500的超级计算机全部运行Linux
3)各大型互联网公司数据中心服务器的底层操作系统大部分为linux


【HTTP1.0 HTTP1.1 HTTP2.0的区别】

参考资料 资料二
一个演示资料

在这里插入图片描述
总的区别就是:

HTTP/2协议解析 采用二进制格式而非文本格式,实现方便且健壮;
HTTP/2 连接特点是完全多路复用的--连接共享,而非有序并阻塞的——只需一个连接即可实现并行;
HTTP/2 使用报头压缩,HTTP/2降低了开销;
HTTP/2 server push 功能 --- 服务器可以将响应主动“推送”到客户端缓存中
;


【char 和 varchar】

区别一:定长和变长
char 表示定长,长度固定,varchar表示变长,即长度可变;
char如果插入的长度小于定义长度时,则用空格填充;varchar小于定义长度时,还是按实际长度存储,插入多长就多长;
因为其长度固定,char的存取速度还是要比varchar要快得多,方便程序的存储与查找;但是char也为此付出的是空间的代价,因为其长度固定,所以会占据多余的空间,可谓是以空间换取时间效率。varchar则刚好相反,以时间换空间。
区别二:存储容量不同
char最多存放的字符个数是255,和编码无关;
varchar最多存放65532个字符;


【ssh命令及操作】

参考资料
ssh是一种用于实现计算机之间加密登录的网络协议。
SSH之所以能够保证安全,原因在于它采用了 公钥加密(非对称加密方式)
整个过程是这样的:
(1)远程主机收到用户的登录请求,把自己的公钥发给用户。
(2)用户使用这个公钥,将登录密码加密后,发送回来。
(3)远程主机用自己的私钥,解密登录密码,如果密码正确,就同意用户登录。


【CPU架构】

CPU架构是CPU商给CPU产品定的一个规范,主要目的是区分不同类型的CPU;
CPU是一个具有特定功能的芯片,里面含有指令集;
CPU分为两个主要的单元,分别是 : 算数逻辑单元和控制单元;
指令集的设计分为两种设计理念,这也是世界上最常见的两种CPU架构:精简指令集和复杂指令集

精简指令集--ARM架构

这种cpu设计中,指令集较为精简,每个指令的运行时间较短,完成的操作也很简单,指令的执行性能较佳,但是如果要做复杂的事情,就需要多条指令来完成。
常见的RISC指令集CPU有Oracle公司的SPARC系列、IBM公司的PowerPC系列和ARM公司的ARM CPU系列等。

复杂指令集--x86架构

另一种cpu指令集架构叫做复杂指令集,复杂指令集顾名思义就是指令数目多且复杂,每条指令的长度并不相同。
每条指令可以执行的操作较为丰富,但执行的时间较长。
常见的使用复杂指令集的CPU有AMD、Intel、VIA等x86架构的CPU。
由于AMD、Intel、VIA开发出来的x86架构CPU被大量的使用再PC上,所以PC常被称为x86架构电脑。
因为Intel最早研发出来的CPU代号称为8086,后来又有8028680386等,所以就称这种CPU为x86架构。
又由于CPU需要从内存中读取数据量进行处理,每次从内存中读取的数据量有816再到32再到后来的64,也就是一次能够读取到的数据的位数,例如一个32位的CPU能够一次读写4GB最大数据量。


【lamda表达式(匿名函数对象) Inline && constexpr函数】

一个lamda表达式表示一个可调用的代码单元;是一个可调用对象;直接调用这块代码可以减少函数调用,代码简洁
它的形式是:
【捕获列表】【参数列表】->【返回类型】【函数体】

关于捕获列表:
空的捕获列表表明该lamda表达式不使用它所在函数中的任何局部变量;
一个lamda表达式只有在其捕获列表中捕获一个它所在函数中的局部变量,才能在函数体中使用该变量

函数式接口:
任何接口,如果只包含唯一方法,那么它就是一个函数式接口。对于函数式接口,我们可以通过lamda表达式来创建该接口的对象;

为什么使用lamda表达式?
避免匿名函数内部类定义过多
可以让你的代码看起来很简洁
去掉了一堆没有意义的代码,只留下核心的逻辑


Inline
在函数的返回类型前加上inline,就将函数声明为内联函数了;
将函数声明为内联函数,通常就是将它在每个调用点上内联的展开;
它的工作机制就是:在需要调用函数的地方,直接调用那一段代码,而不是调用函数,这样会增加代码的体量,增加内存消耗,但是减少了函数调用的开销;用空间换取时间;


constexpr
constexpr函数是指能用于常量表达式的函数
定义constexpr函数的时候需要注意以下几点:
函数的返回类型及所有形参都是字面值常量
函数体中必须有且仅有一条return 语句


猜你喜欢

转载自blog.csdn.net/weixin_48433164/article/details/123348806
今日推荐