TCP和UDP区别
TCP
- 面向连接。在传输数据之前,通信双方需要先建立一个连接(三次握手)。
- 可靠性。TCP提供可靠的数据传输,它通过序列号、确认应答、重传机制和校验和等技术确保数据的正确传输。
- 数据顺序:TCP保证数据按发送书序到达接收端。即使数据包在传输过程中拆分和重组,接收端也会按正确顺序组装数据。
- 流量控制:TCP 提供流量控制机制,通过滑动窗口协议调整数据传输速率,防止发送端过快发送数据而导致接收端处理不过来。
- 拥塞控制:TCP 包含拥塞控制机制,避免网络拥塞,保证网络资源的有效利用。
TCP三次握手
第三次是必须的,为了防止已经失效的连接请求报文突然又传送给服务端,然后发生错误、
TCP三次握手是建立可靠连接的过程,包括:
- 客户端向服务端发送连接请求报文段,包含自身数据通讯初始序号,进入SYS_SENT状态。
- 服务器收到连接请求报文段后,如果同意,发送应答,包含自身数据通讯初始序号,进入SYN-RECEVICED状态。
- 客户端收到应答,最后向服务器端发送确认报文,进入ESTABISHED状态,此时成功建立长连接。
TCP四次挥手是关闭连接的过程:
- 客户端希望关闭连接,发送一个FIN包给服务器,表示数据发送完毕,不再发送数据。这时客户端进入FIN_WAIT_1状态。
- 服务器收到FIN包后,发送一个ACK包给客户端,表示确认收到客户端的FIN包,这个包包含对客户端FIN包的确认序列号。服务器进入CLOSE_WAIT状态,客户端收到这个ACK包后进入FIN_WAIT_2状态。
- 服务器希望关闭连接,发送一个FIN包给客户端,表示数据发送完毕,不再发送数据。服务器进入LAST_ACK状态。
- 客户端收到服务器的FIN包后,发送一个ACK包给服务器,表示确认收到服务器的FIN包,包括确认序号。客户端进入TIME_WAIT状态,等待一段时间(通常是两倍的最大段寿命,MSL),确保服务器收到ACK包后在进入CLOSED状态。
- 服务器收到ACK包后,立即进入CLOSE状态,连接正式关闭。
delete和free的区别
- delete是操作符,而free是函数
- delete用于释放new分配的空间,free用于释放mallo、calloc、realloc分配的空间
- delete会调用对象的析构函数,free不会
- 调用delete之前不用检查指针是否为空,调用free时需要检查
什么是面向对象
面向过程思想
完成一个需求的步骤:首先搞清楚做什么,然后分析怎么做,最后在通过代码体现。一步一步去实现,而具体的每一步都需要我们去实现和操作。这些步骤相互调用和协作,从而完成需求。在上面的每一个具体步骤中我们都是参与者,并且需要面对具体的每一个步骤和过程,这就是面向过程最直接的体现。
面向过程编程,其实就是面向着具体的每一个步骤和过程,把每一个步骤和过程完成,然后由这些功能函数相互调用,完成需求。
面向对象思想
面向对象的思想是尽可能模拟人类的思维方式,使得软件的开发方法与过程尽可能接近人类认识世界。
面向对象以对象为核心,该思想认为程序由一系列对象组成。是一种更符合人类思维习惯的思想,可以将复杂的问题简单化。
- 封装:将事务的属性和行为封装在一起,也就是C++中的类,便于管理,提高代码的复用性。事物的属性和行为分别对应类中的成员变量和成员方法。
- 继承:通过继承父类的属性和方法实现代码的重用和扩展。
- 多态:多个子类可以通过继承同一个父类,并以不同方式实现父类的同一方法,从而使得同一个接口可以调用不同的实现。
C++的重载和重写
重载
- 重载指不同的函数用相同的函数名,但是参数的类型、个数或顺序不同(参数列表不同)。调用的时候根据函数的参数来区别不同的函数,函数重载跟返回值无关。
- 重载的规则------函数名相同------参数列表不同
- 重载用来实现静态多态
- 重载是多个函数或同一个类中的方法,是平行关系。
重写
- 重写也叫覆盖,是指在派生类中重新对基类中的虚函数重新实现。即函数名和参数都一样,只是函数的实现体不一样。
- 重写的规则------方法声明必须完全与父类中被重写的方法相同------访问修饰符的权限要大于或等于父类中被重写的方法的访问修饰符------子类重写的方法可以加virtual,也可以不加。
- 重写用于实现动态多态(根据调用方法的对象的类型不同来执行不同的函数)。
- 重写是父类和子类之间的关系,是垂直关系。
重载在编译时会将同名的函数或方法根据某种规则生成不同的函数或方法名。
C++中重写可以用来实现动态多态。父类中需要重写的方法要加上virtual关键字。虚函数实现的原理是采用虚函数表,多态中每个对象内存都有一个虚函数指针,指向虚函数表,表中记录的是该类的所有虚函数的入口地址,所以对象能根据它自身的类型调用不同的函数。
浅拷贝和深拷贝
- 浅拷贝又称为值拷贝,将源对象的值拷贝到目标对象中,如果对象中有某个成员是指针类型数据,并且是在堆区创建,则使用浅拷贝仅仅拷贝的是这个指针变量的值,也就是在目标对象中该类指针类型数据和源对象中的该成员指向的是同一块堆空间。这样会带来一个问题,在析构函数中释放堆区数据,会被释放多次。默认的拷贝构造函数和默认的赋值运算符重载都是浅拷贝。
- 深拷贝,深拷贝在拷贝的时候先开辟出和源对象大小一样的空间,然后将源对象里的内容拷贝到目标对象中区,这样指针成员就指向了不同的内存位置。并且里面的内容是一样的,两个对象先后调用析构函数,分别释放自己指针成员指向的内存。
深拷贝:每次增加一个指针,便申请一块新的内存,并让这个指针指向新的内存,深拷贝情况下,不会出现重复释放同一块内存的错误。
C++中的多态
- 静态多态:静态多态是编译器在编译期间完成的,编译器会根据实参类型来选择调用合适的参数,如果有合适的参数就调用,没有的话就会发出警告或者报错。静态多态有函数重载、运算符重载、泛型编程等。
- 动态多态:动态多态是在程序运行时根据基类的引用(指针)指向的对象来确定自己具体该调用哪一个类的虚函数。当父类指针指向父类对象时,就调用父类中定义的虚函数;当父类指针指向子类对象时,就调用子类中定义的虚函数。
动态多态行为的表现效果为:
- 同样的调用语句在实际运行时有多种不同的表现形态。
- 实现动态多态的条件:要有继承关系------要有虚函数重写------要有父类指针指向子类对象。
动态多态的实现原理:当类中声明虚函数时,编译器会在类中生成一个虚函数表,虚函数表是一个存储类虚函数指针的数据结构,虚函数表是由编译器自动生成与维护的。
virtual 成员函数会被编译器放入虚函数表中,存在虚函数时,每个对象中都有一个指向虚函数表的指针。在多态调用时,虚函数指针就会根据这个对象在对应类的虚函数表中查找被调用的函数,从而找到函数的入口地址。
虚函数的实现原理
C++中的虚函数的作用主要是实现了动态多态的机制。
动态多态,简单的说就是父类型的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有多种形态,这是一种泛型技术。
虚函数实现原理
编译器处理虚函数时,给每个对象添加一个隐藏的成员。隐藏的成员是一个指针类型的数据,指向的是函数地址数组,这个数组被称为虚函数表。
虚函数表中存储的是虚函数的地址。
如果派生类重写了基类中的虚函数,则派生类对象的虚函数表中保存的是派生类的虚函数地址,如果派生类没有重写基类中的虚函数,则派生类对象的虚函数表中保存的是父类的虚函数地址。
使用虚函数时,对于内存和执行速度方面会有一定的成本:
- 每个对象都会变大,变大的量为存储虚函数表指针;
- 对于每个类,编译器都会创建一个虚函数表;
- 对于每次调用虚函数,都需要额外执行一个操作,就是到表中查找虚函数地址。
纯虚函数
纯虚函数是一种特殊的虚函数,它的格式是:虚函数不给出具体的实现,也就是后面没有大括号实现体,而在后面加上=0。
cpp
class A{
virtual 返回类型 函数名(参数列表) = 0;
};
很多情况下,在基类中不能对虚函数给出具体的有意义的实现,就可以把它声明为纯虚函数,它的实现留给该基类的派生类去做。
如果一个类中有纯虚函数,那么这个类也被称为抽象类,这种类不能实例化对象,也就是不能创建该类的对象。除非在派生类中实现所有基类中的纯虚函数,否则派生类也是抽象类,不能实例化对象。
虚析构函数
当通过基类指针删除派生类对象时,虚析构函数可以确保派生类的析构函数被正确调用。
如果基类的析构函数不是虚函数,通过基类指针删除派生类对象,只会调用基类的析构函数,不会调用派生类的析构函数。这可能导致资源泄露,因为派生类特有的资源没有被正确释放。
cpp
class Base{
public:
virtual ~Base(){}
}
虚析构函数的主要作用是为了防止泄露资源的释放,防止内存泄漏。
重载、重写、隐藏的区别
- 重载:在同一作用域中,同名函数的形式参数不同时,构成函数重载,与返回值类型无关。
- 重写:指不同作用域中定义的同名函数构成隐藏。派生类成员函数隐藏与其同名的基类成员函数、类成员函数隐藏全局外部函数
C++中的四种类型转换
使用C风格的类型转换可以把想要的任何东西转换成我们需要的类型,但是这种类型转换太过松散,对于这种松散的情况,C++提供了更严格的类型转换,可以提供更好的控制转换过程,并添加4个类型转换运算符。
- static_cast:用于在相关类型之间进行转换,比如数值类型之间、枚举类型与整形之间、以及指针类型的向上和向下转换。编译器检查转换是否合法,不会在运行时检查。
cpp
int i = 10;
double b = static_cat<double>(i);
- dynamic_cast:用于在继承体系中的指针或引用类型之间的转换,并且在运行时进行类型检查。它只能用于有虚函数的多态基类。
- const_cast用于在同类型的const和非const之间进行转换。唯一可以用于去除const属性的类型转换操作符。
- reinterpret_cast:用于进行低级别的重新解释转换。
STL常见容器
STL中容器分为顺序容器、关联式容器、容器适配器三种类型。
- 顺序容器 容器并非排序的,元素的插入位置同元素的值无关。包含vector、deque、list。
vector:动态数组 元素在内存连续存放。随机存取任何元素都能在常数时间内完成。在尾端增删元素具有较佳的性能。
deque:双端队列,元素在内存连续存放,随机存取任何元素都能在常数时间内完成,在两端增删元素具有较佳性能。
list:双向链表,元素在内存不连续存放。在任何位置增删元素都能在常数时间完成。不支持随机存取。
- 关联式容器,元素是排序的。
通常以平衡二叉树实现。
set、multiset
map、multimap
map中存放的元素有且仅有两个成员变量,一个名为first,另一个名为second,map根据first值对元素从小到大排序。
- 容器适配器
封装了一些基本的容器,使之具备了新的函数功能,包含 stack、queue、priority_queue。 - stack:栈 栈是项的有限序列,并满足序列中被删除、检索和修改的项只能是最进插入序列的项(栈顶的项),后进先出。 - queue:队列 插入只可以在尾部进行,删除、检索和修改只允许从头部进行,先进先出。
priority_queue:优先级队列,内部维持某种有序,然后保证优先级最高的位于头部,第一个出队列。
默认情况下,priority_queue是最大堆,即优先级最高的元素是最大的元素。
循环队列
循环队列(Circular Queue),也称为环形队列,是一种常见的数据结构,用于在固定大小的缓冲区内进行数据存储。
循环队列的最大特点是首尾相连,当队列的尾部指针到达缓冲区的末尾时,如果有空间空间,它会绕回到缓冲区的开头继续存储数据。
- 通常,循环队列可以用一个固定大小的数组和两个指针来表示,一个指向队列的头部(front),一个指向队列的尾部(rear)。
- 初始化:初始化时,队列为空,头尾指针都指向数组的同一个位置,通常可以选择0位置。
- 队列为空的条件是头尾指针相等,并且没有存储任何元素。
- 队列满的条件是尾指针的下一个位置等于头指针。
- 入队:将新元素加入到队列尾部,并将尾指针后移。如果尾指针到达数组末尾,则环绕到数组开头。
- 出队:从队列头部取出元素,并将头指针后移,如果头指针到达数组末尾,则环绕到数组开头。