经典c语言笔试题和面试题答案(二)
67.
#include
#include
int modifyvalue()
{
int x;
return(x+=10);
}
int changevalue(int x)
{
x+=1;
return(x);
}
int main(int argc,char*argv[])
{
int x=10;
x++;
x=changevalue(x);
printf("changevalue:%d\n",x);//12
x++;
modifyvalue();
printf("Firstoutput:%d\n",x);//13
x++;
x=changevalue(x);//15
printf("Secondoutput:%d\n",x);
modifyvalue();
printf("Thirdoutput:%d\n",x);//15
return 1;
}
int modifyvalue()
{
int x;
return(x+=10);
}
int changevalue(int x)
{
x+=1;
return(x);
}
int main(int argc,char*argv[])
{
int x=10;
x++;
changevalue(x);//变量没有接受返回值
printf("changevalue:%d\n",x);//11
x++;
modifyvalue(); //无形参
printf("Firstoutput:%d\n",x);//12
x++;
changevalue(x);//15
printf("Secondoutput:%d\n",x);//13
modifyvalue();
printf("Thirdoutput:%d\n",x);//13
return 1;
}
考查临时变量(返回值为栈中变量)的生存期:仅仅在于 紧跟着的一条语句;
73、下面的代码输出是什么,为什么?
void foo(void)
{
unsigned inta = 6;
int b = -20;
(a+b> 6)? puts("> 6") : puts("<= 6");
}
【参考答案】这个问题测试你是否懂得C语言中的整数自动转换原则,
我发现有些开发者懂得极少这些东西。不管如何,这无符号整型问题的答案是输出是“>6”。
原因是当表达式中存在有符号类型和无符号类型时所有的数都自动转换为无符号类型。
因此-20变成了一个非常大的正整数,所以该表达式计算出的结果大于6。
这一点对于应当频繁用到无符号数据类型的嵌入式系统来说是丰常重要的
74、评价下面的代码片断:
unsigned intzero = 0;
unsigned intcompzero= 0xFFFF;
/*1‘s complement of zero */
【参考答案】对于一个int型不是16位的处理器为说,上面的代码是不正
确的。应编写如下:
unsigned intcompzero= ~0;
这一问题真正能揭露出应试者是否懂得处理器字长的重要性。在我的经
验里,好的嵌入式程序员非常准确地明白硬件的细节和它的局限,然而
PC机程序往往把硬件作为一个无法避免的烦恼。
75.
char *ptr;
if ((ptr= (char *)malloc(0)) ==NULL)
puts("Gota nullpointer");
else
puts("Gota validpointer");
如果所请求的空间大小为0,其行为由库的实现者定义:可以返回空指针,也可以让效果跟申某个非0大小的空间一样,所不同的是返回的指针不可以被用来访问一个对象。
为什么 new T[0] 和 malloc(0) 不返回空指针
首先需要说明的是,按照C++标准,成功的 new T[0] 是不能返回空指针的。而 malloc(0),C 语言标准则指出,成功的时候可以返回空指针,也可以返回非空指针,多数库一般也选择了返回非空指针这种行为。
76.
int main(int argc,char* argv[])
{
char a='a',b='b';
int p,c,d;
p=a;
p=(p<<8)|b;
d=p&0xff;
c=(p&0xff00)>>8;
printf("a=%d\nb=%d\nc=%d\nd=%d\n",a,b,c,d);
return 1;
}
[运行结果]:97,98,97,98
77.
int main(int argc,char* argv[])
{
unsigned a,b;
printf("please input a number:");
scanf("%d",&a);
b=a>>5;
b=b&15;
printf("a=%d\tb=%d\n",a,b);
return 1;
}
【运行结果】:输入 64,输出2
*****
概念区别
指针数组:存放指针的数组;
Int* ptr[4]; (等同于二级指针 int* ptr)
一级指针是指向定义类型的内存地址,二级指针就是指向定义类型的内存地址所指向的新的内存地址
应用:指向若干字符串,整形数据等,使得元素处理更加方便;其中元素存放各对应地址;
此时,一级指针只能寻址到字符串所在的位置,并不能将其输出,因为没有其首地址,而*p则完成二级寻址,找到了位置,也找到了它的首地址,所以能输出
数组指针:指向一个数组的指针;
Int (*ptr)[4]
应用:可以应用在二维数组中;
指针函数:返回指针值得函数
Int* search(int num);
函数指针:指向函数的指针
Int (*ptr)(int num);/*申明一个函数指针*/
Ptr=fun();/*将fun函数地址付给指针ptr*/
Int a=fun(6).等价于,int a=(*ptr)(6);
函数指针数组: int(*s[10])(int) 函数指针数组
27、关键字volatile有什么含意?并给出三个不同的例子。
它是用来修饰被不同线程访问和修改的变量。
如果没有volatile,基本上会导致:要么无法编写多线程程序,要么编译器失去大量优化的机会。volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1). 并行设备的硬件寄存器(如:状态寄存器)
存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
中断服务程序中修改的供其它程序检测的变量需要加volatile;
3). 多线程应用中被几个任务共享的变量
多任务环境下各任务间共享的标志应该加volatile
3个关联的问题:
1). 一个参数既可以是const还可以是volatile吗?解释为什么。
2). 一个指针可以是volatile 吗?解释为什么。
3). 下面的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
下面是答案:
1). 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
3). 这段代码的有个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
37、Heap与stack的差别。
【标准答案】Heap是堆,stack是栈。
Stack的空间由操作系统自动分配/释放,Heap上的空间手动分配/放。
Stack空间有限,Heap是很大的自由存储区
C中的malloc函数分配的内存空间即在堆上,C++中对应的是new操符。
程序在编译期对变量和函数分配内存都在栈上进行,且程序运行过程中函数调用时参数的传递也在栈上进行
40、带参宏与带参函数的区别(至少说出5点)?
带参宏 带参函数
处理时间: 编译时 运行时
参数类型: 无 定义类型
程序长度: 变长 不变
占用存储空间:否 是
运行时间: 不占用 调用和返回占用时间
38.用宏定义写出swap(x,y),即交换两数
#define swap(x, y) (x)=(x)+(y);(y)=(x)–(y);(x)=(x)–(y);
39. 写一个“标准”宏,这个宏输入两个参数并返回较小的一个。
#define Min(X, Y) ((X)>(Y)?(Y):(X)) //结尾没有;
43、已知一个数组table,用一个宏定义,求出数据的元素个数。
【标准答案】#define NTBL(table) (sizeof(table)/sizeof(table[0]))
1. 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
#define SECONDS_PER_YEAR (60 * 60 * 24* 365)UL
总结:有关宏定义#define 的基本语法
1#define的概念
#define命令是C语言中的一个宏定义命令,它用来将一个标识符定义为一个字符串,该标识符被称为宏名,被定义的字符串称为替换文本。
该命令有两种格式:一种是简单的宏定义,另一种是带参数的宏定义。
(1) 简单的宏定义:
#define <宏名> <字符串>
例: #define PI 3.1415926
(2) 带参数的宏定义
#define <宏名>(<参数表>) <宏体>
例: #defineA(x) x
一个标识符被宏定义后,该标识符便是一个宏名。
这时,在程序中出现的是宏名,在该程序被编译前,先将宏名用被定义的字符串替换,这称为宏替换,替换后才进行编译,宏替换是简单的替换。
2.宏定义的缺点
在简单宏定义的使用中,当替换文本所表示的字符串为一个表达式时,容易引起误解和误用。如下例:
例1 #define N 2+2
void main()
{
int a=N*N;
printf(“%d”,a);
}
(1) 出现问题:在此程序中存在着宏定义命令,宏N代表的字符串是2+2,该题的结果为8,
(2) 问题解析:宏展开是在预处理阶段完成的,这个阶段把替换文本只是看作一个字符串,并不会有任何的计算发生,在展开时是在宏N出现的地方只是简单地使用串2+2来代替N,并不会增添任何的符号,所以对该程序展开后的结果是a=2+2*2+2,计算后=8,这就是宏替换的实质,如何写程序才 能完成结果为16的运算呢?
(3)解决办法:将宏定义写成如下形式
#define N (2+2)
这样就可替换成(2+2)*(2+2)=16
在带参数的宏定义的使用中,极易引起误解。例如我们需要做个宏替换能求任何数的平方,这就需要使用参数,以便在程序中用实际参数来替换宏定义中的参数。一般学生容易写成如下形式:
#define area(x) x*x
这在使用中是很容易出现问题的,看如下的程序
void main()
{
int y=area(2+2);
printf(“%d”,y);
}
按 理说给的参数是2+2,所得的结果应该为4*4=16,但是错了,因为该程序的实际结果为8,仍然是没能遵循纯粹的简单替换的规则,又是先计算再替换了,在这道程序里,2+2即为area宏中的参数,应该由它来替换宏定义中的x,即替换成2+2*2+2=8了。那如果遵循(1)中的解决办法,把2+2 括起来,即把宏体中的x括起来,是否可以呢?#define area(x) (x)*(x),对于area(2+2),替换为(2+2)*(2+2)=16,可以解决,但是对于area(2+2)/area(2+2)又会怎么样 呢,这道题替换后会变为 (2+2)*(2+2)/(2+2)*(2+2)即4*4/4*4按照乘除运算规则,结果为16/4*4=4*4=16,那应该怎么呢?解决方法是在整个宏体上再加一个括号,即
#define area(x) ((x)*(x)),不要觉得这没必要,没有它,是不行的。
要想能够真正使用好宏 定义,
一定要记住先将程序中对宏的使用全部替换成它所代表的字符串,不要自作主张地添加任何其他符号,完全展开后再进行相应的计算,就不会写错运行结果。
如果是自己编程使用宏替换,则在使用简单宏定义时,当字符串中不只一个符号时,加上括号表现出优先级,如果是 带参数的宏定义,则要给宏体中的每个参数加上括号,并在整个宏体上再加一个括号。
宏定义的优点:
(1) 方便程序的修改
使用简单宏定义可用宏代替一个在程序中经常使用的常量,这样在将该常量改变时,不用对整个程序进行修改,只修改宏定义的字符串即可,而且当常量比较长时, 我们可以用较短的有意义的标识符来写程序,这样更方便一些。
(2) 提高程序的运行效率
使用带参数的宏定义可完成函数调用的功能,又能 减少系统开 销,提高运行效率。正如C语言中所讲,函数的使用可以使程序更加模块化,便于组织,而且可重复利用,但在发生函数调用时,需要保留调用函数的现场,以便子函数执行结束后能返回继续执行,同样在子函数执行完后要恢复调用函数的现场,这都需要一定的时间,如果子函数执行的操作比较多,这种转换时间开销可以忽略,但如果子函数完成的功能比较少,甚至于只完成一点操作,如一个乘法语句的操作,则这部分转换开销就相对较大了,但使用带参数的宏定义就不会出现这个问题,因为它是在预处理阶段即进行了宏展开,在执行时不需要转换,即在当地执行。宏定义可完成简单的操作,但复杂的操作还是要由函数调用来完成,而且宏定义所占用的目标代码空间相对较大。所以在使用时要依据具体情况来决定是否使用宏定义。
49、什么是预编译,何时需要预编译:
【标准答案】1、总是使用不经常改动的大型代码体
2、程序由多个模块组成,所有模块都使用一组标准的包含文件和相同的编译选项。在这种情况下,可以将所有包含文件预编译为一个预编译头。
53、Typedef在C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事。
如,思考一下下面的例子:
#define dPSstructs *
typedefstructs * tPS;
以上两种情况的意图都是要定义dPS和tPS作为一个指向结构s指针。哪种方法更好呢?
答案是:typedef更好。思考下面的例子:
dPSp1,p2;
tPSp3,p4;
第一个扩展为
structs * p1, p2;
上面的代码定义p1为一个指向结构的指,p2为一个实际的结构,这也许
不是你想要的。第二个例子正确地定义了p3 和p4 两个指针。
54.在C++ 程序中调用被C 编译器编译后的函数,为什么要加extern “C”?
重点就是C++里面支持函数的重载,因此编译出来的.obj或者库文件里面,函数名会被加上和参数有关的签名,用来区分同样函数名参数不同的函数。然而C语言不支持重载,所以函数名里面没有这样的签名。
这样,当C语言的程序调用C++写成的库的时候,就会找不到函数。比如,一个函数叫 void foo(int bar)之类的,可能会在c++编译成叫 foo_i之类的名字,而在c语言里面就会编译成foo,这样c语言的程序去找foo就会找不到,这样连接的时候会出错。
为了解决这个问题,引入了extrn "c"{},在这个的作用域之内,c++的函数名不会加上参数签名,和c语言的标准保持统一,就兼容c语言的程序了。
2、中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键字__interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。
__interrupt double compute_area(double radius)
{
double area = PI * radius * radius;
printf(" Area = %f", area);
return area;
}
【参考答案】这个函数有太多的错误了,以至让人不知从何说起了:
1). ISR 不能返回一个值。如果你不懂这个,那么你不会被雇用的。
2). ISR 不能传递参数。如果你没有看到这一点,你被雇用的机会等同第
一项。
3). 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编
译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做
浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明
智的。
4). 与第三点一脉相承,printf()经常有重入和性能上的问题。如果你丢掉
了第三和第四点,我不会太为难你的。
35、全局变量可不可以定义在可被多个.C文件包含的头文件中?为什么?
【标准答案】可以