CPP面试题(2)

1. C++引用的概念

  1. 引用(Reference)是 C++ 相对于 C 语言的一个扩充。引用可以看做是数据的一个别名,通过这个别名和原来的名字都能够找到这份数据。引用类似于 Windows 中的快捷方式,一个可执行程序可以有多个快捷方式,通过这些快捷方式和可执行程序本身都能够运行程序;引用还类似于人的绰号(笔名),使用绰号(笔名)和本名都能表示一个人;
  2. 基本语法 typename & ref = varname;
  3. 使用引用的注意事项:
    1. 引用必须引用合法的内存空间;
    2. 引用在定义时必须初始化;
    3. 引用一旦初始化后,就不能再引用其它数据;
    4. 引用在定义时需要加上 &,在使用时不能加 &,使用时加 & 表示取地址;
    5. 函数中不要返回局部变量的引用;
  4. 引用的本质是指针,低层的实现还是指针;

2. 左值、右值、左值引用、右值引用、右值引用的使用场景

  1. 左值:在 C++ 中可以取地址的、有名字的就是左值 int a = 10;// 其中 a 就是左值;
  2. 右值:不能取地址的、没有名字的就是右值 int a = 10;// 其中 10 就是右值;
  3. 左值引用:左值引用就是对一个左值进行引用。传统的 C++ 引用(现在称为左值引用)使得标识符关联到左值。左值是一个表示数据的表达式(如变量名或解除引用的指针),程序可获取其地址。最初,左值可出现在赋值语句的左边,但修饰符 const 的出现使得可以声明这样的标识符,即不能给它赋值,但可获取其地址:int n; int * pt = new int; const int b = 101; int & rn = n; int & rt = *pt; const int & rb = b; const int & rb = 10;
  4. 右值引用:右值引用就是对一个右值进行引用。C++ 11 新增了右值引用(rvalue reference),这种引用可指向右值(即可出现在赋值表达式右边的值),但不能对其应用地址运算符。右值包括字面常量(C-风格字符串除外,它表示地址)、诸如 x + y 等表达式以及返回值的函数(条件是该函数返回的不是引用),右值引用使用 && 声明: int x = 10; int y = 23; int && r1 = 13; int && r2 = x + y; double && r3 = std::sqrt(2.0);
  5. 右值引用的使用场景:右值引用可以实现移动语义、完美转发;

3. 指针和引用的区别

  1. 定义和性质不同。指针是一种数据类型,用于保存地址类型的数据,而引用可以看成是变量的别名。
    1. 指针定义格式为:数据类型 ;
    2. 而引用的定义格式为:数据类型 &;
  2. 引用不可以为空,当被创建的时候必须初始化,而指针变量可以是空值,在任何时候初始化;
  3. 指针可以有多级,但引用只能是一级;
  4. 引用使用时无需解引用,指针需要解引用;
  5. 指针变量的值可以是 NULL,而引用的值不可以为 NULL;
  6. 指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了;
  7. sizeof 引用得到的是所指向的变量(对象)的大小,而 sizeof 指针得到的是指针变量本身的大小;
  8. 指针作为函数参数传递时传递的是指针变量的值,而引用作为函数参数传递时传递的是实参本身,而不是拷贝副本;
  9. 指针和引用进行++运算意义不一样;

4. 简述一下堆和栈的区别

  1. 管理方式:
    1. 对于栈来讲,是由编译器自动管理,无需手动控制;
    2. 对于堆来说,分配和释放都是由程序员控制的;
  2. 空间大小:
    1. 总体来说,栈的空间是要小于堆的。堆内存几乎是没有什么限制的;
    2. 但是对于栈来讲,一般是有一定的空间大小的;
  3. 碎片问题:
    1. 对于堆来讲,由于分配和释放是由程序员控制的(利用new/delete 或 malloc/free),频繁的操作势必会造成内存空间的不连续,从而造成大量的内存碎片,使程序效率降低;
    2. 对于栈来讲,则不会存在这个问题,因为栈是先进后出的数据结构,在某一数据弹出之前,它之前的所有数据都已经弹出;
  4. 生长方向:
    1. 对于堆来讲,生长方向是向上的,也就是沿着内存地址增加的方向;
    2. 对于栈来讲,它的生长方式是向下的,也就是沿着内存地址减小的方向增长;
  5. 分配方式:
    1. 堆都是动态分配的,没有静态分配的堆;
    2. 栈有两种分配方式:静态分配和动态分配;
      1. 静态分配是编译器完成的,比如局部变量的分配;
      2. 动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,它的动态分配是由编译器实现的,无需我们手工实现;
  6. 分配效率:
    1. 栈是机器系统提供的数据结构,计算机会在底层对栈提供支持,分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率很高;
    2. 堆则是 C/C++ 函数提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率要比栈底的多;

5. 浅拷贝和深拷贝

  1. 浅拷贝 浅拷贝又称为值拷贝,将源对象的值拷贝到目标对象中,如果对象中有某个成员是指针类型数据,并且是在堆区创建,则使用浅拷贝仅仅拷贝的是这个指针变量的值,也就是在目标对象中该指针类型数据和源对象中的该成员指向的是同一块堆空间。这样会带来一个问题,就是在析构函数中释放该堆区数据,会被释放多次。默认的拷贝构造函数和默认的赋值运算符重载函数都是浅拷贝;
  2. 深拷贝 深拷贝在拷贝的时候先开辟出和源对象大小一样的空间,然后将源对象里的内容拷贝到目标对象中去,这样指针成员就指向了不同的内存位置。并且里面的内容是一样的,这样不但达到了拷贝的目的,还不会出现问题,两个对象先后去调用析构函数,分别释放自己指针成员所指向的内存。即为每次增加一个指针,便申请一块新的内存,并让这个指针指向新的内存,深拷贝情况下,不会出现重复释放同一块内存的错误;

6. 动态库静态库的区别和优缺点

区别

  1. 命令方式不同:
    1. 静态库命名 Linux : libxxx.a lib : 前缀(固定) xxx : 库的名字,自己起 .a : 后缀(固定) Windows : libxxx.lib;
    2. 动态库命名 Linux : libxxx.so lib : 前缀(固定) xxx : 库的名字,自己起 .so : 后缀(固定) Windows : libxxx.dll;
  2. 链接时间和方式不同:
    1. 静态库的链接是将整个函数库的所有数据在编译时都整合进了目标代码;
    2. 动态库的链接是程序执行到哪个函数链接哪个函数的库;

优缺点

  1. 静态库:
    1. 优点:发布程序时无需提供静态库,移植方便,运行速度相对快些;
    2. 缺点:静态链接生成的可执行文件体积较大,消耗内存,如果所使用的静态库发生更新改变,程序必须重新编译,更新麻烦;
  2. 动态库:
    1. 优点:更加节省内存并减少页面交换,动态库改变并不影响使用的程序,动态函数库升级比较方便;
    2. 缺点:发布程序时需要提供动态库;