C++面经总结

发布时间 2023-07-20 17:13:29作者: mobbu

1 C++基础

1.1 语言基础

1.1.1 简述C++语言特点

  1. 面向对象而且兼容C语言

  2. 三大特性:

    • 封装
    • 继承
    • 多态
  3. C++语言编写出的程序结构清晰,易于扩充,可读性好

  4. 运行效率高,比汇编语言慢10%到20%

  5. C++可复用性高,C++引入了模板的概念,后面在此基础上,实现了方便开发的标准模板库STL(Standard Template Library)

1.1.2 C与C++区别

  1. C是C++的子集,C++能兼容C,但是C++又有很多新特性比如引用、智能指针、auto变量等

  2. C++面向对象,C面向过程

  3. C++更加安全,C有指针使用的潜在危险,强制转换的不确定性和内存泄漏的问题,C++针对次有多const常量、智能指针等。

  4. C++可复用性高,引入模板概念并有STL,比C的函数库更灵活通用。

1.1.3 C++中struct和class的区别

struct class
描述对象 数据结构的集合 一个对象数据的封装
默认访问权限 public private
默认继承方式 默认公有继承 默认私有继承
能否定义模板参数 不能 可以

1.1.4 include<>和include""的区别

  1. 前者是系统文件,后者是自定义文件
  2. 在编译器预处理阶段查找头文件的路径不同
    • include<>:编译器设置的头文件路径-->系统变量。
    • include"":当前头文件目录-->编译器设置的头文件路径-->系统变量。

1.1.5 C++和C的结构体struct区别

C C++
成员函数 不能有 可以(而且可以是虚函数)
静态成员 不能有 可以
访问控制 默认public,不能修改 public/private/protected
继承关系 不可以继承 可从类或者其他结构体继承
初始化 不能直接初始化数据成员 可以
使用时 需要加上struct关键字,或者typedef取别名 不需要

1.1.6 C++代码执行的过程

img

  1. 预编译

    • 展开宏定义,处理条件编译指令和预编译指令
    • 删除所有注释
    • 添加行号和文件名标识
  2. 编译

    • 词法分析:将源代码的字符序列分割成一系列的记号。
    • 语法分析:对记号进行语法分析,产生语法树。
    • 语义分析:判断表达式是否有意义。
    • 代码优化
    • 生成汇编代码
  3. 汇编

    • 这个过程主要是将汇编代码转变成机器可以执行的指令。
  4. 链接

    • 将不同的源文件产生的目标文件进行链接,从而形成一个可以执行的程序。

1.1.7 static关键字的作用

  1. 定义全局静态变量和局部静态变量
  2. 定义静态函数或者静态变量:静态函数或静态变量只能在本源文件中使用;
  3. static关键字可以用于定义类中的静态成员变量(函数)。类中的static静态数据成员拥有一块单独的存储区,而不管创建了多少个该类的对象。所有这些对象的静态数据成员都共享这一块静态存储空间。

1.1.8 为什么静态成员函数没有this指针

当调用一个对象的非静态成员函数时,系统会把该对象的起始地址赋给成员函数的this指针。而静态成员函数不属于任何一个对象,因此C++规定静态成员函数没有this指针。既然它没有指向某一对象,也就无法对一个对象中的非静态成员进行访问。

1.1.9 函数指针

  1. 定义:函数指针就是指向函数的指针变量。每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。
  2. 应用场景:回调(callback)

1.1.10 静态变量初始化

对于C语言的全局和静态变量,初始化发生在任何代码执行之前,属于编译期初始化。

而C++标准规定:全局或静态对象当且仅当对象首次用到时才进行构造。

1.1.11 nullptr调用成员函数可以吗?为什么?

可以,因为在编译时对象就绑定了函数地址,和指针空不空没关系。若函数中没有需要解引用this的地方,则函数运行不会出错,但是若用到this,因为this=nullptr,运行出错。

1.1.13 什么是野指针,怎么产生的,如何避免?

  1. 概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

  2. 产生原因:释放内存后指针不及时置空(野指针),依然指向了该内存,那么可能出现非法访问的错误。这些我们都要注意避免。

  3. 避免办法:

(1)初始化置NULL

(2)申请内存后判空

(3)指针释放后置NULL

(4)使用智能指针

1.1.14 静态局部变量,全局变量,局部变量的特点,以及使用场景

全局变量 静态全局变量 局部变量 静态局部变量
作用域 全局作用域,可以通过extern作用于其他非定义的源文件 全局作用域+文件作用域,所以无法在其他文件中使用 局部作用域,比如函数的参数,函数内的局部变量等等 局部作用域,只被初始化一次,直到程序结束
所在空间 静态存储区 静态存储区 栈 静态存储区
生命周期 直到程序结束才回收内存 直到程序结束才回收内存 了作用域就回收内存 直到程序结束才回收内存
使用场景

1.1.15 ++i和i++

  1. 赋值顺序不同:++ i 是先加后赋值;i ++ 是先赋值后加;++i和i++都是分两步完成的。

  2. 效率不同:后置++执行速度比前置的慢。

  3. i++ 不能作为左值,而++i 可以:

  4. 两者都不是原子操作。

1.1.16 说说new和malloc的区别

new malloc
类型 操作符 函数
调用 先分配内存,在调用构造函数,释放的时候调用析构函数 有构造函数和析构函数
使用方式 不用指定内存的大小,返回指针不用强转 给定申请内存的大小,返回的指针需要强转
能否重载 能 不能
内存分配 更加直接安全
异常情况 直接报错 返回null
  • malloc底层实现:当开辟的空间小于 128K 时,调用 brk()函数;当开辟的空间大于 128K 时,调用mmap()。malloc采用的是内存池的管理方式,以减少内存碎片。先申请大块内存作为堆区,然后将堆区分为多个内存块。当用户申请内存时,直接从堆区分配一块合适的空闲快。采用隐式链表将所有空闲块,每一个空闲块记录了一个未分配的、连续的内存地址。

  • new底层实现:关键字new在调用构造函数的时候实际上进行了如下的几个步骤:

    • 创建一个新的对象
    • 将构造函数的作用域赋值给这个新的对象(因此this指向了这个新的对象)
    • 执行构造函数中的代码(为这个新对象添加属性)
    • 返回新对象

1.1.17 说说const和define的区别。

const用于定义常量;而define用于定义宏,而宏也可以用于定义常量。都定义常量时:

  1. 生效阶段不同:const生效于编译的阶段;define生效于预处理阶段。

  2. 储存位置不同:const定义的常量,在C语言中是存储在内存中、需要额外的内存空间的;define定义的常量,运行时是直接的操作数,并不会存放在内存中。

  3. 定义方式不同:const定义的常量是带类型的;define定义的常量不带类型。因此define定义的常量不利于类型检查。

1.1.18 指针函数和函数指针区别

指针函数本质是一个函数,其返回值为指针。 函数指针本质是一个指针,其指向一个函数。

int *fun(int x,int y);  //指针函数
int (*fun)(int x,int y); //函数指针

1.1.19 const *位置不同的对比

const int a;     //指的是a是一个常量,不允许修改。
const int *a;    //a指针所指向的内存里的值不变,即(*a)不变
int const *a;    //同const int *a;
int *const a;    //a指针所指向的内存地址不变,即a不变
const int *const a;   //都不变,即(*a)不变,a也不变```

1.1.20 使用指针注意事项

  • 定义指针时,先初始化为NULL。

  • 用malloc或new申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。

  • 为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。

  • 避免数字或指针的下标越界

  • 动态内存的申请与释放必须配对,防止内存泄漏

  • 用free或delete释放了内存之后,立即将指针设置为NULL,防止“野指针”

1.1.21 说说内联函数和函数的区别

  1. 内联函数比普通函数多了关键字inline

  2. 内联函数避免了函数调用的开销;普通函数有调用的开销

  3. 普通函数在被调用的时候,需要寻址(函数入口地址);内联函数不需要寻址。

  4. 内联函数有一定的限制,内联函数体要求代码简单,不能包含复杂的结构控制语句;普通函数没有这个要求。

1.1.22 C++传值方式?区别?

值传递:形参即使在函数体内值发生变化,也不会影响实参的值;

引用传递:形参在函数体内值发生变化,会影响实参的值;

指针传递:在指针指向没有发生改变的前提下,形参在函数体内值发生变化,会影响实参的值;

做函数形参时,最好不使用值传递,因为会拷贝一个副本,效率很低,使用引用传递和指针传递更高效,不发生拷贝行为,但是两者间引用传递更加安全。

1.2 C++内存

1.2.1 重写和重载

  1. 重写/覆盖:派生类中与基类同返回值类型、同名和同参数的虚函数重定义,构成虚函数覆盖,也叫虚函数重写。
  2. 重载:同一作用域中,同名函数的形式参数(参数个数、类型或者顺序)不同时,构成函数重载。
  3. 隐藏:不同作用域中,定义的同名函数构成隐藏(不要求函数返回值和函数参数类型相同)。(派生类中与基类同返回值类型、同名和同参数的非虚函数也构成隐藏。)

1.2.2 内存分区

  1. 代码区:存放函数体的二进制代码,由操作系统进行管理;
  2. 全局区:存放全局变量和静态(全局、局部)变量和字符串常量;
  3. 栈区(stack):由编译器自动分配释放, 存放函数的参数值,局部变量等;
  4. 堆区(heap):由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。

内存分区意义:不同区域存放不同的数据,赋予数据不同的生命周期, 更大限度的灵活编程。

1.2.3 内存中的堆区和栈区的区别

堆heap 栈stack
空间分配方式 程序员分配释放 系统自动分配释放
缓存方式 二级缓存中,速度较慢 一级缓存,被调用时处于存储空间中,调用完毕立即释放
数据结构 堆类似数组结构 类似栈结构,先进后出

1.2.4 内存泄露以及解决办法

  • 定义:简单地说就是申请了一块内存空间,使用完毕后没有释放掉
  • 一般出现在
    • new和malloc申请资源使用后,没有用delete和free释放;
    • 子类继承父类时,父类析构函数不是虚函数。
    • Windows句柄资源使用后没有释放。
  • 解决办法,记得检查释放内存或者使用智能指针

1.2.5 程序有哪些setion

img

如上图,从低地址到高地址,一个程序由代码段、数据段、BSS段、堆、共享区、栈等组成。

  1. 数据段:存放程序中已初始化的全局变量和静态变量的一块内存区域。

  2. 代码段:存放程序执行代码的一块内存区域。只读,代码段的头部还会包含一些只读的常数变量。

  3. BSS 段:存放程序中未初始化的全局变量和静态变量的一块内存区域。

  4. 可执行程序在运行时又会多出两个区域:堆区和栈区。

    • 堆区:动态申请内存用。堆从低地址向高地址增长。

    • 栈区:存储局部变量、函数参数值。栈从高地址向低地址增长。是一块连续的空间。

  5. 最后还有一个共享区,位于堆和栈之间。

1.3 面向对象

1.3.1 简述面向对象

1.3.2 简述三大特征

面向对象的三大特征是封装、继承、多态。

  1. 封装

  2. 继承

  3. 多态

1.4 STL

1.5 C++11 新特性

2 操作系统

3 计算机网络

4 数据库

5 算法与数据结构

6 项目相关

7 设计模式