13518219792

建站动态

根据您的个性需求进行定制 先人一步 抢占小程序红利时代

万字长文超全C++面经

本文经自动驾驶之心公众号授权转载,转载请联系出处。

10余年的金林网站建设经验,针对设计、前端、开发、售后、文案、推广等六对一服务,响应快,48小时及时工作处理。营销型网站建设的优势是能够根据用户设备显示端的尺寸不同,自动调整金林建站的显示方式,使网站能够适用不同显示终端,在浏览器中调整网站的宽度,无论在任何一种浏览器上浏览网站,都能展现优雅布局与设计,从而大程度地提升浏览体验。成都创新互联从事“金林网站设计”,“金林网站推广”以来,每个客户项目都认真落实执行。

1. 开始

本文目的是整理面试常见的会问到的题目, 具体细节的学习需要参考 C++ Primer / Effective C++ 系列书籍 / Inside the C++ Object Model 进行学习.

为了方便查阅, 补充了可能没有面试内容的一级标题. 这样一级标题可以和 C++ Primer 书籍保持一致.

1.1. C 和 C++ 的区别

设计思想上:

语法上:

2. 变量和基本类型

2.1. 复合类型

复合类型(compound type)是指基于其他类型定义的类型. 最常见的是引用和指针.

引用即别名: 引用(reference)为对象起了另外一个名字, 引用类型引用(refers to)另外一种类型.

指针(pointer)是指向(point to)另外一种类型的复合类型.

表 2.1 指针与数组的区别

2.2. const限定符

2.2.1. 作用

补充:

2.2.2. 指向常量的指针 VS 常量指针

参考 C++ Primer 2.4.2 指针和const:

2.2.3. cosntexpr

2.2.4. #define VS const

3. 字符串、向量和数组

4. 表达式

4.1. 右值

C++的表达式要不然是右值(rvalue), 要不然是左值(lvalue). 这两个名词是从 C 语言继承过来的, 原本是为了帮助记忆: 左值可以位于赋值语句的左侧, 右值则不能.

当一个对象被用做右值的时候, 用的是对象的值(内容); 当对象被用做左值的时候, 用的是对象的身份(在内存中的位置).

4.2. ++i/i++

前置版本++i: 首先将运算对象加 1, 然后将改变后的对象作为求值结果.

后置版本i++: 也会将运算对象加 1, 但是求解结果是运算对象改变之前的那个值的副本.

以下摘录自 More Effective C++ Item 6:

// prefix form(++i): increment and fetch
UPInt&  UPInt::operator++()
{
    *this +=1;        // increment
    return *this;     // fetch
}
// postfix form(i++): fetch and increment
const UPInt UPInt::operator++(int)
{
    const UpInt oldValue = *this; // fetch
    ++(*this);                    // increment
    return oldValue;             // return what was fetched
}

4.3. sizeof运算符

4.3.1. 普通变量执行sizeof

sizeof运算符的结果部分地依赖于其作用的类型:

4.3.2. 类执行sizeof

class A {};
class B { B(); ~B() {} };
class C { C(); virtual ~C() {} };
class D { D(); ~D() {} int d; };
class E { E(); ~E() {} static int e; };
int main(int argc, char* argv[]) {
    std::cout << sizeof(A) << std::endl; // 输出结果为1
    std::cout << sizeof(B) << std::endl; // 输出结果为1
    std::cout << sizeof(C) << std::endl; // 输出结果为8,实例中有一个指向虚函数表的指针
    std::cout << sizeof(D) << std::endl; // 输出结果为4,int占4个字节
    std::cout << sizeof(E) << std::endl; // 输出结果为1,static不算
    return 0;
}

4.4. 显式转换

5. 语句

6. 函数

6.1. 函数基础

6.1.1. 形参和实参

实参是形参的初始值.

6.1.2. static

6.2. 参数传递

指针参数传递本质上是值传递, 它所传递的是一个地址值.

一般情况下, 输入用传值或者传const reference. 输出传引用(或者指针).

6.3. 内联函数

6.3.1. 使用

将函数指定为内联函数(inline), 通常就是将它在每个调用点上"内联地"展开.

一般来说, 内联机制用于优化规模较小(Google C++ Style 建议 10 行以下)、流程直接、频繁调用的函数.

在类声明中定义的函数, 除了虚函数的其他函数都会自动隐式地当成内联函数.

6.3.2. 编译器对inline函数的处理步骤

6.3.3. 优缺点

优点:

  1. 内联函数同宏函数一样将在被调用处进行代码展开, 省去了参数压栈、栈帧开辟与回收, 结果返回等, 从而提高程序运行速度.
  2. 内联函数相比宏函数来说, 在代码展开时, 会做安全检查或自动类型转换(同普通函数), 而宏定义则不会.
  3. 在类中声明同时定义的成员函数, 自动转化为内联函数, 因此内联函数可以访问类的成员变量, 宏定义则不能.
  4. 内联函数在运行时可调试, 而宏定义不可以.

缺点:

  1. 代码膨胀. 内联是以代码膨胀(复制)为代价, 消除函数调用带来的开销. 如果执行函数体内代码的时间, 相比于函数调用的开销较大, 那么效率的收获会很少. 另一方面, 每一处内联函数的调用都要复制代码, 将使程序的总代码量增大, 消耗更多的内存空间.
  2. inline函数无法随着函数库升级而升级. inline函数的改变需要重新编译, 不像non-inline可以直接链接.
  3. 是否内联, 程序员不可控. 内联函数只是对编译器的建议, 是否对函数内联, 决定权在于编译器.

6.4. 返回类型和return语句

调用一个返回引用的函数得到左值, 其他返回类型得到右值.

6.5. 特殊用途语言特性

6.5.1. 调试帮助

assert是一种预处理器宏. 使用一个表达式作为它的条件:

assert(expr);

首先对expr求值, 如果表达式为falseassert输出信息并终止程序的执行. 如果表达式为trueassert什么也不做.

6.6. 函数指针

函数指针指向的是函数而非对象. 和其他指针一样, 函数指针指向某种特定类型. 函数的类型由它的返回类新和形参共同决定, 与函数名无关.

C 在编译时, 每一个函数都有一个入口地址, 该入口地址就是函数指针所指向的地址.

有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样

用途: 调用函数和做函数的参数, 比如回调函数.

char * fun(char * p)  {…}  // 函数fun
char * (*pf)(char * p);    // 函数指针pf
pf = fun;                  // 函数指针pf指向函数fun
pf(p);                     // 通过函数指针pf调用函数fun

7. 类

7.1. 定义抽象数据类型

7.1.1. this指针

7.1.2. 拷贝函数

7.1.3. 析构函数

(TODO: 整理析构函数的特性)

7.2. 访问控制与封装

7.2.1. public/private/protected

7.2.2. struct和class的区别

7.2.3. 友元

类可以允许其他类或者函数访问它的非公有成员, 方法是令其他类或者函数成为它的有元(friend).

7.3. 构造函数再探

7.3.1. 初始化顺序

成员变量的初始化顺序与它们在类定义中的出现顺序一致: 构造函数初始值列表中初始值的前后位置关系不会影响

7.3.2. explicit

8. I/O 库

9. 顺序容器

9.1. 容器库概览

9.1.1. 迭代器

9.2. 顺序容器操作

9.2.1. emplace

当调用push或insert成员函数时, 我们将元素类型的对象传递给它们, 这些对象被拷贝到容器中. 而当我们调用一个emplace成员函数时, 则是将参数传递给元素类型的构造函数. emplace成员使用这些参数在容器管理的内存空间中直接构造元素.

9.2.2. resize/reserve

9.2.3. 容器操作可能使迭代器失效

在向容器中添加元素后:

从容器删除元素后:

9.3. vector

//动态申请数组
const int M = 10;
const int N = 10;

//使用new申请一个一维数组.访问p_arr[i].
int* p_arr = new int[N];
//使用new申请一个二维数组.访问:p_arr[i][j].
int(*p_arr)[N] = new int[M][N];
//一维数组转化为二维数组.访问:p_arr[i*N+j].
int* p_arr = new int[M*N];
//指向指针的指针(指向一维指针数组).访问p[i][j]
int** p_arr = new int* [M]
for(int i = 0; i < M; i++)
    p_arr[i] = new int[N];
//回收内存
for(int i = 0; i < M; i++)
 delete []p_arr[i];
delete []p_arr;

//使用vector申请一个一维数组
vector v_arr(n, 0);
vector v_arr{1,0};
//使用vector申请一个二维数组, 如果不初始化, 使用[]会报错
vector> v_arr(m, vector(n, 0));
vector> v_arr = {{1,0}};

//一维数组作为函数参数
void function(int* a);
void function(int a[]);
void function(int a[N]);
//二维数组作为函数参数,他们合法且等价
void function(int a[M][N]);
void function(int a[][N]);
void function(int (*a)[N])

9.4. string

string s("hello world")
string s2 = s.substring(0, 5); // s2 = hello
string s3 = s.substring(6);    // s3 = world
string s4 = s.substring(6, 11);// s4 = world
string s5 = s.substring(12);   // 抛出一个out_of_range异常

isalpha(ch); //判断一个字符是否是字母
isalnum(ch); //判断一个字符是数字或字母
tolower(ch); //将字母转化成小写
toupper(ch); //将字母转化为大写

string str = to_string(num); //将数字转换成字符串

9.5. vector对象是如何增长的

当不得不获取新的内存空间时, vector和string的实现通常会分配一个比新的空间需求更大的内存空间. 容器预留这些空间作为备用, 可以用来保存更多的新元素. 这样, 就不需要每次添加新元素都重新分配容器的内存空间了.

9.6. 容器适配器

除了顺序容器外, 标准库还定义了三个顺序容器适配器: stack、queue和priority_queue.

本质上, 一个适配器是一种机制, 能使某种事物的行为看起来像另外一种事物一样.

默认情况下, stack和queue是基于deque实现的, priority_queue是在vector之上实现的.

9.6.1. priority_queue

std::priority_queue q1; // 默认大根堆
std::priority_queue, std::greater>
    q2(data.begin(), data.end()); // 小根堆
// 使用lambda表达式
auto cmp = [](int left, int right) { return (left ^ 1) < (right ^ 1); };
std::priority_queue, decltype(cmp)> q3(cmp);

10. 泛型算法

10.1. lambda 表达式

一个 lambda 表达式表示一个可调用的代码单元. 我们可以将其理解为一个未命名的内联函数. 一个 lambda 表达式具有如下形式:

[capture list](parameter list) -> return type {function body}

其中capture list(捕获列表)是一个 lambda 所在函数中定义的局部变量的列表(通常为空); return type, parameter list和function body与任何普通函数一样, 分别表示返回类型、参数列表和函数体. 但是与普通函数不同, lambda 必须使用尾置返回来制定返回类新.

我们可以忽略参数列表和返回类型, 但必须包含捕获列表和函数体:

auto f = [] {return 42};

11. 关联容器

12. 动态内存

12.1. 智能指针

智能指针的行为类似常规指针, 重要的区别在于它负责自动释放所指向的对象.

shared_ptr
unique_ptr
weak_ptr
make_shared

13. 拷贝控制

13.1. 对象移动

int &&rr1 = 42;  // 正确: 字面常量是右值
int &&rr2 = rr1; // 错误: 表达式rr1是左值
int &&rr3 = std::move(rr1); // ok

14. 重载运算与类型转换

15. 面向对象程序设计

15.1. OOP: 概述

面向对象程序设计(object-oriented programming)的核心思想是数据抽象(封装)、继承和动态绑定(多态).

15.2. 定义派生类和基类

15.2.1. 初始化顺序

15.2.2. 静态多态/动态多态

15.3. 虚函数

15.3.1. 虚析构函数

Q: 析构函数为什么是虚函数?

A: 将可能会被继承的基类的析构函数设置为虚函数, 可以保证当我们new一个派生类, 然后使用基类指针指向该派生类对象, 基类指针时可以释放掉派生类的空间, 防止内存泄漏.

Q: 为什么 C++ 默认析构函数不是虚函数?

A: C++默认的析构函数不是虚函数是因为虚函数需要额外的虚函数表和虚表指针, 占用额外的内存; 所以只有当一个类会被用作基类时才将其设置为虚函数.

15.4. 抽象基类

虚函数 VS 纯虚函数

15.5. 访问控制与继承

16. 模板与泛型编程

17. 标准库特殊实施

18. 用于大型程序的工具

18.1. 多重继承与虚继承

19. 特殊工具和技术

19.1. 控制内存分配

19.1.1. new & delete

string *sp = new string("a value); // 分配并初始化一个string对象
string *arr = new string[10];      // 分配10个默认初始化的string对象

当我们使用一条new表达式时, 实际执行了三步操作:

delete sp;  // 销毁*sp, 然后释放sp指向的内存空间
delete [] arr; // 销毁数组中的元素, 然后释放对应的内存空间

当我们使用一条delete表达式删除一个动态分配的对象时, 实际执行了两步操作:

  1. 对sp所指的对象或者arr所指的数组中的元素执行对应的析构函数.
  2. 编译器调用名为operate delete(或者operate delete[])的标准库函数释放内存空间.

19.1.2. malloc&free

// operate new的一种简单实现
void *operater new(size_t size) {
    if (void *men = malloc(size))
        return mem;
    else
        throw bad_alloc();
}
// opearte delete的一种简单实现
void operator delete(void *mem) noexcept { free(mem); }

19.2. 固有的不可移植特性

19.2.1. volatile

19.2.2. extern

20. 链接装载与库

本小节内容大部分摘录自《程序员的自我修养 - 链接装载与库》

20.1. .h 和 .cpp 文件的区别

20.2. 编译和链接

  1. 预编译(预处理): 预编译过程主要处理那些源代码文件中的以"#"开始的预编译指令. 比如"#include"、"#define"等. 生成.i或者.ii文件.
  2. 编译: 把预处理完的文件进行一系列的词法分析、语法分析、语义分析及优化后生产相应的汇编代码文件(.s文件).
  3. 汇编: 将汇编代码转变成机器可以执行的指令(机器码), 生成.o文件.
  4. 链接: 链接器进行地址和空间分配、符号决议、重定位等步骤, 生成 .out文件.

20.3. 程序的内存布局

一般来讲, 应用程序使用的内存空间里有如下"默认"区域.

图 20.1 Linux 进程地址空间布局

20.3.1. 段错误

Q: 程序出现"段错误(segment fault)"或者"非法操作, 该内存地址不能 read/wirte"的错误信息, 是什么原因?

A: 这是典型的非法指针解引用造成的错误. 当指针指向一个不允许读或写的内存地址, 而程序却试图利用指针来读或写该地址的时候, 就会出现这个错误. 可能的段错误发生的时机如下:

20.4. 编译型语言 VS 解释型语言


当前名称:万字长文超全C++面经
文章URL:http://cdbrznjsb.com/article/dhcppjj.html

其他资讯

让你的专属顾问为你服务