C++11 面试题整理

C++面试题

1 菱形继承

2 多态

多态实现原理:

静态多态

动态多态

静态多态:

依赖函数重载,编译期确定。

函数重载:允许在同一作用于内声明多个功能类似的同名函数,函数列表不同。注意:不能仅通过返回值类型确定重载(函数模板也是其中之一)。

    原理:
    1 函数名修饰;
    2 编译过程:
        预编译 把头文件当中的函数声明拷贝到源文件,避免编译过程中的语法分析找不到函数定义
        * 编译 语法分析,同时进行符号汇总(函数名)
        * 汇编 生成函数名到函数地址的映射,方便通过函数名找到函数定义位置
        链接 将多个文件中的符号表汇总合并

    objdump -t *.o // _Zn + 类长度 + 类名 + 函数名长度 + 函数名 + E + 类型首字母

动态多态:

虚函数重写,运行期确定

在基类的函数名前加virtual关键字,在子类中重写 override

运行时将根据对象的类型调用相应的函数

如果对象的类型是基类,则调用基类函数;反之,若是派生类,则调用派生类函数

原理:
    早绑定:编译期已确定对象调用的函数地址
    晚绑定:若类使用virtual函数,则会为类生成虚函数表(一维数组,存放虚函数地址),类对象构造时会初始化该虚表指针;
        虚函数表指针在构造时初始化

3 override/final

c++11引入这两个关键字

原因:

虚函数重写

不能阻止某个虚函数进一步重写

本意写一个心函数,错误重写基类虚函数(子类中virtual关键字可省略)

本意重写基类虚函数,但是签名不一致,在子类中重新构建了一个新的虚函数

类继承
    不能阻止某个类进一步派生

override

指定子类一个虚函数复写基类的一个虚函数

保证该重写的虚函数与基类的虚函数具有相同的签名

final

指定某个虚函数不能在派生类中被覆盖,或者某个类不能被派生

阻止类进一步派生

阻止虚函数进一步重写

4 类型推导

类型:

模板方法中模板参数类型的推导

auto decltype (都是c++11引入,其中auto的类型推导是c++引入)

原因:

c++是强类型语言

编译器来处理类型推导

提升语言的编码效率

关键字:

auto

原理:用于推导变量的类型,通过强制声明一个变量的初始值,编译器会通过初始值进行推导类型。

规则:变量必须在定义时初始化;如果用auto定义多个变量,那么这些变量必须为同一类型;类型推导时会丢失cv语义/&;

需要保留&/cv,使用const & / const auto

万能引用 auto&&, 根据初始值的属性来判断左值引用/右值引用

auto不能推导数组类型,会得到指针类型

auto可以推导函数返回值类型(c++14)

    应用:尽量使用auto声明变量,除非影响到可读性
        使用容器时,名字较长,使用auto更方便
        匿名函数返回值
        模板函数中可以节约模板参数类型

    延伸:typeid会丢失顶层const;typeid只有在虚表查找时才会有运行时开销,否则都是在编译阶段完成

decltype
    原理:用于推导表达式的类型,只分析表达式类型而不参与运算
    规则:
        exp是普通表达式,推导表达式类型
        exp是函数调用,推导函数返回值类型
        exp是左值引用,推导出左值引用
    应用:范型编程

5 function/lambda/bind

function

类模板

一个抽象了函数参数以及函数返回值的类模板

    抽象方式:将任意函数包装成一个对象,该对象可以保存/传递/复制;动态绑定,只需要修改该对象(赋值不同的function对象),实现类似多态的效果

用途:保存普通函数,类的静态成员数据;保存仿函数;保存类成员函数;保存lambda表达式
cpp 复制代码
function<void(CHello *, int)> f = &CHello::hello;

仿函数(函数对象)

重载了操作符'()'的类

cpp 复制代码
class Hello {
    void operator()(int count) {
        i += count;
        cout << "Hello " << i << endl;
    }

    int i;
};
function<void(int)> f = Hello();
f(3);
特征:可以有状态,通过成员变量进行存储;有状态的函数对象成为闭包

lambda表达式

一种方便创建匿名函数对象的语法糖

cpp 复制代码
    int i = 0;
    // auto f_hello = [i]() mutable -> void { ++i; cout << "lambda i:" << i << endl;}; // 未修改外部变量,闭包
    auto f_hello = [&i]() { ++i; cout << "lambda i:" << i << endl;}; // 修改外部变量
    f_hello();
    f_hello();

    cout << "global i:" << i << endl;
构成:
    捕获列表[]
        值捕获
            默认只读,不能修改
            mutable可读可写,并不会修改外部变量的值
        引用捕获
            可读可写,修改外部变量的值

        本质:外部变量将转变为类的成员变量

    参数列表()

    指定返回值->
        可省略,因为有类型推导

    函数体{}

原理
    编译时,将lambda表达式转变为一个函数对象
    根据lambda参数列表重载operator()

bind

用来通过绑定函数以及函数参数的方式生成函数对象的模板函数

提供占位符,实现灵活的绑定

特征:
    绑定函数以及函数参数 构成一个函数对象(闭包)
    允许修改参数顺序
    部分参数可设置固定值(丢弃多余参数)
cpp 复制代码
auto f_bind = bind(&bind_func, 1, 2); // 函数名取地址

auto f_class_func = bind(&CHello::hello, &obj, 1000);

auto f_placement = bind(&CHello::hello, &obj, placeholders::_1);

总结:

lambda和bind生成一个函数对象

function用来描述函数对象的类型;

lambda用来生成匿名函数,即函数对象(可以访问外部变量的匿名函数);

bind也是用来生成函数对象(函数和参数进行绑定生成函数对象);

6 继承下的构造函数和析构函数执行顺序

继承下,构造函数依照依赖链,从上往下构造;析构函数从下向上析构

单继承

成员类按照顺序构造,按相反顺序析构

类的构造依赖成员类的构造,基类比成员类依赖性更强

多继承

成员类按照顺序构造,按相反顺序析构

类的构造依赖成员类的构造,基类比成员类依赖性更强

多继承中基类按声明顺序构造,按相反顺序析构

7 虚函数表和虚函数表指针的创建时机

虚函数表的创建时机

编译器发现类中包含virtual关键字修饰的函数,虚函数表的内容在编译期已经生成(.o文件)

存放于全局数据区只读数据段

虚函数表中存放虚函数的地址的数组

虚函数表指针的创建时机

对象构造时,在构造函数中将虚函数表的地址赋值给对象vptr

如果没有构造函数,则编译器为类生成默认构造函数,为类对象初始化vptr

继承下,虚函数表指针赋值过程:先是基类赋值,再是子类赋值(覆盖行为)

8 虚析构函数作用

在继承下,为了使子类析构函数能够得到正常调用,需要将基类的析构函数设置为虚析构函数

使用场景:

子类对喜爱嗯指针赋值给基类指针,在调用析构函数时,子类对象的析构函数得不到调用

设计原因:

在c++看来,我们设计某个类,不一定是基类,如果是基类,应该手动将基类的析构函数设置为虚函数;

设置虚析构函数是有代价的,编译器会为类生成虚函数表,每个对象都需要持有vptr

9 面向对象三大特征

封装

目的:隐藏实现细节,实现模块化

特性:

访问权限

public 对所有对象开放

protected 对子类开放

private 只对自己开放;使用友元类打破private权限

继承

目的:无需修改原有类的情况下,实现功能扩展

特性:

权限继承

public继承 基类成员权限保持

protected继承 基类成员public/protected变为protected

private继承 基类成员public/protected/private都变为private

    使用using修改基类成员在子类中的权限
        比如:在public中using data = A::c;

    多继承

    接口继承
        纯虚函数

多态

目的:一个接口多种形态,通过实现接口重用,增强可扩展性

特性:

静态多态: 函数重载

动态多态: 通过虚函数重写

10 动态库与静态库

动态库与静态库都是先生成.o文件

静态库编译

shell 复制代码
ar rcs target.a *.o # r:replace c:grade s:such

g++ -static main.cpp -o static_target -L./ -lapi -I./ # -lapi = libapi.a

动态库编译

shell 复制代码
g++ -shared -fPIC -o target.so *.o

g++ main.cpp -o dynamic_target -L./ -lapi -I./ # -lapi = libapi.so

静态编译产物 > 动态编译产物 # 动态编译未全部装载

动态编译依赖LD_LIBRARY_PATH

shell 复制代码
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:[new_path]

11 智能指针

指针管理的困境

资源释放了,但指针未置空

指针悬挂 -> 一个/多个指针指向同一资源

踩内存

未释放资源 
    内存泄漏

重复释放资源
    coredump错误

使用未初始化指针
    野指针

解决方案

智能指针,利用RAII思想自动化管理指针指向的动态资源的释放(举例:mutex mtx; mtx.lock(); mtx.unlock();)

RAII,利用对象的生命周期管理资源

智能指针利用构造/析构函数来管理资源

智能指针

shared_ptr

解决指针悬挂问题

语义:共享所有权

资源无明确拥有者

原理:引用计数,由最后一个对象释放资源

场景:

容器中管理指针 -> vector<shared_ptr<T>> vec;

资源通过函数传递

使用规范:

使用shared_ptr管理动态资源时,不要使用原来的裸指针

构造智能指针时,不要暴露裸指针;

尽量使用make_shared构造智能指针;

不要使用int *p = sp.get();;

不要使用一个指针构造多个智能指针对象;

不要用类对象指针this作为shared_ptr返回

不能暴露裸指针

cpp 复制代码
// 不推荐用法
int *p = new int();
shared_ptr<int> sp = shared_ptr<int>(p);

// 不太推荐用法
shared_ptr<int> sp = shared_ptr<int>(new int);

// 推荐用法
shared_ptr<int> sp = make_shared<int>();

// 不应该使用的方式
class T {
public:
    // 违反条款:不应该使用一个指针构造多个智能指针对象
    shared_ptr<T> self() {
        return this; // return shared_ptr<T>(this); 也不应该使用
    }
};

// 改进方案,可用
class T : public enable_shared_from_this<T> {
public:
    shared_ptr<T> self() {
        return shared_from_this();
    }
};
weak_ptr
    辅助shared_ptr,用来解决shared_ptr循环引用,原因是弱引用不占用强引用类型
cpp 复制代码
class A {
public:
    ~A() {
        cout << "A deconstructor" << endl;
    }

    // shared_ptr<B> spb; 问题写法
    weak_ptr<B> spb;
};

class B {
public:
    ~B() {
        cout << "B deconstructor" << endl;
    }

    // shared_ptr<A> spa; // 问题写法
    weak_ptr<A> spa;
};

int main()
{
    // 循环引用
    shared_ptr<A> sp1 = make_shared<A>();
    shared_ptr<B> sp2 = make_shared<B>();
    sp1->spb = sp2;
    sp2->spa = sp1;

    cout << "A.use_count(): "<< sp1.use_count() << " B.use_count(): " << sp2.use_count() << endl;
    return 0;
}
unique_ptr
    语义:独享所有权
        没有拷贝语义,仅提供移动语义
    明确某个对象只有一个拥有者
    场景
    使用规范:
        不支持拷贝,但可以从函数中返回一个unique_ptr -> 编译器优化;编译器优化关闭情况下,如果有移动构造,调用移动构造;如果有拷贝构造,调用拷贝构造;没有拷贝构造,返错;
        make_unique (c++14)
cpp 复制代码
// 可用
unique_ptr<T> get_unique() {
    unique_ptr<T> up;
    return up;
}

12 c++11用过的特性

考察思路

回答问题的层次

学习总结的思路

语法糖

关键字

auto / decltype

nullptr

以往用NULL,它是0(int)

nullptr是一个具体的空指针,会去匹配空指针类型的函数参数

final / override

在继承中

constexpr

让编译器协助求值

语法
    基于范围的for循环
    function函数对象
        bind
        lambda
    
目的:
    使代码更便捷/严谨,让编译器做更多的工作

stl容器

array

forward_list

unordered_map

unordered_set

智能指针

shared_ptr

weak_ptr

unique_ptr

多线程

thread

可以传闭包

mutex / lock_guard

可能会休眠

condition_variable

可能会休眠

atomic

原子操作,限定cpu/编译器不要对某些变量或操作做违反一致性的优化

右值引用

T&&

将亡值

移动语义

实现移动语义

std::move

实现完美转发

万能引用 T&&

std::forward

相关推荐
别NULL2 小时前
机试题——疯长的草
数据结构·c++·算法
CYBEREXP20084 小时前
MacOS M3源代码编译Qt6.8.1
c++·qt·macos
yuanbenshidiaos4 小时前
c++------------------函数
开发语言·c++
yuanbenshidiaos4 小时前
C++----------函数的调用机制
java·c++·算法
tianmu_sama4 小时前
[Effective C++]条款38-39 复合和private继承
开发语言·c++
羚羊角uou5 小时前
【C++】优先级队列以及仿函数
开发语言·c++
姚先生975 小时前
LeetCode 54. 螺旋矩阵 (C++实现)
c++·leetcode·矩阵
FeboReigns5 小时前
C++简明教程(文章要求学过一点C语言)(1)
c语言·开发语言·c++
FeboReigns5 小时前
C++简明教程(文章要求学过一点C语言)(2)
c语言·开发语言·c++
264玫瑰资源库5 小时前
从零开始C++棋牌游戏开发之第二篇:初识 C++ 游戏开发的基本架构
开发语言·c++·架构