C和C++经典面试题面试必备.docx
《C和C++经典面试题面试必备.docx》由会员分享,可在线阅读,更多相关《C和C++经典面试题面试必备.docx(25页珍藏版)》请在冰豆网上搜索。
C和C++经典面试题面试必备
C/C++经典面试题(面试必备)
面试题1:
变量的声明和定义有什么区别
为变量分配地址和存储空间的称为定义,不分配地址的称为声明。
一个变量可以在多个地方声明,
但是只在一个地方定义。
加入extern修饰的是变量的声明,说明此变量将在文件以外或在文件后面部分
定义。
说明:
很多时候一个变量,只是声明不分配内存空间,直到具体使用时才初始化,分配内存空间,
如外部变量。
面试题2:
写出bool、int、float、指针变量与“零值”比较的if语句
bool型数据:
if(flag)
{
A;
}
else
{
B;
}
int型数据:
if(0!
=flag)
{
A;
}
else
{
B;
}
指针型数:
if(NULL==flag)
{
A;
}
else
{
B;
}
float型数据:
if((flag>=NORM)&&(flag<=NORM))
{
A;
}2
注意:
应特别注意在int、指针型变量和“零值”比较的时候,把“零值”放在左边,这样当把“==”
误写成“=”时,编译器可以报错,否则这种逻辑错误不容易发现,并且可能导致很严重的后果。
面试题3:
sizeof和strlen的区别
sizeof和strlen有以下区别:
是一个操作符,是库函数。
的参数可以是数据的类型,也可以是变量,而只能以结尾为‘\0‘的字符串作参数。
编译器在编译时就计算出了sizeof的结果。
而strlen函数必须在运行时才能计算出来。
并且sizeof
计算的是数据类型占内存的大小,而strlen计算的是字符串实际的长度。
数组做sizeof的参数不退化,传递给strlen就退化为指针了。
注意:
有些是操作符看起来像是函数,而有些函数名看起来又像操作符,这类容易混淆的名称一定
要加以区分,否则遇到数组名这类特殊数据类型作参数时就很容易出错。
最容易混淆为函数的操作符就
是sizeof。
面试题4:
C语言的关键字static和C++的关键字static有什么区别
在C中static用来修饰局部静态变量和外部静态变量、函数。
而C++中除了上述功能外,还用来定
义类的成员变量和函数。
即静态成员和静态成员函数。
注意:
编程时static的记忆性,和全局性的特点可以让在不同时期调用的函数进行通信,传递信息,
而C++的静态成员则可以在多个对象实例间进行通信,传递信息。
面试题5:
C中的malloc和C++中的new有什么区别
malloc和new有以下不同:
(1)new、delete是操作符,可以重载,只能在C++中使用。
(2)malloc、free是函数,可以覆盖,C、C++中都可以使用。
(3)new可以调用对象的构造函数,对应的delete调用相应的析构函数。
(4)malloc仅仅分配内存,free仅仅回收内存,并不执行构造和析构函数
(5)new、delete返回的是某种数据类型指针,malloc、free返回的是void指针。
注意:
malloc申请的内存空间要用free释放,而new申请的内存空间要用delete释放,不要混用。
因为两者实现的机理不同。
面试题6:
写一个“标准”宏MIN
#definemin(a,b)((a)<=(b)?
(a):
(b))
注意:
在调用时一定要注意这个宏定义的副作用,如下调用:
((++*p)<=(x)?
(++*p):
(x)
。
p指针就自加了两次,违背了MIN的本意。
3面试题7:
一个指针可以是volatile吗
可以,因为指针和普通变量一样,有时也有变化程序的不可控性。
常见例:
子中断服务子程序修改
一个指向一个buffer的指针时,必须用volatile来修饰这个指针。
说明:
指针是一种普通的变量,从访问上没有什么不同于其他变量的特性。
其保存的数值是个整型
数据,和整型变量不同的是,这个整型数据指向的是一段内存地址。
面试题8:
a和&a有什么区别
请写出以下代码的打印结果,主要目的是考察a和&a的区别。
#include
voidmain(void)
{
inta[5]={1,2,3,4,5};
int*ptr=(int*)(&a+1);
printf("%d,%d",*(a+1),*(ptr-1));
return;
}
输出结果:
2,5。
注意:
数组名a可以作数组的首地址,而&a是数组的指针。
思考,将原式的int*ptr=(int*)(&a+1);
改为int*ptr=(int*)(a+1);时输出结果将是什么呢?
面试题9:
简述C、C++程序编译的内存分配情况
C、C++中内存分配方式可以分为三种:
(1)从静态存储区域分配:
内存在程序编译时就已经分配好,这块内存在程序的整个运行期间都存在。
速度快、不容易出错,
因为有系统会善后。
例如全局变量,static变量等。
(2)在栈上分配:
在执行函数时,函数内局部变量的存储单元都在栈上创建,函数执行结束时这些存储单元自动被释
放。
栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
(3)从堆上分配:
即动态内存分配。
程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在何
时用free或delete释放内存。
动态内存的生存期由程序员决定,使用非常灵活。
如果在堆上分配了空间,
就有责任回收它,否则运行的程序会出现内存泄漏,另外频繁地分配和释放不同大小的堆空间将会产生
堆内碎块。
一个C、C++程序编译时内存分为5大存储区:
堆区、栈区、全局区、文字常量区、程序代码区。
4面试题10:
简述strcpy、sprintf与memcpy的区别
三者主要有以下不同之处:
(1)操作对象不同,strcpy的两个操作对象均为字符串,sprintf的操作源对象可以是多种数据类型,
目的操作对象是字符串,memcpy的两个对象就是两个任意可操作的内存地址,并不限于何种数据类型。
(2)执行效率不同,memcpy最高,strcpy次之,sprintf的效率最低。
(3)实现功能不同,strcpy主要实现字符串变量间的拷贝,sprintf主要实现其他数据类型格式到字
符串的转化,memcpy主要是内存块间的拷贝。
说明:
strcpy、sprintf与memcpy都可以实现拷贝的功能,但是针对的对象不同,根据实际需求,来
选择合适的函数实现拷贝功能。
面试题11:
设置地址为0x67a9的整型变量的值为0xaa66
int*ptr;
ptr=(int*)0x67a9;
*ptr=0xaa66;
说明:
这道题就是强制类型转换的典型例子,无论在什么平台地址长度和整型数据的长度是一样的,
即一个整型数据可以强制转换成地址指针类型,只要有意义即可。
面试题12:
面向对象的三大特征
面向对象的三大特征是封装性、继承性和多态性:
封装性:
将客观事物抽象成类,每个类对自身的数据和方法实行protection(private,protected,
public)。
继承性:
广义的继承有三种实现形式:
实现继承(使用基类的属性和方法而无需额外编码的能力)、可
视继承(子窗体使用父窗体的外观和实现代码)、接口继承(仅使用属性和方法,实现滞后到子类实现)。
多态性:
是将父类对象设置成为和一个或更多它的子对象相等的技术。
用子类对象给父类对象赋值
之后,父类对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。
说明:
面向对象的三个特征是实现面向对象技术的关键,每一个特征的相关技术都非常的复杂,程
序员应该多看、多练。
面试题13:
C++的空类有哪些成员函数
缺省构造函数。
缺省拷贝构造函数。
缺省析构函数。
缺省赋值运算符。
缺省取址运算符。
缺省取址运算符const。
注意:
有些书上只是简单的介绍了前四个函数。
没有提及后面这两个函数。
但后面这两个函数也是
空类的默认函数。
另外需要注意的是,只有当实际使用这些函数的时候,编译器才会去定义它们。
5面试题14:
谈谈你对拷贝构造函数和赋值运算符的认识
拷贝构造函数和赋值运算符重载有以下两个不同之处:
(1)拷贝构造函数生成新的类对象,而赋值运算符不能。
(2)由于拷贝构造函数是直接构造一个新的类对象,所以在初始化这个对象之前不用检验源对象
是否和新建对象相同。
而赋值运算符则需要这个操作,另外赋值运算中如果原来的对象中有内存分配要
先把内存释放掉
注意:
当有类中有指针类型的成员变量时,一定要重写拷贝构造函数和赋值运算符,不要使用默认
的。
面试题15:
用C++设计一个不能被继承的类
templateclassA
{
friendT;
private:
A(){}
~A(){}
};
classB:
virtualpublicA
{
public:
B(){}
~B(){}
};
classC:
virtualpublicB
{
public:
C(){}
~C(){}
};
voidmain(void)
{
Bb;
//Cc;
return;
}
注意:
构造函数是继承实现的关键,每次子类对象构造时,首先调用的是父类的构造函数,然后才
是自己的。
面试题16:
访问基类的私有虚函数
写出以下程序的输出结果:
#include
classA
{6
virtualvoidg()
{
cout<<"A:
:
g"<}
private:
virtualvoidf()
{
cout<<"A:
:
f"<}
};
classB:
publicA
{
voidg()
{
cout<<"B:
:
g"<}
virtualvoidh()
{
cout<<"B:
:
h"<}
};
typedefvoid(*Fun)(void);
voidmain()
{
Bb;
FunpFun;
for(inti=0;i<3;i++)
{
pFun=(Fun)*((int*)*(int*)(&b)+i);
pFun();
}
}
输出结果:
B:
:
g
A:
:
f
B:
:
h
注意:
本题主要考察了面试者对虚函数的理解程度。
一个对虚函数不了解的人很难正确的做出本题。
在学习面向对象的多态性时一定要深刻理解虚函数表的工作原理。
面试题17:
简述类成员函数的重写、重载和隐藏的区别
(1)重写和重载主要有以下几点不同。
范围的区别:
被重写的和重写的函数在两个类中,而重载和被重载的函数在同一个类中。
参数的区别:
被重写函数和重写函数的参数列表一定相同,而被重载函数和重载函数的参数列表一
定不同。
的区别:
重写的基类中被重写的函数必须要有修饰,而重载函数和被重载函数可以被
修饰,也可以没有。
(2)隐藏和重写、重载有以下几点不同。
与重载的范围不同:
和重写一样,隐藏函数和被隐藏函数不在同一个类中。
参数的区别:
隐藏函数和被隐藏的函数的参数列表可以相同,也可不同,但是函数名肯定要相同。
当参数不相同时,无论基类中的参数是否被virtual修饰,基类的函数都是被隐藏,而不是被重写。
说明:
虽然重载和覆盖都是实现多态的基础,但是两者实现的技术完全不相同,达到的目的也是完
全不同的,覆盖是动态态绑定的多态,而重载是静态绑定的多态。
面试题18:
简述多态实现的原理
编译器发现一个类中有虚函数,便会立即为此类生成虚函数表vtable。
虚函数表的各表项为指向对
应虚函数的指针。
编译器还会在此类中隐含插入一个指针vptr(对vc编译器来说,它插在类的第一个位
置上)指向虚函数表。
调用此类的构造函数时,在类的构造函数中,编译器会隐含执行vptr与vtable的
关联代码,将vptr指向对应的vtable,将类与此类的vtable联系了起来。
另外在调用类的构造函数时,
指向基础类的指针此时已经变成指向具体的类的this指针,这样依靠此this指针即可得到正确的vtable,。
如此才能真正与函数体进行连接,这就是动态联编,实现多态的基本原理。
注意:
一定要区分虚函数,纯虚函数、虚拟继承的关系和区别。
牢记虚函数实现原理,因为多态
C++面试的重要考点之一,而虚函数是实现多态的基础。
面试题19:
链表和数组有什么区别
数组和链表有以下几点不同:
(1)存储形式:
数组是一块连续的空间,声明时就要确定长度。
链表是一块可不连续的动态空间,
长度可变,每个结点要保存相邻结点指针。
(2)数据查找:
数组的线性查找速度快,查找操作直接使用偏移地址。
链表需要按顺序检索结点,
效率低。
(3)数据插入或删除:
链表可以快速插入和删除结点,而数组则可能需要大量数据移动。
(4)越界问题:
链表不存在越界问题,数组有越界问题。
说明:
在选择数组或链表数据结构时,一定要根据实际需要进行选择。
数组便于查询,链表便于插
入删除。
数组节省空间但是长度固定,链表虽然变长但是占了更多的存储空间。
面试题20:
怎样把一个单链表反序
(1)反转一个链表。
循环算法。
Listreverse(Listn)
{
if(!
n)//判断链表是否为空,为空即退出。
{
returnn;
}
listcur=n.next;//保存头结点的下个结点
listpre=n;//保存头结点
listtmp;
8
pre.next=null;//头结点的指针指空,转换后变尾结点
while(NULL!
=cur.next)//循环直到cur.next为空
{
tmp=cur;//实现如图10.3—图10.5所示
tmp.next=pre
pre=tmp;
cur=cur.next;
}
returntmp;//f返回头指针
}
(2)反转一个链表。
递归算法。
List*reverse(List*oldList,List*newHead=NULL)
{
List*next=oldList->next;//记录上次翻转后的链表
oldList->next=newHead;//将当前结点插入到翻转后链表的开头
newHead=oldList;//递归处理剩余的链表
return(next==NULL)?
newHead:
reverse(t,newHead);
}
说明:
循环算法就是图10.2—图10.5的移动过程,比较好理解和想到。
递归算法的设计虽有一点难
度,但是理解了循环算法,再设计递归算法就简单多了。
面试题21:
简述队列和栈的异同
队列和栈都是线性存储结构,但是两者的插入和删除数据的操作不同,队列是“先进先出”,栈是
“后进先出”。
注意:
区别栈区和堆区。
堆区的存取是“顺序随意”,而栈区是“后进先出”。
栈由编译器自动分
配释放,存放函数的参数值,局部变量的值等。
其操作方式类似于数据结构中的栈。
堆一般由程序员
分配释放,若程序员不释放,程序结束时可能由OS回收。
分配方式类似于链表。
它与本题中的堆和栈是两回事。
堆栈只是一种数据结构,而堆区和栈区是程序的不同内存存储区域。
面试题22:
能否用两个栈实现一个队列的功能
结点结构体:
typedefstructnode
{
intdata;
node*next;
}node,*LinkStack;
创建空栈:
LinkStackCreateNULLStack(LinkStack&S)
{
S=(LinkStack)malloc(sizeof(node));//申请新结点
if(NULL==S)
{
printf("Failtomallocanewnode.\n");
9
returnNULL;
}S
->data=0;//初始化新结点
S->next=NULL;
returnS;
}
栈的插入函数:
LinkStackPush(LinkStack&S,intdata)
{
if(NULL==S)//检验栈
{
printf("Therenonodeinstack!
");
returnNULL;
}
LinkStackp=NULL;
p=(LinkStack)malloc(sizeof(node));//申请新结点
if(NULL==p)
{
printf("Failtomallocanewnode.\n");
returnS;
}
if(NULL==S->next)
{
p->next=NULL;
}
else
{
p->next=S->next;
}p
->data=data;//初始化新结点
S->next=p;//插入新结点
returnS;
}
出栈函数:
nodePop(LinkStack&S)
{
nodetemp;
temp.data=0;
temp.next=NULL;
if(NULL==S)//检验栈
{
printf("Therenonodeinstack!
");
returntemp;
}
temp=*S;
10
if(S->next==NULL)
{
printf("ThestackisNULL,can'tpop!
\n");
returntemp;
}
LinkStackp=S->next;//节点出栈
S->next=S->next->next;
temp=*p;
free(p);
p=NULL;
returntemp;
}
双栈实现队列的入队函数:
LinkStackStackToQueuPush(LinkStack&S,intdata)
{
noden;
LinkStackS1=NULL;
CreateNULLStack(S1);//创建空栈
while(NULL!
=S->next)//S出栈入S1
{
n=Pop(S);
Push(S1,n.data);
}
Push(S1,data);//新结点入栈
while(NULL!
=S1->next)//S1出栈入S
{
n=Pop(S1);
Push(S,n.data);
}
returnS;
}
说明:
用两个栈能够实现一个队列的功能,那用两个队列能否实现一个队列的功能呢?
结果是否定
的,因为栈是先进后出,将两个栈连在一起,就是先进先出。
而队列是现先进先出,无论多少个连在一
起都是先进先出,而无法实现先进后出。
面试题23:
计算一颗二叉树的深度
深度的计算函数:
intdepth(BiTreeT)
{
if(!
T)return0;//判断当前结点是否为叶子结点
11
intd1=depth(T->lchild);//求当前结点的左孩子树的深度
intd2=depth(T->rchild);//求当前结点的右孩子树的深度
return(d1>d2?
d1:
d2)+1;
}
注意:
根据二叉树的结构特点,很多算法都可以用递归算法来实现。
面试题24:
编码实现直接插入排序
直接插入排序编程实现如下:
#include
voidmain(void)
{
intARRAY[10]={0,6,3,2,7,5,4,9,1,8};
inti,j;
for(i=0;i<10;i++)
{
cout<}
cout<for(i=2;i<=10;i++)//将ARRAY[2],…,ARRAY[n]依次按序插入
{
if(ARRAY[i]//ARRAY[i]将保持原位不动
{
ARRAY[0]=ARRAY[i];//将ARRAY[0]看做是哨兵,是ARRAY[i]的副本
j=i-1;
do{//从右向左在有序区ARRAY[1..i-1]中
//查找ARRAY[i]的插入位置
ARRAY[j+1]=ARRAY[j];//将数值大于ARRAY[i]记录后移
j--;
}while(ARRAY[0]ARRAY[j+1]=ARRAY[0];//ARRAY[i]插入到正确的位置上
}
}
for(i=0;i<10;i++)
{
cout<}
cout<}
12
注意:
所有为简化边界条件而引入的附加结点(元素)均可称为哨兵。
引入哨兵后使得查找循环条
件的时间大约减少了一半,对于记录数较大的文件节约的时间就相当可观。
类似于排序这样使用频率非
常高的算法,要尽可能地减少其运行时间。
所以不能把上述算法中的哨兵视为雕虫小技。
面试题25:
编码实现冒泡排序
冒泡排序编程实现如下:
#include
#defineLEN10//数组长度
voidmain(void)
{
intARRAY[10]={0,6,3,2,7,5,4,9,1,8};//待排序数组
printf("\n");
for(inta=0;a{
printf("%d",ARRAY[a]);
}
inti=0;
intj=0;
boolisChange;//设定交换标志
for(i=1;i{//最多做LEN-1趟排序
is