1.TCP(传输控制协议)/UDP(用户数据报协议)区别
1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的
UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
5、TCP首部开销20字节;UDP的首部开销小,只有8个字节
6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道
2.TCP三次握手、四次挥手机制,以及原因。
三次握手:
(1)第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
(2)第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
(3)第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。
四次挥手:
(1)第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
(2)第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
(3)第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
(4)第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
原因:
答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
3.c++class和c的struct有何区别
1. struct与class有着非常明显的区别。C是一种过程化的语言,struct只是作为一种复杂数据类型定义,struct中只能定义成员变量,不能定义成员函数。
C++中的struct和class的区别:
对于成员访问权限以及继承方式,class中默认的是private的,而struct中则是public的。class还可以用于表示模板类型,struct则不行。但两者都能有自己的构造函数/成员函数/能继承/能实现多态。
4.new和malloc的区别
new/delete是C++关键字,需要编译器支持。malloc/free是库函数,需要头文件支持。
使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸。
new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。
new内存分配失败时,会抛出bac_alloc异常。malloc分配内存失败时返回NULL。
new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。
malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。
C++允许重载new/delete操作符,特别的,布局new的就不需要为对象分配内存,而是指定了一个地址作为内存起始区域,new在这段内存上为对象调用构造函数完成初始化工作,并返回此地址。而malloc不允许重载。
new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。自由存储区不等于堆,如上所述,布局new就可以不位于堆中。
5.虚函数机制
重载:同一类中,函数名相同,但参数列表不同。
重写:父子类中,函数名相同,参数列表相同,且有virtual修饰。
隐藏:父子类中,函数名相同,参数列表相同,但没有virtual修饰;
所有以virtual修饰符开始的成员函数都成为虚方法。此时注意是virtual修饰的成员函数不是virtual修饰的成员函数名。
虚函数表是如何创建和继承的。
基类的虚函数表的创建:首先在基类声明中找到所有的虚函数,按照其声明顺序,编码0,1,2,3,4……,然后按照此声明顺序为基类创建一个虚函数表,其内容就是指向这些虚函数的函数指针,按照虚函数声明的顺序将这些虚函数的地址填入虚函数表中。例如若show放在虚函数声明的第二位,则在虚函数表中也放在第二位。
对于子类的虚函数表:首先将基类的虚函数表复制到该子类的虚函数表中。若子类重写了基类的虚函数show,则将子类的虚函数表中存放show的函数地址(未重写前存放的是子类的show虚函数的函数地址)更新为重写后函数的函数指针。若子类增加了一些虚函数的声明,则将这些虚函数的地址加到该类虚函数表的后面。
虚函数表是如何访问的。
当执行pBase->show()时,要观察show在Base基类中声明的是虚函数还是非虚函数。若为虚函数将使用动态联编(使用虚函数表决定如何调用函数),若为非虚函数则使用静态联编(根据调用指针pBase的类型来确定调用哪个类的成员函数)。此处假设show为虚函数,首先:由于检查到pBase指针类型所指的类Base中show定义为虚函数,因此找到pBase所指的对象(有可能是Base类型也可能是Extend类型。),访问对象得到该对象所属类的虚函数表地址。
注意:
1、 若要在子类中重新定义父类的方法(有virtual为重写,无virtual为隐藏),则应确保子类中的函数声明和父类函数声明中的形参完全一样。但返回值类型是基类引用/指针的成员函数在重新定义时可以返回子类的引用/指针(返回值协变),这是由于子类的对象可以赋给基类引用/指针。
2、 若基类中声明了函数的重载版本,则在派生类中重新定义时应该重新定义所有基类的重载版本。这是因为,重新定义一个函数,其他的基类重载版本将被隐藏,导致子类无法使用这些基类的成员方法。所以需要每个都重新定义。若一些父类的重载版本,子类确实不需要修改,则由于重新定义了一个重载版本,即使有些重载版本不需要修改也要重新定义,在定义体中直接调用基类的成员方法(使用作用于限定符访问)。
3、 从虚函数的实现机制可以看到要想在子类中实现多态需要满足三个重要的条件。(1)在基类中函数声明为虚函数。(2)在子类中,对基类的虚函数进行了重写。(3)基类的指针指向了子类的对象。
6.vector和list的区别
vector
vector与数组类似,拥有一段连续的内存空间,并且起始地址不变。便于随机访问,时间复杂度为O(1),但因为内存空间是连续的,所以在进入插入和删除操作时,会造成内存块的拷贝,时间复杂度为O(n)。
此外,当数组内存空间不足,会采取扩容,通过重新申请一块更大的内存空间进行内存拷贝。
List
list底层是由双向链表实现的,因此内存空间不是连续的。根据链表的实现原理,List查询效率较低,时间复杂度为O(n),但插入和删除效率较高。只需要在插入的地方更改指针的指向即可,不用移动数据。
总之,如果需要高效的随机存取,而不在乎插入和删除的效率,使用vector;
如果需要大量的插入和删除,而不关心随机存取,则应使用list。
7.定义宏计算数组长度
#define ARRAY_SIZE(ARRAY) (sizeof(ARRAY)/sizeof(ARRAR[0]))
8.内存模型
内存模型说的是,在多核多线程环境下,各种不同的CPU是如何以一种统一的方式来与内存交互的。CPU和内存并不是直接交换数据的,它们之间还隔着一个高速缓存。在某些特殊的情形下(多核多线程),就不能忽略缓存的存在了。这其实是和缓存的设计有关系,一般多处理器下的每个CPU都有一个自己的缓存,存储在这个缓存的数据是其它CPU是无法查看的。
内存可见性:
通过一个协议来保证数据在各个CPU的缓存是一致性,这就是缓存一致性协议。
关于缓存一致性简单的举个列子。CPU-0尝试STORE(更新)变量x,但其发现其它CPU的缓存也持有这个x的copy(x此时为Shared状态,非单个CPU独占),那么当CPU-0在STORE之前,必须通过一个disable消息,告诉其它CPU所持有的变量x已经为脏数据,是不可用状态。其它CPU在收到这个disable消息后必须回应CPU-0一个ack消息,这时候CPU-0才能开始STORE变量x。
乱序(memory reorder):
乱序,指的是程序指令实际上执行的顺序,和我们书写的指令的顺序不一致。乱序分两种,分别是编译器的指令重排和CPU的乱序执行。本意上乱序是为了优化指令执行的速度而产生的。并且为了维护程序原来的语义,编译器和CPU不会对两个有数据依赖的指令重排(reorder)。这种保护在单线程的环境下是可以工作的,但是到了多线程,问题就复杂了。
举个例子,CPU-0将要执行两条指令,分别是:
当CPU-0执行指令1的时候,发现这个变量x的当前状态为Shared,这意味着其它CPU也持有了x,因此根据缓存一致性协议,CPU-0在修改x之前必须通知其它CPU,直到收到来自其它CPU的ack才会执行真正的修改x。但是,事情没有这么简单。现代CPU缓存通常都有一个Store Buffer,其存在的目的是,先将要Store的变量记下来,注意此时并不真的执行Store操作,然后待时机合适的时候再执行实际的Store。有了这个Store Buffer,CPU-0在向其它CPU发出disable消息之后并不是干等着,而是转而执行指令2(由于指令1和指令2在CPU-0看来并不存在数据依赖)。这样做效率是有了,但是也带来了问题。虽然我们在写程序的时候,是先STORE x再执行LOAD y,但是实际上CPU却是先LOAD y再STORE x,这个便是CPU乱序执行(reorder)的一种情况!
当你的程序要求指令1、2有逻辑上的先后顺序时,CPU这样的优化就是有问题的。但是,CPU并不知道指令之间蕴含着什么样的逻辑顺序,在你告诉它之前,它只是假设指令之间都没有逻辑关联,并且尽最大的努力优化执行速度。因此我们需要一种机制能告诉CPU:这段指令执行的顺序是不可被重排的!做这种事的就是内存屏障(memory barrier)!
还是上面那个例子,如果不想指令1、2被CPU重排,程序应该这么写:
通过在STORE x之后加上这个写内存屏障,就能保证在之后LOAD y指令不会被重排到STORE x之前了。
因此,我们有了内存模型的概念。不同平台下的实现差别被统一的内存模型所隐藏,只需要根据这个抽象的内存模型来编写程序即可,这便是伟大的抽象...
9.内存分区
C++内存分为5个区域(堆栈全常代 ):
10.一个程序大小是由哪些决定的
多使用系统函数,减少循环递归,动态内存分配,然后用完释放掉。
11.不用第三个变量,交换两个值。
a=a*b;b=a/b;a=a/b;
a=a^b;b=a^b;a=a^b;
a=a+b-(b=a);
12.位运算操作
13.堆和栈的区别
(1)管理方式:堆中资源由程序员控制(通过malloc/free、new/delete,容易产生memory leak),栈资源由编译器自动管理。
(2)系统响应:对于堆,系统有一个记录空闲内存地址的链表,当系统收到程序申请时,遍历该链表,寻找第一个大于所申请空间的堆结点,删除空闲结点链表中的该结点,并将该结点空间分配给程序(大多数系统会在这块内存空间首地址记录本次分配的大小,这样delete才能正确释放本内存空间,另外,系统会将多余的部分重新放入空闲链表中)。对于栈,只要栈的剩余空间大于所申请空间,系统就会为程序分配内存,否则报异常出现栈空间溢出错误。
(3)空间大小:堆是不连续的内存区域(因为系统是用链表来存储空闲内存地址的,自然不是连续),堆的大小受限于计算机系统中有效的虚拟内存(32位机器上理论上是4G大小),所以堆的空间比较灵活,比较大。栈是一块连续的内存区域,大小是操作系统预定好的,windows下栈大小是2M(也有是1M,在编译时确定,VC中可设置)。
(4)碎片问题:对于堆,频繁的new/delete会造成大量内存碎片,降低程序效率。对于栈,它是一个先进后出(first-in-last-out)的结构,进出一一对应,不会产生碎片。
(5)生长方向:堆向上,向高地址方向增长;栈向下,向低地址方向增长。
(6)分配方式:堆是动态分配(没有静态分配的堆)。栈有静态分配和动态分配,静态分配由编译器完成(如函数局部变量),动态分配由alloca函数分配,但栈的动态分配资源由编译器自动释放,无需程序员实现。
(7)分配效率:堆由C/C++函数库提供,机制很复杂,因此堆的效率比栈低很多。栈是机器系统提供的数据结构,计算机在底层对栈提供支持,分配专门的寄存器存放栈地址,提供栈操作专门的指令。