面试中那些C++常考问题

1. static和const关键字的作用?
static:
1 修饰普通变量,修改变量的存储区域和生命周期,使变量存储在静态区,在main函数运行前就分配了空间,如果有初始值就用初始值初始化它,没有初始值系统用默认值初始化它;
2 修饰普通函数,表明函数的作用范围,仅在定义该函数的文件内才能使用。在多人开发项目时,为了防止与他人命令函数重名,可以将函数定位为static;
3 修饰成员变量,使所有的对象只保存一个该变量,且不需要生成对象就可以访问该成员;
4 修饰成员函数,使得不需要生成对象就可以访问该函数,但是在静态函数内不能访问非静态成员。
const:
1 要阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对其进行初始化,因为以后就没有机会再去改变它了;
2 对指针来说,const可以修饰指针本身,也可以修饰指针所指的数据,或二者同时指定为const;
3 在函数声明中,const可修饰形参,这表明在函数内部不能改变其值;
4 对于类的成员函数,若指定其为const,则表明其是一个常函数,不能修改类的成员变量。

2. const与#define的区别?
1 const可以用来定义常量、修饰函数参数、修饰函数返回值。被const修饰的东西受到强制保护,能提高程序的健壮性。
2 const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。
3 可以对const常量进行调试,但是不能对宏常量进行调试。
4 #define只在编译预处理阶段起作用,const在编译和运行阶段都起作用。

3. C++和C的区别?
设计思想方面:
C++是面向对象,而C是面向过程(举例 大象装冰箱)
语法方面:
C++有封装、继承和多态三大特性
C++相比C,增加多许多类型安全的功能,比如强制类型转换
C++支持范式编程,比如模板类、函数模板等

4. C++中的四种类型转换是什么?
1 const_cast
用于将const变量转为非const
2 static_cast
用于各种隐式转换,比如非const转const,void*转指针等,static_cast能用于多态向上转化,如果向下转能成功但是不安全,结果未知;
向上转换:子类向基类的转换
向下转换:基类向子类的转换
3 dynamic_cast
用于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上和向下转化。只能转指针或引用。向下转时,如果是非法的,对于指针返回NULL,对于引用抛异常。
它通过判断在执行到该语句的时候变量的运行时类型和要转换的类型是否相同来判断是否能够进行向下转换。
4 reinterpret_cast
几乎什么都可以转,比如将int转指针,可能会出问题,尽量少用;

5. C++中指针和引用的区别
1 指针有自己的一块空间,引用只是一个别名;
2 sizeof一个指针的大小32位系统下是4,64位系统下是8,而引用则是被引用对象的大小;
3 指针可以被初始化为NULL,引用必须被初始化且必须是一个已有对象
的引用;
4 作为参数传递时,指针需要被解引用才可以对对象进行操作,而对引用的修改都会改变引用所指的对象;
5 有const指针,但没有const引用;
6 指针在使用中可以指向其它对象,但是引用只能绑定一个对象,不能被改变;
7 有多级指针(**p),而引用只有一级;
8 指针和引用使用++运算符的意义不一样;
9 若返回动态内存分配的对象或内存,须使用指针,引用可能会引起内存泄露。

声明一个引用的时候,要对其进行初始化。声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元。不能建立数组的引用。

6. 值传递,指针传递和引用传递区别?
传值:实参拷贝传递给形参,单向传递(实参->形参),赋值完毕后实参就和形参没有任何联系,对形参的修改就不会影响到实参。
传指针:传指针也是一种传值,它所传递的是一个地址值。传地址就是把实参的地址复制给形参。复制完毕后实参的地址和形参的地址没有任何联系,对实参形参地址的修改不会影响到实参,但是对形参地址所指向对象的修改却直接反应在实参中。
传引用:本质没有任何实参的拷贝,相当于是让两个变量指向同一个对象。而对形参的修改,必然会反映到实参上。

7. C++面向对象的特性?
1 封装:把类的状态信息隐藏在类的内部,不允许外部程序直接访问,而可以通过该类提供的方法来实现对隐藏信息的访问。好处是隐藏了实现细节,选择性暴露关键信息。对数据提供了不同级别的保护,限制不合理操作。
2 继承:指一种能力,可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
3 多态:不同对象收到相同的消息会产生不同的动作,允许将父类指针指向子类对象并调用子类的方法。多态包括静态多态(早绑定)和动态多态(晚绑定)。
静态多态:编译时就确定要执行哪个函数。 如重载
动态多态:编译时不确定、运行时才确定要执行哪个函数。 如覆盖

8. 重载和覆盖(重写)?
重载:两个函数名相同,但是参数列表不同(个数,类型),返回值类型没有要求,在同一作用域中。
重写(覆盖):子类继承了父类,父类中的函数是虚函数,函数名和参数都相同,在子类中重新定义了该虚函数,父类函数被覆盖。
隐藏:指派生类的函数屏蔽了与其同名的基类函数。
如果派生类的函数与基类的函数同名,但参数不同,则无论有无virtual关键字,基类的函数都被隐藏。
如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字,此时基类的函数被隐藏。

9. 指针函数和函数指针?
指针函数
一个返回指针的函数,本质是一个函数,而该函数的返回值是一个指针。
声明: int* fun(int x, int y);
函数指针
本质是一个指针变量,该指针指向这个函数。函数指针就是指向函数的指针。
声明: int (*fun)(int x, int y);
函数指针是需要把一个函数的地址赋值给它,有两种写法:
fun = &Function; fun = Function; 一个函数标识符就表示了它的地址
调用函数指针的方式也有两种:
x =(*fun)(); x = fun();

10. C++里面的智能指针?
shared_ptr, weak_ptr, unique_ptr
智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个,当超出了类的作用域是,类会自动调用析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。
智能指针内存泄漏:当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。
解决方式:引入weak_ptr弱指针,weak_ptr的构造函数不会修改引用计数的值,从而不会对对象的内存进行管理,其类似一个普通指针,但不指向引用计数的共享内存,但是其可以检测到所管理的对象是否已经被释放,从而避免非法访问。

11. 数组和指针的区别?
1 概念:
数组:用于储存多个相同类型数据的集合。
指针:相当于一个变量,但和普通变量不同,它存放的是变量在内存中的地址。
2 赋值、存储方式、求sizeof
赋值:
同类型指针变量可以相互赋值,数组不行,只能一个一个元素的赋值或拷贝
存储方式:
数组:数组在内存中是连续存放的,开辟一块连续的内存空间。数组是根据数组下标进行访问的,多维数组在内存中是按照一维数组存储的,只是在逻辑上是多维的。
指针:指针可以指向任意类型的数据。指针的类型说明了它所指向地址空间的内存。由于指针本身就是一个变量,再加上它所存放的也是变量,所以指针的存储空间不能确定。
求sizeof:
数组所占存储空间的内存:sizeof(数组名)
数组的大小:sizeof(数组名)/sizeof(数据类型)
在32位平台下,无论指针的类型是什么,sizeof(指针)都是4,64位平台下,sizeof(指针)是8。

12. 野指针成因、危害及避免方法?
概念:野指针指向了一块随机内存空间,不受程序控制。如指针指向已经被删除的对象或者指向一块没有访问权限的内存空间,之后如果对其再解引用的话,就会出现问题。
野指针产生的原因
1 指针定义时未被初始化:指针在被定义时如果不对其进行初始化,它会指向随机区域。
2 指针被释放时没有被置空:指针指向的内存空间在用free()或者delete释放后,如果程序员没有对其置空或者其他的赋值操作,就会使其成为一个野指针。
3 指针操作超越变量作用域:不要返回指向栈内存的指针或引用,因为栈内存在函数结束的时候会被释放。
避免方法:
初始化指针时将其置为NULL,之后再对其进行操作。 释放指针时将其置为NULL。要想彻底地避免野指针,最好的办法就是养成良好的编程习惯。

13. C++中类成员的访问权限?
C++通过 public、protected、private 三个关键字来控制成员变量和成员函数的访问权限,分别表示公有的、受保护的、私有的,被称为成员访问限定符。
在类的内部(定义类的代码内部),无论成员被声明为 public、protected 还是private,都是可以互相访问的。
在类的外部(定义类的代码之外),只能通过对象访问成员,并且通过对象只能访问public 属性的成员,不能访问 private、protected属性的成员。

14. 虚函数和多态?
多态是不同对象收到相同消息产生不同动作。主要分为静态多态动态多态,静态多态主要是重载,在编译的时候就已经确定要执行哪个函数;动态多态是用虚函数机制实现的,编译时不知道要执行哪个函数,只有在运行期间才知道。
举个例子:一个父类指针指向一个子类对象,使用父类的指针调用子类中重写了的父类中的虚函数的时候,会调用子类重写过后的函数。在父类中声明为加了virtual关键字的函数,在子类中重写时候不需要加virtual也是虚函数。
虚函数的实现:在有虚函数的类中,类的最开始部分是一个虚函数表的指针,这个指针指向一个虚函数表,表中放了虚函数的地址,实际的虚函数在代码段(.text)中。当子类继承了父类的时候也会继承其虚函数表,当子类重写父类中虚函数时候,会将其继承到的虚函数表中的地址替换为重新写的函数地址。使用了虚函数,会增加访问内存开销,降低效率。
为什么降低效率:一般的函数可在编译时定位到函数的地址,虚函数是要根据某个指针定位到函数的地址,多增加了一个过程,效率会低一些,但好处是运行时多态。

15. C++中析构函数的作用?
析构函数与构造函数对应,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统会自动执行析构函数。
析构函数名应与类名相同,只是在函数名前面加一个位取反符~,例如~stud( ),以区别于构造函数。它没有参数,没有返回值(包括void类型),不能重载。
用户没有编写析构函数,系统会自动生成一个缺省的析构函数。
如果一个类中有指针,且在使用的过程中动态的申请了内存,那么最好显示构造析构函数在销毁类之前,释放掉申请的内存空间,避免内存泄漏。
析构顺序:1)派生类本身的析构函数;2)对象成员析构函数;3)基类析构函数。

16. 为什么C++默认的析构函数不是虚函数 ?
可能会被继承的父类的析构函数设置为虚函数,可以保证当我们new一个子类,然后使用父类指针指向该子类对象,释放父类指针时可以释放掉子类的空间,防止内存泄漏。否则的话是不会释放子类的空间的。
C++默认的析构函数不是虚函数是因为虚函数需要额外的虚函数表和虚表指针,占用额外的内存。对于不会被继承的类来说,其析构函数如果是虚函数,就会浪费内存。

17. 子类析构时要调用父类的析构函数吗?
不会。析构函数调用的次序是先派生类后基类,也就是说在基类的的析构调用的时候,派生类的信息已经全部销毁了。但定义一个对象时先调用基类的构造函数,然后调用派生类的构造函数。

18. C++中struct和class的区别?
在C++中,可以用struct和class定义类,都可以继承。
区别在于:
struct的默认继承和访问权限是public,而class的默认继承和访问权限是private。
struct是值类型,class是引用类型。
由于栈的执行效率要比堆的执行效率高,但是栈资源很有限,不适合处理逻辑复杂的大对象,因此struct常用来处理小对象,而class来处理某个商业逻辑。
另外,class还可以定义模板类形参,比如template <class T, int i>。

19. 必须在构造函数初始化式里进行初始化的数据成员有哪些?
常量成员:因为常量只能初始化不能赋值,所以必须放在初始化列表里。
引用类型:引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里。
没有默认构造函数的类类型:因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。

20. 一个C++源文件从文本到可执行文件经历的过程?
预处理阶段:对源代码文件中文件包含关系(头文件)、预编译语句(宏定义)进行分析和替换,生成预编译文件;
编译阶段:将经过预处理后的预编译文件转换成特定汇编代码,生成汇编文件 ;
汇编阶段:将编译阶段生成的汇编文件转化成机器码,生成可重定位目标文件;
链接阶段:将多个目标文件及所需要的库连接成最终的可执行目标文件。

21. include头文件的顺序以及双引号“ ”和尖括号< >的区别?
包含C++提供的标准头文件或系统头文件时应使用尖括号,包含自定义头文件时可使用双引号。
使用尖括号,编译时会先在系统目录里搜索,如果找不到才会在用户工作目录搜索;使用双引号则相反,会先在源代码目录里搜索。
这就意味着,当系统里有一个叫做math.h的头文件,而你的源代码目录里也有一个你自己写的math.h头文件,那使用尖括号时用的就是系统里的;而使用双引号的话则会使用你自己写的。

22. C++程序编译时内存分为5大存储区:
1 栈区(stack):编译器自动分配释放,主要存放函数的参数值,局部变量值等;
2 堆区(heap):由程序员分配释放;
3 全局区或静态区: 存放全局变量和静态变量;程序结束时由系统释放,分为全局初始化区和全局未初始化区;
4 字符常量区: 常量字符串放与此,程序结束时由系统释放;
5 程序代码区:存放函数体的二进制代码。

23. 什么是内存泄漏?
内存泄漏(memory leak)是指由于疏忽或错误造成了程序未能释放掉不再使用的内存的情况。
分类:
1 堆内存泄漏:通过malloc, new从堆中分配的一块内存,没有free或者delete 掉。
2 系统资源泄露:如Bitmap,handle,SOCKET等没有使用相应的函数释放掉,导致系统资源的浪费,严重可导致系统效能降低,系统运行不稳定。
3 没有将基类的析构函数定义为虚函数:当基类指针指向派生类类对象时,如果基类的析构函数不是virtual,那么派生类的析构函数将不会被调用,派生类的资源没有释放,造成内存泄露。

24. 如何判断内存泄漏?
内存泄漏通常是由于调用了malloc/new等内存申请的操作,但是缺少了对应的free/delete。
为了判断内存是否泄露,我们一方面可以使用linux环境下的内存泄漏检查工具Valgrind,另一方面我们在写代码时可以添加内存申请和释放的统计功能,统计当前申请和释放的内存是否一致,以此来判断内存是否泄露。

25. new和malloc的区别?
1 new分配的内存要用delete销毁,malloc要用free来销毁;delete销毁的时候会调用对象的析构函数,而free则不会;
2 malloc与free是C++/C语言的标准库函数,new/delete是C++的操作符;
3 new分配内存按照数据类型进行分配,malloc分配内存按照指定的大小分配;
4 new如果分配失败了会抛出bad_malloc的异常,而malloc失败了会返回NULL;
5 new返回的是指定对象的指针,而malloc返回的是void*;
6 申请数组时, new[]一次分配所有内存,多次调用构造函数,搭配使用delete[],delete[]多次调用析构函数,销毁数组中的每个对象。而malloc则只能sizeof(int) * n。

26. C++11新特性列举几个?
1 空指针nullptr:消除NULL的二义性问题。因为c++中NULL就是0,0 既可以表示整型,也可以表示一个空指针(void *)。nullptr有类型,且可以被隐式转换为指针类型。
2 auto类型指示符:让编译器通过初值推断变量的类型(auto定义的变量必须要有初始值),编译时对变量进行了类型推导,所以不会对程序的运行效率造成不良影响。
3 范围for语句:遍历给定序列的每个元素并对序列中的每个值执行某种操作。
4 Lambda 表达式:用于定义并创建匿名的函数对象,以简化编程工作。

27. C++函数栈和堆的空间最大值?
是向低地址扩展的数据结构,是一块连续的内存区域。栈顶的地址和栈的最大容量是系统预先规定好的,在Window下,栈的大小是1MB,Linux下,默认栈空间大小为8MB。
特点:栈的速度快、空间小,不灵活。
是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储空闲内存地址的,自然是不连续的,而链表的便利方向是由低地址向高地址的。在Window下,堆的大小一般小于2GB。
特点:堆的大小受限于计算机系统中有效的虚拟内存,所以堆获得的空间比较灵活,也比较大,但速度相对慢一些,也容易产生内存泄露问题。

28. C++中拷贝构造函数的形参能否进行值传递?
不可以。
拷贝构造函数具有单个形参,该形参(常用const修饰)是对该类类型的引用。采用直接初始化(a(b))和复制初始化(值传递是复制 a=b)时调用拷贝构造函数。
为啥形参必须是对该类型的引用呢?试想一下,假如形参是该类的一个实例,由于是传值参数,我们把形参复制到实参会调用拷贝构造函数,如果允许拷贝构造函数传值,就会在拷贝构造函数内调用拷贝构造函数,从而形成无休止的递归调用导致栈溢出
拷贝构造函数不能被重载,因为参数个数和类型都是确定的。

29. vector和list的区别以及应用?
区别:
1 vector底层实现是数组,list是双向链表;
2 vector支持随机访问,list不支持。;
3 vector是顺序内存,list不是。;
4 vector在中间节点进行插入删除会导致内存拷贝,list不会;
5 vector一次性分配好内存,不够时才进行2倍扩容,list每次插入新节点都会进行内存申请。;
6 vector随机访问性能好,插入删除性能差;list随机访问性能差,插入删除性能好。
应用
vector拥有一段连续的内存空间,因此支持随机访问,如果需要高效的随机访问,而不在乎插入和删除的效率,使用 vector。
list拥有一段不连续的内存空间,如果需要高效的插入和删除,而不关心随机访问,则应使用 list。

30. STL里resize和reserve的区别?
resize()
改变当前容器内含有元素的数量(size()),
eg: vector< int >v;v.resize(len);
则v的size变为len,如果原来v的size小于len,那么容器新增(len-size)个元素,元素的值为默认为0。当v.push_back(3);之后,则3放在了v的末尾,即下标为len,此时容器是size为len+1;如果原来v的size大于len,会报错。
reserve()
改变当前容器的最大容量(capacity),
它不会生成元素,只是确定这个容器允许放入多少对象,如果reserve(len)的值大于当前的capacity(),那么会重新分配一块能存len个对象的空间,然后把之前v.size()个对象复制过来,销毁之前的内存.
 
 
 
 
 
**以上是本人在面试中感觉高频出现的一些问题,整理不易,希望对大家有所帮助。**

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值