一、运算符优先级
优先级【高到低】:
第一级:() 【】 -> .
圆括号【()】、下标运算符【[]】、分量运算符的指向结构体成员运算符【->】、结构体成员运算符【.】
第二级:! ~ ++ -- - (int..) * & sizeof
逻辑非运算符【!】、按位取反运算符【~】、自增自减运算符【++ --】、负号运算符【-】、类型转换运算符【(类型)】、指针运算符和取地址运算符【*和&】、长度运算符【sizeof】
第三级:乘法运算符【*】、除法运算符【/】、取余运算符【%】
第四级:加法运算符【+】、减法运算符【-】
第五级:左移动运算符【<<】、右移动运算符【>>】
第六级:关系运算符【< > <= >= 】
第七级:等于运算符【==】、不等于运算符【!=】
第八级:按位与运算符【&】
第九级:按位异或运算符【^】
第十级:按位或运算符【|】
第十一级:逻辑与运算符【&&】
第十二级:逻辑或运算符【||】
第十三级:条件运算符【?:】
第十四级:赋值运算符【= += -= *= /= %= >>= <<.= &= |= ^=】
第十五级:逗号运算符【,】
二、排序相关知识点:
稳定排序与不稳定排序:
假设 Ri = Rj ,且排序前序列中 Ri 领先于 Rj ;
若在排序后的序列中 Ri 仍领先于 Rj ,则称排序方法是稳定的。
若在排序后的序列中 Rj 仍领先于 Ri ,则称排序方法是不稳定的。
例:序列 3 15 8 8 6 9
若排序后得 3 6 8 8 9 15 稳定的
若排序后得 3 6 8 8 9 15 不稳定的
内部排序: 指的是待排序记录存放在计算机随机存储器中进行的排序过程。
外部排序: 指的是待排序记录的数量很大,以致内存一次不能容纳全部记录,在排序过程中尚需对外存进行访问的排序过程。
排序分类:
►插入排序
直接插入排序 希尔排序
►交换排序
冒泡排序 快速排序
►选择排序
简单选择排序 堆排序
►归并排序
►基数排序
1.插入排序:
#include <stdio.h>
void InsertSort(int par_array[], int array_size)
{
int i, j;
int temp;
for (i = 1; i < array_size; i++)// 从第二个开始 一个循环中 新加入的数去与前面的排好序的数比较 比前一个小 就不断往前挪 相等或者大于了 那就结束了(稳定
{
temp = par_array[i];
for (j = i - 1; j >= 0; j--)
{
if (temp < par_array[j])
{
par_array[j + 1] = par_array[j];
}
else
{
break;
}
}
par_array[j + 1] = temp;//temp向后挪一个 即加入后面的新元素
}
}
int main()
{
int i = 0;
int a[] = {3, 5, 2, 1, 9, 0, 6, 4, 7, 8};
int length = sizeof(a) / sizeof(a[0]);
InsertSort(a, length);
for (i = 0; i < length; i++)
{
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
2.希尔排序
#include <stdio.h>
void ShellSort(int array[], int length)
{
int i, j;
int h;
int temp;
for (h = length / 2; h > 0; h = h / 2)//对输入的数组大小 取半(取半后的大小就是步长 以此步长进行两个两个数的比较) 直到最后为0为止
{
for (i = h; i < length; i++)
{
temp = array[i];
for (j = i - h; j >= 0; j -=h)//这里j 因为 i++ 所以j 也会++ 步长就是上面计算的
{
if (temp < array[j])
{
array[j + h] = array[j];
}
else
{
break;
}
}
array[j + h] = temp;
}
}
}
int main()
{
int i = 0;
int a[] = {0, 5, 2, 4, 3, 1, 7, 6, 8, 9};
int length = sizeof (a) / sizeof(a[0]);
ShellSort(a, length);
for (i = 0; i < length; i++)
{
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
3.冒泡排序
大家都会就不列了
4.快速排序
#include "stdio.h"
void find_frst(int *s,int left,int right)
{
int i=left,j=right,temp; //(1)初始化i、j
if(left>=right)
return ;
temp=s[i]; //(2)以第一个数组为比较值,保存到temp中
while(i<j)
{
while(j>i&&s[j]>=temp) //(3)j--,找小值
j--;
s[i]= s[j]; //保存小值,到s[i]上
while(i<j&&s[i]<=temp) //(4)i++,找大值
i++;
s[j--]=s[i]; //保存大值 到s[j]上
}
s[i]=temp; //(5)将比较值放在s[i]上
/*(6)拆分成两个数组 s[0,i-1]、s[i+1,n-1]又开始排序 */
find_frst(s,left,i-1); //左
find_frst(s,i+1,right); //右
}
int main()
{
int i=0,s[100],n;
scanf("%d",&n); //输入数组长度
for(i=0;i<n;i++)
scanf("%d",&s[i]);
find_frst(s,0,n-1);
for(i=0;i<n;i++)
printf("%d ",s[i]); //打印
printf("\n");
}
5.简单选择排序
#include <stdio.h>
void SelectSort(int *a, int n)
{
int i, j;
int temp = 0;
int flag = 0;
for (i = 0; i < n - 1; i++)
{
temp = a[i];
flag = i;
for (j = i + 1; j < n; j++)
{
if (a[j] < temp)
{
temp = a[j];
flag = j;
}
}
if (flag != i)
{
a[flag] = a[i];
a[i] = temp;
}
}
}
int main()
{
int i = 0;
int a[] = {5, 4, 3, 6, 1, 9, 7, 0, 2, 8};
int length = sizeof(a) / sizeof(a[0]);
SelectSort(a, length);
for (i = 0; i < length; i++)
{
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
6.后面慢慢加上来
3.三次握手四次挥手
1. 序列号seq
占4个字节,用来标记数据段的顺序,TCP把连接中发送的所有数据字节都编上一个序号,第一个字节的编号由本地随机产生,给字节编上序号后,就给每一个报文段指派一个序号,序列号seq就是这个报文段中的第一个字节的数据编号。
2. 确认号ack
占4个字节,期待收到对方下一个报文段的第一个数据字节的序号,序列号表示报文段携带数据的第一个字节的编号,而确认号指的是期望接受到下一个字节的编号,因此挡墙报文段最后一个字节的编号+1即是确认号。
3. 确认ACK
占1个比特位,仅当ACK=1,确认号字段才有效。ACK=0,确认号无效。
4. 同步SYN
连接建立时用于同步序号。当SYN=1,ACK=0表示:这是一个连接请求报文段。若同意连接,则在响应报文段中使用SYN=1,ACK=1.因此,SYN=1表示这是一个连接请求,或连接接收报文,SYN这个标志位只有在TCP建立连接才会被置为1,握手完成后SYN标志位被置为0.
5. 终止FIN
用来释放一个
TCP三次握手以及四次挥手的过程
三次握手的过程
step1:第一次握手
建立连接时,客户端发送SYN包到服务器,其中包含客户端的初始序号seq=x,并进入SYN_SENT状态,等待服务器确认。(其中,SYN=1,ACK=0,表示这是一个TCP连接请求数据报文;序号seq=x,表明传输数据时的第一个数据字节的序号是x)。
step2:第二次握手
服务器收到请求后,必须确认客户的数据包。同时自己也发送一个SYN包,即SYN+ACK包,此时服务器进入SYN_RECV状态。(其中确认报文段中,标识位SYN=1,ACK=1,表示这是一个TCP连接响应数据报文,并含服务端的初始序号seq(服务器)=y,以及服务器对客户端初始序号的确认号ack(服务器)=seq(客户端)+1=x+1)。
step3:第三次握手
客户端收到服务器的SYN+ACK包,向服务器发送一个序列号(seq=x+1),确认号为ack(客户端)=y+1,此包发送完毕,客户端和服务器进入ESTAB_LISHED(TCP连接成功)状态,完成三次握手。
未连接队列
在三次握手协议中,服务器维护一个未连接队列,该队列为每个客户端的SYN包(syn=j)开设一个条目,该条目表明服务器已收到SYN包,并向客户发出确认,正在等待客户的确认包时,删除该条目,服务器进入ESTAB_LISHED状态。
常见面试题:
1.为什么需要三次握手,两次不可以吗?或者四次、五次可以吗?
我们来分析一种特殊情况,假设客户端请求建立连接,发给服务器SYN包等待服务器确认,服务器收到确认后,如果是两次握手,假设服务器给客户端在第二次握手时发送数据,数据从服务器发出,服务器认为连接已经建立,但在发送数据的过程中数据丢失,客户端认为连接没有建立,会进行重传。假设每次发送的数据一直在丢失,客户端一直SYN,服务器就会产生多个无效连接,占用资源,这个时候服务器可能会挂掉。这个现象就是我们听过的“SYN的洪水攻击”。
总结:第三次握手是为了防止:如果客户端迟迟没有收到服务器返回确认报文,这时会放弃连接,重新启动一条连接请求,但问题是:服务器不知道客户端没有收到,所以他会收到两个连接,浪费连接开销。如果每次都是这样,就会浪费多个连接开销。
四次挥手过程(关闭客户端到服务器的连接)
step1:第一次挥手
首先,客户端发送一个FIN,用来关闭客户端到服务器的数据传送,然后等待服务器的确认。其中终止标志位FIN=1,序列号seq=u。
step2:第二次挥手
服务器收到这个FIN,它发送一个ACK,确认ack为收到的序号加一。
step3:第三次挥手
关闭服务器到客户端的连接,发送一个FIN给客户端。
step4:第四次挥手
客户端收到FIN后,并发回一个ACK报文确认,并将确认序号seq设置为收到序号加一。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
客户端发送FIN后,进入终止等待状态,服务器收到客户端连接释放报文段后,就立即给客户端发送确认,服务器就进入CLOSE_WAIT状态,此时TCP服务器进程就通知高层应用进程,因而从客户端到服务器的连接就释放了。此时是“半关闭状态”,即客户端不可以发送给服务器,服务器可以发送给客户端。
此时,如果服务器没有数据报发送给客户端,其应用程序就通知TCP释放连接,然后发送给客户端连接释放数据报,并等待确认。客户端发送确认后,进入TIME_WAIT状态,但是此时TCP连接还没有释放,然后经过等待计时器设置的2MSL后,才进入到CLOSE状态。
2.为什么需要2MSL时间?
首先,MSL即Maximum Segment Lifetime,就是最大报文生存时间,是任何报文在网络上的存在的最长时间,超过这个时间报文将被丢弃。《TCP/IP详解》中是这样描述的:MSL是任何报文段被丢弃前在网络内的最长时间。RFC 793中规定MSL为2分钟,实际应用中常用的是30秒、1分钟、2分钟等。
TCP的TIME_WAIT需要等待2MSL,当TCP的一端发起主动关闭,三次挥手完成后发送第四次挥手的ACK包后就进入这个状态,等待2MSL时间主要目的是:防止最后一个ACK包对方没有收到,那么对方在超时后将重发第三次握手的FIN包,主动关闭端接到重发的FIN包后可以再发一个ACK应答包。在TIME_WAIT状态时两端的端口不能使用,要等到2MSL时间结束才可以继续使用。当连接处于2MSL等待阶段时任何迟到的报文段都将被丢弃。
总结:
(1)为了保证客户端发送的最后一个ACK报文段能够到达服务器。即最后一个确认报文可能丢失,服务器会超时重传,然后客户端再一次确认,同时启动2MSL计时器。如果没有等待时间,发送完确认报文段就立即释放连接的话,服务器就无法重传,因此也就收不到确认,就无法按步骤进入CLOSE状态,即必须收到确认才能close。
(2)防止已经失效的连接请求报文出现在连接中。经过2MSL,在这个连续持续的时间内,产生的所有报文段就可以都从网络消失。