1、什么是死锁,产生的原因,如何避免死锁
死锁是指多个进程因竞争资源而造成的一种僵局, 若无外力作用这些进程将永远不可能向前推进。
原因: 竞争资源, 进程推进顺序非法。必要条件: 互斥条件, 请求和保持条件, 不剥夺条件,环路等待条件。
处理死锁:预防死锁,避免死锁,检测死锁,解除死锁
如何避免: 如果所有并发事务按同一顺序访问对象, 则发生死锁的可能性会降低; 避免事务中的用户交互;保持事务简短并在一个批处理中。
2、什么是大端什么是小端字节序?网络字节序是大端还是小端的?
小端:低地址存放低字节,高地址存放高字节;
大端:高地址存放低字节,低地址存放高字节;
网络字节序是:大端。
3、哈希表原理
根据关键码值直接进行访问的数据结构, 也就是说, 它通过把关键码值映射到表中某一个位置来访问记录, 以加快查找的速度; 这个映射函数叫做散列函数,存放记录的数组叫做散列表。
哈希表是一个以空间换取时间的数据结构,理想情况下的时间复杂度为O(1) 。散列函数的构造方法:
(1)直接定址法
取关键字或关键字的某个线性函数值为散列地址; 即H(key)=key 或H(key) = a?key +b,其中a 和b 为常数(这种散列函数叫做自身函数) 。
(2)数字分析法(3)平方取中法(4)折叠法(5)随机数法(6)除留余数法拉链法创建散列表
4、函数memset的实现
原型: void *memset(void *buffer, int c, int count);
功能:把buffer 所指内存区域的前count 个字节设置成字符c。
void *memset(void *src, int c, size_t count)
{
assert(src!=NULL);
char *tmpsrc=(char*)src;
while(count--)
*tmpsrc++ =(char)c;
return src;
}
5、双链表插入节点
//=============================================================
// 语法格式: insert(TYPE * head,TYPE * pi)
// 实现功能: 将新申请的节点加入到指定链表中,并按照num进行从小到大排序
// 参数: head: 待插入链表
// pi :带插入节点
// 返回值: 插入指定节点后的新链表首址
//=============================================================
TYPE * insert(TYPE * head,TYPE * pi)
{
TYPE*pb=head ,*pf;
if(head==NULL)// 如果为空就建立,空间在传入前申请好
{
head=pi;
pi->prior=head;
pi->next=head;
}
else
{
while((pi->num > pb->num)&&(pb->next!=head)) // 找到一个比插入值大的节点,
然后插在它的前面
{
pf=pb;//pf 指向前, pb 指向后
pb=pb->next; // 节点后移
}
if(pi->num <= pb->num) // 找到所要插入节点位置,插到pb 的前面
{
if(head==pb) // 在第一结点之前插入
{
pi->next = pb;
pi->prior = head->prior;
pb->prior->next = pi; // 尾节点
pb->prior = pi;
head=pi; // 保存头节点
}
else
{
pf->next = pi; // 在中间位置插入
pb->prior = pi;
pi->next = pb;
pi->prior = pf;
}
}
else
{
pb->next = pi; // 在表末插入
pi->next = head;
pi->prior = pb;
head->prior = pi; // 头始终指向新插入的节点
}
}
return head;
}
6、排序问题
(1) 冒泡法
其原理为从a[0] 开始,依次将其和后面的元素比较, 若a[0]>a[i] ,则交换它们,一直比较
到a[n] 。同理对a[1],a[2],...a[n-1] 处理,即完成排序。
void bubble(int *a,int n) /* 定义两个参数:数组首地址与数组大小*/
{
int i,j,temp;
for(i=0;i
{
temp=a[i];
a[i]=a[j];
a[j]=temp;
}
}
冒泡法原理简单,但其缺点是交换次数多,效率低。
(2) 选择法
选择法循环过程与冒泡法一致, 它还定义了记号k=I ,然后依次把a[k] 同后面元素比较,
若a[k]>a[j], 则使k=j 。最后看看k=i 是否还成立,不成立则交换a[k],a[i] ,这样就比冒
泡法省下许多无用的交换,提高了效率。
void choise(int *a,int n)
{
int i,j,min,temp;
for(i=0;i
min=i; /* 给记号赋值*/
for(j=i+1;j
if(a[min]>a[j])
min=j; /* 是k 总是指向最小元素*/
}
if(i!=min)
{ /* 当k!=i 是才交换,否则a[i] 即为最小*/
temp=a[i];
a[i]=a[min];
a[min]=temp;
}
}
}
(3) 插入法
插入法是一种比较直观的排序方法。
它首先把数组头两个元素排好序,再依次把后面的元素插入适当的位置。
把数组元素插完也就完成了排序。
void insert(int *a,int n)
{
int i,j,temp;
for(i=1;i
temp=a[i]; /*temp 为要插入的元素*/
j=i-1;
while( j >= 0 && temp < a[j] )
{ /* 从a[i-1] 开始找比a[i] 小的数,同时把数组元素向后移*/
a[j+1]=a[j];
j--;
}
a[j+1]=temp; /* 插入*/
}
}
(4) 快速排序法
快速法定义了三个参数:
(数组首地址a,要排序数组起始元素下标i ,要排序数组结束元素下标j)
它首先选一个数组元素(一般为a[ (i+j)/2 ], 即中间元素)作为参照, 把比它小的元素
放到它的左边,比它大的放在右边。
然后运用递归,在将它左,右两个子数组排序,最后完成整个数组的排序。
void quick(int *a,int i,int j)
{
int m,n,temp;
int k;
m=i;
n=j;
k=a[(i+j)/2]; /* 选取的参照*/
do
{
while( a[m]
while( a[n] >k && n>i )
n--; /* 从右到左找比k 小的元素*/
if(m<=n)
{ /* 若找到且满足条件,则交换*/
temp=a[m];
a[m]=a[n];
a[n]=temp;
m++;
n--;
}
}
while(m<=n);
if(m
if(n>i)
quick(a,i,n);
}
7、折半查找(二分法)
分析: 首先得从小到大排好序,二分再比较,不等则继续二分,直到高低碰头遍历结束
// 二分法对以排好序的数据进行查找
int binary_search(int array[],int value,int size)
{
int low=0,high=size-1,mid;
while(low<=high) // 只要高低不碰头就继续二分查找
{
mid=(low+high)/2;
if(value==array[mid]) // 比较是不是与中间元素相等
return mid;
else if(value > array[mid])
// 每查找一次,就判断一次所要查找变量所在范围,并继续二分
low=mid+1;
// 如果大小中间值,下限移到中间的后一个位,上限不变,往高方向二分
else
high=mid-1; // 上限移到中间的前一个位,往低方向二分
}
return -1;
}
8、字符串反转
char* reverse(const char* str)
{
int len = strlen(str);
char* tmp = new char[len + 1];
strcpy(tmp,str);
for (int i = 0; i < len/2; ++i)
{
char c = tmp[i];
tmp[i] = tmp[len – i - 1];
tmp[len – i - 1] = c;
}
return tmp;
}
9、Static 作用
1) 在函数体内, 一个被声明为静态的变量在这一函数被调用过程中维持其值不变(该变量存
放在静态变量区) 。
2) 在模块内(但在函数体外) ,一个被声明为静态的变量可以被模块内所用函数访问, 但不
能被模块外其它函数访问。它是一个本地的全局变量。
3) 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个
函数被限制在声明它的模块的本地范围内使用。
10、Strcat 的实现.
11、进程、线程区别
一个程序至少有一个进程, 一个进程至少有一个线程;
进程在执行过程中拥有独立的内存单元,而多个线程共享内存;
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动, 进程是系统进行
资源分配和调度的一个独立单位,线程是进程的一个实体, 是CPU调度和分派的基本单位,
它是比进程更小的能独立运行的基本单位。
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空
间,一个进程崩溃后, 在保护模式下不会对其它进程产生影响, 而线程只是一个进程中的不
同执行路径。线程有自己的堆栈和局部变量, 但线程之间没有单独的地址空间, 一个线程死
掉就等于整个进程死掉, 所以多进程的程序要比多线程的程序健壮, 但在进程切换时, 耗费
资源较大,效率要差一些。
对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
12、数组和链表区别
数组是将元素在内存中连续存放, 由于每个元素占用内存相同, 可以通过下标迅速访问
数组中任何元素。如果应用需要快速访问数据,很少或不插入和删除元素,就应该用数组。
链表恰好相反, 链表中的元素在内存中不是顺序存储的, 而是通过存在元素中的指针联
系到一起。但是增加和删除一个元素对于链表数据结构就只要修改元素中的指针就可以了。
如果应用需要经常插入和删除元素你就需要用链表数据结构了。
13、Tcp、udp 区别基于包,基于流?
1:用户数据报协议(UDP),UDP 协议是面向无连接的不可靠服务,在传输数据之前不需
要先建立连接。远地主机的运输层收到UDP报文后,不需要给出任何确认,传输数据快,不
能广播。
2: 传输数据报协议(TCP),TCP 则提供面向连接的可靠服务。在传输数据前必须先建立
连接,数据传输完毕后要释放连接,传输数据慢,能广播。
14、epoll
epoll 是Linux 下多路复用IO 接口select/poll 的增强版本,它能显著提高程序在大
量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它会复用文件描述符集合来传
递结果而不用每次等待事件之前都必须重新准备要被侦听的文件描述符集合, 另一点原因就
是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO 事件异步
唤醒而加入Ready 队列的描述符集合。