C++常见面试问题汇总

指针和引用的区别

(1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。
(2)可以有const指针,但是没有const引用;
(3)指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a是不合法的)
(4)指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化;
(5)指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了。
(6)"sizeof引用"得到的是所指向的变量(对象)的大小,而"sizeof指针"得到的是指针本身的大小;
(7)指针和引用的自增(++)运算意义不一样;

堆和栈的区别

link

new和delete是如何实现的,new 与 malloc的异同处

link

C和C++的区别

link
1.函数默认值
2.inline内联函数
3.函数重载
4.const
5.引用
6.malloc,free && new,delete
7.作用域

C++、Java的联系与区别,包括语言特性、垃圾回收、应用场景等(java的垃圾回收机制)

Struct和class的区别

struct默认public类型,class默认private类型

define 和const的区别(编译阶段、安全性、内存占用等)

一:区别
(1)就起作用的阶段而言: #define是在编译的预处理阶段起作用,而const是在 编译、运行的时候起作用。
(2)就起作用的方式而言: #define只是简单的字符串替换,没有类型检查。而const有对应的数据类型,是要进行判断的,可以避免一些低级的错误。
(3)就存储方式而言:#define只是进行展开,有多少地方使用,就替换多少次,它定义的宏常量在内存中有若干个备份;const定义的只读变量在程序运行过程中只有一份备份。
(4)从代码调试的方便程度而言: const常量可以进行调试的,define是不能进行调试的,因为在预编译阶段就已经替换掉了。
二:const优点
(1)const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。
(2)有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。
(3)const可节省空间,避免不必要的内存分配,提高效率

在C++中const和static的用法(定义,用途)

const和static在类中使用的注意事项(定义、初始化和使用)

const作为类成员为什么只能在初始化列表中被初始化,C++11提供了类内初始化的方式
static 作为类成员,所有类对象共享该成员,只能在类外部的.cpp文件中被初始化,初始化时不用指明static属性;

C++中的const类成员函数(用法和意义),以及和非const成员函数的区别

非静态成员函数后面加const(加到非成员函数或静态成员后面会产生编译错误),表示成员函数隐含传入的this指针为 const指针,决定了在该成员函数中,任意修改它所在的类的成员的操作都是不允许的(因为隐含了对this指针的const引用);唯一的例外是对于 mutable修饰的成员。加了const的成员函数可以被非const对象和const对象调用,但不加const的成员函数只能被非const对象调 用。

C++的顶层const和底层const

link
底层const是代表对象本身是一个常量(不可改变);
顶层const是代表指针的值是一个常量,而指针的值(即对象的地址)的内容可以改变(指向的不可改变);

final和override关键字

final关键字说明某个类不能被继承,或者某个虚函数不能被重写(必须是虚函数),放在函数后面。
override关键字在函数后面,显示说明这个函数是重写的。
link

拷贝初始化和直接初始化,初始化和赋值的区别

拷贝初始化和直接初始化:(1)对于一般的内建类型,这两种初始化基本上没有区别。(2)当用于类类型对象时,初始化的复制形式和直接形式有所不同:直接初始化直接调用与实参匹配的构造函数,复制初始化总是调用复制构造函数。复制初始化首先使用指定构造函数创建一个临时对象,然后使用复制构造函数将那个临时对象复制到正在创建的对象

初始化和赋值的区别:对象的初始化是说你在声明的时候就调用默认的或者非默认的构造函数进行初始化工作,而赋值指的是你用一个已经存在的对象去给另一个已经存在的对象赋值。

extern "C"的用法

被extern “C”的段用c语言的方式编译。

模板函数和模板类的特例化

C++的STL源码(这个系列也很重要,建议侯捷老师的STL源码剖析书籍与视频),其中包括内存池机制,各种容器的底层实现机制,算法的实现原理等)

STL源码中的hashtable的实现

STL中unordered_map和map的区别和应用场景

STL中vector的实现

STL容器的几种迭代器以及对应的容器(输入迭代器,输出迭代器,前向迭代器,双向迭代器,随机访问迭代器)

顺序容器:vector,deque是随机访问迭代器;list是双向迭代器
容器适配器:stack,queue,priority_queue没有迭代器
关联容器:set,map,multiset,multimap是双向迭代器
unordered_set,unordered_map,unordered_multiset,unordered_multimap是前向迭代器

STL中的traits技法

type_traits
iterator_traits
char traits
allocator_traits
pointer_traits
array_traits

vector使用的注意点及其原因,频繁对vector调用push_back()对性能的影响和原因。

在一个vector的尾部之外的任何位置添加元素,都需要重新移动元素。而且,向一个vector添加元素可能引起整个对象存储空间的重新分配。重新分配一个对象的存储空间需要分配新的内存,并将元素从旧的空间移到新的空间

C++中的重载和重写的区别

link

C++内存管理,内存池技术(热门问题),与csapp中几种内存分配方式对比学习加深理解

link

介绍面向对象的三大特性,并且举例说明每一个

link

C++多态的实现

link

C++虚函数相关(虚函数表,虚函数指针),虚函数的实现原理(包括单一继承,多重继承等)(拓展问题:为什么基类指针指向派生类对象时可以调用派生类成员函数,基类的虚函数存放在内存的什么区,虚函数表指针vptr的初始化时间)

  1. 每个类都有虚指针和虚表;
  2. 如果不是虚继承,那么子类将父类的虚指针继承下来,并指向自身的虚表(发生在对象构造时)。有多少个虚函数,虚表里面的项就会有多少。多重继承时,可能存在多个的基类虚表与虚指针;
  3. 如果是虚继承,那么子类会有两份虚指针,一份指向自己的虚表,另一份指向虚基表,多重继承时虚基表与虚基表指针有且只有一份。

虚函数表指针vptr的初始化时间:
父类构造函数之后,子类构造函数之前
多重继承

C++中类的数据成员和成员函数内存分布情况

link

this指针

link

析构函数一般写成虚函数的原因

如果基类没有定义为虚函数,则delete 的时候,仅仅调用了父类的析构函数,子类的没有调用,如果在父类和子类的构造函数中都有动态内存分配,那么就会存在内存泄漏的问题。一般析构函数最好都写成虚函数,尤其是父类。

构造函数、拷贝构造函数和赋值操作符的区别

构造函数:对象不存在,没用别的对象初始化
拷贝构造函数:对象不存在,用别的对象初始化
赋值运算符:对象存在,用别的对象给它赋值

构造函数声明为explicit

  1. 将构造函数声明为explicit 来阻止类类型隐式转换
  2. 关键字explicit 只对一个实参的构造函数有效
  3. explicit 构造函数只能用于直接初始化
  4. 在类内声明构造函数时使用explicit 关键字,在类外部定义时不应重复

构造函数为什么一般不定义为虚函数

全都需要构造

虚函数调用是在部分信息下完成工作的机制,允许我们只知道接口而不知道对象的确切类型。 要创建一个对象,你需要知道对象的完整信息。 特别是,你需要知道你想要创建的确切类型。 因此,构造函数不应该被定义为虚函数。

构造函数的几种关键字(default delete 0)

= default:将拷贝控制成员定义为=default显式要求编译器生成合成的版本

= delete:将拷贝构造函数和拷贝赋值运算符定义删除的函数,阻止拷贝(析构函数不能是删除的函数 C++Primer P450)

= 0:将虚函数定义为纯虚函数(纯虚函数无需定义,= 0只能出现在类内部虚函数的声明语句处;当然,也可以为纯虚函数提供定义,不过函数体必须定义在类的外部)

构造函数或者析构函数中调用虚函数会怎样

纯虚函数

静态类型和动态类型,静态绑定和动态绑定的介绍

link

引用是否能实现动态绑定,为什么引用可以实现

因为对象的类型是确定的,在编译期就确定了
指针或引用是在运行期根据他们绑定的具体对象确定。

深拷贝和浅拷贝的区别(举例说明深拷贝的安全性)

link
浅拷贝可能会析构两次

对象复用的了解,零拷贝的了解

零拷贝
对象复用

介绍C++所有的构造函数

赋值函数(重载),拷贝构造函数,移动构造函数,构造函数,移动赋值函数,析构函数
link

什么情况下会调用拷贝构造函数(三种情况)

1 当用类的一个对象去初始化类的另一个对象时。
2 当函数的形参是类的对象,调用函数进行形参和实参的结合时。
3 当函数的返回值是对象,函数执行完成返回调用者时

结构体内存对齐方式和为什么要进行内存对齐?

link
平台移植性好,cpu效率高

内存泄露的定义,如何检测与避免?

1.什么是内存泄漏(Memory Leak)?
简单地说就是申请了一块内存空间,使用完毕后没有释放掉。它的一般表现方式是程序运行时间越长,占用内存越多,最终用尽全部内存,整个系统崩溃。由程序申请的一块内存,且没有任何一个指针指向它,那么这块内存就泄露了。

2、如何检测内存泄露
第一:良好的编码习惯,尽量在涉及内存的程序段,检测出内存泄露。当程式稳定之后,在来检测内存泄露时,无疑增加了排除的困难和复杂度。使用了内存分配的函数,一旦使用完毕,要记得要使用其相应的函数释放掉。
第二:将分配的内存的指针以链表的形式自行管理,使用完毕之后从链表中删除,程序结束时可检查改链表。
第三:Boost 中的smart pointer。
第四:一些常见的工具插件,如ccmalloc、Dmalloc、Leaky等等。

手写智能指针的实现(shared_ptr和weak_ptr实现的区别)

link
shared_ptr:在析构函数中和赋值重载中,要判断引用计数,若为0,要释放自己的空间,在赋值时引用计数加一。

智能指针的循环引用

link

遇到coredump要怎么调试

core dump

内存检查工具的了解

valgrind
mtrace

模板的用法与适用场景

link

成员初始化列表的概念,为什么用成员初始化列表会快一些(性能优势)?

因为对于非内置类型,少了一次调用默认构造函数的过程。

用过C++ 11吗,知道C++ 11哪些新特性?

link

C++的调用惯例(简单一点C++函数调用的压栈过程)

link

C++的四种强制转换

static_cast
dynamic_cast
const_cast
reinterpret_cast
link

C++中将临时变量作为返回值的时候的处理过程(栈上的内存分配、拷贝过程)

link

C++的异常处理

link

volatile关键字

volatile
禁止编译器优化,每次从内存中读取数据,有利于线程安全。
volatile用在如下的几个地方:

  1. 中断服务程序中修改的供其它程序检测的变量需要加volatile;
  2. 多任务环境下各任务间共享的标志应该加volatile;
  3. 存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;

优化程序的几种方法

优化方法

public,protected和private访问权限和继承

link
访问范围:

private: 只能由该类中的函数、其友元函数访问,不能被任何其他访问,该类的对象也不能访问.
protected: 可以被该类中的函数、子类的函数、以及其友元函数访问,但不能被该类的对象访问
public: 可以被该类中的函数、子类的函数、其友元函数访问,也可以由该类的对象访问

注:友元函数包括两种:设为友元的全局函数,设为友元类中的成员函数
父类与其直接子类的访问关系如上,无论是哪种继承方式(private继承、protected继承、public继承)。

对于三种继承关系的不同:
public继承:public继承后,从父类继承来的函数属性不变(private、public、protected属性不变,)。
private继承:private继承后,从父类继承来的函数属性都变为private
protected继承:protected继承后,从父类继承过来的函数,public、protected属性变为protected,private还是private。

class和struct的区别

class默认为private,struct默认为public

decltype()和auto

链接
1, auto的作用
一般来说, 在把一个表达式或者函数的返回值赋给一个对象的时候, 我们必须要知道这个表达式的返回类型, 但是有的时候我们很难或者无法知道这个表达式或者函数的返回类型. 这个时候, 我们就可以使用auto关键字来让编译器帮助我们分析表达式或者函数所属的类型.
2, auto和const
auto会忽略掉顶层const, 保留底层const.
3, auto和引用
① 如果表达式是引用类型, 那么auto的类型是这个引用的对象的类型.
② 如果要声明一个引用, 就必须要加上&, 如果要声明为一个指针, 既可以加上也可以不加

1, decltype的作用
decltype只是为了推断出表达式的类型而不用这个表达式的值来初始化对象.
2, decltype和const
不论是顶层const还是底层const, decltype都会保留
3, decltype和引用
① 如果表达式是引用类型, 那么decltype的类型也是引用
② 如果表达式是引用类型, 但是想要得到这个引用所指向的类型, 需要修改表达式:
③ 对指针的解引用操作返回的是引用类型
④ 如果一个表达式的类型不是引用, 但是我们需要推断出引用, 那么可以加上一对括号, 就变成了引用类型了

inline和宏定义的区别

内联和宏
(1)内联函数在编译时展开,宏在预编译时展开;
(2)内联函数直接嵌入到目标代码中,宏是简单的做文本替换;
(3)内联函数有类型检测、语法判断等功能,而宏没有;
(4)inline函数是函数,宏不是;
(5)宏定义时要注意书写(参数要括起来)否则容易出现歧义,内联函数不会产生歧义;

C++和C的类型安全

类型安全
c的类型安全只在局部上下文中体现,c只在结构体指针转换上会报错,并且有隐式转换,malloc和new相比也并不是类型安全的。
c++类型安全:
(1)操作符new返回的指针类型严格与对象匹配,而不是void*;
(2)C中很多以void*为参数的函数可以改写为C++模板函数,而模板是支持类型检查的;
(3)引入const关键字代替#define constants,它是有类型、有作用域的,而#define constants只是简单的文本替换;
(4)一些#define宏可被改写为inline函数,结合函数的重载,可在类型安全的前提下支持多种类型,当然改写为模板也能保证类型安全;
(5)C++提供了dynamic_cast关键字,使得转换过程更加安全,因为dynamic_cast比static_cast涉及更多具体的类型检查。

右值和右值引用

右值引用
1.通过右值引用的声明,右值又“重获新生”,其生命周期与右值引用类型变量的生命周期一样长,只要该变量还活着,该右值临时量将会一直存活下去。
2.常量左值引用是一个“万能”的引用类型,可以接受左值、右值、常量左值和常量右值,常量左值引用只能接受左值。
3.意思是右值引用类型的变量可能是左值也可能是右值。
4.T&& t在发生自动类型推断的时候,它是未定的引用类型(universal references),如果被一个左值初始化,它就是一个左值;如果它被一个右值初始化,它就是一个右值,它是左值还是右值取决于它的初始化。
5.以移动构造函数替代拷贝构造函数的深拷贝可以节省深拷贝的构造析构开销,把左值转为右值引用,转移资源所有权
6.完美转发:在函数模板中,完全依照模板的参数的类型(即保持参数的左值、右值特征),将参数传递给函数模板中调用的另外一个函数。C++11中的std::forward正是做这个事情的,他会按照参数的实际类型进行转发。

实现unique_ptr

unique_ptr

你可能感兴趣的:(面经)