分享

2013年微软实习生招聘笔试题目(c/c 你懂多少?)

 昵称42652335 2017-05-04

今天本是清明放假,但是,但是微软就是在今天下午组织了笔试考试,据说是全国统考。75分钟,20 道选择题,问题是,问题是神马?先看下简单的评分:

1-8 3 2 -2 09-18 5 3 -3 019-20 13 7 -7 0

1-8 每题 3 分,完全正确 3 分,不完全正确但是没有错误 2 分,有错误 -2 分,不做 0 分;19、20 题,完全正确 13 分,有错就是 -7 分,不做 0 分。是的,么有看错,有负分!做错了,不仅意味着本道题的分不得,而且要倒扣,简单滴说,最后一题做错了,就会从 100 分中扣去 20 分,因为本题得分 -7 分!如果题目全部都错做了,估计是 -50 多分!

题目给 me 的赶脚是,非大牛不能得高分,像 me 们这些半斤八两的,还不敢轻易选择,估计多数都是半对,实际上得一半的分。废话不多说,上题(题目是 me 翻译过来的,无碍大局的地方有所修改,所以不算原模原样的题)。me 会将 me 认为的正确答案标注为蓝颜色,而 me 选的答案标注为红颜色(me 没有标准和正确答案),后面加上题目分析。

笔试题目

  1. 下面哪些调用转换支持可变长度参数:( 3 分 )
    A. cdecl B. stdcall C. pascal D. fastcall
    注:me 表示丫梨很大,第一题不会,~~~~(>_<>

    分析:关于上面的函数修饰符的详细说明,可以看看百度百科。这里也简单说明,cdecl 是 C 语言的调用方式,函数参数从右到左求值,然后入栈,参数由调用方负责清理,传多少参数其实都无所谓的,于是就支持所谓的可变长度的参数;stdcall 是 C++ 的调用方式,参数从右到左求值,入栈,由被调用方处理参数,所以参数个数类型都必须匹配;pascal 是对 PASCAL 函数的调用方式,参数自左向右求值,其他类似于 stdcall;而 fastcall 的情况忽略。

  2. 下面程序执行结果:( 3 分 )
    1. #include
    2. using namespace std;
    3. class A{
    4. public:
    5.     virtual void f() {   cout  'A::f() '; }
    6.     void f() const {   cout  'A::f() const '; }
    7. };
    8. class B : public A
    9. {
    10. public:
    11.     void f() {   cout  'B::f() '; }
    12.     void f() const {   cout  'B::f() const '; }
    13. };
    14. void g(const A* a)
    15. {
    16.     a->f();
    17. }
    18. int main(int argc, char *argv[])
    19. {
    20.     A* p = new B();
    21.     p->f();
    22.     g(p);
    23.     delete(p);
    24.     return 0;
    25. }

    A. B::f() B::f() const
    B. B::f() A::f() const
    C. A::f() B::f() const
    D. A::f() A::f() const

    分析:用 const 来修饰函数和没用 const 是不同的重载,const 修饰函数表明,函数不能修改对象的状态/值。对于非 const 对象调用非 const 函数,当然也可以调用 const 函数(优先前者);然而 const 对象只能调用 const 函数。其次,A 中的 void f() 标注为 virtual ,那么子类 B 中跟它原模原样声明即使米有标注为 virtual 的函数依然是 virtual 函数。virtual 函数就是通常所谓的实现多态的方法了,比如,上面的 p->f(); p 虽然是 A 类型指针,但是实际上指向 B 对象,调用的时候调用的是 B 的 f() 方法。为嘛 a->f() 不调用 B 的 f() const 方法呢?c++ 除非标注为 virtual,否则不进行多态,也就是说 a 是 A 类型的指针,就调用 A 对应的 f() const 方法。
    问题并没有到此结束,上面的代码就是有 bug 的,试问,delete p; 的时候删除对象,是作为 A 对象删呢,还是 B 对象删呢?p 的类型是 A*,但是指向的是 B 对象丫,好吧,这里应该使用多态,正确的做法应该将 A 的析构函数标注为 virtual,否则,程序就有 undefinde behaviour(未定义的行为)。继续讨论,看下面的 code :

    1. int main(int argc, char *argv[])
    2. {
    3.     A base = B();
    4.     base.f();
    5.     g(&base);
    6.     return 0;
    7. }

    这里的结果会和上面的一样么?!——,实际情况是神马呢,——,不一样!这个时候的结果选 D,理由:多态只能通过指针和引用使用,对象不能使用。说到引用,将上面的代码修改为 A& base = B(); 会怎么样呢?编译错误!引用不能引用临时对象!所以代码只能改为 B b; A& base = b; 比如下面的:

    1. int main(int argc, char *argv[])
    2. {
    3.     B b;    // B b();
    4.     A& base = b;
    5.     base.f();
    6.     g(&base);
    7.     return 0;
    8. }

    这个时候程序的结果还是 B。定义 b 的时候,使用上面注释掉的行不行呢?不行,因为那样,编译器会认为 b 是一个函数!!what?!!默认构造对象的时候,() 是不能加的??!!好吧,就是这样。再进一步说,会不会赶脚上面的代码有点“蹩脚”?如果 A a = B(); 就是可以的,然而 A& a = B(); 不可以,而要首先去定义一个 B对象,然后去引用,麻烦!好吧,c++11给 me 们带来了新希望(如果 u 不认为是灾难的话):

    1. int main(int argc, char *argv[])
    2. {
    3.     A&& base = B();
    4.     base.f();
    5.     g(&base);
    6.     return 0;
    7. }

    这里的结果的答案还是 B。A&& 是右值引用,使得 me 们可以引用到一个临时量 B(); 自此以后那个临时量就归右值引用管了,不再算是临时量。基本到此为止,有点混乱,有点O__O'…。再最后补充一句,少用指针,多用对象和引用!(使用引用的时候,析构函数貌似不需要显式地标注为 virtual,这一点细节有点不清楚,至少编译器对此没有提示。)

  3. 链表和数组的区别: ( 3 分 )
    A. 在有序的情况下搜索
    B. 插入和删除
    C. 随机访问

    D. 数据存储类型

    分析:ABC 是明显的,有序的情况下数组可以二分查找,而链表不可以;插入和删除,数组不如链表灵活;链表不如数组可以随机访问。D 选项,不敢选,me 数据结构感觉跟存储类型么有必然关系,如果说存储类型指的是栈 (stack) 或是堆 (heap) 的话,数组也可以在堆上分配,链表也可以是静态链表(栈上的空间)。

  4. Windows 下进程和线程的描述,哪些是对的:( 3 分 )
    A. 操作系统的一个程序必须有一个进程,但是不必须有一个线程
    B. 进程有自己的栈空间,而线程只共享父进程的栈空间
    C. 线程必从属于一个进程
    D. 线程可以更改从属的进程

    分析:最初 me 不确定有些说法,因为题目强调说的是 Windows 下的进程和线程,不过后来翻看了下书,基本确定了某些说法。Windows 中的进程和线程的概念和一般操作系统书中的说法是一致的,进程是资源分配的基本单位,而线程是 CPU 调度的基本单位;一个线程从属于一个进程,当然一个进程可以创建多个线程,一个进程中的多个线程共享进程的栈空间(当然也有其他一些资源);但是每一个线程也可以有自己的栈空间,叫 TLS(线程本地存储);如果一个进程中没有显式地创建一个线程,那么就是所谓的单线程进程,其实也就是 main 执行流对应的线程。这样的话,A、B 是错的,C 是对的,而线程可以更改所属进程,太扯了,错误!补充一点,记得以前看 UNIX 高级编程,貌似有这样的说法:UNIX 系统的进程和线程和前面的说法也基本一致,但是 Linux 却特殊一点,特殊到哪一点呢?似乎没有进程和线程的明确划分,都是线程,只不过有些线程就是共享一些数据(类似于线程共享进程的数据),给人的赶脚就是同属于一个进程;有些线程不同享数据,就如同进程一般有所区分。(这里的说法不一定准确,所以,O__O'…)

  5. 下面代码段的运行结果:( 3 分 )
    1. #include
    2. int main()
    3. {
    4.     int x = 10;
    5.     int y = 10;
    6.     x = x++;
    7.     y = ++y;
    8.     printf('%d %d', x, y);
    9.     return 0;
    10. }

    A. 10 10 B. 10 11 C. 11 10 D. 11 11

    分析:神奇的题目有木有丫!按标准的说法,像 x=x++; 这样的表达式,是 undefined !就是标准中没有定义的。因为 x++ 表达式的值是 10 ,后来要赋值给 x,其次 x 还要自加 1,问题是,先赋值呢,还是先自加呢?这里没有统一的说法!虽然 y = ++y; 不管怎么说结果都一样,me 认为这样的表达式也不是很合理!在一个表达式中对同一个变量多次赋值和多次使用,结果很难把握。
    说说运行结果:多数人在 vc++ 和 gcc 下运行结果都是 D,但是 me 的运行结果是 B,me 的系统,win7 64位,使用 Mingw-w64 gcc 4.8.0 编译器。

  6. C# 或是 Java 程序段的结果:( 3 分 )
    1. int[][] array = new int[3][]{
    2.     new int[3]{5,6,2},
    3.     new int[5]{6,9,7,8,3},
    4.     new int[2]{3,2}
    5. };

    array[2][2] 返回神马 ?
    A. 9 B. 6 C. 2 D. 溢出

    分析:介个,貌似不用多说,跟 C 中的数组有所区分,C 中的二维数组就是相同一维数组的数组,结构比较整齐,而 Java/C# 中的二维数组仅仅是一维数组的数组而已,所以 array[2][2] 这个元素其实不存在,在 Java 中运行会抛出越界异常。(实际上面的代码段不是合法的 Java 代码段,不过意思是那样的。)

  7. 下面说法哪些正确?( 3 分 )
    A. const int a; // a 是常数
    B. int const a; // a 是常数
    C. int const *a; // a 指向常数的指针

    D. const int *a; // a 是常指针
    E. int const *a; // a 是常指针

    分析:关于 const 和 static 貌似永远有说不完的话题,O__O'…。A、B、C 都是正确的,D 和 E 都是指向常量的指针,const 放置到 int 前面还是后面不是问题所在,而 int * const a; 才是常量指针,可以修改指向的值,但是不能修改指向。实际话题还可以再深入些,比如 A 的声明是合法的 C++ 声明吗?是合法的 C 声明吗?在 C++ 中 A 的写法肯定是 error,因为没有初始化!而在 C 中,A 只是声明而已,就是说明它是一个 const int,maybe 在其他处定义了。下面的代码段:

    1. int const a = 3;
    2. int const a;
    3. int main(int argc, char *argv[])
    4. {
    5.     return 0;
    6. }

    c 程序上面是合法程序,c++ 是错误程序!如果 typedef int *PtrInt; 这种情况下 const PtrInt a; a 是常量指针呢,还是指向常量呢?—— 可以自己去尝试一下。(答案:常量指针。)

  8. 下面程序的执行结果:( 3 分 )
    1. #include
    2. class A{
    3. public:
    4.     long a;
    5. };
    6. class B : public A
    7. {
    8. public:
    9.     long b;
    10. };
    11. void seta(A* data, int idx)
    12. {
    13.     data[idx].a = 2;
    14. }
    15. int main(int argc, char *argv[])
    16. {
    17.     B data[4];
    18.     for(int i=0; i4; ++i){
    19.         data[i].a = 1;
    20.         data[i].b = 1;
    21.         seta(data, i);
    22.     }
    23.     for(int i=0; i4; ++i){
    24.         std::cout   data[i].a  data[i].b;
    25.     }
    26.     return 0;
    27. }

    A. 11111111 B. 12121212 C. 11112222 D. 21212121

    分析:程序的运行结果是 22221111,这里没有答案!至于答案的分析,应该涉及到 c/c++ 内存对象的布局问题,虽然 me 不大懂,但是还是可以“想当然”滴班门弄斧一下。像上面的类 A,和 c 中的结构体是兼容的,在 c++ 中叫做 POD,结构体中数据布置,应该是按照声明的顺序放置的(中间可能有空隙),所谓名字的访问其实也是根据顺序访问的,看个下面的例子:

    1. #include
    2. struct Test{
    3.     int a;
    4.     int b;
    5. };
    6. int main(int argc, char *argv[])
    7. {
    8.     long long test = 0x1234567887654321;
    9.     printf('%x %x'(*(struct Test*)&test).a(*(struct Test*)&test).b);    // 87654321 12345678
    10.     return 0;
    11. }

    int 是 4 个字节,long long 是 8 个字节,强制将 long long 的 test 当做结构体 struct Test 处理,.a 访问的是低 4 个字节的内容,.b 访问的是高 4 个字节的内容。有了这点基础,再来看原来的题目,A 是一个类(结构体),里面只容纳一个 long,(me 电脑上 4 个字节),B 继承了 A,又多容纳了一个 long,实际就是俩 long。data 的 B 类型数组 4 个对象,本来是 8 个 long,不看 seta 函数的话,全部是 1,而在函数 seta 中处理的时候,数组变成 A 类型的了,一个 A 元素是一个 long,所以在函数 seta 内部,访问 data[0] - data[3] 实际是访问的 8 个 long 的前 4 个 long,也就是将前 4 个 long 置为 2,前 4 个 long 在 main 函数中对应的就是 B 类型数组的 data[0].a、data[0].b、data[1].a、data[1].b,于是乎 main 函数输出 B 数组对象,结果是 22221111。

  9. 1000 个瓶子中有一瓶毒药,一只老鼠吃到毒药一周之内会死,如果要在一周之内检测出有毒药的一瓶,问至少需要几只老鼠?( 5 分 )
    A. 9 B. 10 C. 32 D. 999 E. 上面答案都不对

    分析:当初图样图森破,图拿衣服。老鼠吃过药不会立马死,当时 me 这个重点么抓到,其次,要测出来这瓶毒药至少几只老鼠,me 当时傻×:最少一只吧,比如正好碰巧测出来!~~~~(>_<)~~~~好吧,网上这个题目有很多人已经回答了,10 只老鼠,因为每只老鼠的死亡状态可能有="" 2="" 种,10="" 只的话,可以拼凑出="" 2="" ^="" 10="1024" 种状态,也就是="" 1024="">

    0 - 1023 的话,用二进制表示正好是 10 位,每一位都是 0 或是 1。如果第一位表示第一只老鼠吃不吃的情况,第二位表示第二只老鼠吃不吃的情况,第 n 位表示第 n 只老鼠吃不吃的情况,首先说,这 1024 个瓶子,正好可以给老鼠们一种吃法,现在的问题是,比如第 k 瓶有毒,老鼠们肯定有一种状态吧,比如有些死了,有些没有死,我们希望第 k 瓶有毒就对应一种状态,这样的话,根据状态就可以反推是第 k 瓶。现在就看是不是有这么个一一对应关系。
    比如第 1 瓶有毒,0000...0001,第一只老鼠吃了,其他的老鼠都没有吃,如果有毒的话,第一只老鼠就死了,其他老鼠都没事(注:其他瓶没有毒,所以即使第一只老鼠吃了其他瓶,其他老鼠也吃了其他的瓶,但是它们的命运却不改变,就因为第 1 瓶有毒!);再比如第 3 瓶有毒,0000...0011,第一、二只老鼠吃了,其他老鼠没有吃,结果第一和第二只老鼠死了,其他老鼠没事(注:它们有没有吃其他的瓶子也不影响它们的命运,因为第 3 瓶有毒!)。由此可见,第 k 瓶有毒,就是 k 对应的二进制数的二进制位为 1 的老鼠死掉,这是必然的。一瓶毒药的方法,比如第 k 瓶,对应一个二进制数,对应一种老鼠死法,所以,一种老鼠死法,就对应一瓶毒药的方法,也就是根据老鼠的死法,就能判断出是第多少瓶:

    将第 i 只老鼠死了,记二进制的第 i 位为 1,然后看看这个二进制是多少,就可以了。(前提,根据瓶子的编号,分配老鼠吃药,二进制位上为 1 表示吃。)

    方法是不是唯一的呢?对称的还有一种,根据瓶子的编号,还是给老鼠喂药,对应编号为 1 的不喂,对应编号为 0 的反而喂药,最终判断方法如何呢?

    将第 i 只老鼠死了,记二进制的第 i 位为 0,然后看看这个二进制是多少,就可以了。(前提,根据瓶子的编号,分配老鼠吃药,二进制位上为 1 表示不吃。)

  10. 在 C 语言中下面那个语句的结果是 1 ?( 5 分 )
    A. main 函数正常结束的返回值
    B. return 7&1;
    C. char *p='hello'; return p == 'hello';
    D. return 'hello' == 'hello';
    E. 上面都不对

    分析:A 一定是错的,C/C++ main 函数正常结束,返回 0;B 一定是对的,C 和 D 呢?标准 C 规定是,同样两个字符串字面量,比如 'hello',内存是放一份还是两份,由编译器决定!所以,C 和 D 的结果,一般的编译器应该会有警告提示,说明是 undefined behaviour !(前面第 2 题和第 5 题,也有遇到过!) 所以,C 和 D 只能说很可能是对的,但是却不一定!

  11. F、G、X 都是32位有符号整数,F=X/2,G=X>>1,如果 F != G,那么:( 5 分 )
    A. 编译错误
    B. X 是奇数
    C. X 是负数
    D. F-G=1
    E. G-F=1

    分析:前面第 5 题和第 10 题都出现了 undefined behavior 的行为!不幸的是,这里又出现了一个!~~~~(>_<)~~~~ 这是="" microsoft="">

    A 肯定是错的,C 肯定是对的,因为正数和0,一定不会出现 /2 和 >>1 不相等的情况。然后 B 和 D 呢?如同上一题,几乎可以肯定滴说,B 和 D 也是对的,为神马是几乎肯定?因为负数的右移,多数都是实现为“算术移位”,如果是“逻辑移位”的话,一个负数移位之后变成了一个正数,B 和 D 都不对。
    现在说,“算术移位”,B 是对的。算术移位,最高位填 1,对于负偶数来说,/2 和 >>1 结果还是一样,跟正数类似,所以,结果不一样,一定是奇数!其次,其次!!欲哭无泪丫……5/2 == 2,5%2 == 1,这个结果是绝对的!但是对于 -5/2 == ? -5%2 == ? 上,标准是没有规定的,于是又出现一个 undefined behaviour! 一般的实现是,模运算的结果要和被除数一致,然后保证 (a/b)*b + (a%b) == a,所以-5%2 == -1,-5/2 == -2,同时 -5 >> 1 == -3,于是乎 F-G = 1。me 死的心都有了,O__O'…

  12. 3*4 的方格,有多少个长方形?( 5 分 )
    A. 18
    B. 20
    C. 40
    D. 60
    E. 上面都不对

    分析:高中数学题目,O__O'…(本来想解释一下的,但是赶脚么必要丫,公式 C(2,4) * C(2,5) = 6*10 = 60,横向取 2 条线,纵向取 2 条线,就是一个长方形。)

  13. 一个直线将一个平面分成 2 部分,两条直线分成 4 部分,如果直线不平行,多条直线不共一点,问 100 条直线将平面分成几部分?( 5 分 )
    A. 5051
    B. 5053
    C. 5510
    D. 5511

    分析:小学数学题目,O__O'…(公式:1+1+2+3+4+...+n=1+(n+1)n/2 = 1+5050 = 5051)

  14. 下面哪些是稳定排序:( 5 分 )
    A. 冒泡儿排序
    B. 快速排序
    C. 堆排序
    D. 归并排序
    E. 选择排序

    分析:略。

  15. Web 应用程序中常使用 MVC 模式,关于说法下面哪些是对的?( 5 分 )
    A. 模型 ( Model )表示数据以及处理数据的业务逻辑
    B. 视图 ( View ) 是对模型的(可视化)展示,它渲染模型的结果,典型的是一个用户接口元素(user interface element)
    C. 控制器介于用户和系统之间,它接受用户的输入,指挥着模型和视图来完成输入对应的任务

    D. MVC 的常用实践是,模型从用户接收 GET 和 POST 的请求,然后决定做神马,通过移交给控制器和视图
    E. 上面都不对

    分析:MVC 貌似就是 ABC 的说法。

  16. 根据下面哪些可以确定一棵二叉树?( 5 分 )
    A. 前序遍历和中序遍历
    B. 前序遍历和后序遍历
    C. 中序遍历和后序遍历
    D. 后序遍历

    分析:至少要一个中序遍历,前序+后序遍历不中。

  17. n 个字符构成的字符串,假设每个字符都不一样,问有多少个子串?( 5 分 )
    A. n+1
    B. n^2
    C. n(n+1)/2
    D. 2^n-1
    E. n!

    分析:介个,貌似也简单,长度为 1 的字符串 n 个,长度为 2 的 n-1 个,长度为 3 的 n-2 个,...,长度为 n 的 1 个,然后 n+(n-1)+(n-2)+...+1 = ?。

  18. 根据下面给的表和 SQL 语句,问执行 SQL 语句更新多少条数据?( 5 分 )
    sql 语句: update Books set NumberOfCopies = NumberOfCopies + 1 where AuthorID in select AuthorID from Books group by AuthorID having sum(NumberOfCopies) <= 8="" 表中数据:bookid="" tittle="" category="" numberofcopies="" authorid1="" sql="" server="" 2008="" ms="" 3="" 12="" sharepoint="" 2007="" ms="" 2="" 23="" sharepoint="" 2010="" ms="" 4="" 25="" db2="" ibm="" 10="" 37="" sql="" server="" 2012="" ms="" 6="">

    A. 1 B. 2 C. 3 D. 4 E. 5

    分析:不多说,SQL 语句执行。

  19. 下图中从 S 到 T 的最短路径是多少?(边上是连接点的长度):( 13 分 )



    A. 17
    B. 18
    C. 19
    D. 20
    E. 21

    分析:从前向后找最短路径,先是 A1、A2、A3,然后 B1、B2,其次 C1、C2,最后 T 。

  20. N个球中有一个假冒伪劣(重量不足),如果给你一个天平允许你测 3 次找出那个假冒伪劣,问 N 可能的值?( 13 分 )
    A. 12
    B. 16.
    C. 20
    D. 24

    E. 28

    分析:3 个一次可以测出来,3*3 = 9 个以内 2 次,3*3*3 = 27 个以内,3次!所以,有个公式:n 次可以测出来 3^n 以内的假冒伪劣,至于怎么测,方法都一样,平均分成三墩,然后,略。

题目简单分析:

  1. C 语言 3 道:5、10、11:++ 运算符、字符串指针、按位运算;(各种 bug 程序!)
  2. C++ 语言 3 道:2、7、8:多态、const 关键字、const 和指针、POD;
  3. Java/C# 语言 1 道:6:多维数组;
  4. 数据结构 5 道:3、14、16、17、19:链表和数组、稳定排序、二叉树、字符串、最短路径;
  5. Windows 程序设计 1 道:1:函数修饰符;
  6. 操作系统 1 道:4:线程和进程;
  7. 设计模式 1 道:15:MVC 模式;
  8. 数据库 1 道:18:sql 语句;
  9. 数学 2 道:12、13:组合数学;
  10. 智力题 2 道:9、20:二进制、等比数列;

分值估计

下面是 me 的分值估计,最佳估计,就是将那些比较肯定的暂时米有发现有问题的,就看成是“完全正确”;大致估计,将有些拿不准的但是现在没有发现错误的,化作“半对”;最坏估计,将某些可能错的(和其他人想法有出入),就化为“错误”,但是比较肯定的还是算做“完全正确”。分值列出来,原来自己的水平就是那样丫!希望尽可能滴接近实际情况:

最佳估计:0 + 3 + 3 + 2 - 2 + 3 + 3 + 0 - 3 + 5 + 3 + 5 + 5 + 5 + 5 + 5 + 5 + 5 + 13 + 7 = 72大致估计:0 + 3 + 2 + 2 - 2 + 3 + 3 + 0 - 3 + 3 + 3 + 5 + 5 + 5 + 3 + 5 + 5 + 5 + 13 + 7 = 67最差估计:0 + 3 - 2 + 2 - 2 + 3 + 3 + 0 - 3 + 3 - 3 + 5 + 5 + 5 - 3 + 5 + 5 + 5 + 13 + 7 = 51

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多