[C++]C++面试知识总结

只谈情不闲聊 提交于 2020-01-08 23:40:04

1.程序运行知识

1.1 内存布局和分配方式

C程序的内存布局如下:

 

  • 静态存储区:存储全局变量和static变量,通常在程序编译期间已经分配好了。
    • BSS段:存放未初始化的static变量和全局变量
    • Data段:存放初始化过的static变量和全局变量
    • Text段:存储程序的二进制代码,程序代码区。  
  • 堆:程序运行时通过malloc申请的内存区存放在堆中,需要使用free来释放该内存空间,生存期在malloc和free之间。
  • 栈:执行函数时,函数的局部变量存储在栈中,执行结束后自动释放该内存区域,栈内存分配运算内置与处理器指令集中。

C++程序的内存布局与C程序布局类似,区别是C++不再区分全局变量和静态变量是否已经初始化,全部存储在静态存储区;另外堆中存放new/delete申请释放的资源,而malloc和free申请的资源存放在自由存储区。

1.2 内存溢出原因

  • 栈溢出:越界访问造成,例如局部变量数组越界访问或者函数内局部变量使用过多,超出了操作系统为该进程分配的栈的大小,还有递归函数层次过多超过了栈大小。
  • 堆溢出:程序申请了资源但忘记释放该资源,造成内存泄露,累积泄露内存过多会造成内存溢出。

1.3 内存泄露和检测

  • C++内存泄漏检测内存泄露是指程序中动态分配了内存,但是在程序结束时没有释放这部分内存,从而造成那一部分内存不可用的情况。

  •  动态内存泄露检测:检查new/delete的资源是否正确释放,检查程序运行期间内存是否一直在增长,使用内存检测工具来检测泄露情况。

1.4  程序生成过程

  • 预处理阶段:根据文件中的预处理指令来修改源文件的内容。如#include指令,作用是把头文件的内容添加到.cpp文件中。
  • 编译阶段:将其翻译成等价的中间代码或汇编代码。
  • 汇编阶段:把汇编语言翻译成目标机器指令。
  • 链接阶段:例如,某个源文件中的函数可能引用了另一个源文件中定义的某个函数;在程序中可能调用了某个库文件中的函数。

1.5 预编译

  • 定义:预编译又称为预处理 , 是做些代码文本的替换工作。处理 # 开头的指令 , 比如拷贝 #include 包含的文件代码, #define 宏定义的替换 , 条件编译等。
  • 功能:宏定义,文件包含,条件编译。

1.6 头文件的作用

  • 保存程序的声明。
  • 通过头文件可以来调用库函数。因为有些代码不能向用户公布,只要向用户提供头文件和二进制的库即可。用户只需要按照头文件中的接口声明来调用库功能,编译器会从库中提取相应的代码。
  • 如果某个接口被实现或被使用时,其方式与头文件中的声明不一致,编译器就会指出错误,这一简单的规则能大大减轻程序员调试、改错的负担。

2. 基本数据类型和用法知识

2.1 struct与class的区别

  • 默认情况下,struct的成员变量是public的,而class是private的。
  • struct保证成员按照声明顺序在内存中存储,而class不能保证。
  • 默认情况下,struct是public继承,而class是private继承。

2.2 struct与union的区别

  • struct中各个成员变量是独立的,union中的成员变量共享同一片内存区域,内存区域长度由成员变量中长度最大的一个决定。
  • struct不能保证分配的是连续内存,但union分配的是连续内存。

2.3 const和define的用途以及区别

  • const用途:用来定义常量、修饰函数参数、修饰函数返回值,可以避免被修改,提高程序的健壮性。
  • define用途:是宏定义,在编译的时候会进行替换,这样做的话可以避免没有意义的数字或字符串,便于程序的阅读。
  • 区别:const定义的数据有数据类型,而宏常量没有数据类型。编译器可以对const常量进行类型检查。而对宏定义只进行字符替换,没有类型安全检查,所以字符替换时可能出错。

2.4 枚举和define的区别

  • #define 是在预编译阶段进行简单替换。枚举常量则是在编译的时候确定其值。
  • 一般在编译器里,可以调试枚举常量,但是不能调试宏常量。
  • 枚举可以一次定义大量相关的常量,而#define 宏一次只能定义一个。

2.5 内联函数和宏的区别

  • 内联函数在编译时展开,宏在预编译时展开。
  • 在编译的时候内联函数可以直接被嵌入到目标代码中,而宏只是一个简单的文本替换,内联函数可以完成诸如类型检测、语句是否正确等编译功能,宏就不具备这样的功能。inline函数是函数,宏不是函数。   

2.6 new/delete和malloc/free的区别

  • new/delete用调用构造函数来实例化对象和调用析构函数释放对象申请的资源。
  • malloc/free用来申请内存和释放内存,但是申请和释放的对象只能是内部数据类型。
  • malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。

2.7 delete和delete[]的区别

  • delete只会调用一次析构函数,delete[]会调用每一个成员的析构函数。
  • delete与new配套,delete []与new []配套,用new分配的内存用delete删除用new[]分配的内存用delete[]删除。

2.8 指针和引用的概念和区别

  • 指针指向一块内存,指针保存的是内存的地址;引用是变量的别名,本质是引用该变量的地址。解引用是取指针指向的地址的内容,取地址是获得变量在内存中的地址。

  • 引用在创建的同时必须初始化,保证引用的对象是有效的,所以不存在NULL引用。
  • 指针在定义的时候不必初始化,所以,指针则可以是NULL,可以在定义后面的任何地方重新赋值。
  • 引用一旦被初始化为指向一个对象,它就不能被改变为另一个对象的引用。
  • 指针在任何时候都可以改变为指向另一个对象。
  • 引用的创建和销毁并不会调用类的拷贝构造函数。
  • 因为不存在空引用,并且引用一旦被初始化为指向一个对象,它就不能被改变为另一个对象的引用,所以比指针安全。由于const 指针仍然存在空指针,并且有可能产生野指针,所以还是不安全。
  • 程序会给指针变量分配内存区域,而引用不需要分配内存区域。
  • 返回引用时,在内存中不产生被返回值的副本。

2.9 memset,memcpy和strcpy的区别

  • memset用来对一段内存空间全部设置为某个字符。
  • memcpy是内存拷贝函数,可以拷贝任何数据类型的对象。
  • strcpy只能拷贝字符串,遇到’\0′结束拷贝。

2.10 指针在16位机,32位机,64位机中分别占多大内存

  • 16位机:2字节。
  • 32位机:4字节。
  • 64位机:8字节。

2.11 字符指针,浮点数指针和函数指针哪个占用内存更大

  • 一样大,指针的占用内存大小只和机器相关。

2.12 如何引用一个全局变量

  • 在同一文件中:直接引用。
  • 咋不同文件中:直接引用头文件;使用extern声明变量。

2.13 变量声明和定义的区别

  • 变量声明:告诉编译器有某个类型的变量,但不会为其分配内存。
  • 变量定义:位该类型的变量分配内存。

2.14 野指针,未初始化指针和空指针的区别

  • 野指针:指向一个已删除的对象或无意义地址的指针。
    • 原因:指针变量没有被初始化,或者指针p被free或者delete之后,没有置为NULL。  
  • 空指针:空指针表示“未分配” 或者“尚未指向任何地方” 的指针。
  • 区别:空指针可以确保不指向任何对象或函数; 而未野指针或初始化指针则可能指向任何地方。

2.15 常量指针和指针常量的区别

  • 常量指针:是一个指向常量的指针。可以防止对指针误操作而修改该常量。
  • 指针常量:是一个常量,且是一个指针。指针常量不能修改指针所指向的地址,一旦初始化,地址就固定了,不能对它进行移动操作。但是指针常量的内容是可以改变。

2.16 指针函数和函数指针的区别

  • 指针函数:返回值是指针的函数。
  • 函数指针:一个指向函数的指针。函数名被括号括起来,并且加有指针符号。

2.17 const char*, char const*, char* const的区别

  • char * const cp;//cp是常指针,指向char类型的数据
  • const char * cp;//cp是char类型的指针,指向const char
  • char const * p;//C++里面没有const*的运算符,所以const属于前面的类型。

2.18 static全局变量与普通的全局变量的区别

  • 全局变量在整个工程文件内都有效。
  • 静态全局变量只在定义它的文件内有效。
  • 全局变量和静态变量如果没有手工初始化,则由编译器初始化为0。

2.19 static局部变量和普通局部变量的区别

  • 静态局部变量只在定义它的函数内有效,只是程序仅分配一次内存,函数返回后,该变量不会消失,直到程序运行结束后才释放。
  • 普通局部变量在定义它的函数内有效,这个函数返回会后失效。
  • static局部变量会自动初始化,而局部变量不会。

2.20 sizeof用在不同对象上的区别

  • sizeof是C语言的一种单目操作符,并不是函数。sizeof以字节的形式返回操作数的大小。
  • 若操作数具有类型char、unsigned char或signed char,其结果等于1。
  • 当操作数是指针时,sizeof依赖于系统的位数。
  • 当操作数具有数组类型时,其结果是数组的总字节数。
  • 联合类型操作数的sizeof是其最大字节成员的字节数。
  • 结构类型操作数的sizeof是这种类型对象的总字节数。
  • 如果操作数是函数中的数组形参或函数类型的形参,sizeof给出其指针的大小。

2.21 sizeof与strlen的区别

  • sizeof是运算符,计算数据所占的内存空间;strlen()是一个函数,计算字符数组的字符数。
  • sizeof可以用类型作参数;strlen()只能用char*作参数,必须是以‘/0’结束。
  • 数组做sizeof的参数不退化,传递给strlen就退化为指针了。
  • sizeof操作符的结果类型是size_t,它在头文件中typedef为unsigned int类型。该类型保证能容纳实现建立的最大对象的字节大小。

2.22 空指针指向了内存的什么地方

  • 标准并没有对空指针指向内存中的什么地方这一个问题作出规定,一般取决于系统的实现。我们常见的空指针一般指向 0 地址,即空指针的内部用全 0 来表示。空指针的“逻辑地址”一定是0,对于空指针的地址,操作系统是特殊处理的。并非空指针指向一个0地址的物理地址。在实际编程中不需要了解在我们的系统上空指针到底是一个 0指针还是非0地址,我们只需要了解一个指针是否是空指针就可以了——编译器会自动实现其中的转换,为我们屏蔽其中的实现细节。

2.23 有一个char * 型指针刚好指向一些int 型变量, 我想跳过它们。 为什么((int *)p)++; 不行?

  • 类型转换的实质“把这些二进制位看作另一种类型, 并作相应的对待”。
  • ((int *)p)++是一个转换操作符, 根据定义它只能生成一个右值(rvalue)。
  • 而右值既不能赋值, 也不能用++ 自增。
  • 正确的做法:p = (char *)((int *)p + 1);。

3. 面向对象知识

3.1 面向对象三个基本特点

  • 封装:将客观事物抽象成类,每个类对自身的数据和方法。封装可以使得代码模块化,目的是为了代码重用。
  • 继承:子类继承父类的方法和属性,继承可以扩展已存在的代码,目的是为了代码重用。
  • 多态:通过继承同一个基类,产生了相关的不同的派生类,与基类中同名的成员函数在不同的派生类中会有不同的实现,也就是说:一个接口、多种方法。

3.2 多态的作用

  • 可以隐藏实现的细节,使得代码模块化;方便扩展代码;
  • 可以实现接口重用。

3.3 空类默认的成员函数

  • 默认构造函数
  • 析构函数
  • 复制构造函数
  • 赋值运算符

3.4 类的成员函数重载、覆盖和隐藏的概念和区别

  • 重载是指再同一个作用域内,有几个同名的函数,但是参数列表的个数和类型不同。
  • 函数覆盖是指派生类函数覆盖基类函数,函数名、参数类型、返回值类型一模一样。派生类的对象会调用子类中的覆盖版本,覆盖父类中的函数版本。
  • 隐藏”是指派生类的函数屏蔽了与其同名的基类函数。
  • 覆盖和隐藏的区别:
    • 派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏。
    • 派生类的函数与基类的函数同名,参数也相同。基类函数有virtual,是覆盖,没有virtual就是隐藏。

3.5 基类和子类的构造、析构顺序

  • 定义一个对象时先调用基类的构造函数、然后调用派生类的构造函数
  • 先派生类的析构后基类的析构,也就是说在基类的的析构调用的时候,派生类的信息已经全部销毁了

3.6 深拷贝与浅拷贝的区别

  • 深拷贝意味着拷贝了资源和指针

  • 浅拷贝只是拷贝了指针,没有拷贝资源

3.7 构造函数的特点

  • 构造函数只在建立对象的时候自动被调用一次

  • 构造函数必须是公共的,否则无法生成对象

  • 构造函数只负责为自己的类构造对象 

3.8 析构函数的特点

  • 函数名称固定:~类名( )
  • 没有返回类型,没有参数
  • 不可以重载,一般由系统自动的调用

3.8 公有继承、私有继承、受保护的继承

  • 公有继承时,派生类对象可以访问基类中的公有成员,派生类的成员函数可以访问基类中的公有和受保护成员;公有继承时基类受保护的成员,可以通过派生类对象访问但不能修改。
  • 私有继承时,基类的成员只能被直接派生类的成员访问,无法再往下继承。
  • 受保护继承时,基类的成员也只被直接派生类的成员访问,无法再往下继承。

3.9 类成员中只能使用构造函数的初始化列表而不能赋值的有哪些

  • const成员
  • 引用成员

3.10 函数模板与类模板的区别

  • 函数模板是模板的一种,可以生成各种类型的函数实例,函数模板的实例化是由编译程序在处理函数调用时自动完成的
  • 类模板的实例化必须由程序员在程序中显式地指定。

3.11 引用与多态的关系

  • 引用就是对象的别名。
  • 引用主要用作函数的形参。
  • 引用必须用与该引用同类型的对象初始化: 引用是除指针外另一个可以产生多态效果的手段。
  • 一个基类的引用可以指向它的派生类实例。

3.12 static成员变量和static成员函数

  • static数据成员独立于该类的任意对象而存在。
  • tatic数据成员(const static数据成员除外)在类定义体内声明,必须在类外进行初始化。
  • static数据成员定义放在cpp文件中,不能放在初始化列表中。
  • static成员函数在类的外部定义。
  • Static成员函数没有this形参。
  • 可以直接访问所属类的static成员,不能直接使用非static成员。
  • 因为static成员不是任何对象的组成部分,所以static成员函数不能被声明为const。
  • static成员函数也不能被声明为虚函数。

3.13 static总结

  • 函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值。
  • 在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问。
  • 在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内。
  • 在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝。
  • 在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。

3.13 const总结

  • 欲阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了。
  • 对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const。
  • 在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值。
  • 对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量。
  • 对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”。

4. STL标准库

4.1 STL

  • 容器:主要的七种容器 vector,list,deque,map,multimap,set,multiset。
  • 算法
  • 迭代器

参考文献

[1] c++面试知识,知乎。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!