C++常用技术点梳理

一、static与volatile的用法

(1)函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;
(2)在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;
(3)在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;
(4)在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
(5)在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。

一个指针可以是 volatile 吗?
可以,因为指针和普通变量一样,有时也有变化程序的不可控性。常见例:子中断服务子程序修改一个指向一个 buffer 的指针时,必须用 volatile 来修饰这个指针。
说明:指针是一种普通的变量,从访问上没有什么不同于其他变量的特性。其保存的数值是个整型数据,和整型变量不同的是,这个整型数据指向的是一段内存地址。

C++的引用和 C 语言的指针有什么区别?
指针和引用主要有以下区别:
(1)引用必须被初始化,但是不分配存储空间。指针不声明时初始化,在初始化的时候需要分配存储空间。
(2)引用初始化以后不能被改变,指针可以改变所指的对象。
(3)不存在指向空值的引用,但是存在指向空值的指针。
注意:引用作为函数参数时,会引发一定的问题,因为让引用作参数,目的就是想改变这个引用所指向地址的内容,而函数调用时传入的是实参,看不出函数的参数是正常变量,还是引用,因此可能会引发错误。所以使用时一定要小心谨慎。

 类(class)与结构(struct)的区别?

(1)默认的继承访问权限: struct是public的,class是private的;
(2)class是引用类型,struct是值类型;
(3)class可以继承类、接口和被继承,struct只能继承接口,不能被继承;
(4)class有默认的无参构造函数,有析构函数,struct没有默认的无参构造函数,且只能声明有参的构造函数,没有析构函数;
(5)class可以使用abstract和sealed,有protected修饰符,struct不可以用abstract和sealed,没有protected修饰符;
(6)class必须使用new初始化,结构可以不用new初始化;
(7) class实例由垃圾回收机制来保证内存的回收处理,而struct变量使用完后立即自动解除内存分配;

从职能观点来看,class表现为行为,而struct常用于存储数据;

作为参数传递时,class变量以按址方式传递,而struct变量是以按值方式传递的。

如何选择使用结构还是类:
1).堆栈的空间有限,对于大量的逻辑的对象,创建类要比创建结构好一些
2).结构表示如点、矩形和颜色这样的轻量对象,例如,如果声明一个含有 1000 个点对象的数组,则将为引用每个对象分配附加的内存。在此情况下,结构的成本较低
3).在表现抽象和多级别的对象层次时,类是最好的选择
4).大多数情况下该类型只是一些数据时,结构是最佳的选择

C、C++中内存分配方式可以分为三种:

(1)从静态存储区域分配:
内存在程序编译时就已经分配好,这块内存在程序的整个运行期间都存在。速度快、不容易出错,因为有系统会善后。例如全局变量,static 变量等。
(2)在栈上分配:
在执行函数时,函数内局部变量的存储单元都在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
(3)从堆上分配:
即动态内存分配。程序在运行的时候用 malloc 或 new 申请任意大小的内存,程序员自己负责在何时用 free 或 delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活。如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,另外频繁地分配和释放不同大小的堆空间将会产生堆内碎块。
一个 C、C++程序编译时内存分为 5 大存储区:堆区、栈区、全局区、文字常量区、程序代码区。
 

二、new/delete与malloc/free的区别与详解

malloc 和 new 有以下不同:
(1)new、delete 是操作符,可以重载,只能在 C++中使用。
(2)malloc、free 是函数,可以覆盖,C、C++中都可以使用。
(3)new 可以调用对象的构造函数,对应的 delete 调用相应的析构函数。
(4)malloc 仅仅分配内存,free 仅仅回收内存,并不执行构造和析构函数
(5)new、delete 返回的是某种数据类型指针,malloc、free 返回的是 void 指针。
注意:malloc 申请的内存空间要用 free 释放,而 new 申请的内存空间要用 delete 释放,不要混用。因为两者实现的机理不同。

三、C++ static_cast、dynamic_cast、const_cast和reinterpret_cast(四种类型转换运算符)

四、POD类型与非POD类型用法区别

五、TCP连接的三次握手与四次挥手

TCP协议通过三个报文段完成连接的建立建立连接的过程称为三次握手(three-way handshake)。

        第一次握手建立连接时客户端发送syn包(syn=j)到服务器并进入SYN_SEND状态等待服务器确认SYN同步序列编号(Synchronize Sequence Numbers)。

    第二次握手服务器收到syn包必须确认客户的SYNack=j+1同时自己也发送一个SYN包syn=k即SYN+ACK包此时服务器进入SYN_RECV状态
第三次握手客户端收到服务器的SYN+ACK包向服务器发送确认包ACK(ack=k+1)此包发送完毕客户端和服务器进入ESTABLISHED状态完成三次握手。
    一个完整的三次握手是 请求---应答---再次确认。

    当客户端调用connect时触发了连接请求向服务器发送了SYN J包这时connect进入阻塞状态服务器监听到连接请求即收到SYN J包调用accept函数接收请求向客户端发送SYN K ACK J+1这时accept进入阻塞状态客户端收到服务器的SYN K ACK J+1之后这时connect返回并对SYN K进行确认服务器收到ACK K+1时accept返回至此三次握手完毕连接建立。

终止一个连接要经过四次握手简称四次挥手释放

    TCP连接是全双工的每个方向都必须单独进行关闭。当一方完成数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭而另一方执行被动关闭。

A、客户端A发送一个FIN用来关闭客户A到服务器B的数据传送

B、服务器B收到这个FIN它发回一个ACK确认序号为收到的序号加1。和SYN一样一个FIN将占用一个序号。

   C、服务器B关闭与客户端A的连接发送一个FIN给客户端A。

   D、客户端A发回ACK报文确认并将确认序号设置为收到序号加1。

    为什么建立连接协议是三次握手而关闭连接却是四次挥手呢
     因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后它可以把ACK和SYNACK起应答作用而SYN起同步作用放在一个报文里来发送。但关闭连接时当收到对方的FIN报文通知时仅仅表示对方没有数据发送给你了但你所有的数据未必都全部发送给对方了你未必会马上会关闭SOCKET你可能还需要发送一些数据给对方之后再发送FIN报文给对方来表示你同意现在可以关闭连接了所以ACK报文和FIN报文多数情况下都是分开发送的。
    为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态
        虽然双方都同意关闭连接了而且握手的4个报文也都协调和发送完毕按理可以直接回到CLOSED状态就好比从SYN_SEND状态到 ESTABLISH状态那样但是因为我们必须要假想网络是不可靠的你无法保证你最后发送的ACK报文会一定被对方收到因此对方处于 LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文而重发FIN报文所以这个TIME_WAIT状态的作用就是用来重发可能丢失的 ACK报文。
 

setsockopt的几个常用用法

1、不需要等发送缓冲区的数据发送完,直接关闭socket,这个在close(socket)中使用。

socklen_t optlen;
struct linger sLinger;
optlen = sizeof(struct linger);
if(setsockopt(pTCP->tcpSock, SOL_SOCKET, SO_LINGER, (const char*)&sLinger, optlen) < 0)
{
	err_handler();
}

2、发送缓冲区的设置,网上大多数用32k,防止循环发送?但其实实现的要比设置的大很多,经过测试为2倍,所以16k足矣。

optval = 16*1024;	
optlen =  sizeof(int);	
if(setsockopt(pTCP->tcpSock, SOL_SOCKET, SO_SNDBUF, (const char*)&optval, optlen) < 0)
{
	err_handler();
}

3、连接服务器的最大时间设置为20s,这个应发送时间超时时间。是写操作,和connect无关。

struct timeval timeo; 
timeo.tv_sec = 20;
timeo.tv_usec = 0;
optlen =  sizeof(timeo);	
if(setsockopt(pTCP->tcpSock, SOL_SOCKET, SO_SNDTIMEO, &timeo, optlen) < 0)
{
	err_handler();
}

4、心跳,tcp命令都是有心跳的,在心跳包处发一个空数据帧,

开启心跳监测,默认的太久了,不现实。

int keepAlive = 1;
setsockopt(pTCP->tcpSock, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));

在keepIdle秒内,没有任何数据来往,则进行监测
发包的时间间隔为keepInterval秒,探测尝试的次数为keepCount次,如果第一次探测包收到响应了,第二次就不发了(有的是tcp_keepalive_probes)

setsockopt(pTCP->tcpSock, SOL_TCP, TCP_KEEPIDLE, (void*)&keepIdle, sizeof(keepIdle));
setsockopt(pTCP->tcpSock, SOL_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
setsockopt(pTCP->tcpSock, SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));

六、实现memcpy内存拷贝,注意参数检查和内存重叠

七、输入数学公式,输出计算结果

八、实现线程安全的hashmap

九、有100G数据,只有4G内存,如何完成100G数据的排序

十、(互斥锁、条件变量、读写锁、自旋锁、信号量)多线程的同步与互斥

十一、找出两个链表并集

十二、反转一个链表

// (1)反转一个链表。循环算法。
AList reverse(List n)
{
	if(!n) 	//判断链表是否为空,为空即退出。
	{
		return n;
	}
	list cur = n.next; //保存头结点的下个结点
	list pre = n; 	//保存头结点
	list tmp;
	pre.next = null; //头结点的指针指空,转换后变尾结点
	while ( NULL != cur.next ) 	//循环直到 cur.next 为空
	{
		tmp = cur;
		tmp.next = pre
		pre = tmp;
		cur = cur.next;
	}
	return tmp; 	//f 返回头指针
}
// (2)反转一个链表。递归算法。
List *reverse( List *oldList, List *newHead = NULL )
{
	List *next = oldList-> next; 	//记录上次翻转后的链表
	oldList-> next = newHead; 	//将当前结点插入到翻转后链表的开头
	newHead = oldList; 	//递归处理剩余的链表
	
	return ( next==NULL )? newHead: reverse( t, newHead );
}

说明:循环算法的移动过程,比较好理解和想到。递归算法的设计虽有一点难度,但是理解了循环算法,再设计递归算法就简单多了。

C++知识点整理_yxtxiaotian的专栏-CSDN博客_c++知识点梳理

猜你喜欢

转载自blog.csdn.net/shaderdx/article/details/104706797
今日推荐