真题笔试解题报告

2017阿里实习生笔试题(二)

下面哪一个不是动态链接库的优点?

A.共享
B.装载速度快
C.开发模式好
D.减少页面交换


静态链接与动态链接:

1 静态链接库的优点 

 (1) 代码装载速度快,执行速度略比动态链接库快; 

 (2) 只需保证在开发者的计算机中有正确的.LIB文件,在以二进制形式发布程序时不需考虑在用户的计算机上.LIB文件是否存在及版本问题,可避免DLL地狱等问题。 

2 动态链接库的优点 

 (1) 更加节省内存并减少页面交换(静态库把所有的文件都装入了,而动态库只是装入了部分需要的);

 (2) DLL文件与EXE文件独立,只要输出接口不变(即名称、参数、返回值类型和调用约定不变),更换DLL文件不会对EXE文件造成任何影响,因而极大地提高了可维护性和可扩展性;

 (3) 不同编程语言编写的程序只要按照函数调用约定就可以调用同一个DLL函数;

 (4)适用于大规模的软件开发,使开发过程独立、耦合度小,便于不同开发者和开发组织之间进行开发和测试。

3 不足之处

 (1) 使用静态链接生成的可执行文件体积较大,包含相同的公共代码,造成浪费;

 (2) 使用动态链接库的应用程序不是自完备的,它依赖的DLL模块也要存在,如果使用载入时动态链接,程序启动时发现DLL不存在,系统将终止程序并给出错误信 息。而使用运行时动态链接,系统不会终止,但由于DLL中的导出函数不可用,程序会加载失败;速度比静态链接慢。当某个模块更新后,如果新模块与旧的模块 不兼容,那么那些需要该模块才能运行的软件,统统撕掉。这在早期Windows中很常见。


由权值分别为1、12、13、4、8的叶子节点生成一颗哈夫曼树,它的带权路径长度为()




哈弗曼树:哈夫曼树是一种带权路径长度最短的二叉树,也称为最优二叉树。
哈夫曼编码:利用哈夫曼树求得的用于通信的二进制编码称为哈夫曼编码。树中从根到每个叶子节点都有一条路径,对路径上的各分支约定指向左子树的分支表示”0”码,指向右子树的分支表示“1”码,取每条路径上的“0”或“1”的序列作为各个叶子节点对应的字符编码,即是哈夫曼编码。
ds50

就拿上图例子来说:

A,B,C,D对应的哈夫曼编码分别为:111,10,110,0


 

已知IBM的PowerPC是big-endian字节序列而Intel的X86是little-endian字节序,如果在地址啊存储的整形值时0x04030201,那么地址为a+3的字节内存储的值在PowerPC和Intel X86结构下的值分别是?
 
大端从大地址开始存储,小端相反,两者都是从数据低位开始存起;
假设从上至下地址递增,则
PowerPC(大):                    Intel X86(小):
04                                            01                    低
03                                            02                      |
02                                            03                      | 
01                                            04                     高

 a+3指向最大的地址,所以分别为1 4


 

在TCP/IP建立连接过程中,客户端或服务器的状态转移说法错误的是?
经历SYN_RECV状态
经历SYN_SEND状态
经历ESTABLISHED状态
经历TIME_WAIT状态
服务器在收到syn包时将加入半连接队列
服务器收到客户端的ack包后将从半连接队列删除



Tcp/Ip有3次握手:
第一次握手:客户端向服务器端发送SYN包(syn=j),进入SYN_SEND状态,等待服务器确认。
第二次握手:服务器收到 SYN包,确认SYN,此时syn=j+1,同时发送一个SYN包(syn=k)即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手: 客户端收到SYN+ACK包,向服务器发送ACK确认包,此时客户端和服务器端均进入ESTABLISHED状态。
 
其中有一个半连接状态:服务器维护一个半连接队列(可以利用半连接进行ddos syn泛洪攻击),该队列卫每个客户端SYN包开设一个条目,标明服务器已经接到SYN包,并向客户端发出确认,这些条目表示的连接处于SYN_RECV状态,得到客户端的确认后进入ESTABLISHED状态。
TIME_WAIT是断开连接时的状态

问题描述: 
#pragma pack(2)
class BU
{
    int number;
    union UBffer
    {
        char buffer[13];
        int number;
    }ubuf;
    void foo(){}
    typedef char*(*f)(void*);
    enum{hdd,ssd,blueray}disk;
}bu;

 

sizeof(bu)的值是()

第一行的使得对齐的补齐字节为2。

因此union的大小为13+1=14.

void foo(){} 不占   ——函数不占用sizeof的内存。

typedef char*(*f)(void*); 不占——声明不占用内存

 enum{hdd,ssd,blueray}disk; 4个字节—— -- 实质上就是整型,所以 size 是 4

 


在动态分区分配方案中,系统回收主存,合并空闲空间时需修改空闲区表,以下哪种情况空闲区会减1?  

A.只要回收主存,空闲区数就会减一
B.空闲区数和主存回收无关
C.无上邻空闲区,也无下邻空闲区
D.有上邻空闲区,但无下邻空闲区
E.有下邻空闲区,但无上邻空闲区
F.有上邻空闲区,也有下邻空闲区

A:错误,因为回收主存时,要根据相邻分区空闲情况决定空闲分区个数,如果不考虑合并的话,空闲分区个数增加一个,因为可能发生合并情况,所以可能- ,可能不变;
B:由上知,错误;
C:无上邻空闲区,也无下邻空闲区,不需要合并空闲分区,空闲分区个数加1;
D:有上邻空闲区,但无下邻空闲区,需要将刚刚的空闲分区表的起始地址修改为上邻空闲区的起始地址和空闲分区大小,但是空闲分区个数不变;
E:有下邻空闲区,但无上邻空闲区,刚刚的空闲分区表起始位置不用改变,空闲分区大小改变,空闲分区个数不变;
F:有上邻空闲区,也有下邻空闲区,假设原来是2个空闲分区,新回收一个,发现前后都是空闲的,将三个合并为1个,最后结果为1个空闲分区,空闲分数个数减1

int* pint = 0; 
pint += 6; 
cout << pint << endl;

 

以上程序的运行结果是: 24


首先如果直接cout一个指针,打印的是其所村春的变量的地址。
这个指针+6,一个指针的大小是4,加4*6就是24.


下面哪种协议在数据链路层?

ARP
ICMP
FTP
UDP
HTTP
VPN

ICMP是网络层,UDP是传输层,FTP和HTTP是应用层

目前VPN隧道协议主要有4种:点到点隧道协议PPTP、第二层隧道协议L2TP、网络层隧道协议IPSec以及SOCKS v5协议。其中,PPTP和L2TP工作在数据链路层,IPSec工作在网络层,SOCK v5工作在会话层。

TCP/IP模型中,ARP协议属于网络层,在OSI参考模型中,ARP属于数据链路层

 


 

以下哪种方式,在读取磁盘上多个顺序数据块时的效率最高?
中断控制方式
DMA方式
通道方式
程序直接访问方式
循环检查I/O方式
以上访问方式都一样


答案选C,通道方式。
(1)程序直接访问方式跟循环检测IO方式:CPU和IO串行,每读一个字节(或字),CPU都需要不断检测状态寄存 器的busy标志,当busy=1时,表示IO还没完成;当busy=0时,表示IO完成。此时读取一个字的过程才结束,接着读取下一个字。
(2)中断控制方式:循环检测先进些,IO设备和CPU可以并行工作,只有在开始IO和结束IO时,才需要CPU。但每次只能读取一个字。
(3)DMA方式:Direct Memory Access,直接存储器访问,比中断先进的地方是每次可以读取一个块,而不是一个字。在中断驱动方式中,I/O设备与内存之间的数据交换必须经过CPU中的寄存器,所以速度还是受限,而DMA方式的基本思想是在I/O设备和内存之间开辟直接的数据交换通路。
(4)通道方式:I/O通道控制方式是DMA控制方式的发展,是以一组数据块为单位的,即可以连续读取多个数据块(每次可以处理多个块,而不只是一个块)

C/C++工程师能力评估

以下prim函数的功能是分解质因数。括号内的内容应该为?
void prim(int m, int n)
 {
     if (m >= n)
     {
         while (        ) n++;
         (     );
         prim(m, n);
         cout << n << endl;
     }
 }
m/=n   m%=n

分解质因数:从n=2开始,找到第一个m的质因子,因此需要m&n==0,然后继续找剩下的质因子,此时m=m/n。也可以用循环法做,此处不再赘述。
补充一下判断是否为质数的代码:
int isPrime(int n)  
{  
    int flag = 1;  
    for(int i = 2; i*i <= n; i++)  
        if(n%i == 0)  
            flag = 0;  
    return flag;  
}  

补充一下最大公约数的代码:(辗转相除法)

 

int gcd(int x,int y) //用辗转相除法,求两数的最大公约数  
{  
    int r;  
    while(y>0)  
    {  
        r=x%y;  
        x=y;  
        y=r;  
    }  
    return x;  
}

求两数的最大公约数,可采用欧几里得方法:只要两数不相等,就反复用大数减小数,直到相等为止,此相等的数就是两数的最大公约数。

int gcd(int x,int y)
{
    while(x!=y)
    {
        if(x>y) x=x-y;
        else y=y-x;
    }
    return x;
}

补充一下最小公倍数的代码:先求出最大公约数,用x*y/gcd(x,y)就可以得到最小公倍数。

 


 

enum string{    
    x1,    
    x2,    
    x3=10,    
    x4,    
    x5,    
} x;

函数外部问x等于什么?0

如果是函数外定义那么是0
如果是函数内定义,那么是随机值,因为没有初始化;

  • 数组在当做参数传给函数的时候,会被退化为普通指针,因此,sizeof还是4字节。
  • 指针在做加减的时候,是根据其所指的类型进行加减的,因此加减sizeof(type)。
  • 静态局部变量要比全局变量先销毁。

 

  • 重载和重写的一些要求:
方法重载(overload):
1.必须是同一个类
2方法名(也可以叫函数)一样
3参数类型不一样或参数数量不一样

方法的重写(override)两同两小一大原则:
方法名相同,参数类型相同
子类返回类型小于等于父类方法返回类型,
子类抛出异常小于等于父类方法抛出异常,
子类访问权限大于等于父类方法访问权限。
 
  • char(* ss)[N]表示是一个指向一个数组(字符串数组),这个数组存的类型是——char[N],即又是一个一位数组。
  • 类的大小注意点:
    1 先找有没有virtual 有的话就要建立虚函数表,+4
    2 static的成员变量属于类域,不算入对象中,成员函数也不占空间      +0
    3 神马成员都没有的类,或者只有成员函数        +1
    4 对齐法则,对大家都没有问题,看看有没有被#pragma pack(n)设置大小

int main()
{
    MyClass obj1(1), obj2(2);
    MyClass obj3 = obj1;
    return 0;
}

关于拷贝构造和赋值的问题:以上例子中obj3调用的是拷贝构造函数,因为obj3还未存在,需要调用拷贝构造函数。

假如obj3已经存在,才会调用赋值函数。

 


 数组全排列:

 

算法思路:

 

(1)n个元素的全排列=(n-1个元素的全排列)+(另一个元素作为前缀);

 

(2)出口:如果只有一个元素的全排列,则说明已经排完,则输出数组;

 

(3)不断将每个元素放作第一个元素,然后将这个元素作为前缀,并将其余元素继续全排列,等到出口,出口出去后还需要还原数组

 

void perm(int list[], int k, int m)
{
    if (k==m)
    {
        copy(list,list+m,ostream_iterator<int>(cout," "));
        cout<<endl;
        return;
    }
    for (int i=k; i<=m; i++)
    {
        swap(&list[k],&list[i]);
        perm(list,k+1,m);
        swap(&list[k],&list[i]);
    }
}

腾讯2016研发工程师在线模拟笔试题 

1、 32位系统中,定义**a[3][4],则变量占用内存空间为()。
解析:**a[3][4]表示a存储的地址,指向一个大小为3*4的数组,这个数组里存储的是指针。指针大小为4,4*3*4=48。

2、有36辆自动赛车和6条跑道,没有计时器的前提下,最少用几次比赛可以筛选出最快的三辆赛车?

解析:6次得到小组第一,a1 b1 c1 d1 e1 f1,def所在的小组的二三名舍弃。
第七次,a1,b1,c1,d1,e1,f1进行比赛。得到前三,假设为b1,d1,e1。则a1,c1,f1被舍弃。
第八次,在b2,b3,d1,d2,e1种得到第二名和第三名。

3、下列哪些http方法对于服务端和用户端一定是安全的?()

解析:

Get:通过请求URI得到资源。
Post:不仅可以查询也可以添加新的内容,请求放在Body里。
Head:类似于Get,但是不返回body,获得请求uri的响应报头,用于检查对象是否存在。
Trace:用于远程诊断服务器。
Options:列出请求的资源支持的所有方法。
Head、Get、Options和Trace视为安全的方法,因为它们只是从服务器获得资源,而不对服务器做任何修改,但对于用户端未必安全。例如get容易将敏感信息暴露在uri中。

4、一个系统,提供多个http协议的接口,返回的结果Y有json格式和jsonp格式。Json的格式为 {"code":100,"msg":"aaa"},为了保证该协议变更之后更好的应用到多个接口,为了保证修改协议不影响到原先逻辑的代码,以下哪些设 计模式是需要的?协议的变更指的是日后可能返回xml格式,或者是根据需求统一对返回的消息进行过滤。()

解析:装饰者模式:动态添加功能,例如过滤功能,并不修改原来的逻辑。
proxy模式:为类提供代理,使得代理类可以控制对 对象的访问,例如实现延迟实例化,控制访问等。
composite模式:组合模式,将对象组合成树形结构以表示"部分-整体"的层次结构。使得用户对单个对象和组合对象的使用具有一致性。

5、对于定义"int *p",下列哪些说明可能是正确的?()

int (*p)「10」才表示一个指向一位数组的指针。
int * (*p)「10」表示一个指向二位数组的指针。
int arr「10」;
int *p=arr;此时p保存的是第一个元素的地址。

6、十字链表是无向图的一种存储结构()是

解析:

图的存储结构:

邻接矩阵,也成为数组表示法。用一个一维数组存储图中顶点的信息,用一个二维数组存储边的信息。

邻接表:是一种顺序存储和链式存储结合的存储方法。

十字链表:即把每一条边的边结点分别组织到以弧尾顶点为头结点的链表和以弧头顶点为头顶点的链表中。在十字链表表示中,顶点表和边表的结点结构分别如图8.13 的(a)和(b)所示。

http://c.biancheng.net/cpp/uploads/allimg/120223/1-120223224210X3.jpg

http://c.biancheng.net/cpp/uploads/allimg/120223/1-120223224315394.jpg

7、在一个元素个数为N的数组里,找到升序排在N/5位置的元素的最优算法时间复杂度是 O(n)

解析:算法课上讲过的,线性复杂度的找第K大的元素的算法。

BFPRT算法解决的问题十分经典,即从某n个元素的序列中选出第k大(第k小)的元素,通过巧妙的分 析,BFPRT可以保证在最坏情况下仍为线性时间复杂度。

该算法的思想与快速排序思想相似,当然,为使得算法在最坏情况下,依然能达到o(n)的时间复杂 度,五位算法作者做了精妙的处理。

算法步骤:

1. 将n个元素每5个一组,分成n/5(上界)组。

2. 取出每一组的中位数,任意排序方法,比如插入排序。

3. 递归的调用selection算法查找上一步中所有中位数的中位数,设为x,偶数个中位数的情况下设定为选取中间小的一个。

4. 用x来分割数组,设小于等于x的个数为k,大于x的个数即为n-k。

5.  若i==k,返回x;(i就是中位数)

  若i<k,在小于x的元素中递归查找第i小的元素;

  若i>k,在大于x的元素中递归查找第i-k小的元素。

  终止条件:n=1时,返回的即是i小元素。

8、下面的程序输出可能是什么?一直是“ab”

class Printer{
    public:
        Printer(std::string name) {std::cout << name;}
};
class Container{
    public:
        Container() : b("b"), a("a") {}
    Printer a;
    Printer b;
};
int main(){
    Container c;
    return 0;
}

 

 初始化列表的初始化顺序  与在列表中的顺序无关,由变量在类中定义的先后顺序决定.

因此先初始化a,然后再初始化b。

还有一种题目:

    class A  
    {  
    private:  
        int n1;  
        int n2;  
          
    public:  
        A():n2(0),n1(n2+2){}  
      
        void Print(){  
            cout << "n1:" << n1 << ", n2: " << n2 <<endl;    
        }  
    };  
      
    int main()  
    {  
      
        A a;  
        a.Print();  
      
        return 1;  
    }  

 

 这个题目,因为先初始化n1,此时n2还未初始化,所以是未定义值。后来n2才被初始化为0.因此结果是下图:

9、下面程序段包含4个函数,其中具有隐含this指针的是() f4

int f1();
class T
{
    public:static int f2();
    private:friend int f3();
    protect:int f4();
};

解析:

静态成员函数属于整个类所拥有,没有this指针
友员函数不是这个类的成员,没有
类的非静态成员函数  有
补充:之前有个问题,能不能同时用const和static修饰类的成员函数。答案是不能,为什么?因为const函数(成员函数)会隐含const this*以保证不修改类的成员。而static不能有this指针。因此不能同时修饰。

 10、如果在一个排序算法的执行过程中,没有一对元素被比较过两次或以上,则称该排序算法为节俭排序算法,以下算法中是节俭排序算法的有________。插入排序,归并排序

解析:

A。插入排序的思想是对第i+1位置上的数,将其插入前i个有序数组中。插入以后形成新的有序数组,根据排序数组不会在比较的原则,该元素不可能再次比较了。

B。选择排序的思想是对当前第i个位置上的数,那么在后续数组中,选最小的与i对换。说明肯定比较过第二小和第三的数。那么在i+1位置上,上次第二小和第三小的数还需要比较一次选出最小的与i+1交换。那么至少比较了两次。
C。堆排序。堆排序分两步。初始建堆和堆重建。当最大元素与最末尾元素交换后。面临堆重建的问题。那么堆顶元素下层过程中,必然与第二小的元素比较一次。再一次堆重建,假设第二小元素被替换的时候,他们会在比较一次。
D。归并排序思路是对两个已经排好序的数组,同时向后移动。那么每个元素只会与其他数组中的元素比较一次。然后合并在一起。根据同组元素不会比较的原则的,以后两个元素不可能在比较到。
11、一棵哈夫曼树的带权路径长度等于其中所有分支结点的权值之和()错
解析:哈弗曼树又称最优树,
 
路径长:第L层的路经长为L-1
带权路径长度(Weighted Path Length of Tree):定义为树中所有叶结点的带权路径长度之和。
结点的带权路径长度:结点到树根之间的路径长度与该结点上权的乘积。
————————————————————————————————

哈夫曼编码:(左1右0/左0右1)。得到码表,根据出现的概率乘以码长可以得到平均码长。平均码长/等长编码=压缩率。

哈夫曼编码步骤:(找最小权值的节点拿出来构成一颗二叉树,生成一个中间节点,再重复)

一、对给定的n个权值{W1,W2,W3,...,Wi,...,Wn}构成n棵二叉树的初始集合F= {T1,T2,T3,...,Ti,...,Tn},其中每棵二叉树Ti中只有一个权值为Wi的根结点,它的左右子树均为空。(为方便在计算机上实现算 法,一般还要求以Ti的权值Wi的升序排列。)
二、在F中选取两棵根结点权值最小的树作为新构造的二叉树的左右子树,新二叉树的根结点的权值为其左右子树的根结点的权值之和。
三、从F中删除这两棵树,并把这棵新的二叉树同样以升序排列加入到集合F中。
四、重复二和三两步,直到集合F中只有一棵二叉树为止。

12、若以下程序

#include <stdio.h>
main()
{ 
    FILE *fp;
    int i,a[ 6]={1,2,3,4,5,6},k;
    fp = fopen ("data.dat", "w+b");
    for (i=0;i<6;i+ +)
    { 
        fseek(fp,0L,0);
        fwrite(&a[5—i],sizeof(int),1,fp);
    }
    rewind(fp);
    fread(&k,sizeof(int),1,fp);
    fclose(fp);
    printf("%d",k);
}

 

解析:

C的关于文件I/O的函数库,首先定义了一个文件流fp。然后以w+b的方式打开data文件。下图是open的mode选项:r读w写a追加  加b表示binary二进制模式
然后fseek 用于二进制方式打开的文件,移动文件读写指针位置。最后开头只有1。将文件内部的位置指针重新指向一个流(数据流/文件)的开头。654321,每次都写开头,都覆盖模式。rewind(fp);也是将读写指针移到开头。最后读入k只有1.

 

13.对{05,46,13,55,94,17,42}进行基数排序,一趟排序的结果是:()42,13,94,05,55,46,17

解析:

算法导论中原话:“基数排序是首先按最低位有效数字进行排序,才是稳定的算法”。有悖于比较两个数的常理。
理由:基数排序,又称为bucket sort,因为Hollerith当时的人们是拿着真的桶进行排序的。如果数字从高位开始排序,意味着有n位,就得有10^n个桶。因为你先排最高位,然 后对于这个最高位又要分出十个桶排下一位。比如现在有13,23,12,22,11这五个数。你先为高位排序,就相当于把十位为1的分在一个桶1里 (13,12,11),十位为2的分在一个桶2(22,23)里。然后在桶1和桶2之中剩下的元素排序((11),(12),(13))和((22), (23))。这样如果有很多位数,桶就很多。但是从最低位开始排就只需要10个桶,每移动一位,就用针对那一位排序(把元素扔进桶)
14、下列关于 clone 和 fork 的区别描述正确的有?
clone是fork的升级版本,不仅可以创建进程或者线程,还可以指定创建新的命名空间(namespace)、有选择的继承父进程的内存、甚至可以将创建出来的进程变成父进程的兄弟进程等等
解析:
fork()是全部复制
vfork()是共享内存
clone()是可以将父进程资源有选择地复制给子进程,而没有复制的数据结构则通过指针的复制让子进程共享,具体要复制哪些资源给子进程,由参数列表中的clone_flags来决定。另外,clone()返回的是子进程的pid。

14、下列关于bool,int,float,指针类型的变量a 与“零”的比较语句正确的有?

bool : if(!a)

int : if(a == 0)

float: if(a == 0.0) 错误:

指针: if(a == NULL)

解析:
由于计算机二进制表示浮点数有精度的问题,0.0(浮点double)实际上不是0,而是非常接近零的小数,所以C错!
在ANSIC C中定义了FLT_EPSILON/DBL_EPSILON/LDBL_EPSILON来用于浮点数与零的比较,一般if(fabs(a)<FLT_EPSILON)或if(fabs(a)< DBL_EPSILON)就可以表示a是否“为0”。而if(a==0.0)是永远不会成立的,达不到要求!

 

 





















 
 
posted @ 2016-09-07 22:36  0giant  阅读(2968)  评论(0编辑  收藏  举报