C面试汇总.docx
《C面试汇总.docx》由会员分享,可在线阅读,更多相关《C面试汇总.docx(109页珍藏版)》请在冰点文库上搜索。
C面试汇总
C面试汇总
(1)如何样才能检测到链表中存在循环
面试者可能如下作答
1.
对访问过的每个元素做个标记,连续遍历那个链表,假如遇到某个差不多做过标记的元素,说明链表存在循环。
链表位于只读区域,无法在元素上做标记
2.
当访问每个元素时,把它存在一个数组里。
检查每个后据元素,看看它是否差不多存在数组中。
(哈哈,也许有些人连续想用散列表来优化数组的访问)
内存空间有限,无法创建一个足够长的数组。
然而,能够假定假如链表中存在循环,那么它显现在前面的N个元素中
3.
设置一个指针,指向链表的头部。
在接下去对直到第N个元素的访问中,把N-1个元素依次同指向的元素进行比较。
然后指针移向下一个元素,把同后面的N-2个元素进行比较。
依照那个方法依次进行比较,假如显现比较相等的情形,就说明前N个元素中存在循环,否那么假如所有N个元素两两之间进行比较都不相等,说明链表中不存在循环。
链表的长度是任意的,而且循环可能显现在任何位置。
4.参考答案
第一,排除一种情形:
3个元素的链表,第2个元素的后面是第一个元素。
设置两个指针P1和P2,P1指向一个元素,P2指向第3个元素,看看它们是否相等。
假如相等就属于上述这种专门情形。
假如不等,把P1后移一个元素,P2后移两个元素。
检查两个指针的值,假如相等,说明链表中存在循环。
假如不相等,连续按照前述方法进行。
假如显现两个指针差不多上NULL的情形,说明链表中不存在循环。
假如链表中存在循环,用这种方法一定能检查出来,因为其中一个指针确信能追上另一个(两个指针具有相同的值),尽管可能要对那个链表通过几次遍历才能检测出来。
(2)不同的增值语句有什么区别
x=x+1;
++x;
x++;
x+=1;
需要提供适当的上下文才好找出其中的区别
(1)
x+=1;是在算法语言中表达"x=x+1;"的便利方法
(2)
++x它先增加x的值,然后再在周围的表达式中使用x的值
x++先在周围的表达式中使用x的值,然后再增加x的值
(3)
当x不是一个简单的变量而是一个涉及数组的表达式,像x+=1如此的形式是专门有用的。
假如你有一个复杂的数组引用,并需要证明同一种下标形式在两种引用中都能够使用,那么
node[i>>3]+=-(0x01<<(i&0x7));
确实是你应该采纳的方法。
(4)
左值(通常具有一个地址,它也可能是一个寄存器,也可能是地址或者寄存器加上一个位段)
只被运罢了一次。
这意味着,表达式
mango[j++]+=y;
被当作
mango[j]=mango[j]+y;
j++;
而不是
mango[j++]=mango[j++]+y;
有人说明说,这些区别与编译器的中间代码有关。
例如,"++x"表示取地址x的地址,增加它的内容,然后把值放在寄存器中:
"x++"那么表示取x的地址,把它的值转入寄存器,然后增加在内存中的x的值
(5)
K&R认为++比直截了当加1更有效率。
然而当代编译器在没有区别的上下文中,产生的代码相同的指令,它们应该是增加一个变量最快的一种指令。
一样较短的形式比较长的形式更容易阅读一些。
然而,过度简洁的代码也会导致代码难以阅读
frotz[--j+i++]+=--y;
改成
--y;
--j;
frotz[j+i]=frotz[j+i]+y;
i++;
因此,不要在一行代码里实现太多的功能
因为这并可不能使编译器产生更有效率的代码,但会使你丧失调试代码的机会。
K&R说:
人人都明白调试比第一次编写代码要难上一倍。
因此,假如在编写代码时就把自己的聪慧发挥到极致,那么调试时又该如何办呢?
(3)库函数调用和系统调用
简单的回答是
库函数调用是语言或应用程序的一部分,
而系统调用是操作系统的一部分。
你要确保弄明白trap的含义。
系统调用是操作系统内核发觉一个trap或者中断之后进行的。
库函数调用 系统调用
在ANSIC编译器中,C函数库是系统的 各个操作系统的系统调用是不同的。
符合Posix标准的OS,它们的系统调用是相同的吗?
调用函数库的一个程序 调用系统内核服务
与用户程序相联系 是操作系统的一个进入点
在用户的地址空间执行 在内核的地址空间执行
运行时刻属于〝user〞时刻 属于〝system"时刻
属于过程调用,开销较小 需要切换到内核上下文环境然后在
切换回来,开销较大
在C函数库libc中大约300个程序 UNIX中约90个系统调用
典型调用
system fprintf malloc ---------- chdirforkwritebrk
然而,你必须记住:
许多C函数库中的程序是通过系统调用来实现功能的。
比如文件系统相关的操作
windows中fopen 大致确实是调用CreateFile
(4)文件描述符和文件指针
系统IO调用有creat(),open(),read(),write(),close(),ioctl(),同意一个文件描述符,是一个整数,用于索引开放文件的每个进程表(per-processtable-of-open-file)
为了确保程序的可移植性应该使用标准IO库调用,如fopen(0,fclose(),fputc(),fseek()等,它们绝大多数中的名字中带有一个"f"。
这些调用都同意一个类型为FILE结构的指针(有时称为流指针)的参数。
FILE指针指向一个流结构,在中定义。
结构的内容依照编译器的不同而有所不同,在UNIX中通常是OpenFile的每个进程表的一个条目。
在典型情形下,它包含了流缓冲区、所有用于提示缓冲区有多少字节是实际的文件数据的变量以及提示流状态的标志(如ERROR和EOF)等
因此,文件描述符确实是OpenFile中的每个进程表的一个偏移量(如"3")。
它用于UNIX系统中,用于标识文件。
FILE指针储存了一个FILE结构的地址。
FILE结构用于表示开放的I/O流(如hex20938).它用于ANSIC标准的IO库调用中,用于标识文件。
(5)确定一个变量unsigned依旧signed
函数的参数形式是在函数内部定义的,因此你无法用函数来实现那个目的。
那么用宏。
有符号数的本质确实是对最左边的一个位取补将会改变它的符号。
由于其他位与那个测试无关,因此你能够将它的所有位都取补。
#defineISUNSIGNED(a)(a>=0&&~a>=0)
ANSIC的类型提升规那么(所有的表达式中,假如是个变量,因为编译器无法判定结果是否溢出,都对类型进行提升)
char
shortint
bit(unsigned/signed)
enum
假如int能够完整的容纳原先的数据,否那么将被转换为unsignedint
gcc难道丢出如此一句话来
warning:
comparisonisalwaystrueduetolimitedrangeofdatatype
假如是一个类型
#defineISUNSIGNED(type)((type)0-1)>0)
预处理器〔Preprocessor〕
1.用预处理指令#define声明一个常数,用以说明1年中有多少秒〔忽略闰年问题〕
#defineSECONDS_PER_YEAR(60*60*24*365)UL
我在这想看到几件情况:
1)#define语法的差不多知识〔例如:
不能以分号终止,括号的使用,等等〕
2)明白得预处理器将为你运算常数表达式的值,因此,直截了当写出你是如何运算一年中有多少秒而不是运算出实际的值,是更清晰而没有代价的。
3)意识到那个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器那个常数是的长整型数。
4)假如你在你的表达式中用到UL〔表示无符号长整型〕,那么你有了一个好的起点。
记住,第一印象专门重要。
2.写一个"标准"宏MIN,那个宏输入两个参数并返回较小的一个。
#defineMIN(A,B)〔〔A〕<=(B)?
(A):
(B))
那个测试是为下面的目的而设的:
1)标识#define在宏中应用的差不多知识。
这是专门重要的。
因为在 嵌入(inline)操作符变为标准C的一部分之前,宏是方便产生嵌入代码的唯独方法,关于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。
2)三重条件操作符的知识。
那个操作符存在C语言中的缘故是它使得编译器能产生比if-then-else更优化的代码,了解那个用法是专门重要的。
3)明白得在宏中小心地把参数用括号括起来
4)我也用那个问题开始讨论宏的副作用,例如:
当你写下面的代码时会发生什么事?
least=MIN(*p++,b);
3.预处理器标识#error的目的是什么?
假如你不明白答案,请看参考文献1。
这问题对区分一个正常的伙计和一个书呆子是专门有用的。
只有书呆子才会读C语言课本的附录去找出象这种问题的答案。
因此假如你不是在找一个书呆子,那么应试者最好期望自己不要明白答案。
死循环〔Infiniteloops〕
4.嵌入式系统中经常要用到无限循环,你如何样用C编写死循环呢?
那个问题用几个解决方案。
我首选的方案是:
while
(1)
{
}
一些程序员更喜爱如下方案:
for(;;)
{
}
那个实现方式让我为难,因为那个语法没有确切表达到底如何回事。
假如一个应试者给出那个作为方案,我将用那个作为一个机会去探究他们如此做的差不多原理。
假如他们的差不多答案是:
"我被教着如此做,但从没有想到过什么缘故。
"这会给我留下一个坏印象。
第三个方案是用goto
Loop:
...
gotoLoop;
应试者如给出上面的方案,这说明或者他是一个汇编语言程序员〔这也许是好事〕或者他是一个想进入新领域的BASIC/FORTRAN程序员。
数据声明〔Datadeclarations〕
5.用变量a给出下面的定义
a)一个整型数〔Aninteger〕
b)一个指向整型数的指针〔Apointertoaninteger〕
c)一个指向指针的的指针,它指向的指针是指向一个整型数〔Apointertoapointertoanintege〕r
d)一个有10个整型数的数组〔Anarrayof10integers〕
e)一个有10个指针的数组,该指针是指向一个整型数的。
〔Anarrayof10pointerstointegers〕
f)一个指向有10个整型数数组的指针〔Apointertoanarrayof10integers〕
g)一个指向函数的指针,该函数有一个整型参数并返回一个整型数〔Apointertoafunctionthattakesanintegerasanargumentandreturnsaninteger〕
h)一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数〔Anarrayoftenpointerstofunctionsthattakeanintegerargumentandreturnaninteger〕
答案是:
a)inta;//Aninteger
b)int*a;//Apointertoaninteger
c)int**a;//Apointertoapointertoaninteger
d)inta[10];//Anarrayof10integers
e)int*a[10];//Anarrayof10pointerstointegers
f)int(*a)[10];//Apointertoanarrayof10integers
g)int(*a)(int);//Apointertoafunctionathattakesanintegerargumentandreturnsaninteger
h)int(*a[10])(int);//Anarrayof10pointerstofunctionsthattakeanintegerargumentandreturnaninteger
人们经常声称那个地点有几个问题是那种要翻一下书才能回答的问题,我同意这种说法。
当我写这篇文章时,为了确定语法的正确性,我的确查了一下书。
然而当我被面试的时候,我期望被问到那个问题〔或者相近的问题〕。
因为在被面试的这段时刻里,我确定我明白那个问题的答案。
应试者假如不明白所有的答案〔或至少大部分答案〕,那么也就没有为这次面试做预备,假如该面试者没有为这次面试做预备,那么他又能什么缘故出预备呢?
Static
6.关键字static的作用是什么?
那个简单的问题专门少有人能回答完全。
在C语言中,关键字static有三个明显的作用:
1)在函数体,一个被声明为静态的变量在这一函数被调用过程中坚持其值不变。
2)在模块内〔但在函数体外〕,一个被声明为静态的变量能够被模块内所用函数访问,但不能被模块外其它函数访问。
它是一个本地的全局变量。
3)在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。
那确实是,那个函数被限制在声明它的模块的本地范畴内使用。
大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是专门少的人能明白得第三部分。
这是一个应试者的严峻的缺点,因为他明显不明白得本地化数据和代码范畴的好处和重要性。
Const
7.关键字const有什么含意?
我只要一听到被面试者说:
"const意味着常数",我就明白我正在和一个业余者打交道。
去年DanSaks差不多在他的文章里完全概括了const的所有用法,因此ESP(译者:
EmbeddedSystemsProgramming)的每一位读者应该专门熟悉const能做什么和不能做什么.假如你从没有读到那篇文章,只要能说出const意味着"只读"就能够了。
尽管那个答案不是完全的答案,但我同意它作为一个正确的答案。
〔假如你想明白更详细的答案,认真读一下Saks的文章吧。
〕
假如应试者能正确回答那个问题,我将问他一个附加的问题:
下面的声明差不多上什么意思?
constinta;
intconsta;
constint*a;
int*consta;
intconst*aconst;
/******/
前两个的作用是一样,a是一个常整型数。
第三个意味着a是一个指向常整型数的指针〔也确实是,整型数是不可修改的,但指针能够〕。
第四个意思a是一个指向整型数的常指针〔也确实是说,指针指向的整型数是能够修改的,但指针是不可修改的〕。
最后一个意味着a是一个指向常整型数的常指针〔也确实是说,指针指向的整型数是不可修改的,同时指针也是不可修改的〕。
假如应试者能正确回答这些问题,那么他就给我留下了一个好印象。
顺带提一句,也许你可能会问,即使不用关键字const,也依旧能专门容易写出功能正确的程序,那么我什么缘故还要如此看重关键字const呢?
我也如下的几下理由:
1)关键字const的作用是为给读你代码的人传达专门有用的信息,实际上,声明一个参数为常量是为了告诉了用户那个参数的应用目的。
假如你曾花专门多时刻清理其它人留下的垃圾,你就会专门快学会感谢这点余外的信息。
〔因此,明白得用const的程序员专门少会留下的垃圾让别人来清理的。
〕
2)通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。
3)合理地使用关键字const能够使编译器专门自然地爱护那些不期望被改变的参数,防止其被无意的代码修改。
简而言之,如此能够减少bug的显现。
Volatile
8.关键字volatile有什么含意?
并给出三个不同的例子。
一个定义为volatile的变量是说这变量可能会被意想不到地改变,如此,编译器就可不能去假设那个变量的值了。
精确地说确实是,优化器在用到那个变量时必须每次都小心地重新读取那个变量的值,而不是使用储存在寄存器里的备份。
下面是volatile变量的几个例子:
1)并行设备的硬件寄存器〔如:
状态寄存器〕
2)一个中断服务子程序中会访问到的非自动变量(Non-automaticvariables)
3)多线程应用中被几个任务共享的变量
回答不出那个问题的人是可不能被雇佣的。
我认为这是区分C程序员和嵌入式系统程序员的最差不多的问题。
搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道,所有这些都要求用到volatile变量。
不明白得volatile的内容将会带来灾难。
假设被面试者正确地回答了这是问题〔嗯,怀疑是否会是如此〕,我将略微深究一下,看一下这家伙是不是直正明白得volatile完全的重要性。
1)一个参数既能够是const还能够是volatile吗?
说明什么缘故。
2);一个指针能够是volatile吗?
说明什么缘故。
3);下面的函数有什么错误:
intsquare(volatileint*ptr)
{
return*ptr**ptr;
}
下面是答案:
1)是的。
一个例子是只读的状态寄存器。
它是volatile因为它可能被意想不到地改变。
它是const因为程序不应该试图去修改它。
2);是的。
尽管这并不专门常见。
一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
3)这段代码有点变态。
这段代码的目的是用来返指针*ptr指向值的平方,然而,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
intsquare(volatileint*ptr)
{
inta,b;
a=*ptr;
b=*ptr;
returna*b;
}
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。
结果,这段代码可能返不是你所期望的平方值!
正确的代码如下:
longsquare(volatileint*ptr)
{
inta;
a=*ptr;
returna*a;
}
位操作〔Bitmanipulation〕
9.嵌入式系统总是要用户对变量或寄存器进行位操作。
给定一个整型变量a,写两段代码,第一个设置a的bit3,第二个清除a的bit3。
在以上两个操作中,要保持其它位不变。
对那个问题有三种差不多的反应
1)不明白如何下手。
该被面者从没做过任何嵌入式系统的工作。
2)用bitfields。
Bitfields是被扔到C语言死角的东西,它保证你的代码在不同编译器之间是不可移植的,同时也保证了的你的代码是不可重用的。
我最近不幸看到Infineon为其较复杂的通信芯片写的驱动程序,它用到了bitfields因此完全对我无用,因为我的编译器用其它的方式来实现bitfields的。
从道德讲:
永久不要让一个非嵌入式的家伙粘实际硬件的边。
3)用#defines和bitmasks操作。
这是一个有极高可移植性的方法,是应该被用到的方法。
最正确的解决方案如下:
#defineBIT3(0x1<<3)
staticinta;
voidset_bit3(void)
{
a|=BIT3;
}
voidclear_bit3(void)
{
a&=~BIT3;
}
一些人喜爱为设置和清除值而定义一个掩码同时定义一些说明常数,这也是能够同意的。
我期望看到几个要点:
说明常数、|=和&=~操作。
访问固定的内存位置〔Accessingfixedmemorylocations〕
10.嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。
在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。
编译器是一个纯粹的ANSI编译器。
写代码去完成这一任务。
这一问题测试你是否明白为了访问一绝对地址把一个整型数强制转换〔typecast〕为一指针是合法的。
这一问题的实现方式随着个人风格不同而不同。
典型的类似代码如下:
int*ptr;
ptr=(int*)0x67a9;
*ptr=0xaa55;
Amoreobscureapproachis:
一个较晦涩的方法是:
*(int*const)(0x67a9)=0xaa55;
即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案。
中断〔Interrupts〕
11.中断是嵌入式系统中重要的组成部分,这导致了专门多编译开发商提供一种扩展—让标准C支持中断。
具代表事实是,产生了一个新的关键字__interrupt。
下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。
__interruptdoublecompute_area(doubleradius)
{
doublearea=