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