微软时期实习生笔试题详解.docx

上传人:b****6 文档编号:5623425 上传时间:2022-12-29 格式:DOCX 页数:16 大小:31.67KB
下载 相关 举报
微软时期实习生笔试题详解.docx_第1页
第1页 / 共16页
微软时期实习生笔试题详解.docx_第2页
第2页 / 共16页
微软时期实习生笔试题详解.docx_第3页
第3页 / 共16页
微软时期实习生笔试题详解.docx_第4页
第4页 / 共16页
微软时期实习生笔试题详解.docx_第5页
第5页 / 共16页
点击查看更多>>
下载资源
资源描述

微软时期实习生笔试题详解.docx

《微软时期实习生笔试题详解.docx》由会员分享,可在线阅读,更多相关《微软时期实习生笔试题详解.docx(16页珍藏版)》请在冰豆网上搜索。

微软时期实习生笔试题详解.docx

微软时期实习生笔试题详解

2013年微软暑期实习生笔试试题详解

以下为本人根据不清楚的录像整理,如有错误欢迎反馈!

1、下面哪项可以实现可变参数的传递,例如函数printf的参数就是可变的。

(D)

A.cdecl

B.stdcall

C.pascal

D.fastcall

1._cdecl

(1).是CDeclaration的缩写,表示C语言默认的函数调用方法,实际上也是C++的默认的函数调用方法。

(2).所有参数从右到左依次入栈,这些参数由调用者清除,称为手动清栈。

具体所示:

调用方的函数调用->被调用函数的执行->被调用函数的结果返回->调用方清除调整堆栈。

(3).被调用函数无需要求调用者传递多少参数,调用者传递过多或者过少的参数,甚至完全不同的参数都不会产生编译阶段的错误。

总的来说函数的参数个数可变的(就像printf函数一样),因为只有调用者才知道它传给被调用函数几个参数,才能在调用结束时适当地调整堆栈。

(4).因为每个调用的地方都需要生成一段调整堆栈的代码,所以最后生成的文件较大。

2._stdcall(CALLBACK/WINAPI)

(1).是StandardCall的缩写,要想函数按照此调用方式必须在函数名加入_stdcall,通常_win32api应该是_stdcall调用规则。

通过VC++编写的DLL欲被其他语言编写的程序调用,应将函数的调用方式声明为_stdcall 方式,WINAPI都采用这种方式。

(2).所有参数从右到左依次入栈,如果是调用类成员的话,最后一个入栈的是this指针。

具体所示:

调用方的函数调用->被调用函数的执行->被调用方清除调整堆栈->被调用函数的结果返回。

(3).这些堆栈中的参数由被调用的函数在返回后清除,使用的指令是retnX,X表示参数占用的字节数,CPU在ret之后自动弹出X个字节的堆栈空间。

称为自动清栈。

(4).函数在编译的时候就必须确定参数个数,并且调用者必须严格的控制参数的生成,不能多,不能少,否则返回后会出错。

总的来说,就是函数的参数个数不能是可变的。

是从_cdecl修改而来,_stdcall不支持可变参数,并且清栈由被调用者负责,其他的都一样

(5).因为只需在被调用函数的地方生成一段调整堆栈的代码,所以最后生成的文件较小。

3.PASCAL是Pascal语言的函数调用方式,也可以在C/C++中使用,参数压栈顺序与前两者相反。

返回时的清栈方式与_stdcall相同。

4._fastcall是编译器指定的快速调用方式。

由于大多数的函数参数个数很少,使用堆栈传递比较费时。

因此_fastcall通常规定将前两个(或若干个)参数由寄存器传递,其余参数还是通过堆栈传递。

不同编译器编译的程序规定的寄存器不同。

返回方式和_stdcall相当。

5._thiscall是为了解决类成员调用中this指针传递而规定的。

_thiscall要求把this指针放在特定寄存器中,该寄存器由编译器决定。

VC使用ecx,Borland的C++编译器使用eax。

返回方式和_stdcall相当。

6._fastcall和_thiscall涉及的寄存器由编译器决定,因此不能用作跨编译器的接口。

所以Windows上的COM对象接口都定义为_stdcall调用方式。

7.C中不加说明默认函数为_cdecl方式(C中也只能用这种方式),C++也一样,但是默认的调用方式可以在IDE环境中设置。

8.带有可变参数的函数必须且只能使用_cdecl方式,例如下面的函数:

intprintf(char*fmtStr,...);

intscanf(char*fmtStr,...);

9.函数名修饰

(1)._cdecl:

对于_cdecl而言,如果对于定义在C程序文件(编译器会通过后缀名为.C判断)的输出函数,函数名会保持原样;对于定义在C++程序文件中的输出函数,函数名会被修饰(见10)。

为使函数名不被修饰,有两种方法:

A.可通过在前面加上extern“c”以去除函数名修饰;B.可通过.def文件去除函数名修饰。

(2)._stdcall:

无论是C程序文件中的输出函数还是C++程序文件中的输出函数,函数名都会被修饰。

对于定义在C++程序文件中的输出函数,好像更复杂,和_cdecl的情况类似。

去除函数名修饰方法:

只能通过.def文件去除函数名修饰。

10.函数名修饰规则:

(1).为什么要函数名修饰:

函数名修饰就是编译器在编译期间创建的一个字符串,用来指明函数的定义和原型。

LINK程序或其他工具有时需要指定函数的名字修饰来定位函数的正确位置。

多少情况下程序员并不需要知道函数的名字修饰,LINK程序或其他工具会自动区分他们。

当然,在某些情况下需要指定函数名修饰,例如在c++程序中,为了让LINK程序或其他工具能够匹配到正确的函数名字,就必须为重载函数后一些特殊函数(如构造函数和析构函数)指定名字修饰。

另一种需要指定函数名修饰的情况是在汇编程序中调用C或C++函数。

(2).C语言:

对于_stdcall调用约定,编译器和链接器会在输出函数名前加上一个下划线前缀,函数名后面加上一个“@”符号和其参数的字节数,例如_functionname@number。

_cdecl调用约定仅在输出函数名前加上一个下划线前缀,例如_functionname。

_fastcall调用约定在输出函数名前加上一个“@“符号,后面也是一个”@“符号和其参数的字节数,例如@functionname@number。

(3).C++语言:

   C++的函数名修饰规则有些复杂,但是信息更充分,通过分析修饰名不仅能够知道函数的调用方式,返回值类型,参数个数甚至参数类型。

不管__cdecl,__fastcall还是__stdcall调用方式,函数修饰都是以一个“?

”开始,后面紧跟函数的名字,再后面是参数表的开始标识和按照参数类型代号拼出的参数表。

对于__stdcall方式,参数表的开始标识是“@@YG”,对于__cdecl方式则是“@@YA”,对于__fastcall方式则是“@@YI”。

参数表的拼写代号如下所示:

X--void   

D--char   

E--unsignedchar   

F--short   

H--int   

I--unsignedint   

J--long   

K--unsignedlong(DWORD)

M--float   

N--double   

_N—bool

U—struct

....

指针的方式有些特别,用PA表示指针,用PB表示const类型的指针。

后面的代号表明指针类型,如果相同类型的指针连续出现,以“0”代替,一个“0”代表一次重复。

U表示结构类型,通常后跟结构体的类型名,用“@@”表示结构类型名的结束。

函数的返回值不作特殊处理,它的描述方式和函数参数一样,紧跟着参数表的开始标志,也就是说,函数参数表的第一项实际上是表示函数的返回值类型。

参数表后以“@Z”标识整个名字的结束,如果该函数无参数,则以“Z”标识结束。

下面举两个例子,假如有以下函数声明:

intFunction1(char*var1,unsignedlong);

其函数修饰名为“?

Function1@@YGHPADK@Z”,而对于函数声明:

oidFunction2();

其函数修饰名则为“?

Function2@@YGXXZ”。

对于C++的类成员函数(其调用方式是thiscall),函数的名字修饰与非成员的C++函数稍有不同,首先就是在函数名字和参数表之间插入以“@”字符引导的类名;其次是参数表的开始标识不同,公有(public)成员函数的标识是“@@QAE”,保护(protected)成员函数的标识是“@@IAE”,私有(private)成员函数的标识是“@@AAE”,如果函数声明使用了const关键字,则相应的标识应分别为“@@QBE”,“@@IBE”和“@@ABE”。

如果参数类型是类实例的引用,则使用“AAV1”,对于const类型的引用,则使用“ABV1”。

11.查看函数的名字修饰

 有两种方式可以检查你的程序中的函数的名字修饰:

使用编译输出列表或使用Dumpbin工具。

使用/FAc,/FAs或/FAcs命令行参数可以让编译器输出函数或变量名字列表。

使用dumpbin.exe/SYMBOLS命令也可以获得obj文件或lib文件中的函数或变量名字列表。

此外,还可以使用undname.exe将修饰名转换为未修饰形式。

12._beginthread需要_cdecl的线程函数地址,_beginthreadex和_CreateThread需要_stdcall的线程函数地址。

13.#defineCALLBACK__stdcall//这就是传说中的回调函数

#defineWINAPI__stdcall//这就是传说中的WINAPI

#defineWINAPIV__cdecl

#defineAPIENTRYWINAPI//DllMain的入口就在这里

#defineAPIPRIVATE__stdcall

#definePASCAL__stdcall

2、如下程序输出什么?

#include

usingnamespacestd;

classA

{

public:

virtualvoidf()

{

cout<<"A:

:

f()"<

}

voidf()const

{

cout<<"A:

:

f()Const"<

}

};

classB:

publicA

{

public:

virtualvoidf()

{

cout<<"B:

:

f()"<

}

voidf()const

{

cout<<"B:

:

f()Const"<

}

};

voidg(constA*a)

{

a->f();

}

intmain()

{

A*a=newB();

a->f();

g(a);

system("pause");

return0;

}

正确答案:

3、Whatisthedifferencebetweenalinkedlistandanarray?

(A、B、C、D)

A、Searchcomplexitywhenbotharesorted

B、Dynamiclyandandremove

C、RandomAccessEfficiency

D、Datastoragetype

4、关于进程和线程的说法(C)

A、操作系统中的每一个应用程序必须有一个进程,但不必有进程

B、一个进程可以有自己的堆栈,但是线程只能和进程共享堆栈

C、线程必须隶属于一个进程

D、线程可以改变它所属于哪个进程

简而言之,一个程序至少有一个进程,一个进程至少有一个线程.

线程的划分尺度小于进程,使得多线程程序的并发性高。

另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

线程在执行过程中与进程还是有区别的。

每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。

但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。

但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。

这就是进程和线程的重要区别。

进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.

一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.

线程是进程中的实体,一个进程可以拥有多个线程,一个线程必须有一个父进程。

线程不拥有系统资源,只有运行必须的一些数据结构;它与父进程的其它线程共享该进程所拥有的全部资源。

线程可以创建和撤消线程,从而实现程序的并发执行。

一般,线程具有就绪、阻塞和运行三种基本状态

进程和线程的主要差别在于它们是不同的操作系统资源管理方式。

进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。

线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。

但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

如果有兴趣深入的话,我建议你们看看《现代操作系统》或者《操作系统的设计与实现》。

对就个问题说得比较清楚。

5、如下程序输出什么?

intmain()

{

intmx=10,my=10;

mx=mx++;

my=++my;

cout<

system("pause");

return0;

}

输出:

1111

扩展阅读:

考点:

i++和++i的效率比较。

出现频率:

★★★

解析:

简单的比较前缀自增运算符和后缀自增运算符的效率是片面的,因为存在很多因素影响这个问题的答案。

首先考虑内建数据类型的情况:

如果自增运算表达式的结果没有被适用,而是仅仅简单地用于增加一元操作数,答案是明确的,前缀法和后缀法没有任何区别。

编译器的处理都应该是相同的,很难想象得出有什么编译器实现可以别出心裁地在二者之间制造任何差异。

示例程序如下。

1#include

2

3intmain()

4{

5inti=0;

6intx=0;

7

8i++;

9++i;

10x=i++;

11x=++I;

12

13return0;

14}

上面的代码在VISUALC++6.0上编译得到的汇编如下:

;Line5

movDWORDPTR_i$[dbp],0

;Line6

movDWORDPTR_i$[dbp],0

;Line8

moveax,DWORDPTR_i$[ebp]

addeax,1

movDWORDPTR_i$[ebp],eax

;Line9

moveax,DWORDPTR_i$[ebp]

addeax,1

movDWORDPTR_i$[ebp],eax

;Line10

movedx,DWORDPTR_i$[ebp]

movDWORDPTR_x$[ebp],edx

moveax,DWORDPTR_i$[ebp]

addeax,1

movDWORDPTR_i$[ebp],eax

;Line11

movedx,DWORDPTR_i$[ebp]

addeax,1

movDWORDPTR_x$[ebp],edx

moveax,DWORDPTR_i$[ebp]

movDWORDPTR_i$[ebp],eax

■代码段第8行和第9行生成的汇编代码分别对应Line8和Line9下面的汇编代码,可以看到3个步骤几乎完全一样。

■代码段第10行和第11行生成的汇编代码分别对应Line10和Line11下面的汇编代码,可以看到都是5个步骤,只是在加1的先后顺序上有一些区别,效率也是完全一样的。

由此说明,考虑内建数据类型时,它们的效率差别不大(取出编译器优化的影响)。

再考虑自定义数据类型(主要是指类)的情况。

此时不需要再做很多汇编代码的分析,因为前缀是(++i)可以返回对象的应用,而后缀是(i++)必须返回对象的值,所以导致在大对象的时候产生了较大的复制开销,引起效率降低,因此使用自定义类型(注意不是指内建类型)的时候,应该尽可能地使用前缀式递增或递减。

答案:

在内建数据类型的情况下,效率没有区别。

在自定义数据类型的情况下,++i的效率较高。

6、在C#中或者Java中定义如下数组

int[][]data=newint[3][]

{

newint[3]{5,6,2},

newint[5]{6,9,7,8,3},

newint[2]{3,2}

};

则调用data[2][2]会输出什么?

答案:

数组溢出。

7、下列描述正确的是(A、B、C)

A、constinta;//constinteger

B、intconsta;//constinteger

C、intconst*a;//常量指针

D、constint*a;//常量指针

E、intconst*a;//指针常量

解析:

constint和intconst是一样的

常量指针和指针常量,前一个词是修饰语,后一个词是中心语。

常量指针即常量的指针,是指向常量的指针,其所指向的内容不能变;

指针常量即指针的常量,本身是一个常量,不能对其赋值。

在*前加const为常量指针,在*后加const为指针常量。

8、如下程序输出什么?

#include

usingnamespacestd;

classA

{

public:

inta;

};

classB:

publicA

{

public:

intb;

};

voidset(A*d,intind)

{

d[ind].a=2;

}

intmain()

{

Bdata[4];

for(inti=0;i<4;i++)

{

data[i].a=1;

data[i].b=1;

set(data,i);

}

for(inti=0;i<4;i++)

{

cout<

}

system("pause");

return0;

}

输出正确结果为:

解析:

调用voidset(A*d,intind);函数时,d接收实参的值,即data数组的首地址,然后按“类A”的存储方式对数组的连续存储空间进行下标操作,这样就会把data地址开始的前4*4个字节分别解析成4个int型数据。

9、1000瓶水中有一瓶是有毒的,老鼠喝任意量的毒水都会在一周后死亡,问至少用多少只老鼠可以测出哪瓶水是有毒的?

(B)

A、9B、10C、12D、999

解析:

对1000水进行编码,需要10位

10只老鼠分别代表10个有效位

对每一瓶水分别由对应编码的老鼠喝,

最后死亡老鼠的有效们置1,活着老鼠的有效们置0,则该编码对应的水即为有毒的水。

10、下列项返回1的是(B、C、D)

A、主函数正常结束返回1

B、return(7&1);

C、char*str=”Microsoft”;returnstr==”Microsoft”

D、return“Microsoft”==”Microsoft”

E、Noneoftheabove

解析:

C++编译器会对字符常量的存储进行优化

11、32位有符号整数F和G是由F=X/2和G=X>>1得到,如果F不等于G,说明什么?

A.编译错误

B.X是奇数

C.X是负数

D.F-G=1

E.G-F=1

解析:

不太会,不过C应该是正确的,其他选项不会

一、下面的题目你能全做对吗?

1.7/4=?

2.7/(-4)=?

3.7%4=?

4.7%(-4)=?

5.(-7)/4=?

6.(-7)%4=?

7.(-7)/(unsigned)4=?

答案:

1

-1

3

3

-1

-3

1073741822

二、除法的取整分类

除法的取整分为三类:

向上取整、向下取整、向零取整。

1.向上取整:

向+∞方向取最接近精确值的整数。

在这种取整方式下,7/4=2,7/(-4)=-1,6/3=2,6/(-3)=-2

2.向下取整:

向-∞方向取最接近精确值的整数。

在这种取整方式下,7/4=1,7/(-4)=-2,6/3=2,6/(-3)=-2

3.向零取整:

向0方向取最接近精确值的整数,换言之就是舍去小数部分,因此又称截断取整。

在这种取整方式下,7/4=1,7/(-4)=-1,6/3=2,6/(-3)=-2

通过观察可以发现,无论是向上取整还是向下取整,(-a)/b==-(a/b)都不一定成立。

这给程序设计者带来了极大的麻烦。

而对于向零取整,(-a)/b==-(a/b)是成立的,以此,C/C++采用这种取整方式。

、负数取模

回想小学的公式:

被除数÷除数=商……余数。

由此可知,余数=被除数-商×除数(*)

对C/C++而言,(*)式依然成立。

并且,该式是解决负数取模问题的关键。

例一:

7%(-4)=?

解:

由C/C++向零取整的整除方式可知,7/(-4)=-1;由(*)式知,余数=7-(-4)*(-1)=3.所以,7%(-4)=3

例二:

(-7)%4=?

解:

由C/C++向零取整的整除方式可知,(-7)/4=-1;由(*)式知,余数=(-7)-4*(-1)=-3.所以,(-7)%4=-3

例三:

(-7)%(-4)=?

解:

由C/C++向零取整的整除方式可知,(-7)/(-4)=1;由(*)式知,余数=(-7)-(-4)*1=-3.所以,(-7)%(-4)=-3

四、相关知识的拓展

1.对于有符号整数与无符号整数间的除法,C/C++会将有符号整数转换为无符号整数,需要特别注意的是,符号位并没有丢失,而是变成了数据位参与运算。

这就是(-7)/(unsigned)4不等于-1,而等于1073741822的原因。

2.编译器对除法的优化

①在“无优化”条件下,编译器会在不影响正常调试的前提下,对除法进行简单的优化。

A.“常量/常量”型除法:

编译器会直接计算出结果。

B.“变量/变量”型除法:

无优化。

C.“变量/常量”型除法:

若常量≠2^n,无优化;否则,除法将被转换为右移运算。

由于由右移运算实现的整除实质上是向下取整,所以编译器会通过一些附加的指令在不产生分支结构的情况下将向下取整转换为向零取整。

以【变量/2^3】为例,反汇编代码如下:

moveax,被除数

cdq;若eax<0,则edx=0xFFFFFFFF;否则edx=0

andedx,7;若eax<0,则edx=7;否则edx=0

addeax,edx;若eax<0,【(eax+7)/(2^3)】向下取整的值与【eax/(2^3)】向零取整的值相等,从而实现向零取整

sareax,3;右移,完成除法

②在“O2优化”条件下,“常量/变量”型除法中,常量若≠2^n,也可以优化。

此时,除法将被转换为乘法与右移的结合形式。

例如,a/b=a*(1/b)=a*((2^n)/b)*(1/(2^n)),其中,((2^n)/b为MagicNumber,由编译

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > PPT模板 > 商务科技

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1