1 C++基础
1.1 语言基础
1.1.1 简述C++语言特点
-
面向对象而且兼容C语言
-
三大特性:
- 封装
- 继承
- 多态
-
C++语言编写出的程序结构清晰,易于扩充,可读性好
-
运行效率高,比汇编语言慢10%到20%
-
C++可复用性高,C++引入了模板的概念,后面在此基础上,实现了方便开发的标准模板库STL(Standard Template Library)
1.1.2 C与C++区别
-
C是C++的子集,C++能兼容C,但是C++又有很多新特性比如引用、智能指针、auto变量等
-
C++面向对象,C面向过程
-
C++更加安全,C有指针使用的潜在危险,强制转换的不确定性和内存泄漏的问题,C++针对次有多const常量、智能指针等。
-
C++可复用性高,引入模板概念并有STL,比C的函数库更灵活通用。
1.1.3 C++中struct和class的区别
| struct | class | |
|---|---|---|
| 描述对象 | 数据结构的集合 | 一个对象数据的封装 |
| 默认访问权限 | public | private |
| 默认继承方式 | 默认公有继承 | 默认私有继承 |
| 能否定义模板参数 | 不能 | 可以 |
1.1.4 include<>和include""的区别
- 前者是系统文件,后者是自定义文件
- 在编译器预处理阶段查找头文件的路径不同
- include<>:编译器设置的头文件路径-->系统变量。
- include"":当前头文件目录-->编译器设置的头文件路径-->系统变量。
1.1.5 C++和C的结构体struct区别
| C | C++ | |
|---|---|---|
| 成员函数 | 不能有 | 可以(而且可以是虚函数) |
| 静态成员 | 不能有 | 可以 |
| 访问控制 | 默认public,不能修改 | public/private/protected |
| 继承关系 | 不可以继承 | 可从类或者其他结构体继承 |
| 初始化 | 不能直接初始化数据成员 | 可以 |
| 使用时 | 需要加上struct关键字,或者typedef取别名 | 不需要 |
1.1.6 C++代码执行的过程

-
预编译
- 展开宏定义,处理条件编译指令和预编译指令
- 删除所有注释
- 添加行号和文件名标识
-
编译
- 词法分析:将源代码的字符序列分割成一系列的记号。
- 语法分析:对记号进行语法分析,产生语法树。
- 语义分析:判断表达式是否有意义。
- 代码优化
- 生成汇编代码
-
汇编
- 这个过程主要是将汇编代码转变成机器可以执行的指令。
-
链接
- 将不同的源文件产生的目标文件进行链接,从而形成一个可以执行的程序。
1.1.7 static关键字的作用
- 定义全局静态变量和局部静态变量
- 定义静态函数或者静态变量:静态函数或静态变量只能在本源文件中使用;
- static关键字可以用于定义类中的静态成员变量(函数)。类中的static静态数据成员拥有一块单独的存储区,而不管创建了多少个该类的对象。所有这些对象的静态数据成员都共享这一块静态存储空间。
1.1.8 为什么静态成员函数没有this指针
当调用一个对象的非静态成员函数时,系统会把该对象的起始地址赋给成员函数的this指针。而静态成员函数不属于任何一个对象,因此C++规定静态成员函数没有this指针。既然它没有指向某一对象,也就无法对一个对象中的非静态成员进行访问。
1.1.9 函数指针
- 定义:函数指针就是指向函数的指针变量。每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。
- 应用场景:回调(callback)
1.1.10 静态变量初始化
对于C语言的全局和静态变量,初始化发生在任何代码执行之前,属于编译期初始化。
而C++标准规定:全局或静态对象当且仅当对象首次用到时才进行构造。
1.1.11 nullptr调用成员函数可以吗?为什么?
可以,因为在编译时对象就绑定了函数地址,和指针空不空没关系。若函数中没有需要解引用this的地方,则函数运行不会出错,但是若用到this,因为this=nullptr,运行出错。
1.1.13 什么是野指针,怎么产生的,如何避免?
-
概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
-
产生原因:释放内存后指针不及时置空(野指针),依然指向了该内存,那么可能出现非法访问的错误。这些我们都要注意避免。
-
避免办法:
(1)初始化置NULL
(2)申请内存后判空
(3)指针释放后置NULL
(4)使用智能指针
1.1.14 静态局部变量,全局变量,局部变量的特点,以及使用场景
| 全局变量 | 静态全局变量 | 局部变量 | 静态局部变量 | |
|---|---|---|---|---|
| 作用域 | 全局作用域,可以通过extern作用于其他非定义的源文件 | 全局作用域+文件作用域,所以无法在其他文件中使用 | 局部作用域,比如函数的参数,函数内的局部变量等等 | 局部作用域,只被初始化一次,直到程序结束 |
| 所在空间 | 静态存储区 | 静态存储区 | 栈 | 静态存储区 |
| 生命周期 | 直到程序结束才回收内存 | 直到程序结束才回收内存 | 了作用域就回收内存 | 直到程序结束才回收内存 |
| 使用场景 |
1.1.15 ++i和i++
-
赋值顺序不同:++ i 是先加后赋值;i ++ 是先赋值后加;++i和i++都是分两步完成的。
-
效率不同:后置++执行速度比前置的慢。
-
i++ 不能作为左值,而++i 可以:
-
两者都不是原子操作。
1.1.16 说说new和malloc的区别
| new | malloc | |
|---|---|---|
| 类型 | 操作符 | 函数 |
| 调用 | 先分配内存,在调用构造函数,释放的时候调用析构函数 | 有构造函数和析构函数 |
| 使用方式 | 不用指定内存的大小,返回指针不用强转 | 给定申请内存的大小,返回的指针需要强转 |
| 能否重载 | 能 | 不能 |
| 内存分配 | 更加直接安全 | |
| 异常情况 | 直接报错 | 返回null |
-
malloc底层实现:当开辟的空间小于 128K 时,调用 brk()函数;当开辟的空间大于 128K 时,调用mmap()。malloc采用的是内存池的管理方式,以减少内存碎片。先申请大块内存作为堆区,然后将堆区分为多个内存块。当用户申请内存时,直接从堆区分配一块合适的空闲快。采用隐式链表将所有空闲块,每一个空闲块记录了一个未分配的、连续的内存地址。
-
new底层实现:关键字new在调用构造函数的时候实际上进行了如下的几个步骤:
- 创建一个新的对象
- 将构造函数的作用域赋值给这个新的对象(因此this指向了这个新的对象)
- 执行构造函数中的代码(为这个新对象添加属性)
- 返回新对象
1.1.17 说说const和define的区别。
const用于定义常量;而define用于定义宏,而宏也可以用于定义常量。都定义常量时:
-
生效阶段不同:const生效于编译的阶段;define生效于预处理阶段。
-
储存位置不同:const定义的常量,在C语言中是存储在内存中、需要额外的内存空间的;define定义的常量,运行时是直接的操作数,并不会存放在内存中。
-
定义方式不同: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 说说内联函数和函数的区别
-
内联函数比普通函数多了关键字inline
-
内联函数避免了函数调用的开销;普通函数有调用的开销
-
普通函数在被调用的时候,需要寻址(函数入口地址);内联函数不需要寻址。
-
内联函数有一定的限制,内联函数体要求代码简单,不能包含复杂的结构控制语句;普通函数没有这个要求。
1.1.22 C++传值方式?区别?
值传递:形参即使在函数体内值发生变化,也不会影响实参的值;
引用传递:形参在函数体内值发生变化,会影响实参的值;
指针传递:在指针指向没有发生改变的前提下,形参在函数体内值发生变化,会影响实参的值;
做函数形参时,最好不使用值传递,因为会拷贝一个副本,效率很低,使用引用传递和指针传递更高效,不发生拷贝行为,但是两者间引用传递更加安全。
1.2 C++内存
1.2.1 重写和重载
- 重写/覆盖:派生类中与基类同返回值类型、同名和同参数的虚函数重定义,构成虚函数覆盖,也叫虚函数重写。
- 重载:同一作用域中,同名函数的形式参数(参数个数、类型或者顺序)不同时,构成函数重载。
- 隐藏:不同作用域中,定义的同名函数构成隐藏(不要求函数返回值和函数参数类型相同)。(派生类中与基类同返回值类型、同名和同参数的非虚函数也构成隐藏。)
1.2.2 内存分区
- 代码区:存放函数体的二进制代码,由操作系统进行管理;
- 全局区:存放全局变量和静态(全局、局部)变量和字符串常量;
- 栈区(stack):由编译器自动分配释放, 存放函数的参数值,局部变量等;
- 堆区(heap):由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。
内存分区意义:不同区域存放不同的数据,赋予数据不同的生命周期, 更大限度的灵活编程。
1.2.3 内存中的堆区和栈区的区别
| 堆heap | 栈stack | |
|---|---|---|
| 空间分配方式 | 程序员分配释放 | 系统自动分配释放 |
| 缓存方式 | 二级缓存中,速度较慢 | 一级缓存,被调用时处于存储空间中,调用完毕立即释放 |
| 数据结构 | 堆类似数组结构 | 类似栈结构,先进后出 |
1.2.4 内存泄露以及解决办法
- 定义:简单地说就是申请了一块内存空间,使用完毕后没有释放掉
- 一般出现在
- new和malloc申请资源使用后,没有用delete和free释放;
- 子类继承父类时,父类析构函数不是虚函数。
- Windows句柄资源使用后没有释放。
- 解决办法,记得检查释放内存或者使用智能指针
1.2.5 程序有哪些setion

如上图,从低地址到高地址,一个程序由代码段、数据段、BSS段、堆、共享区、栈等组成。
-
数据段:存放程序中已初始化的全局变量和静态变量的一块内存区域。
-
代码段:存放程序执行代码的一块内存区域。只读,代码段的头部还会包含一些只读的常数变量。
-
BSS 段:存放程序中未初始化的全局变量和静态变量的一块内存区域。
-
可执行程序在运行时又会多出两个区域:堆区和栈区。
-
堆区:动态申请内存用。堆从低地址向高地址增长。
-
栈区:存储局部变量、函数参数值。栈从高地址向低地址增长。是一块连续的空间。
-
-
最后还有一个共享区,位于堆和栈之间。
1.3 面向对象
1.3.1 简述面向对象
1.3.2 简述三大特征
面向对象的三大特征是封装、继承、多态。
-
封装
-
继承
-
多态