C++面试常见问题汇总

 

1.      指针和引用的区别

    1. 可以有多级指针,但是没有多级引用
    2. 指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。
    3. 引用不可以为空,当被创建的时候,必须初始化,而指针可以是空值,可以在任何时候被初始化。
    4. 可以有const指针,但是没有const引用;
    5. 指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了
    6. 指针和引用的自增(++)运算意义不一样;

2.      堆和栈的区别

    1. 堆空间的内存是动态分配的,一般存放对象,并且需要手动释放内存。栈空间的内存是由系统自动分配,一般存放局部变量,比如对象的地址等值,不需要程序员对这块内存进行管理,
    2. 栈是运行时的单位,而堆是存储的单位。堆中的共享常量和缓存 。栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;堆解决的是数据存储的问题,即数据怎么放、放在哪儿。
    3. 栈因为是运行单位,因此里面存储的信息都是跟当前线程(或程序)相关信息的。包括局部变量、程序运行状态、方法返回值等等;而堆只负责存储对象信息。

3.      malloc/free与new/delete异同点,new和delete是如何实现的,new 与 malloc的异同处

相同点:

  1. malloc/free与new/delete都可以用于申请动态内存和释放内存,他们申请的空间都在堆上分配。

不同点:

  • 操作对象不同

malloc/free是C++/C语言的标准库文件,new/delete是C++的运算符;

对非内部数据对象,malloc/free无法满足动态对象要求。对象在创建时要自动执行构造函数,对象消亡之前要自动执行析构函数,而malloc/free是库函数,不是运算符,故不在编译器控制权限之内,不能够将执行构造函数和析构函数强加于malloc/free身上。而由于new/delete是C++语言,能够完成动态内存分配和初始化工作,并能够完成清理与释放内存工作,即能够自动执行构造函数和析构函数;

  • 用法不同

malloc分配内存空间前需要计算分配内存大小;而new能够自动分配内存空间;

malloc是底层函数,其函数返回值类型为void *;而new运算符调用无参构造函数,故返回值为对应对象的指针;

malloc函数类型不是安全的,编译器不对其进行类型转换、类型安全的相关检查。malloc申请空间后,不会对其初始化,要单独初始化;而new类型是安全的,因为它内置了sizeof、类型转换和类型安全检查功能,且在创建对象时,就完成了初始化工作,一般初始化调用无参构造函数;

operator new对应于malloc,且operator new可以重载,可以自定义内存分配策略,甚至不做内存分配,甚至分配到非内存设备上;但malloc不能。

free只进行释放空间;而delete则释放空间的同时调用析构函数。此外delete使用是注意释放数组的方法为delete []数组名。

  • new先调用malloc,后调用构造函数,delete先调用析构后调用free;

4.      C和C++的区别?面向对象编程的特点?

"面向过程的模型",按这种模型编写的程序以一系列的线性步骤(代码)为特征,可被理解为作用于数据的代码.

"面向对象的模型",按这种模型编写的程序围绕着程序的数据(对象)和针对该对象而严格定义的接口来组织程序,它的特点是数据控制代码的访问.通过把控制权转移到数据上,面向对象的模型在组织方式上有:抽象,封装,继承和多态的好处.

 (数据抽象)如一棵树的高度、一本书的页数等。不过,有些特征并不能用这样的办法来表示,(过程抽象)如一只狗会跑。用数据来表示这个跑的动作是做不到的,因为这是一个动作过程,而不是一种状态。

多态指同样的方法在不同的对象中有不同的表现方式。多态实现的方法,

静态多态----函数重载  泛型编程

动态多态----虚函数

5.      Struct和class的区别

    1. 默认的继承与访问权限:struct默认是公有继承(public),class默认是私有继承(private)
    2. 在C++中对struct的功能进行了扩展,struct可以被继承,可以包含成员函数,也可以实现多态,当用大括号对其进行初始化需要注意:

当struct和class中都定义了构造函数,就不能使用大括号对其进行初始化

若没有定义构造函数,struct可以使用{ }进行初始化,而只有当class的所有数据成员及函数为public时,可以使用{ }进行初始化

所以struct更适合看成是一个数据结构的实现体,class更适合看成是一个对象的实现体。

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

    1.  编译器处理方式不同

define宏是在预处理阶段展开。

const常量是编译运行阶段使用。

  1. 类型和安全检查不同

  define宏没有类型,不做任何类型检查,仅仅是展开。

  const常量有具体的类型,在编译阶段会执行类型检查。

  1. 存储方式不同

  define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存。

  const常量会在内存中分配(可以是堆中也可以是栈中)。

  1. const 可以节省空间,避免不必要的内存分配。 例如:

#define PI 3.14159 //常量宏

const doulbe Pi=3.14159; //此时并未将Pi放入ROM中 ......

double i=Pi; //此时为Pi分配内存,以后不再分配!

double I=PI; //编译期间进行宏替换,分配内存

double j=Pi; //没有内存分配

double J=PI; //再进行宏替换,又一次分配内存!

const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而 #define定义的常量在内存中有若干个拷贝。

  1. 提高了效率。

编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高

7.      C/C++中static和const关键字的作用总结

  • static 关键字至少有下列 n 个作用:

  (1)函数体内 static 变量的作用范围为该函数体,不同于auto 变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;

  (2)在模块内的 static 全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;

  (3)在模块内的 static 函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;

  (4)在类中的 static 成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;

(5)在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因而只能访问类的 static 成员变量。

  • const 关键字至少有下列 n 个作用

(1)欲阻止一个变量被改变,可以使用 const 关键字。在定义该 const 变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;

(2)对指针来说,可以指定指针本身为 const,也可以指定指针所指的数据为 const,或二者同时指定为 const;

(3)在一个函数声明中,const 可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;

(4)对于类的成员函数,若指定其为 const 类型,则表明其是一个常函数,不能修改类的成员变量;

8.      C++的顶层const和底层const

顶层const表示指针本身是个常量,底层const表示指针所指的对象是一个常量。

Int *const p1 = &i;  不能改变P1的值,这是一个顶层const

Const int *p2 = &i;  允许改变P2的值,这是一个底层const

用实参初始化形参时会忽略到顶层const。换句话说,形参的顶层const被忽略掉了

9.      final和override关键字

  • final限定某个类不能被继承或某个虚函数不能被重写。如果修饰函数只能修饰虚函数,且要写到类或函数后面。

class Base

{

    virtual void foo();

};

class A : Base

{

    void foo() final; // foo 被override并且是最后一个override,在其子类中不可以重写

    void bar() final; // Error: 父类中没有 bar虚函数可以被重写或final

};

class B final : A // 指明B是不可以被继承的

{

    void foo() override; // Error: 在A中已经被final了

};

class C : B // Error: B is final

{

};

  • override关键字保证了派生类中声明重写的函数与基类虚函数有相同的签名,可避免一些拼写错误,如加了此关键字但基类中并不存在相同的函数就会报错,也可以防止把本来想重写的虚函数声明成了重载。同时在阅读代码时如果看到函数声明后加了此关键字就能立马知道此函数是重写了基类虚函数。保证重写虚函数的正确性的同时也提高了代码可读性。

class A

{

    virtual void foo();

};

class B :A

{

    virtual void foo() override;    //OK

    virtual void f00() override;     //Error ,因为基类中没有void f00()函数

};

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

  • 对象不存在,且没用别的对象来初始化,调用构造函数
  • 对象不存在,且用别的对象来初始化,调用拷贝构造
  • 对象存在,用别的对象给他赋值,就是赋值函数

https://www.cnblogs.com/cposture/p/4925736.html

https://blog.csdn.net/ljianhui/article/details/9245661

https://blog.csdn.net/splendid7/article/details/81537706

https://www.cnblogs.com/qingergege/p/7607089.html(移动构造函数)

(1)什么是拷贝初始化(也称为复制初始化):将一个已有的对象拷贝到正在创建的对象,如果需要的话还需要进行类型转换。拷贝初始化发生在下列情况:

1.使用赋值运算符定义变量

2.将对象作为实参传递给一个非引用类型的形参

3.将一个返回类型为非引用类型的函数返回一个对象

4.用花括号列表初始化一个数组中的元素或一个聚合类中的成员

(2)什么是直接初始化:在对象初始化时,通过括号给对象提供一定的参数,并且要求编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数

ClassTest ct2 = "ab";//复制初始化

ClassTest ct2("ab");//直接初始化
  • 直接初始化和拷贝初始化效率基本一样,因为在底层的实现基本一样,所以将拷贝初始化改为直接初始化效率提高不大
  • 拷贝初始化什么时候使用拷贝构造函数:???
  1. 赋值表达式右边是一个对象
  2. 直接初始化时,括号内的参数是一个对象
  3. 用花括号列表初始化一个数组中的元素或一个聚合类中的成员
  4. 将一个返回类型为引用类型的函数返回一个对象
  5. 形参为非引用类型的函数,其中是将实参拷贝到临时对象
  • 什么时候使用到拷贝赋值运算符:???
  1. 赋值表达式右边是一个左值对象(如果需要,可以调用构造函数类型转换,生成一个临时对象)
  2. 当赋值表达式右边是一个右值对象,且没有定义移动赋值运算符函数
  • 什么时候使用移动赋值运算符:???
  1. 当赋值表达式右边是一个右值对象,且定义了移动赋值运算符函数

11.  extern "C"的用法

为了能够正确的在C++代码中调用C语言的代码;在程序中加上extern "C"后,相当于告诉编译器这部分代码是C语言写的,因此要按照C语言进行编译,而不是C++;

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

 Typename关键字 告诉编译把一个特殊的名字解释成一个类型,类模板时可以换成class

  template<typename T> void swap(T& t1, T& t2)

{

T tmpT; tmpT = t1;

t1 = t2; t2 = tmpT;

}

#include <stdio.h> #include "method.h"

int main()

{

//模板方法 int num1 = 1, num2 = 2;

swap<int>(num1, num2);

   printf("num1:%d, num2:%d\n", num1, num2);

return 0;

  }

13.  C++STL源码

STL是一个c++里面非常强大的库,c11引进的,里面封装例如容器,泛型算法等

14.  STL源码中的hashtable的实现

hash_table是STL中hash_map 和 hash_set 的内部数据结构,hash_table的插入/删除/查找的时间复杂度都为O(1),是查找速度最快的一种数据结构,但是hash_table中的数据是无序的,一般也只有在数据不需要排序,只需要满足快速查找/插入/删除的时候使用hash_table。hash_table的扩展是将原hash_table中的数据摘下来插入到一个临时的hash_table中,因为每个桶都使用list来实现的,因此插入删除都不存在内存copy,所以也是很高效的,最后再将临时hash_table和原来的hash_table(此时已经为空)交换。

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

  • map是一种映射,这种映射是有序的,底层是使用红黑树来完成的,数据通过键值才存储,键是唯一的。
  • unordered_map,是一种无序的,底层是通过hash表来完成的。unordered库使用“桶”来存储元素,散列值相同的被存储在一个桶里。当散列容器中有大量数据时,同一个桶里的数据也会增多,造成访问冲突,降低性能。为了提高散列容器的性能,unordered库会在插入元素是自动增加桶的数量,不需要用户指定。每个桶都是用list来完成的。

map

优点:

● 有序性,这是map结构最大的优点,其元素的有序性在很多应用中都会简化很多的操作

● 红黑树,内部实现一个红黑书使得map的很多操作在lgn的时间复杂度下就可以实现,因此效率非常的高

缺点:

● 空间占用率高,因为map内部实现了红黑树,虽然提高了运行效率,但是因为每一个节点都需要额外保存父节点,孩子节点以及红/黑性质,使得每一个节点都占用大量的空间

● 适用处,对于那些有顺序要求的问题,用map会更高效一些

unordered_map

优点

● 因为内部实现了哈希表,因此其查找速度非常的快

缺点

● 哈希表的建立比较耗费时间

● 适用处,对于查找问题,unordered_map会更加高效一些,因此遇到查找问题,常会考虑一下用unordered_map

16.  STL中vector的实现

注意两个点:

1.vector有备用空间,当备用空间不够的时候,会重新开辟原空间两倍的空间进行重写分配。

2.vector支持随机的存取,但是最好是选择从末尾插入,因为从中间插入会导致元素的移动,带来了性能的开销。

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

  • 顺序容器:vector,deque是随机访问迭代器;list是双向迭代器
  • 容器适配器:stack,queue,priority_queue没有迭代器
  • 关联容器:set,map,multiset,multimap是双向迭代器
  • unordered_set,unordered_map,unordered_multiset,unordered_multimap是前向迭代器
  1. STL中的traits技法???

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

    1. 在一个vector的尾部之外的任何位置添加元素,都需要重新移动元素。
    2. 而且,向一个vector添加元素可能引起整个对象存储空间的重新分配。重新分配一个对象的存储空间需要分配新的内存,并将元素从旧的空间移到新的空间(因为vector在内存中的地址是连续的,所以加入一个可能造成地址不够)

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

  • 重定义发生在继承中,重新定义了一个函数;重写(虚函数多态关键):与重定义差不多,只是重写的是虚函数;
  • 重载是指发生在同一个类中,名称相同,但是参数个数或者类型不同。

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

c++的内存管理延续c语言的内存管理,但是也增加了其他的,例如智能指针,除了常见的堆栈的内存管理之外,c++支持智能指针,智能指针的对象进行赋值拷贝等操作的时候,每个智能指针都有一个关联的计数器,该计数器记录共享该对象的指针个数,当最后一个指针被销毁的时候,计数器为0,会自动调用析构函数来销毁函数。

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

继承,多态,封装。

  • 继承:某些相似的特性,可以从一个类继承到另一个类,类似生活中的继承,例如有个所有的汽车都有4个轮子,那么我们在父类中定义4个轮子,通过继承获得4个轮子的功能,不用再类里面再去定义这4个轮子的功能。。
  • 多态:多态和虚函数。多态指的相同的功能,不同的状态,多态在面向对象c++里面是通过重载和覆盖来完成的,覆盖在c++里面通过虚函数来完成的。例如鸭子的例子,所有的鸭子都有颜色,我们可以将这个颜色设置成为一个虚函数,通过继承子类对虚函数进行覆盖,不同子类中有各自的颜色,也就是有各自不同的鸭子颜色,这就是多态的典型表现之一
  • 封装:将很多有相似特性的内容封装在一个类中,例如学生的成绩学号、课程这些可以封装在同一个类中。

23.  C++多态的实现

多态通过覆盖和重载来完成。

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

虚函数分为两种,纯虚函数和虚函数,纯虚函数适用于抽象基类,不需要定义,类似一种接口,是多态的典型处理方式。

一个类如果定义了虚函数,那么编译器会自动为它加上一个虚函数表,并提供一个指向虚函数表的指针,子类通过继承,可以覆盖父类的虚函数,当用户调用虚函数的时候,会调用指针,去虚函数表中找匹配的虚函数,如果当前对象有覆盖的虚函数,则去执行覆盖的虚函数,否则执行父类的虚函数。

 

 

26.  this指针

    1. 一个对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果。
    2. 全局函数,静态函数都不能使用this。静态函数表示了整个类范围意义上的信息,而this指针却实实在在的对应一个对象
    3. this在成员函数的开始执行前构造的,在成员的执行结束后清除
    4. this指针只能在一个类的成员函数中调用,它表示当前对象的地址

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

delete父类指针时,可以调用对象真正的析构函数。否则可能会出错,如子类中有指针类型,并且申请了内存,这时就会造成内存泄漏。

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

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

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

  • 对象都还没有实例化,怎么去找虚表

三个原因

1.虚函数的作用是什么?是实现部分或默认的功能,而且该功能可以被子类所修改。如果父类的构造函数设置成虚函数,那么子类的构造函数会直接覆盖掉父类的构造函数。而父类的构造函数就失去了一些初始化的功能。这与子类的构造需要先完成父类的构造的流程相违背了。而这个后果会相当严重。

2.虚函数的调用是需要通过“虚函数表”来进行的,而虚函数表也需要在对象实例化之后才能够进行调用。在构造对象的过程中,还没有为“虚函数表”分配内存。所以,这个调用也是违背先实例化后调用的准则。

3.虚函数的调用是由父类指针进行完成的,而对象的构造则是由编译器完成的,由于在创建一个对象的过程中,涉及到资源的创建,类型的确定,而这些是无法在运行过程中确定的,需要在编译的过程中就确定下来。而多态是在运行过程中体现出来的,所以是不能够通过虚函数来创建构造函数的,与实例化的次序不同也有关系。

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

    1. = default:将拷贝控制成员定义为=default显式要求编译器生成合成的版本
    2. = delete:将拷贝构造函数和拷贝赋值运算符定义删除的函数,阻止拷贝(析构函数不能是删除的函数 C++Primer P450)
    3. = 0:将虚函数定义为纯虚函数(纯虚函数无需定义,= 0只能出现在类内部虚函数的声明语句处;当然,也可以为纯虚函数提供定义,不过函数体必须定义在类的外部)

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

结果上来看和调用普通函数无异。即基类调用基类的虚函数,派生类调用派生类的虚函数。

不要在构造函数和析构函数内调用虚函数。假如你定义了一个对象,首先调用的是基类的构造函数,而此时基类构造函数内的虚函数不是调用的是此对象类的对应函数,是基类的对应函数,与我们的预期不一样。

 

 

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

 1、对象的静态类型(static type):就是它在程序中被声明时所采用的类型(或理解为类型指针或引用的字面类型),在编译期确定;

  2、对象的动态类型(dynamic type):是指“目前所指对象的类型”(或理解为类型指针或引用的实际类型),在运行期确定;

 这两个概念一般发生在基类和派生类之间。

 

 

  3、静态绑定(statically bound):又名前期绑定(eraly binding),绑定的是静态类型,所对应的函数或属性依赖于对象的静态类型,发生在编译期;

  4、动态绑定(dynamically bound):又名后期绑定(late binding),绑定的是动态类型,所对应的函数或属性依赖于对象的动态类型,发生在运行期;

  比如常见的,virtual函数是动态绑定,non-virtual函数是静态绑定,缺省参数值也是静态绑定。当缺省参数和虚函数一起出现的时候情况有点复杂,极易出错。我们知道,虚函数是动态绑定的,但是为了执行效率,缺省参数是静态绑定的  

https://blog.csdn.net/chgaowei/article/details/6427731

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

可以实现,因为动态绑定是发生在程序运行阶段的,c++中动态绑定是通过对基类的引用或者指针调用虚函数时发生。

引用和指针的静态类型和动态类型可以不一样。

  • 静态类型:变量声明时的类型或表达式生成的类型。编译时已经知道。
  • 动态类型:变量或表达式表示的内存的对象的类型。

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

  • 浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。

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

  • 对象复用指得是设计模式,对象可以采用不同的设计模式达到复用的目的,最常见的就是继承和组合模式了
  • 零拷贝:零拷贝主要的任务就是避免CPU将数据从一块存储拷贝到另外一块存储,主要就是利用各种零拷贝技术,避免让CPU做大量的数据拷贝任务,减少不必要的拷贝,或者让别的组件来做这一类简单的数据传输任务,让CPU解脱出来专注于别的任务。这样就可以让系统资源的利用更加有效。

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

默认,普通,拷贝,赋值:默认无参,普通有参数,其实还有this指针,拷贝就是const class &A,赋值构造函数是对赋值符号的重载,如果有动态生成的对象的话,那么它做赋值,原先的动态空间一定要被释放掉,然后再赋上新的值,所以必须得自己重载=,实现delete。

 

 

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

一个对象去初始化,值传递类,返回值是类

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

保证计算机可以一次读出。

结构体不像数组,结构体中可以存放不同类型的数据,它的大小也不是简单的各个数据成员大小之和,限于读取内存的要求,而是每个成员在内存中的存储都要按照一定偏移量来存储,根据类型的不同,每个成员都要按照一定的对齐数进行对齐存储,最后整个结构体的大小也要按照一定的对齐数进行对齐。

同样的结构体,里面的数据排放顺序不一样,最后占用的内存也不一样大

//平台VS2013下(默认对齐数为8)

//练习一

    struct S1

    {

        char c1;

        int i;

        short s2;

    };

    printf("%d\n", sizeof(struct S1));//12

//练习二

    struct S2

    {

        char c1;

        short s2;

        int i;

    };

printf("%d\n", sizeof(struct S2));//8

 

 

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

  • 内存泄漏(memory leak)是指由于疏忽或错误造成了程序未能释放掉不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
  • 堆内存泄漏 (Heap leak)。对内存指的是程序运行中根据需要分配通过malloc,realloc new等从堆中分配的一块内存,再是完成后必须通过调用对应的 free或者delete 删掉。如果程序的设计的错误导致这部分内存没有被释放,那么此后这块内存将不会被使用,就会产生Heap Leak.
  • 系统资源泄露(Resource Leak).主要指程序使用系统分配的资源比如 Bitmap,handle ,SOCKET等没有使用相应的函数释放掉,导致系统资源的浪费,严重可导致系统效能降低,系统运行不稳定。
  • 解决内存泄漏最有效的办法就是使用智能指针

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

  • shared_ptr共享的智能指针

shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。在最后一个shared_ptr析构的时候,内存才会被释放。

注意事项:

1.不要用一个原始指针初始化多个shared_ptr。

2.不要再函数实参中创建shared_ptr,在调用函数之前先定义以及初始化它。

3.不要将this指针作为shared_ptr返回出来。

4.要避免循环引用。

  • unique_ptr独占的智能指针:

<1>Unique_ptr是一个独占的智能指针,他不允许其他的智能指针共享其内部的指针,不允许通过赋值将一个unique_ptr赋值给另外一个 unique_ptr。

<2>unique_ptr不允许复制,但可以通过函数返回给其他的unique_ptr,还可以通过std::move来转移到其他的unique_ptr,这样它本身就不再 拥有原来指针的所有权了。

<3>如果希望只有一个智能指针管理资源或管理数组就用unique_ptr,如果希望多个智能指针管理同一个资源就用shared_ptr。

  • weak_ptr弱引用的智能指针:

弱引用的智能指针weak_ptr是用来监视shared_ptr的,不会使引用计数加一,它不管理shared_ptr内部的指针,主要是为了监视shared_ptr的生命周期,更像是shared_ptr的一个助手。 weak_ptr没有重载运算符*和->,因为它不共享指针,不能操作资源,主要是为了通过shared_ptr获得资源的监测权,它的构造不会增加引用计数,它的析构不会减少引用计数,纯粹只是作为一个旁观者来监视shared_ptr中关连的资源是否存在。 weak_ptr还可以用来返回this指针和解决循环引用的问题。

  1. 智能指针的循环引用

44.  遇到coredump要怎么调试???

  • core dump又叫核心转储。当程序运行过程中发生异常, 程序异常退出时, 由操作系统把程序当前的内存状况存储在一个core文件中, 叫core dump。

45.  内存检查工具的了解???

linux可以使用开源的Valgrind工具包,包含多个工具:Memcheck常用语检测malloc和new这类的问题,callgrind用来检查函数调用,cachegrind缓存使用,helgrind多线程程序中的竞争。除了valgrind还可以用mtrace这些工具

46.  模板的用法与适用场景

代码可重用,泛型编程,在不知道参数类型下,函数模板和类模板

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

使用初始化list这样可以直接调用成员的构造函数,不用去赋值产生临时变量,所以更快。如果不用,可能会去调用成员的构造函数和赋值构造函数(多出来的)。

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

Ambda表达式, 智能指针 移动,auto,范围for,decltype,array,forward_list,tuple(元组),正则表达式库,随机数库,bitset运算

  • c++11提供的<random>实现了随机数库,它通过随机数引擎(random_number_engines)产生随机数序列,随机数分布类(random-number distribution)使用随机数引擎生成服从特定概率分布的随机数。

https://www.cnblogs.com/byhj/p/4149467.html

  • tuple是一个固定大小的不同类型值的集合,是泛化的std::pair

https://blog.csdn.net/fjb2080/article/details/15809097

tuple<const char*, int>tp = make_tuple(sendPack,nSendSize); //构造一个tuple

这个tuple等价于一个结构体

struct A

{

char* p;

int len;

};

用tuple<const char*, int>tp就可以不用创建这个结构体了,而作用是一样的,是不是更简洁直观了。还有一种方法也可以创建元组,用std::tie,它会创建一个元组的左值引用。

auto tp = return std::tie(1, "aa", 2);

//tp的类型实际是:std::tuple<int&,string&, int&>

再看看如何获取它的值:

const char* data = tp.get<0>(); //获取第一个值

int len = tp.get<1>(); //获取第二个值

  • 引入了emplace_front、emplace、emplace_back,这些操作构造而不是拷贝元素。这些操作分别对应于push_front、insert、push_back。Emplace成员使用这些参数在容器管理的内存空间中直接构造函数。例如,在c的末尾构造一个Sales_data

   c. emplace_back( “666-556695” , 25 , 52.36);///直接使用Sales_data的三个参数就可以了

   c.push_back(“666-556695” , 25 , 52.36); //错误,.push_back不接受3个参数

c.push_back( Sales_data(“666-556695” , 25 , 52.36) ); //正确,创建一个临时对象

 

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

函数调用大家都不陌生,调用者向被调用者传递一些参数,然后执行被调用者的代码,最后被调用者向调用者返回结果。

对于程序,编译器会对其分配一段内存,在逻辑上可以分为代码段,数据段,堆,栈

代码段:保存程序文本,指令指针EIP就是指向代码段,可读可执行不可写

数据段:保存初始化的全局变量和静态变量,可读可写不可执行

BSS(静态内存分配):未初始化的全局变量和静态变量

堆(Heap):动态分配内存,向地址增大的方向增长,可读可写可执行

栈(Stack):存放局部变量,函数参数,当前状态,函数调用信息等,向地址减小的方向增长,非常非常重要,可读可写可执行

程序开始,从main开始,首先将参数压入栈,然后压入函数返回地址,进行函数调用,通过跳转指定进入函数,将函数内部的变量去堆栈上开辟空间,执行函数功能,执行完成,取回函数返回地址,进行下一个函数。

 

 

 

50.  C++的四种强制转换

  • static_cast:可以实现相同类型之间的转换,如:double b ;int  a = static_cast<int>(b);
  • reinterpreter_cast :可以实现不同类型之间的转换,如指针到整型

int* aas = new int[1] ;

aas[0] = 1;

int b = reinterpret_cast<int>(aas);   ////b中的值为aas 指针的地址

  • const_cast :中的类型必须是指针、引用或指向对象成员类型的指针

const int constA = 10;

const int *pConstA = &constA;

int* b = const_cast<int*>(pConstA);

*b = *b + 1;

printf("%d ,%d",constA,*b);/////10  11     可以发现constA中的值并没有发生变化,因为constA是一个常量,若constA没有const的修饰,最后的结果将是11  11

  • dynamic_cast实现的是多态类之间的的转换

对于“向上转换”(即派生类指针或引用类型转换为其基类类型),无论是指针还是引用向上转换都是安全地。

对于“向下转型”有两种情况:

一种是基类指针所指对象是派生类类型的,这种转换是安全的;另一种是基类指针所指对象为基类类型,在这种情况下dynamic_cast在运行时做检查,转换失败,返回结果为0;

 

53.  volatile关键字

volatile关键字是一种限定符用来声明一个对象在程序中可以被语句外的东西修改,比如操作系统、硬件或并发执行线程。

遇到该关键字,编译器不再对该变量的代码进行优化,不再从寄存器中读取变量的值,而是直接从它所在的内存中读取值,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。

一般说来,volatile用在如下的几个地方:

(1)、中断服务程序中修改的供其它程序检测的变量需要加volatile;

(2)、多任务环境下各任务间共享的标志应该加volatile;

(3)、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同

54.  优化程序的几种方法

https://www.cnblogs.com/mini-coconut/p/8511872.html

1、 选择合适的算法和数据结构

2、 使用尽量小的数据类型

3、 减少运算的强度

4、 结构体成员的布局

5、 循环优化

6、 提高CPU的并行性(使用并行代码,把长的代码分解成短的)

7、 循环不变计算(不使用的循环变量,放到外面)

8、 函数优化(内联函数)

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

 

 

不管哪种继承方式,父类的私有成员都不可以访问,只有间接的通过公有成员才能获取到私有成员的值。

protected存在的意义是当我不想向外部暴露某个函数或者成员变量,但是我又想让派生类知道和访问这个成员,就将其用protected标志。

将普通函数添加为友元函数,可以实现在该函数内调用类内的私有与保护成员

在类外定义对象,该对象只能访问类内的公有成员二不能访问其类内的私有与保护成员

类内的成员可以使用类内的所有成员,继承类可以使用父类的保护成员函数

 

56.  decltype()和auto

  • decltype类型指示符

Const int c = 0,&b = c;

Decltype(c) x =3;//x是const int 类型

Decltype(b) y = x;//y是const int&类型;所以定义y时必须初始化

  • auto

使用auto也能在一条语句中声明多个变量,因为一条语句只能有一个基本的数据类型。

Auto定义的变量必须有初值,这样才能推导出类型是什么

57.  inline和宏定义的区别

(1)内联函数在编译时展开,宏在预编译时展开;

(2)内联函数直接嵌入到目标代码中,宏是简单的做文本替换;

(3)内联函数有类型检测、语法判断等功能,而宏没有;

(4)inline函数是函数,宏不是;

(5)宏定义时要注意书写(参数要括起来)否则容易出现歧义,内联函数不会产生歧义;

  1. C++和C的类型安全

59.  调试程序的方法:

归纳法、演绎法、测试法,回溯法,暴力法。设置断点,进入调试,单步运行,进入函数,查看变量。linux gbk,win windbg

60.  类型别名

Typedef  double  wages; //wages是double的同义词

Typedef  wages   base, *p; // base是double的同义词,p是double*的同义词

 

 

 

 

 

 

 

posted on 2019-07-18 16:16  逆光也很美  阅读(1207)  评论(0编辑  收藏  举报

导航