校园招聘常见C语言类笔试题文档格式.docx
- 文档编号:17853053
- 上传时间:2022-12-11
- 格式:DOCX
- 页数:44
- 大小:40.80KB
校园招聘常见C语言类笔试题文档格式.docx
《校园招聘常见C语言类笔试题文档格式.docx》由会员分享,可在线阅读,更多相关《校园招聘常见C语言类笔试题文档格式.docx(44页珍藏版)》请在冰豆网上搜索。
//Anarrayof10pointerstointegers
f)int(*a)[10];
//Apointertoanarrayof10integers
g)int(*a)(int);
//Apointertoafunctionathattakesanintegerargumentandreturnsaninteger
h)int(*a[10])(int);
//Anarrayof10pointerstofunctionsthattakeanintegerargumentandreturnaninteger
三、Static
关键字static的作用是什么?
在C语言中,关键字static有三个明显的作用:
1).在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
2).在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。
它是一个本地的全局变量。
3).在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。
那就是,这个函数被限制在声明它的模块的本地范围内使用。
四、Const
关键字const是什么含意?
1).合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。
简而言之,这样可以减少bug的出现。
2).通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。
3).关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。
如果你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。
(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。
)
#include<
stdio.h>
usingnamespacestd;
intmain(){
constchar*pa;
charconst*pb;
charca='
a'
;
charcb='
b'
char*constpc=&
ca;
constchar*constpd=&
cb;
pa=&
pb=&
*pc='
d'
printf("
ca=%c\n"
ca);
return0;
}
经过以上测试
constchar*pa;
charconst*pb;
上面两种定义方法一样都是pa(pb)指向的变量的值不可改变,及*pa,*pb,而pa,和pb本身是可变的,如:
pa=&
//ok
×
pa='
c'
//error
char*constpc=&
pc本身是不可变的(只能在定义时初始化),但指向的变量值是可变的,如
pc=&
*pc='
constchar*constpd=&
pd本身是不可变的,且指向的变量也是不可变的(只能在定义时初始化)
pd=&
*pd='
/error
通过以上总结,无论怎样定义p都是一指针
如果const在*左边,表示该指针指向的变量是不可变的
如果const在*右边,表示该指针本身是不可变得
五、Volatile
关键字volatile有什么含意并给出三个不同的例子。
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。
精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。
下面是volatile变量的几个例子:
1).并行设备的硬件寄存器(如:
状态寄存器)
2).一个中断服务子程序中会访问到的非自动变量(Non-automaticvariables)
3).多线程应用中被几个任务共享的变量
这是区分C程序员和嵌入式系统程序员的最基本的问题。
嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都要求volatile变量。
不懂得volatile内容将会带来灾难。
回答以下问题:
1).一个参数既可以是const还可以是volatile吗?
解释为什么。
2).一个指针可以是volatile吗?
3).下面的函数有什么错误:
intsquare(volatileint*ptr){
return*ptr**ptr;
}
下面是答案:
1).是的。
一个例子是只读的状态寄存器。
它是volatile因为它可能被意想不到地改变。
它是const因为程序不应该试图去修改它。
2).是的。
尽管这并不很常见。
一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
3).这段代码的有个恶作剧。
这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
inta,b;
a=*ptr;
b=*ptr;
returna*b;
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。
结果,这段代码可能返不是你所期望的平方值!
正确的代码如下:
longsquare(volatileint*ptr){
inta;
returna*a;
六、位操作(Bitmanipulation)
嵌入式系统总是要用户对变量或寄存器进行位操作。
给定一个整型变量a,写两段代码,第一个设置a的bit3,第二个清除a的bit3。
在以上两个操作中,要保持其它位不变。
解答:
采用#defines和bitmasks操作。
这是一个有极高可移植性的方法,是应该被用到的方法。
最佳的解决方案如下:
#defineBIT3(0x1<
<
3)
staticinta;
voidset_bit3(void){
a|=BIT3;
voidclear_bit3(void){
a&
=~BIT3;
一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数,这也是可以接受的。
主要考点:
说明常数、|=和&
=~操作。
七、访问固定的内存位置(Accessingfixedmemorylocations)
嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。
在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。
编译器是一个纯粹的ANSI编译器。
写代码去完成这一任务。
这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指针是合法的。
这一问题的实现方式随着个人风格不同而不同。
典型的类似代码如下:
int*ptr;
ptr=(int*)0x67a9;
*ptr=0xaa55;
一个较晦涩的方法是:
*(int*const)(0x67a9)=0xaa55;
建议采用第一种方法;
八、代码例子(Codeexamples)
1.下面的代码输出是什么,为什么?
(考查有符号类型与无符号类型之间的转换)
voidfoo(void){
unsignedinta=6;
intb=-20;
(a+b>
6)?
puts("
>
6"
):
=6"
);
这个问题测试你是否懂得C语言中的整数自动转换原则;
这无符号整型问题的答案是输出是“>
6”。
原因是当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。
因此-20变成了一个非常大的正整数,所以该表达式计算出的结果大于6。
这一点对于应当频繁用到无符号数据类型的嵌入式系统来说是丰常重要的。
2.评价下面的代码片断:
(考查是否懂得处理器字长)
unsignedintzero=0;
unsignedintcompzero=0xFFFF;
/*1'
scomplementofzero*/
对于一个int型不是16位的处理器为说,上面的代码是不正确的。
应编写如下:
unsignedintcompzero=~0;
这一问题真正能揭露出应试者是否懂得处理器字长的重要性。
好的嵌入式程序员非常准确地明白硬件的细节和它的局限,然而PC机程序往往把硬件作为一个无法避免的烦恼。
九、Typedef
Typedef作用是声明一个新的类型名代替已有的类型名;
也可以用预处理器做类似的事。
例如,思考一下下面的例子:
#definedPSstructs*
typedefstructs*tPS;
以上两种情况的意图都是要定义dPS和tPS作为一个指向结构s指针。
哪种方法更好呢?
(如果有的话)为什么?
这是一个非常微妙的问题,任何人答对这个问题(正当的原因)是应当被恭喜的。
typedef更好。
思考下面的例子:
dPSp1,p2;
tPSp3,p4;
第一个扩展为
structs*p1,p2;
上面的代码定义p1为一个指向结构的指,p2为一个实际的结构,这也许不是你想要的。
第二个例子正确地定义了p3和p4两个指针。
#define在预编译时处理,只作简单的字符串替换;
Typedef在编译时处理,不是简单的字符串替换;
(一)
单向链表的反转是一个经常被问到的一个面试题,也是一个非常基础的问题。
比如一个链表是这样的:
1->
2->
3->
4->
5通过反转后成为5->
1。
最容易想到的方法遍历一遍链表,利用一个辅助指针,存储遍历过程中当前指针指向的下一个元素,然后将当前节点元素的指针反转后,利用已经存储的指针往后面继续遍历。
源代码如下:
structlinka{
intdata;
linka*next;
};
voidreverse(linka*&
head){
if(head==NULL)
return;
linka*pre,*cur,*ne;
pre=head;
cur=head->
next;
while(cur)
{
ne=cur->
cur->
next=pre;
pre=cur;
cur=ne;
}
head->
next=NULL;
head=pre;
还有一种利用递归的方法。
这种方法的基本思想是在反转当前节点之前先调用递归函数反转后续节点。
源代码如下。
不过这个方法有一个缺点,就是在反转后的最后一个结点会形成一个环,所以必须将函数的返回的节点的next域置为NULL。
因为要改变head指针,所以我用了引用。
算法的源代码如下:
linka*reverse(linka*p,linka*&
head)
if(p==NULL||p->
next==NULL)
head=p;
returnp;
else
linka*tmp=reverse(p->
next,head);
tmp->
next=p;
②已知String类定义如下:
classString
public:
String(constchar*str=NULL);
//通用构造函数
String(constString&
another);
//拷贝构造函数
~String();
//析构函数
String&
operater=(constString&
rhs);
//赋值函数
private:
char*m_data;
//用于保存字符串
尝试写出类的成员函数实现。
答案:
String:
:
String(constchar*str)
if(str==NULL)//strlen在参数为NULL时会抛异常才会有这步判断
m_data=newchar[1];
m_data[0]='
'
;
m_data=newchar[strlen(str)+1];
strcpy(m_data,str);
String(constString&
another)
m_data=newchar[strlen(another.m_data)+1];
strcpy(m_data,other.m_data);
String&
String:
operator=(constString&
rhs)
if(this==&
return*this;
delete[]m_data;
//删除原来的数据,新开一块内存
m_data=newchar[strlen(rhs.m_data)+1];
strcpy(m_data,rhs.m_data);
~String()
delete[]m_data;
1.求下面函数的返回值(微软)
intfunc(x)
intcountx=0;
while(x)
countx++;
x=x&
(x-1);
returncountx;
假定x=9999。
答案:
8
思路:
将x转化为2进制,看含有的1的个数。
2.什么是“引用”?
申明和使用“引用”要注意哪些问题?
答:
引用就是某个目标变量的“别名”(alias),对应用的操作与对变量直接操作效果完全相同。
申明一个引用的时候,切记要对其进行初始化。
引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,不能再把该引用名作为其他变量名的别名。
声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。
不能建立数组的引用。
3.将“引用”作为函数参数有哪些特点?
(1)传递引用给函数与传递指针的效果是一样的。
这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。
(2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;
而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;
如果传递的是对象,还将调用拷贝构造函数。
因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。
(3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"
*指针变量名"
的形式进行运算,这很容易产生错误且程序的阅读性较差;
另一方面,在主调函数的调用点处,必须用变量的地址作为实参。
而引用更容易使用,更清晰。
4.在什么时候需要使用“常引用”?
如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。
常引用声明方式:
const类型标识符&
引用名=目标变量名;
例1
inta;
constint&
ra=a;
ra=1;
//错误
a=1;
//正确
例2
stringfoo();
voidbar(string&
s);
那么下面的表达式将是非法的:
bar(foo());
bar("
helloworld"
原因在于foo()和"
串都会产生一个临时对象,而在C++中,这些临时对象都是const类型的。
因此上面的表达式就是试图将一个const类型的对象转换为非const类型,这是非法的。
引用型参数应该在能被定义为const的情况下,尽量定义为const。
5.将“引用”作为函数返回值类型的格式、好处和需要遵守的规则?
格式:
类型标识符&
函数名(形参列表及类型说明){//函数体}
好处:
在内存中不产生被返回值的副本;
(注意:
正是因为这点原因,所以返回一个局部变量的引用是不可取的。
因为随着该局部变量生存期的结束,相应的引用也会失效,产生runtimeerror!
注意事项:
(1)不能返回局部变量的引用。
这条可以参照EffectiveC++[1]的Item31。
主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"
无所指"
的引用,程序会进入未知状态。
(2)不能返回函数内部new分配的内存的引用。
虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。
例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memoryleak。
(3)可以返回类成员的引用,但最好是const。
这条原则可以参照EffectiveC++[1]的Item30。
主要原因是当对象的属性是与某种业务规则(businessrule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。
如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。
(4)流操作符重载返回值申明为“引用”的作用:
流操作符<
和>
,这两个操作符常常希望被连续使用,例如:
cout<
"
hello"
<
endl;
因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。
可选的其它方案包括:
返回一个流对象和返回一个流对象指针。
但是对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个<
操作符实际上是针对不同对象的!
这无法让人接受。
对于返回一个流指针则不能连续使用<
操作符。
因此,返回一个流对象引用是惟一选择。
这个唯一选择很关键,它说明了引用的重要性以及无可替代性,也许这就是C++语言中引入引用这个概念的原因吧。
赋值操作符=。
这个操作符象流操作符一样,是可以连续使用的,例如:
x=j=10;
或者(x=10)=100;
赋值操作符的返回值必须是一个左值,以便可以被继续赋值。
因此引用成了这个操作符的惟一返回值选择。
例3
#include
int&
put(intn);
intvals[10];
interror=-1;
voidmain()
put(0)=10;
//以put(0)函数值作为左值,等价于vals[0]=10;
put(9)=20;
//以pu
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 校园 招聘 常见 语言 笔试