C++常见面试问题.docx
- 文档编号:23485941
- 上传时间:2023-05-17
- 格式:DOCX
- 页数:27
- 大小:198.38KB
C++常见面试问题.docx
《C++常见面试问题.docx》由会员分享,可在线阅读,更多相关《C++常见面试问题.docx(27页珍藏版)》请在冰豆网上搜索。
C++常见面试问题
1.常量指针和指针常量:
常量指针:
constint*p:
指针指向一个常量,const修饰*,也就是说,*p是常量,不可变,但p是变量,p可变(指向内容不可变,指向可以改变),不需要初始化,可以在定义后赋值。
指针常量:
int*constp:
指针是一个常量,const修饰p,即是p是常量,p不可变,但指向的*p可变。
(指向不可变,内容可变),必须初始化
2.堆和栈的生长方向?
答:
对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,他的生长方向是向下的,是向着内存地址减小的方向增长。
3.Dynamic_cast,const_cast,static_cast,reinterpret_cast区别:
答:
(1)dynamic_cast:
基类和派生类之间的转换,运行期间,会检查这个转换是否可能。
表达式dynamic_cast
如果类型T不是a的某个基类型,该操作将返回一个空指针。
(2)static_cast
编译期间处理,T和a必须是指针、引用、算术类型或枚举类型,运行时转换过程中,不进行类型检查保证安全。
(3)const_cast
去掉类型中的常量
4.C++编译流程:
答:
1)预处理:
对所有伪指令进行处理(#开头的指令,如#define,#if),删除注释,添加行号和文件名标识,保留#pragma指令
2)编译:
对文件进行词法分析、语法分析、语义分析、优化,产生相关的汇编代码
3)汇编:
将汇编代码文件翻译成机器指令,生成目标程序文件(后缀为.o),该文件为二进制文件。
4)链接:
通过链接器将一个个目标文件链接在一起生成一个完整的可执行文件。
(会发现有声明但没有定义的文件)
5.enum类型占内存大小是多少?
答:
无论enum有多少个成员,enum的大小都是4字节
6.质数:
比1大的整数中,除了1和本身,再没有其他因数的整数称之为质数,1不是质数也不是合数。
质数一定是正数,因为比1大
7.enumtest{
test1,test2=2,test3=1,test4
};
上面代码中,test1,test4的值是多少?
答:
test1的值是0,test4的值是2,默认的值随前一个变量加1.
8.类成员中,如果有enum类型的定义,(不是成员变量),该定义是不占内存的!
9.Enum类型中,如果不指定值,那么第一个默认的值是什么?
答:
默认为0,后面一次增加1.
10.用new生成对象时,加括号和不加括号的区别。
答:
11.使用构造函数注意事项:
<1>构造函数的函数名必须与类名相同,而且没有返回值,更不能用void来修饰。
<2>当一个类没有定义构造函数时,编译器会为每个类添加一个默认的构造函数。
默认构造函数访问权限是public的,且为inline函数(常识)。
12.
13.位运算符:
14.如何判断字符指针是否已经到了字符串的末尾?
答:
1.先用strlen求出字符个数N,然后判断字符串名减指针是否小于N
2.判断指针指向的字符是否为空(0)。
15.如何快速交换两个指针?
Int*temp=p1;
P1=p2;
P2=temp;
16.Charstr[10]=”abc”;
那么sizeof(str)=10;
Strlen(str)=3,不算结束符的字符个数。
但,
若charstr[]=”abc”;
Sizeof(str)=4;
Strlen(str)=3;
17.可以使用[]的容器包括:
vector,deque.
18.当元素唯一且需要排序的时候,应选用容器set(键值相等)或map(键值不一样)。
Set和map都会自动对元素进行升序排序。
当元素不唯一且需要排序的时候,可选用multimap或者multiset。
虽然也可以选用vector,但vector不会进行自动排序,调用sort函数,并且需要调用unique和erase函数才能使到元素唯一。
19.自定义的类型需要排序时,重载==和<、>运算符
20.Deque常见用法:
答:
deque双向队列是一种双向开口的连续线性空间,可以高效的在头尾两端插入和删除元素。
操作包括:
21.仿函数是什么?
答:
C++中仿函数是通过重载()运算符实现的,创建一个类似函数行为的对象。
例如,在一个类中实现:
Intoperator()(intx,inty)
{
Returnx+y;
}
仿函数的作用:
使到迭代和计算分离。
例如for_each函数的实现:
1.template < typename Iterator, typename Functor >
2.void for_each( Iterator begin, Iterator end, Fucntor func )
3.{
4.for( ; begin!
=end; begin++ )
5.func( *begin );
6.}
22.STL组成部分?
答:
六大部分:
(1):
容器(container):
包括vector、list、deques、map、set、multiset、multimap等
(2)迭代器(Iterator):
提供了访问对象的方法。
(3)算法(algorithm):
操作容器数据的模板函数,例如find、sort
(4)仿函数(functionobject):
重载了()运算符的结构体
(5)迭代适配器(adapter):
stack和queue,底层实现可以是deque、vector、list等。
(默认都是deque)
(6)空间配置器(allocator):
主要工作是对象的创建和销毁,内存的获取和释放。
23.STL容器:
(1)序列容器(sequencecontainters):
每个元素都有固定的位置。
--Vector:
动态数组,尾部添加元素很快,中间或头部添加元素会慢很多。
--deques:
是“double-ended-queue”的缩写,可以随机存储元素,头部和尾部添加元素都很快。
--lists:
双向链表,不支持随机访问,但任何位置插入和删除很快。
(2)关联式容器(associatedcontainters):
元素的位置取决于特定的排序准则。
--set/multiset:
内部自动升序排序。
Set键唯一,multiset不唯一。
--map/multimap:
map是键值对,自动排序
24.STL中算法分类:
答:
大致分为四种:
(1)非可变序列算法:
不直接修改其所操作容器的算法。
(2)可变序列算法:
可以修改操作容器内容的算法
(3)排序算法:
(4)数值算法:
对容器内容进行数值计算。
25.迭代器失效的原因?
答:
以vector为例,当进行插入操作的时候,如果预分配的空间不足的话,会重新分配一段新的内存,然后将原来的数据拷贝过去,然后新的元素添加到末尾。
而原来的空间则被系统回收。
则指向原来空间的迭代器就会指向了一片非法的地方。
26.怎么对基本数据类型进行排序?
答:
首先需要对头文件进行包含,
#include
Usingnamespacestd;
然后确定排序的范围:
Sort(array,array+N);//该排序是从小到大排序的。
若需要从大到小排序,则写:
Sort(array,array+N,greater
仿函数列表:
名称
功能描述
equal_to
相等
not_equal_to
不相等
less
小于
greater
大于
less_equal
小于等于
greater_equal
大于等于
27.引用的本质:
引用必须在定义的时候初始化,初始化之后只能够修改其指向的内容值,而不能修改其指向的值,实质上跟指针常量一样?
28.内联函数inline
1)内联的目的:
为了提高函数的执行效率(速度)。
2)内联提出的原因:
替代C中的宏定义,宏定义,宏定义没有参数压栈,代码生成的一系列操作,效率很高,但不能进行参数有效性检查,其返回值也不能被强制转换成适合的类型,因此使用存在着一系列的隐患和局限;另外宏定义无法访问保护成员和私有成员。
而inline的提出,就是为了解决宏定义的缺点。
3)内联的使用:
关键字inline必须与函数定义体放在一起才能使函数成为内联,仅将inline放在函数声明前面不起任何作用。
所以说,inline是一种“用于实现的关键字”。
定义在类声明之中的成员函数将自动地成为内联函数。
如:
classA
{
public:
voidFoo(intx,inty){ }//自动地成为内联函数
}
但编译器会自动地判断该成员函数能否作为内联函数。
4)内联函数的工作:
对于任何内联函数,编译器在符号表里放入函数的声明(包括名字、参数类型、返回值类型)。
如果编译器没有发现内联函数存在错误,那么该函数的代码也被放入符号表里。
在调用一个内联函数时,编译器首先检查调用是否正确 (进行类型安全检查,或者进行自动类型转换,当然对所有的函数都一样)。
如果正确,内联函数的代码就会直接替换函数调用,于是省去了函数调用的开销。
这个过程与预处理有显著的不同,因为预处理器不能进行类型安全检查,或者进行自动类型转换。
假如内联函数是成员函数,对象的地址(this)会被放在合适的地方,这也是预处理器办不到的。
29.符号表
30.C++数据在内存中存放的位置:
局部变量,全局变量,静态变量,new数据,函数,const常量
程序内存分配:
1)栈(stack):
一种先进后出的数据结构,一般讲堆栈就是指栈。
栈地址向下增长,由编译器自动释放和分配。
存放函数参数值,局部变量值
2)堆:
一般由程序员分配释放,若程序员不释放,程序结束时可能由OS(操作系统)回收。
注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
用new分配
3)自由存储区:
由malloc分配
4)常量区:
这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改
5)全局/静态存储区:
全局变量和静态变量被分配到同一块内存中。
(静态变量由static定义)
一个内存分配的例子:
void f(){
int* p=newint[5];
}
P指针是分配在栈上的,而new分配的int空间则是在堆上。
所以,这段代码的操作是:
在栈内存中存放了一个指向一块堆内存的指针 p。
在程序会先确定在堆中分配内存的大小,然后调用 operatornew 分配内存,然后返回这块内存的首地址,放入栈中。
31.常量和常变量区别?
常量是define定义的,在预编译阶段进行替换,不进行类型检查。
常变量用const定义,放在静态表中,会进行类型检查,只是不能修改其值。
本质上仍然是一个变量。
32.Const修饰函数和函数返回值的区别?
答:
const修饰函数返回值:
如果函数是以“值传递”的方式返回,则用const修饰毫无意义,因为函数会把返回值复制到外部临时存储单元,例如:
Constintfun()
这样定义是毫无意义的。
但是,如果返回的类型是用户自定义类型,用constT&这样的形式确实能够提高效率。
Const修饰函数:
const修饰成员函数,表示该函数里面类的数据成员不可修改。
Const的声明在函数后。
Const对象只能访问const成员函数,因为非const成员函数会修改const对象?
33.为什么要用constT&作为形参或者函数的返回值?
答:
因为用constT&作为形参的话,传值时候无需要进行值复制,只是传递了别名,效率会快很多,同时,用const的话,保证了参数不会在函数内部被修改。
但是,如果函数时值传递的话,函数内部会对参数的值进行复制,因此不需要用const进行修饰。
例如不要将函数voidFunc1(intx)写成voidFunc1(constintx)。
同理不要将函数voidFunc2(Aa)写成voidFunc2(constAa)。
其中A为用户自定义的数据类型。
对于非内部数据类型的参数而言,象voidFunc(Aa)这样声明的函数注定效率比较底。
因为函数体内将产生A类型的临时对象用于复制参数a,而临时对象的构造、复制、析构过程都将消耗时间。
为了提高效率,可以将函数声明改为voidFunc(A&a),因为“引用传递”仅借用一下参数的别名而已,不需要产生临时对象。
但是函数voidFunc(A&a)存在一个缺点:
“引用传递”有可能改变参数a,这是我们不期望的。
解决这个问题很容易,加const修饰即可,因此函数最终成为voidFunc(constA&a)。
以此类推,是否应将voidFunc(intx)改写为voidFunc(constint&x),以便提高效率?
完全没有必要,因为内部数据类型的参数不存在构造、析构的过程,而复制也非常快,“值传递”和“引用传递”的效率几乎相当。
问题是如此的缠绵,我只好将“const&”修饰输入参数的用法总结一下。
对于非内部数据类型的输入参数,应该将“值传递”的方式改为“const引用传递”,目的是提高效率。
例如将
voidFunc(Aa)改为
voidFunc(constA&a)。
对于内部数据类型的输入参数,不要将“值传递”的方式改为“const引用传递”。
否则既达不到提高效率的目的,又降低了函数的可理解性。
例如voidFunc(intx)不应该改为voidFunc(constint&x)。
34.当不写访问控制权限的时候,默认是private还是public?
答:
默认是private(无论是数据成员还是函数)。
35.Friend关键字的作用
一些在外部定义的函数,有时候需要频繁地访问类的内部数据,就可以把这个外部函数定义为该类的友元函数(但是注意,友元函数不属于类的一部分)。
加上friend关键字的函数就是友元函数,在private和public中声明没有任何区别。
36.运算符重载一定是成员函数吗?
为什么要把运算符重载函数定义成友元函数?
答:
运算重载符不一定是成员函数,也可以是普通的函数,但是只能访问类的公有数据,不能访问私有数据,因此要把运算符重载设置成类的成员函数,这样的的话,运算符重载函数就可以访问类的私有数据了。
把运算符重载函数定义成友元函数的原因是为了访问别的类的私有数据。
37.运算符重载的语法
答:
语法形式为:
函数类型 operator 运算符(形参表)
{
函数体;
}
38.不能重载的运算符有哪些?
答:
不能重载的运算符只有五个,它们是:
成员运算符“.”、指针运算符“*”、作用域运算符“:
:
”、“sizeof”、条件运算符“?
:
”。
39.虚函数表的作用?
答:
实现多态。
怎么实现?
如下图:
在class2的虚函数表中,vf1没有被重载,因此调用时会直接调用class1的vf1,而vf2被重载了,因此调用时就会调用class2的vf2.
40.数组指针和指针数组的区别?
答:
数组指针:
指向数组的指针,形式为:
Int(*p)[n]:
指向一维的指针,也称为行指针。
指针数组:
数组里保存的是指针。
Int*p[n]:
[]的优先级比*高,因此p会先和[]结合,成为一个数组,然后int*修饰的是数组保存的类型。
41.字符串中,两种定义方式有什么区别?
Charp[]=“string”;
Char*q=“string”;
答:
字符数组存放在堆栈,而字符指针的内容存放在常量区,不能修改。
如:
P[0]=’x’//合法
q[0]=’x’//非法。
42.指针大小由什么决定?
答:
指针就是可以改变的地址,因此就是地址的大小,32位系统里,指针为32位,4字节;64位系统里,指针大小为8字节。
43.数组和指针的区别
答:
最直接的:
数组是地址名,常量,不可改变;指针是存储地址的变量。
Sizeof的值也不一样,sizeof数组得到的结果是数组大小(单位是字节);sizeof指针的结果是地址的大小,在32位为4.
44.如果类中的数据成员被定义成private,而在接口中返回了该数据成员的引用,则该数据成员可以在外部被修改。
Private关键字只能防止“直接”的修改,而不能防止“间接”修改。
如果希望返回引用提高效率而又不希望私有数据被修改的话,可以加上const关键字,这样就算是返回了引用也无法对其进行修改。
45.当一个子类函数通过基类的指针调用时,访问权限取决于基类对该函数的声明。
所以一个指向基类的指针可能会访问到私有的子类成员函数。
46.虚函数表位于内存哪里?
答:
只读数据段。
47.重载、覆盖、隐藏的区别?
答:
重载是在同一类中,名字相同,参数相同的函数。
覆盖是派生类中覆盖基类的同名虚函数,要求返回值、函数名、参数都一致(函数名、参数一致,但返回值不一致的话会报错;如果函数名一致,参数,返回值不一致不会报错,但那不是覆盖,而是一个新的函数,跟基类的函数无关)
隐藏是派生类中的同名函数(无论参数相同还是不同)都会隐藏了基类的函数。
48.(int*)p->a代码中,是先运行强制类型转换还是先执行成员访问运算符?
答:
是先执行成员访问运算,因为成员访问运算优先级比强制类型转换优先级高。
49.Public,private、protected继承之后的权限?
答private属性是不能够被继承的
protected继承和private继承能降低访问权限:
使用private继承,父类的protected和public属性在子类中变为private;
使用protected继承,父类的protected和public属性在子类中变为protected;
使用public继承,父类中的protected和public属性不发生改变;
50.常量对象只能调用常量成员函数。
51.Cosnt也可以用来做重载,例如
Voidf()
Voidf()const
52.怎么从补码中求原码?
答:
除了符号位外,补码取反加1就是原码。
53.函数指针的调用方式(假设pf为函数指针):
pf(a)或者(*pf)(a)都可以
54.虚函数、纯虚函数和普通成员函数的区别?
答:
纯虚函数:
只有声明没有实现,是一个接口,不能够通过new来生成对象,派生类必须实现该接口。
定义形式为:
virtualvoidfuntion()=0;
虚函数:
实现多态,添加virtual关键字的成员函数,运行时会根据实际的类型来调用相关的函数。
(也就是看等式右边是什么类型,系统就会调用相关的类型函数)
普通函数:
编译期间根据左边类型决定调用哪一个函数。
55.是否每一个类都有无参构造函数?
(考点:
默认无参构造函数)
答:
否,如果自定义了有参数的构造函数,就不会生成无参构造函数!
!
56.注意,classNamea=b;//调用的是拷贝构造函数!
!
classNamea,b;
a=b;//调用的是赋值运算符重载函数!
!
57.对函数指针赋值可以采用以下方式pf=&p1或者pf=p1,其中,pf为函数指针,p1为函数名。
58.对于二维数组,行数可以省略,但列数一定要指定,因为编译器根据列数来进行寻址。
(PS:
对于多维数组的定义,只有靠近数组名那一维可以省略。
)
59.析构函数:
没有参数,没有返回值,不能重载,可以作为虚函数。
构造函数:
有参数,没有返回值,可以重载,不可以作为虚函数
60.构造函数可否是虚函数?
可否用static修饰?
析构函数可否是虚函数?
构造函数和析构函数中调用虚函数会调用哪一个?
为什么?
答:
(1)构造函数不可以是虚函数。
因为如果声明构造函数是虚函数的话,运行时只会调用一个函数,而构造函数需要从父类开始到子类进行构造。
(2)不可以用static修饰。
(3)析构函数可以是虚函数,如果析构函数是虚函数,那么删除基类指针的时候,系统会自动调用派生类的析构函数和基类的析构函数;如果析构函数不是虚函数的话,删除基类指针只会调用基类的析构函数而不会调用派生类的析构函数导致派生类的资源没有释放。
(4)构造函数中调用虚函数是调用该构造函数所在类的虚函数(也就是父类的虚函数而不是像普通虚函数一样,会调用子类的虚函数)。
因为构造函数是从父类开始进行初始化的,如果此时调用的虚函数是子类的函数,就会出现一个问题:
子类的构造函数还没有执行,子类的内部数据没有初始化,如果执行的虚函数中数据成员的调用,那么就是危险的。
因此,构造函数中的虚函数应该是调用本类中的虚函数。
(5)在析构函数中也不要调用虚函数。
在析构的时候会首先调用子类的析构函数,析构掉对象中的子类部分,然后在调用基类的析构函数析构基类部分,如果在基类的析构函数里面调用虚函数,会导致其调用已经析构了的子类对象里面的函数,这是非常危险的。
61.前缀++和后缀++的区别:
答:
//前缀形式:
int&int:
:
operator++()//这里返回的是一个引用形式,就是说函数返回值也可以作为一个左值使用
{//函数本身无参,意味着是在自身空间内增加1的
*this+=1; //增加
return*this; //取回值
}
//后缀形式:
constintint:
:
operator++(int)//函数返回值是一个非左值型的,与前缀形式的差别所在。
{//函数带参,说明有另外的空间开辟
intoldValue=*this; //取回值
++(*this); //增加
returnoldValue; //返回被取回的值
}
所以,
i++返回的是一个临时变量,函数返回后不能被寻址得到,它只是一个数据值,而非地址,因此不能作为左值。
++i返回的是i,是一个左值。
62.那些字符不能做变量名?
答:
以字母、下划线(也就是‘_’)、数字命名组成
命名的第一个字符必须是字母或者下划线。
63.字符串和浮点数都不能用==进行比较。
64.一个指向子类的指针如何调用基类的虚函数?
答:
不能通过指针的转换调用父类的函数,因为虚函数是运行时根据实际类型决定调用父类还是子类的函数的,需要通过调用作用于运算符:
:
Base*p=newderived;
p->base:
:
f();
65.函数指针和指针函数的区别
答:
指针函数就是返回值是一个指针(int*fun());
函数指针:
指向函数的指针,形式:
int(*p)(inta,intb):
一个名为p的指向返回值为int,参数为a和b的函数。
66.++前置和后置区别?
*和[]运算符优先级?
&&和||优先级?
答:
(1)++c是先对c的值进行加1在对c的值进行使用,如
c=0;
b=++c;
则b的值为1;
(2)c++是先对c的值进行使用再加1.
c=0;
b=c++;
b的值为0;
67.函数参数入栈顺序?
答:
入栈顺序是从右到左。
68.Memcpy和memmov
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- C+ 常见 面试 问题