Cpp高频问题整理

参考资料

  • [] 指针和引用的区别

    1. 本质上:引用是别名,指针是地址
    2. 现象上:指针可以在运行时改变所指向的值,但引用初始化后不能当做其他变量的别名了。
    3. 内存上:程序为指针变量分配区域,但不为引用分配内存区域,因为引用必须初始化时指向一个存在的对象,引用不能为空值。
    4. 编译时:程序在编译时分别将指针和引用添加到符号表上,符号表上记录的是变量名及变量所对应地址。指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值。符号表生成后就不会再改,因此指针可以改变指向的对象(指针变量中的值可以改),而引用对象不能改。这是使用指针不安全而使用引用安全的主要原因。从某种意义上来说引用可以被认为是不能改变的指针
    5. 级数:从理论上来说,对于指针没有级数限制,但是引用只有一级。

      1
      2
      3
      4
      int** p1; // 合法。指向指针的指针
      int*& p2; // 合法。指向指针的引用
      int&* p3; // 非法。指向引用的指针是非法的
      int&& p4; // 非法。指向引用的引用是非法的
    6. 效率上:
      其实两者的效率是一致的,因为在底层中,指针和引用的参数都指向同一个地址。在高级编程语言中,因为用*传参可能会指向空的地址或者错误的地址,所以要时时判断参数是否为空,导致降低效率。而用&传参数,则参数不可能为空或者错误地址,这也算稍微提升了一些效率。

  • [] 堆和栈的区别

    1. 申请方式不同。栈上有系统自动分配和释放;堆上有程序员自己申请并指明大小;
    2. 栈是向低地址扩展的数据结构,大小很有限;堆是向高地址扩展,是不连续的内存区域,空间相对大且灵活;
    3. 栈由系统分配和释放速度快;堆由程序员控制,一般较慢,且容易产生碎片;
  • [] new和delete是如何实现的,new 与 malloc的异同处

    1. new/new[]和delete/delete[]是操作符;是C++用来实现动态内存管理的操作符;
    2. new/new[] 操作符是用来申请空间的;delete/delete[]操作数是用来释放动态申请出来的空间;
    3. 内置类型:
      如果申请的是内置类型的空间,new和malloc,delete和free基本类似;
      不同之处:
      new在申请空间失败时会抛异常;
      malloc在申请空间失败时会返回NULL;
    4. 自定义类型:
      new的原理:
      调用operator new函数申请空间;
      在申请的空间上执行构造函数,完成对象的构造;
      delete的原理:
      在空间上执行析构函数,完成对象中资源的清理工作;
      调用operator delete函数释放对象的空间;
      new[N]的原理:
      调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请;
      在申请的空间上执行N次构造函数;
      delete[N]的原理:
      在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理;
      调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间;
  • [] C和C++的区别

    1. C语言是一种结构化语言,面向过程,基于算法和数据结构,所考虑的是如何通过一个过程或者函数从输入得到输出;

    2. C++是面向对象,基于类、对象和继承,所考虑的是如何构造一个对象模型,让这个模型能够契合与之对应的问题,通过获取对象的状态信息得到输出或实现过程控制。

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

    1. Java为解释性语言,其运行过程为:程序源代码经过Java编译器编译成字节码,然后由JVM解释执行。而C/C++为编译型语言,源代码经过编译和链接后生成可执行的二进制代码,可直接执行。因此Java的执行速度比C/C++慢,但Java能够跨平台执行,C/C++不能。
    2. Java是纯面向对象语言,所有代码(包括函数、变量)必须在类中实现,除基本数据类型(包括int、float等)外,所有类型都是类。此外,Java语言中不存在全局变量或者全局函数,而C++兼具面向过程和面向对象编程的特点,可以定义全局变量和全局函数。
    3. 与C/C++语言相比,Java语言中没有指针的概念,这有效防止了C/C++语言中操作指针可能引起的系统问题,从而使程序变得更加安全。
    4. 与C++语言相比,Java语言不支持多重继承,但是Java语言引入了接口的概念,可以同时实现多个接口。由于接口也有多态特性,因此Java语言中可以通过实现多个接口来实现与C++语言中多重继承类似的目的。
    5. 在C++语言中,需要开发人员去管理内存的分配(包括申请和释放),而Java语言提供了垃圾回收器来实现垃圾的自动回收,不需要程序显示地管理内存的分配。在C++语言中,通常会把释放资源的代码放到析构函数中,Java语言中虽然没有析构函数,但却引入了一个finalize()方法,当垃圾回收器要释放无用对象的内存时,会首先调用该对象的finalize()方法,因此,开发人员不需要关心也不需要知道对象所占的内存空间何时被释放。
  • [] Struct和class的区别
    面向过程的编程认为,数据和数据操作是分开的。然而当struct进入面向对象的c++时,其特性也有了新发展,在c++中就能运行,因为在c++中认为数据和数据对象是一个整体,不应该分开,这就是struct在c和c++两个时代的差别。
    在C++中struct得到了很大的扩充:

    1. struct可以包括成员函数
    2. struct可以实现继承
    3. struct可以实现多态

    区别主要在于:

    1. 默认的访问权限,class默认是private而struct是public。
    2. 默认的继承访问权。class默认的是private,strcut默认的是public。
    3. “class”这个关键字还用于定义模板参数,就像“typename”。但关键字“struct”不用于定义模板参数。
  • [] define 和const的区别(编译阶段、安全性、内存占用等)

    • const定义的常数是变量,也带类型,#define定义的只是个常数,不带类型。

    • define在预处理阶段进行替换;const在编译时确定其值;

    • define只进行简单的字符串替换,没有类型检查;而const由对应的数据类型,编译时会进行类型检查。

    • define定义的变量在预处理后存放在代码段的空间里,const修饰的变量占用数据段空间。

    • 宏定义的作用范围仅限于当前文件,const对象使用extern声明后可以在多个文件之间共享。

  • [] 在C++中const和static的用法(定义,用途)
    • const
      • 修饰变量,说明该变量不可被改变;
      • 修饰指针,分为指向常量的指针和指针常量
      • 常量引用,经常用于形参类型,既避免了拷贝,又避免了函数对值的修改
      • 修饰成员函数,说明该成员函数内不能修改成员变量。
    • static
      • 修饰普通变量,修改变量的存储区域和生命周期,使变量存储在静态区,在main函数运行前就分配了空间,如果有初始值就用初始值初始化它,如果没有初始值系统用默认值初始化它。
      • 修饰普通函数,表明函数的作用范围,仅在定义该函数的文件内才能使用。在多人开发项目时,为了防止与他人命名空间里的函数重名,可以将函数定位为static。
      • 修饰成员变量,修饰成员变量使所有的对象只保存一个该变量,而且不需要生成对象就可以访问该成员。
      • 修饰成员函数,修饰成员函数使得不需要生成对象就可以访问该函数,但是在static函数内不能访问非静态成员。
  • [] const和static在类中使用的注意事项(定义、初始化和使用)

    • static数据成员的初始化必须在类外初始化;const数据成员必须在构造函数初始化列表中进行初始化,因为类成员不能声明初始化,同时const成员不能在成员函数中赋值,因为const不能被改变。

    • const成员函数不能改变任何一个数据成员的值,但是可以改变static成员的值。

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

    • const和非const函数是可以构成重载的,但是仅凭返回值是否为const是无法构成重载的。

    • 若将成员函数声明为const,则该函数不允许修改类的数据成员。值得注意的是,把一个成员函数声明为const可以保证这个成员函数不修改数据成员,但是,如果据成员是指针,则const成员函数并不能保证不修改指针指向的对象,编译器不会把这种修改检测为错误。

    • const成员函数可以被具有相同参数列表的非const成员函数重载。

    • const成员函数可以访问非const对象的非const数据成员、const数据成员,也可以访问const对象内的所有数据成员,但不能修改任何变量;非const成员函数可以访问非const对象的数据成员、const数据成员,但不可以访问const对象的任意数据成员。

    • const成员函数只能用于非静态成员函数,不能用于静态成员函数

  • [] C++的顶层const和底层const
    • 底层const是代表对象本身是一个常量(不可改变);
    • 顶层const是代表指针的值是一个常量,而指针的值(即对象的地址)的内容可以改变(指向的不可改变);
  • [] final和override关键字

    • final限定某个类不能被继承或某个虚函数不能被重写。如果修饰函数只能修饰虚函数,且要放到类或函数后面。
    • override关键字保证了派生类中声明重写的函数与基类虚函数有相同的签名,可避免一些拼写错误,如加了此关键字但基类中并不存在相同的函数就会报错,也可以防止把本来想重写的虚函数声明成了重载。
  • [] 拷贝初始化和直接初始化,初始化和赋值的区别

    • 参考资料
    • 直接初始化:编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数。
    • 拷贝初始化:复制初始化首先使用指定构造函数创建一个临时对象,然后用复制构造函数将那个临时对象复制到正在创建的对象
  • [] extern “C”的用法

    • 首先,extern是C/C++语言中表明函数和全局变量作用范围的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。
    • 通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。extern “C”是连接申明(linkage declaration),被extern “C”修饰的变量和函数是按照C语言方式编译和连接的。作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:void foo( int x, int y);该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。
    • 所以,可以用一句话概括extern “C”这个声明的真实目的:解决名字匹配问题,实现C++与C的混合编程。
  • [] 模板函数和模板类的特例化
  • [] C++的STL源码(这个系列也很重要,建议侯捷老师的STL源码剖析书籍与视频),其中包括内存池机制,各种容器的底层实现机制,算法的实现原理等)
  • [] STL源码中的hashtable的实现
  • [] STL中unordered_map和map的区别和应用场景
  • [] STL中vector的实现
  • [] STL容器的几种迭代器以及对应的容器(输入迭代器,输出迭代器,前向迭代器,双向迭代器,随机访问迭代器)
  • [] STL中的traits技法
  • [] vector使用的注意点及其原因,频繁对vector调用push_back()对性能的影响和原因。
  • [] C++中的重载和重写的区别

    • 重载从overload翻译过来,是指同一可访问区内被声明的几个具有不同参数列表(参数的类型,个数,顺序不同)的同名函数,根据参数列表确定调用哪个函数,重载不关心函数返回类型。
    • 重写翻译自override,是指派生类中存在重新定义的函数。其函数名,参数列表,返回值类型,所有都必须同基类中被重写的函数一致。只有函数体不同(花括号内),派生类调用时会调用派生类的重写函数,不会调用被重写函数。重写的基类中被重写的函数必须有virtual修饰。
  • [] C++内存管理,内存池技术(热门问题),与csapp中几种内存分配方式对比学习加深理解

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

    • 封装:使用函数指针把属性与方法封装到结构体中
      • 把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。关键字:public, protected, private。不写默认为 private
      • public 成员:可以被任意实体访问
      • protected 成员:只允许被子类及本类的成员函数访问
      • private 成员:只允许被本类的成员函数、友元类或友元函数访问
    • 继承:结构体嵌套
      • 基类(父类)——> 派生类(子类)
    • 多态:父类与子类方法的函数指针不同
      • 多态,即多种状态(形态)。简单来说,我们可以将多态定义为消息以多种形式显示的能力。
      • 多态是以封装和继承为基础的。
      • C++ 多态分类及实现:
        • 重载多态(Ad-hoc Polymorphism,编译期):函数重载、运算符重载
        • 子类型多态(Subtype Polymorphism,运行期):虚函数
        • 参数多态性(Parametric Polymorphism,编译期):类模板、函数模板
        • 强制多态(Coercion Polymorphism,编译期/运行期):基本类型转换、自定义类型转换
  • [] C++多态的实现

    • 见上
  • [] C++虚函数相关(虚函数表,虚函数指针),虚函数的实现原理(包括单一继承,多重继承等)(拓展问题:为什么基类指针指向派生类对象时可以调用派生类成员函数,基类的虚函数存放在内存的什么区,虚函数表指针vptr的初始化时间)
    • 虚函数表指针vptr的初始化时间:在所有基类构造函数之后,但又在自身构造函数或初始化列表之前
    • 虚函数存放在内存的全局区
  • [] C++中类的数据成员和成员函数内存分布情况
  • [] this指针
  • [] 析构函数一般写成虚函数的原因
  • [] 构造函数、拷贝构造函数和赋值操作符的区别
  • [] 构造函数声明为explicit
  • [] 构造函数为什么一般不定义为虚函数
  • [] 构造函数的几种关键字(default delete 0)
  • [] 构造函数或者析构函数中调用虚函数会怎样
  • [] 纯虚函数
  • [] 静态类型和动态类型,静态绑定和动态绑定的介绍
  • [] 引用是否能实现动态绑定,为什么引用可以实现
  • [] 深拷贝和浅拷贝的区别(举例说明深拷贝的安全性)
  • [] 对象复用的了解,零拷贝的了解
  • [] 介绍C++所有的构造函数
  • [] 什么情况下会调用拷贝构造函数(三种情况)
  • [] 结构体内存对齐方式和为什么要进行内存对齐?
  • [] 内存泄露的定义,如何检测与避免?
  • [] 遇到coredump要怎么调试
  • [] 内存检查工具的了解
  • [] 模板的用法与适用场景
  • [] 成员初始化列表的概念,为什么用成员初始化列表会快一些(性能优势)?
  • [] 用过C++ 11吗,知道C++ 11哪些新特性?
  • [] C++的调用惯例(简单一点C++函数调用的压栈过程)
  • [] C++的四种强制转换
  • [] C++中将临时变量作为返回值的时候的处理过程(栈上的内存分配、拷贝过程)
  • [] C++的异常处理
  • [] volatile关键字
  • [] 优化程序的几种方法
  • [] public,protected和private访问权限和继承
  • [] class和struct的区别
    • class默认private访问,而struct是public
  • [] decltype()和auto
  • [] inline和宏定义的区别
  • [] C++和C的类型安全