1. 网络编程:
- TCP协议三次握手:
- 客户端向服务器发送一个 SYN J;
- 服务器向客户端响应一个SYN K(同步信号),并返回一个ACK J+1;
- 客户端再向服务器发送一个确认ACK K+1;
- TCP协议释放连接四次挥手:
- 客户端发送FIN,进入FIN_WAIT1状态;
- 服务端收到FIN,发送ACK,进入CLOSE_WATI状态,客户端收到这个ACK,进入FIN_WAIT2状态;
- 服务端发送FIN,进入LAST_ACK状态;
- 客户端收到FIN,发送ACK,进入TIME_WAIT状态,服务端收到ACK,进入CLOSE状态;
- 客户端TIME_WAIT持续两倍MSL时长,Linux中大概60s,转换成CLOSE状态;
- 存在TIME_WAIT状态的原因:
- 实现终止TCP全双工连接的可靠性:假设最后的ACK丢失,服务器会重发FIN,因此客户端需要维护状态信息以允许重发最终的ACK(对于主动断开连接的服务器是同样的道理)
- 保证老的重复分节在网络消失:保证来自先前连接的老的重复分组已经消失,2MSL(maximum segment lifetime 最长分节生命)时间足够让某个方向上的分组丢弃
- (存疑)不同协议可以监听同一个端口
2. 子网掩码的问题(注意)
3. 线性链表
- 存储顺序与逻辑顺序不一定一致
- 删除头结点与链表长度无关,删除尾结点时要注意链表长度问题(链表可能为空等情况)
4. 移位操作问题
5. 64平台 与 32 平台
- 32位平台的寻址空间为32bit,也就是4个字节
基本类型 | 32位系统 | 64位系统 |
---|---|---|
char | 1 | 1 |
char * 指针变量 | 4 | 8 |
short int | 2 | 2 |
int | 4 | 4 |
unsigned int | 4 | 4 |
float | 4 | 4 |
double | 8 | 8 |
long | 4 | 8 |
long long | 8 | 8 |
unsigned long | 4 | 8 |
6. %zu
- 格式符z和整数转换说明符一起使用,表示对应数字是一个size_t值。属于C99。
7. 进程/线程之间同步机制; 进程/线程之间的通信
注意区分一下进程和线程之间的不同,同时注意同步和通信在概念上应该是属于一个范畴
进程间的通信:管道(pipe)消息队列,信号量,信号,共享内存,套接字
线程间的同步(进城内线程的同步):临界区,互斥量,信号量,事件
-
wait,sleep,notify,notifyAll(java)
wait:wait是object的类,范围是该object实例所在的线程,导致当前线程等待,直到其他线程调用此对象的notify或者notifyAll方法,wait方法使得实体所在的线程暂停执行,从而使得对象进入等待状态。wait方法会在进入等待状态时释放同步锁,当前线程拥有对该对象的监视器,该线程发布对此监视器的所有权并等待,直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行;
-
sleep:sleep方法来自thread类,在指定的毫秒内让正在执行的线程休眠,暂停执行,该线程不会丢失任何监视器的所属权。sleep方法使得线程进入休眠状态,直到使用interrupt方法打断休眠或者sleep休眠时间结束,sleep不会释放同步锁。
注意:当一个线程无限Sleep时又没有任何人去interrupt它的时候,程序就产生大麻烦了notify是用来通知线程,但在notify之前线程是需要获得lock的。另个意思就是必须写在synchronized(lockobj) {...}之中。wait也是这个样子,一个线程需要释放某个lock,也是在其获得lock情况下才能够释放,所以wait也需要放在synchronized(lockobj) {...}之中。
异同:
(1)来自的类不同,sleep方法来自Thread,wait方法来自Object
(2)sleep方法是线程内部方法,没有释放对象的锁,而wait方法释放了对象 锁,使得其他线程可以使用同步控制块或者方法。
(3)wait,notify和notifyAll是对象操作方法,必须在同步下进行,只有在synchronized里面使用,而sleep可以在任何地方使用。
(4)两者都可以让线程暂停一段时间,但是本质的区别是一个线程的运行状态控制,一个是线程之间的通讯的问题,就需要激活才会进入runing状态。
注意:运行wait和notify的对象不能是基本类型,应该为可引用类型或者javabean;
synchronized(x){ try { x.wait();或者x.notify() } catch (InterruptedException e) { e.printStackTrace(); } }
4.(1)notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
(2)notifyAll():唤醒所有处入等待状态的线程,并可以理解为把他们排进一个队列,只不过只有头部的线程获得了锁,才能运行,注意!!并不是给所有唤醒线程一个对象的锁,而是让它们竞争,当其中一个线程运行完就开始运行下一个已经被唤醒的线程,因为锁已经转移了。(这个时候是否运行已经不是因为等待状态,而是处于runnning队列中)。
8. C++ 虚函数
- 基类的虚构函数为什么常设置为虚函数
9. 二叉树
- 二叉树常见遍历和操作(见另一篇文章)
10. Linux命令:grep, awk,正则表达式
11.数据库常用操作
腾讯(选择题)
1. Linux 进程通信
- Linux进程通信六大方式:
1,管道及(pipe)有名管道
2,信号(signal)
3,报文队列
4,共享内存
5,信号量(semaphore)
6,套接字(socket)
注意文件锁?
2. 指针数组和数组指针的区别
int *p[4]; //定义一个指针数组,该数组中每个元素是一个指针,每个指针指向哪里就需要程序中后续再定义了。
int (*p)[4]; //定义一个数组指针,该指针指向含4个元素的一维数组(数组中每个元素是int型)。
//区分 int *p[n]; 和 int (*p)[n],就要看运算符的优先级了。
int *p[n] 中,运算符 [ ] 优先级高,先与 p 结合成为一个数组,再由 int* 说明这是一个整型指针数组。
int (*p)[n] 中( )优先级高,首先说明 p 是一个指针,指向一个整型的一维数组。
1. int (*p)[4]:定义一个数组指针,该指针指向含4个元素的一维数组(数组中每个元素是int型);
2. (int (*)[4])m:表示将m强制转换为大小为4的数组指针;
#include <stdio.h>
int main()
{
int m []={1,2,3,4,5,6,7,8,9,0};
int(*p)[4]=(int(*)[4])m;
printf(“%”,p[1][2]);
return 0;
}
// 1.int (*p)[4]:表示行指针,单位移动量为4个int类型。即p+1,则一次移动4个int类型
// 2.(int (*)[4])m:表示以数组指针类型组织m,每4个为一个数组
// 3.这样一来,m为{{1,2,3,4},{5,6,7,8},{9,0, , }},p指向第一行
// 4.故p[1][2]即*(*(p+1)+2),表示第二行第三个元素,为7
3. ARP协议
- 地址解析协议,根据IP地址获取物理地址的一个TCP/IP协议
- 注意区分DNS:域名到IP地址,UDP协议
- 主机发送信息时将包含目标IP地址的ARP请求广播到网络上的所有主机,并接收返回消息,以此确定目标的物理地址;收到返回消息后将该IP地址和物理地址存入本机ARP缓存中并保留一定时间,下次请求时直接查询ARP缓存以节约资源。
4. 死锁
死锁是因为多个进程因竞争资源而造成的一种僵局,通常是因为系统资源竞争,进程推进顺序非法等原因而产生。
-
死锁产生的4个必要条件:(只要任一条件不成立,死锁就不会发生)
- 互斥条件:在一个时间段内某资源仅为一个进程所占有,若有其他资源请求,请求进程智能等待。
- 不剥夺条件:进程已获得的资源在未使用完成之前,不能被其他进程强行夺走,即只能由进程自己释放
- 请求和保持条件:部分分配条件。进程每次申请它所需要的一部分资源时,在等待新资源的时候对已分配的资源继续占有
- 循环等待:存在进程资源循环等待链,链中每个进程已获得的资源同时被链中下一个进程请求。
-
死锁的预防:设置限制条件,要求进程申请资源时遵循某种协议,从而打破产生死锁的四个必要条件中的一个或几个,保证系统不会进入死锁状态。
- 打破互斥条件:允许进程同时访问某些资源
- 打破不可抢占条件:允许进程强行剥夺某些资源(不建议)
- 打破占有且申请条件:即进程在运行前一次性地向系统申请它所需要的全部资源。如果某个进程所需的全部资源得不到满足,则不分配任何资源,此进程暂不运行。只有当系统能够满足当前进程的全部资源需求时,才一次性地将所申请的资源全部分配给该进程
-
死锁的避免:在动态分配的过程中,用某种方法防止系统进入不安全状态。
- 安全序列: 我们首先引入安全序列的定义:所谓系统是安全的,是指系统中的所有进程能够按照某一种次序分配资源,并且依次地运行完毕,这种进程序列{P1,P2,...,Pn}就是安全序列。如果存在这样一个安全序列,则系统是安全的;如果系统不存在这样一个安全序列,则系统是不安全的。
安全序列{P1,P2,...,Pn}是这样组成的:若对于每一个进程Pi,它需要的附加资源可以被系统中当前可用资源加上所有进程Pj当前占有资源之和所满足,则{P1,P2,...,Pn}为一个安全序列,这时系统处于安全状态,不会进入死锁状态。 - 银行家算法(著名死锁避免算法):银行家算法是从当前状态出发,逐个按安全序列检查各客户谁能完成其工作,然后假定其完成工作且归还全部贷款,再进而检查下一个能完成工作的客户,......。如果所有客户都能完成工作,则找到一个安全序列,银行家才是安全的。
- 安全序列: 我们首先引入安全序列的定义:所谓系统是安全的,是指系统中的所有进程能够按照某一种次序分配资源,并且依次地运行完毕,这种进程序列{P1,P2,...,Pn}就是安全序列。如果存在这样一个安全序列,则系统是安全的;如果系统不存在这样一个安全序列,则系统是不安全的。
-
死锁检测与解除:允许死锁的发生,通过系统的检测机制及时检测死锁发生,然后采取措施解除死锁。
- 撤销所有死锁进程
- 进程回退再启动
- 按照某种原则逐一撤销死锁
- 按照某种原则逐一抢占资源
4. 数据库唯一索引
- 表可以包含多个唯一约束,但只能有一个主键
- 唯一约束列可以包含null值
- 唯一约束列可修改和更新
5 . 空类 和 sizeof
- 空类求sizeof为1,加不加构造函数对sizeof没影响,但有了虚函数,则需要有一个指针指向虚函数表,32位下,指针sizeof为4
6. 大端、小端和网络字节序
大端和小端表示多字节值的哪一端存储在该值的起始地址处,小端存储在起始地址处,即小端字节序,大端存储在起始地址处,及大端字节序。
小端法(Little-Endian)就是低位字节排放在内存的低地址端(即该值的起始地址),高位字节排放在内存的高地址端;
-
大端法(Big-Endian)就是高位字节排放在内存的低地址端(即该值的起始地址),低位字节排放在内存的高地址端;
网络字节序:UDP/TCP/IP协议规定:把接收到的第一个字节当作高位字节看待,这就要求发送端发送的第一个字节是高位字节;而在发送端发送数据时,发送的第一个字节是该数值在内存中的起始地址处对应的那个字节,也就是说,该数值在内存中的起始地址处对应的那个字节就是要发送的第一个高位字节(即:高位字节存放在低地址处);由此可见,多字节数值在发送之前,在内存中因该是以大端法存放的;
所以说,网络字节序是大端字节序;我们经过网络发送整型数值0x12345678时,在80X86平台中,它是以小端发存放的,在发送之前需要使用系统提供的字节序转换函数htonl()将其转换成大端法存放的数值;如下图所示:
7. 基类的析构函数
- 在C++中,析构函数的作用是:当一个对象被销毁时,调用析构函数对类对象和对象成员进行释放内存资源。
当我们定义一个指向派生类类型对象指针时,构造函数按照从基类到派生类的顺序被调用,但是当删除指向派生类的基类指针时,派生类的析构函数没有被调用,只是调用了基类的析构函数,此时派生类将会导致内存泄漏
我们需要将基类的析构函数声明为虚函数,此时在调用析构函数的时候是根据ptr指向的具体类型来调用析构函数,此时会调用派生类的析构函数。
- 构造函数为什么不能声明为虚函数?
1 构造一个对象的时候,必须知道对象的实际类型,而虚函数行为是在运行期间确定实际类型的。而在构造一个对象时,由于对象还未构造成功。编译器无法知道对象的实际类型,是该类本身,还是该类的一个派生类,或是更深层次的派生类。无法确定。
2 虚函数的执行依赖于虚函数表。而虚函数表在构造函数中进行初始化工作,即初始化vptr,让他指向正确的虚函数表。而在构造对象期间,虚函数表还没有被初 始化,将无法进行。 析构函数执行时先调用派生类的析构函数,其次才调用基类的析构函数。
8. p++ 和 ++P
#include<stdio.h>
int main()
{
int*p = NULL;
int a[]={1,2,3,4}
p=a;
*(p++) + = 100;
printf(%d%d%d%d%d\n”,*p,a[0],a[1],a[2],a[3]);
return 0;
}
//p++是运行完代码在向p添加1,而++p是先加再执行代码
//p指向(等价)a[0],
//*(p++) + = 100;
//等价*p + = 100,p++;即a[0]+=100,pl指向a[1];
//*p = 2,a[0]=101...
9. ICMP
- ping 使用的协议为icmp,通过域名解析,需要用到DNS,局域网中使用ARP进行主机间的通信。
10 .字节对齐
- pragma pack(n)表示默认以n字节对齐,若某变量(如int)的字节大于n,则以n的倍数对齐。
11.宏替换和优先级
12. 枚举赋值
- 枚举赋值问题,从上一个定义开始逐个低增
#include <stdio.h>
enum etest{
eparam1,
eparam2,
eparam3=10,
eparam4,
eparam5='a',
eparam6
}epr;
int main()
{
printf(“%d,%d”,eparam4,eparam6);
return 0;
}
- 11 & 98
13. 子进程和父进程
父进程和子进程拥有独立的地址空间和PID参数,所以子进程不会继承父进程的地址空间,
子进程从父进程继承了用户号和用户组号,用户信息,目录信息,环境(表),打开的文件描述符,堆栈,(共享)内存等。
子进程继承父进程:用户号UIDs和用户组号GIDs、环境Environment、堆栈、共享内存、打开文件的描述符、执行时关闭(Close-on-exec)标志、信号(Signal)控制设定、进程组号、当前工作目录、根目录、文件方式创建屏蔽字、资源限制、控制终端
子进程独有:进程号PID、不同的父进程号、自己的文件描述符和目录流的拷贝、子进程不继承父进程的进程正文(text),数据和其他锁定内存(memory locks)、不继承异步输入和输出
父进程和子进程拥有独立的地址空间和PID参数。
经过fork()以后,父进程和子进程拥有相同内容的代码段、数据段和用户堆栈,就像父进程把自己克隆了一遍。事实上,父进程只复制了自己的PCB块。而代码段,数据段和用户堆栈内存空间并没有复制一份,而是与子进程共享。只有当子进程在运行中出现写操作时,才会产生中断,并为子进程分配内存空间。由于父进程的PCB和子进程的一样,所以在PCB中断中所记录的父进程占有的资源,也是与子进程共享使用的。这里的“共享”一词意味着“竞争”
14. 磁盘文件管理
微软操作系统(DOS、WINDOWS等)中磁盘文件存储管理的最小单位叫做“簇”
扇区:硬盘不是一次读写一个字节而是一次读写一个扇区(512个字节)
簇:系统读读写文件的基本单位,一般为2的n次方个扇区(由文件系统决定)
15 子网掩码
- IP地址为140.123.0.0的地址是B类地址,若要切割为10个子网,而且都要连接上Internet,请问子网掩码应设为?
- IP地址:类似于你这台电脑的标志,但在网络上是靠IP地址识别的。如果利用TCP/IP协议组网,那么一个网段内的所有电脑都必须有一个IP地址,并且不能重复。
- 子网掩码和IP地址是配合一起的,将IP地址分成两段,网络段和主机段。
例如你的IP地址是192.168.1.2,子网掩码是255.255.255.0,那么子网掩码全是255的对应的IP地址段表示网络段,是0的对应的IP地址段表示主机段,以上为例,则192.168.1表示网络,2表示主机。
如果需要在这个网络内新增一台主机,则只要改变仅也只能改变最后一位。这样才能保证在同一网络。
答:默认B类地址的子网掩码是/16,也就是255.255.0.0,切割成10个子网的话,就得向主机位借2的四次方=16>10,即向主机位借4位。
所以子网掩码要设置成/20,也就是255.255.240.0