代码改变世界

16年面试提问

2016-02-23 21:26  sylar_liang  阅读(290)  评论(0编辑  收藏  举报

2016-2-23面试提问:

 

1.什么是句柄?

句柄是一个32位的证书,是windows在内存中维护的一个对象(窗口等)内存物理地址列表的整数索引。

句柄是指使用的一个唯一的整数值,用来标识应用程序中的不同对象和同类中的不同的实例。

 

2.什么是哈希表,哈希表原理是什么,哈希表的提出主要用来解决什么问题?

>哈希表最大的优点:把数据的存储和查找消耗的时间大大降低,几乎可以看成是常数时间,而代价仅仅是消耗比较多的内存。

>什么时候适合应用哈希表?

某个元素是否在已知集合中,也就是需要高效得数据存储和查找。

设计一个好的哈希函数很关键,而好的标准就是较低的冲突率和易于实现。

>基本原理:

使用一个下标范围都比较大的数组来存储元素,可以设计一个函数,使得每个元素的关键字都与一个函数值(即数组下标)相对应。但不能保证每个元素的关键字与函数值一一对应,不同的元素却计算出相同的函数值,这就产生了冲突。

 

3.常用的排序算法。6种,时间复杂度,空间复杂度,稳定度。

1>冒泡排序。(从后往前比)

#define MAX_SIZE 10
typedef struct { int data[MAX_SIZE]; int nLen; }SqList; // 交换SqList中下标为i和j元素。 void swap(SqList *L, int i, int j) { int temp = L->data[i]; L->data[i] = L->data[j]; L->data[j] = temp; } void BubbleSort(SqList *array) { for(int i=0; i<array->nLen-1; ++i) // 比较的次数。下标从0开始算。 { for(int j=array->nLen-2; j>=i; j--) // 从尾部开始往前比较。 { if( array->data[j] > array->data[j+1] ) // 若前者大于后者 { swap(array, j, j+1); } } } } 改进的冒泡排序:添加一个标志位,假如标志位为假,则不进入循环内比较 void BubbleSort2(SqList *array) { bool flag = true; for(int i=0; i<array->nLen-1 && flag; ++i) // 假如flag为0,说明没有变动,不再往下循环比较。 { flag = false; // 初始化为false for(int j=array->nLen-2; j>=i; j--) { if( array->data[j] > array->data[j+1] ) { swap(array, j, j+1); flag = true; // 有改变则置1 } } } }

 

2>选择排序。 (从前往后比)

关键点:从前往后逐个比较,查找最小的小标,再将最小下标的元素与当前位置交换。

在待排序的n个记录中选择一个最小的记录需要比较n-1次。

void SelectSort(SqList *array)
{
    int min;
    for(int i=0; i<array->nLen-1; ++i) // 比较的次数
    {
        min = i;
        for(int j=i+1; j<array->nLen; j++)
        {
            if( array->data[min] > array->data[j] )
            {
                min = j; // 获取最小值下标
            }
        }
        if(min != i)  // min不等于i,说明找到最小值下标了。
            swap(array, i, min);
    }
}

 

 

3>直接插入排序。(从2开始,与前一个比较)

直接插入排序:将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增1的有序表。
// 数组第0位 作为 哨兵

void InsertSort(SqList *array)
{
    int i, j;
    for(i=2; i<array->nLen; ++i) // a[0] 哨兵, 也算进 nLen长度里。这里是i<nLen,没有=,否则5个元素,会出现a[5]越界了。
    {
        if( array->data[i] < array->data[i-1] ) // 从2开始比较,跟前一个比较
        {
            array->data[0] = array->data[i]; // 把 较小值 存进 哨兵
            for(j=i-1; array->data[j] > array->data[0]; --j ) // 与哨兵逐一比较, j是自减
            {
                array->data[j+1] = array->data[j]; // 往后移位 
            }
            array->data[j+1] = array->data[0]; // 赋值给j+1, 将哨兵插入到正确的位置
        }
    }
}

 

 

4>希尔排序: 将相距某个"增量"的记录组成一个子序列。

关键不是随便分组后各自排序,而是将相隔某个"增量"的记录组成一个子序列,实现跳跃式的移动,使得排序的效率提高。
☆增量的选取非常关键。

基本排序:小的在前面,大的在后边,不大不小的在中间。

// 存在 a[0] 作为哨兵
void ShellSort(SqList *array)
{
    int i, j;
    int add = array->nLen; // 增量初始化 为 SqList元素个数
    do
    {
        add = add/3 + 1; // 设置增量. 这个值很关键,后续直接影响到排序的效率。
        for(i=add+1; i<array->nLen; ++i)
        {
            if( array->data[i] < array->data[i-add] )
            {
                array->data[0] = array->data[i]; // 暂存进哨兵
            
                for(j=i-add; j>0 && array->data[0] < array->data[j]; j -= add) // 哨兵元素 比较小,则互换位置
                {
                    array->data[j+add] = array->data[j]; // 记录后移
                }
                array->data[j+add] = array->data[0]; // 把哨兵值 存入。
            }
        }        
    } while(add > 1);
}

 

 

5>堆排序。

6>归并排序。

7>快速排序

 

4.http协议。

>超文本协议。

>超文本传输协议URL= “http:” “//” 主机名【":"端口号(可选)】【绝对路径【"?"查询】】。

>通常使用TCP/IP连接,缺省是80端口。

 

http协议请求:包括请求行,消息报头,请求正文。

http响应:状态行,消息报头,响应正文。

请求:数据长度,字符集,接受html文本,编码格式,语言,授权,主机host,

响应:Location重定向,Server服务器类型,

 

GET与POST区别?

?GET用于获取数据,POST用户发送数据

?POST比GET安全。

?GET对URL长度有限制,1024,POST没有。

 

5.DNS与ARP分别指什么?

 >DNS(Domain Name System):域名系统。使用户不用去记IP数串。每个IP地址都可以有一个主机名。

通过主机名,最终得到该主机名对应的IP地址的过程叫做域名解析。

>主机名到IP地址的映射有2种方式:

静态映射:每台设备上都配置主机到IP地址的映射。

动态映射:建立一套域名解析系统,使用主机名通信的设备时,先到DNS服务器查找对应的IP地址。

 

>ARP(Address Resolution Protocol):地址解析协议。是根据IP地址获取物理地址的一个TCP/IP协议。

 

6.Socket的类型。

1>SOCK_STREAM流套接字:读取TCP协议的数据,提供面向连接的、可靠的数据传输服务。该服务能保证数据能够实现无差错无重复发送,并按顺序接收。

2>SOCK_DGRAM数据报套接字:读取UDP协议的数据,提供一种无连接的服务。不保证数据传输的可靠性。

3>SOCK_RAW原始套接字:可以读取内核没有处理的IP数据包。

 

7.select与epoll的实现,他们的共同点与区别。

>select几大缺点:相关函数(FD_ZERO,FD_SET,FD_CLR,FD_ISSET)

1>每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时很大。

2>每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大。 

3>select支持的文件描述符数量太小了,默认为1024.

 

>epoll

1>epoll是在每次注册新的事件到epoll句柄中时(EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait时重复拷贝。epoll保证了每个fd在整个过程只会拷贝一次。

2>epoll只用维护一个队列,看队列是否为空就可以了。epoll只会对活跃的socket进行操作,只有活跃的fd才会主动去调用对应的callback函数。

3>epoll没有文件描述符的限制。

 

区别:

>select的句柄数目受限(最多同时监听1024个fd,因为内核代码的限制)。epoll则没有,它的限制是最大的打开文件句柄数。

>select采用的是轮询处理,其中的数据结构类似以个数组的数据结构,而epoll是维护一个队列,直接看队列是否为空就可以。

 

8.进程与线程。

>进程是系统进行资源分配和调度的一个独立单位,线程是进程的一个实体,是CPU调度和分派的基本单位。

>进程有独立的地址空间。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,线程共享进程的地址空间。

>一个线程死掉就等于整个进程死掉,所以多进程的程序比多线程的程序健壮,但在进程切换时,耗费资源较大,效率差一点。

>线程执行开销小,但不利于资源的管理和保护,进程则相反。同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。

SMP:一组处理器(多CPU)之间共享内存子系统以及总线结构。

 

9.抓包软件。

 WireShark,HttpWatch。

 

10.linux下查看网络流量的命令。

>nload: (不是系统自带)快速查看总带宽使用情况,无需每个进程的详细情况。

查看系统端口使用情况

>netstat 

netstat -tln 用来查看linux的端口使用情况

 

11.如何处理TCP黏包问题。

>现在主流且有效的做法就是加包头,不要考虑这些不实用的方式了。

>在发送每个数据包加上一个自定义的数据头,所以都没出现过黏包现象。

push的作用:对方的包源源不断的发过来,接收方的内核未必收到一个包就通知上层一次,如果包足够频繁,它可以积累一些一块通知上层。push标识就是告诉接收者,这个包尽快上报,最好不要缓存了。接收方看到这个标识,会酌情处理(看协议实现者的心情了)。

因此,所有的包都加了push,也不能保证不会粘包。

 

===========================

1.OSI参考模型:物理层,数据链路层,网络层(IP),传输层(TCP,UXP),会话层,表示层,应用层(HTTP,DNS,SMTP)。

TCP/IP五层模型:物理层,数据链路层,网络层,传输层,应用层。

2.串口通信(RS232):按位(bit)发送和接收。

>分为:单工(只能A->B,),半双(A->B,B->A,只能一个方向),全双工(允许双向传输)。

>将接受的串行数据转换为并行的数据字符传输给CPU器件。

串口通信最重要的参数是:

>波特率:衡量符号传输速率的参数。1个起始位,一个停止位,剩下数据位。

>数据位

>停止位

>奇偶校验:四种校错方式:奇,偶,高,低。

窗口通信编程:串口初始化,打开串口,发送读取数据命令,等待接受数据,数据处理与显示,关闭串口。

 

3.JTAG技术

 

========================================

2016-2-24面试提问:

 

1.String的4种构造函数。

// 构造函数

String::String(const char * ptr)

{

  if(ptr == NULL)

  {

    m_pData = new char[1];

    m_pData[0] = '\0';

  }

  else

  {

    int n = strlen(ptr);

    m_pData = new char[n+1];

    strcpy(m_pData, ptr);

  }

}

// 析构函数

String::~String()

{

  if(m_pData)

  {

    delete [] m_pData;

    m_pData = NULL;

  }

}

// 拷贝构造函数

String::String(const String & s1)

{

  int n = strlen(s1.m_pData);

  m_pData = new char[n+1];

  strcpy(m_pData, s1.m_pData);

}

// 赋值构造函数

String & String::operator =(const String & s1)

{

  if( this  == &s1) // 检查自赋值

    return *this;

 

  delete [] m_pData;

  int n = strlen(s1.m_pData);

  m_pData = new char[n+1];

  strcpy(m_pData, s1.m_pData);

  return *this;

}

 

2.单链表的插入删除。

struct Node 

{

  int data;

  Node * pNext; 

};

 

 

 

3.什么是网络字节序、主机字节序?如何转换?

网络字节序是TCP/IP中规定好的一种数据表示格式,采用大端排序方式。

主机字节序是指整数在内存中保存的顺序。

高地址在低字节 大端。

高地址在高字节 小端。

如: 0x01020304

        4000   4001   4002  4003

小      04        03      02       01

大      01        02      03       04

htons htonl ntohs ntohl

h 主机序

n 网络字节序

s 短整形

l 长整形

 

4.什么是虚拟内存?

虚拟内存是计算机系统内存管理的一种技术。当内存消耗完时,系统匀出一部分硬盘空间来充当内存使用,系统将数据移入分页文件来释放RAM。

 

5.MFC,API,STL,SSL等等分别代表什么。

MFC:Microsoft Foundation Classes 微软基础类库。

API:Application Programming Interface 应用程序编程接口。

STL:Standard Template Library 标准模板库。分为容器,迭代器,空间配置器,配接器,算法,仿函数。

SSL:Secure Sockets Layer 安全套接层。利用加密技术,确保数据在网络传输过程中不会被截取及窃听。

 

6.memcpy与memmove的区别?

memcpy是memmove的一个子集。

唯一的区别是,当内存发生局部重叠的时候,memmove保证拷贝的结果是正确的,memcpy不保证拷贝的结果正确。memmove内部定义了一个局部数组来作为中间的存储。所以memcpy效率高一点。

区别是它们处理内存区域重叠(overlapping)的方式不同。

 

7.12种常用的设计模式,分别用在什么情景下?

>简单工厂模式

>抽象工厂模式

>工厂模式

>策略模式

>装饰模式

>单例模式

>代理模式

>

 

x.数据结构查找方法

1>顺序表查找算法。复杂度O(n)

// a为数组,n为要查找的数组个数,key为要查找的关键字
for(int i=0; i<n; ++i)
{
  if( a[i] == key )
    return i; // 从0开始计数。
}
return -1; // 失败返回-1

// 改良的顺序查找算法,添加哨兵。可在数组头部或者尾部添加哨兵。复杂度比上面的有所降低。
// a为数组,n为要查找的数组个数,key为要查找的关键字
a[0] = key;
i = n; // 从尾部开始查找
while( a[i] != key ) // 因为添加了a[0],所以数组从1开始计数。
{
  i--;
}
return i;

 

2>有序表查找。复杂度O(logn)

>折半查找/二分法查找:前提是记录是有序的。一般是从小到大。

//
int binarysearch(int *a, int len, int key)
{
  int high,low;
  high = len - 1;
  low = 0;
  while(low <= high)
  {
    mid = (low+high)/2;
    if ( key < a[mid] )
      high = mid-1;
    else if ( key > a[mid] )
      low = mid + 1;
    else
      return mid; // 相等,则说明mid为要查找的位置
  }
  return -1; // 失败返回-1
}

升级版:

>插值查找:关键字分布比较平均时适用。极度不平均的分布则不适用。

关键>>>mid = (low + high )/2 改为 mid = low + (key-a[low])/(a[high]-a[low])*(high-low);

关键计算公式: (key-a[low])/(a[high]-a[low])

 

>斐波那契查找。利用黄金分割原理

 

========================================

2016-2-25面试提问:

1.Windows下注册表有什么用?

 

2.写一个函数将字符串转换为数字。如"1234"-->1234

 

3.数据库的建表,插入,删除,查询语句。

 

4.二分法链表的查找。

 

5.Windows下进程间通信的方法,共享内存的原理是什么?Linux下进程通信的方法又有什么?

 

6.static在3种情况下的作用。

>在函数内声明

>在模块内声明

>在模块内,但不在函数内

 

7.继承有什么好处?多态的作用是什么?

 

8.int const a 与 const int a; const int *a; int * const a;

 

9.