C++开发工程师面经

C++开发工程师 ---- 基础知识点整理(面经)

2022-03-13  本文已影响0人  wolfaherd

C++语言的八股文

C++面向对象的特性

多态的优点?

参考

虚函数

虚继承和虚基类

指针和引用

指针和引用的不同点 指针 引用
是否可以为空
是否必须初始化
初始化后是否可以改变
是否有const
是否需要分配内存空间
访问对象的方式 间接访问 直接访问
大小 指针本身的大小,32位系统为4字节,64位系统为8字节 引用对象的大小
++自增运算符 指向下一个地址 所引用的对象执行自增操作

内置数组和指针

不同点 内置数组 指针
赋值 一个个元素赋值或拷贝 同一类型的指针可以相互赋值
存储方式 连续存放于静态区或栈上。 很灵活,可以指向任意类型的数据。指针的类型说明了它所指向地址空间的内存。
sizeof大小 数组元素个数 ✖ 单个元素所占大小 根据操作系统的位数来确定,32位大小为4,64位大小为8
传参 传递指向该数组的指针 传递本身

static关键字

全局静态变量和局部静态变量的异同

C++11新特性

for(auto ch : s){
}

 if (std::regex_match ("subject", std::regex("(sub)(.*)") ))
    std::cout << "匹配成功\n";
#include <iostream>
#include <functional>

void f(int n1, int n2, int n3, const int& n4, int n5)
{
    std::cout<< n1 << ' ' << n2 << ' ' << n3 << ' ' << n4 << ' ' << n5 << '\n';
}


int main(){
   using namespace std::placeholders;
    std::cout << "1) argument reordering and pass-by-reference: ";
    int n = 7;
    // (_1 and _2 are from std::placeholders, and represent future
    // arguments that will be passed to f1)
    auto f1 = std::bind(f, _2, 42, _1, std::cref(n), n);
    n = 10;
    f1(1, 2, 1001); // 1 is bound by _1, 2 is bound by _2, 1001 is unused
                    // makes a call to f(2, 42, 1, n, 7)

  return 0;
}
//输出为:argument reordering and pass-by-reference: 2 42 1 10 7

为什么使用C++11?

智能指针

名称 描述
auto_ptr 这个类模板因存在潜在的内存崩溃问题在c++ 11中已弃用。取代它的是unique_ptr。
unique_ptr 实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。
shared_ptr 实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。
weak_ptr 一种不控制对象生命周期的智能指针, 它指向一个shared_ptr管理的对象,主要用作解决当两个对象互相使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏的问题。

shared_ptr如何实现?

template<typename T>

class SharedPtr {

public:

        SharedPtr(T* p) :countPtr(new int(1)), point(p) {}

       SharedPtr(SharedPtr<T>& other) :countPtr(&(++*other.countPtr)), point(other.point) {}

       SharedPtr<T>& operator=(SharedPtr<T>& other) {

              ++* other.countPtr;

              if (this->_ptr && 0 == -- * this->countPtr) {

                     delete countPtr;

                     delete point;

              }

              this->countPtr = other.countPtr;

              this->point = other.point;

              return *this;

       }
       T& operator*() {

              return *point;

       }

       T* operator->() {

              return point;

       }

       ~SharedPtr() {

              if (-- * countPtr == 0) {

                     delete countPtr;

                     delete point;

              }

       }

private:

       int* countPtr;

       T* point;

};


std::make_shared和std::shared_ptr<T>(new T(args...))有啥区别?

RAII

野指针

#pragma once和#ifndef的区别

new和malloc

特征 new/delete malloc/free
分配内存的位置 自由存储区
内存分配成功的返回值 完整类型指针 void*
内存分配失败的返回值 默认抛出异常 返回NULL
分配内存的大小 由编译器根据类型计算得出 必须显式指定字节数
处理数组 有处理数组的new版本new[] 需要用户计算数组的大小后进行内存分配
已分配内存的扩充 无法直观地处理,可以用vector实现 使用realloc简单完成
是否相互调用
分配内存时内存不足 客户能够指定处理函数或重新制定分配器 无法通过用户代码进行处理
是否允许函数重载
是否调用构造函数与析构函数

内存管理

名称 作用
栈区 由编译器自动分配释放,存放为函数运行的局部变量,函数参数,返回数据,返回地址等。操作方式与数据结构中的类似。
堆区 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。分配方式类似于链表。
全局数据区 也叫做静态区,存放全局变量,静态数据。程序结束后由系统释放。
文字常量区 可以理解为常量区,常量字符串存放这里。程序结束后由系统释放。
程序代码区 存放函数体的二进制代码。但是代码区中也分为代码段和数据段。

堆和栈

不同点
管理方式 堆中资源由程序员控制(容易产生memory leak) 栈资源由编译器自动管理,无需手工控制
系统响应 对于堆,应知道系统有一个记录空闲内存地址的链表,当系统收到程序申请时,遍历该链表,寻找第一个空间大于申请空间的堆结点,删除空闲结点链表中的该结点,并将该结点空间分配给程序(大多数系统会在这块内存空间首地址记录本次分配的大小,这样delete才能正确释放本内存空间,另外系统会将多余的部分重新放入空闲链表中) 对于栈,只要栈的剩余空间大于所申请空间,系统为程序提供内存,否则报异常提示栈出。
空间大小 堆是不连续的内存区域(因为系统是用链表来存储空闲内存地址,自然不是连续的),堆大小受限于计算机系统中有效的虚拟内存(32bit 系统理论上是4G),所以堆的空间比较灵活,比较大。 栈是一块连续的内存区域,大小是操作系统预定好的,windows下栈大小是2M(也有是1M,在 编译时确定,VC中可设置)。
碎片问题 对于堆,频繁的new/delete会造成大量碎片,使程序效率降低。 对于栈,它是一个先进后出的线性表,进出一一对应,不会产生碎片。
生长方向 堆向上,向高地址方向增长。 栈向下,向低地址方向增长。
分配方式 堆都是动态分配(没有静态分配的堆)。 栈有静态分配和动态分配,静态分配由编译器完成(如局部变量分配),动态分配由alloca函数分配,但栈的动态分配的资源由编译器进行释放,无需程序员实现。
分配效率 堆由C/C++函数库提供,机制很复杂。所以堆的效率比栈低很多。 栈是机器系统提供的数据结构,计算机在底层对栈提供支持,分配专门寄存器存放栈地址,栈操作有专门指令。

内存对齐的原因

Cast变换

名称 作用
const_cast 去除const(volatile)属性,将只读变为可读写。
dynamic_cast 在进行下行转换时,dynamic_cast具有类型检查的功能,弥补了static_cast类型不安全的缺陷,比static_cast更安全。
多用于有虚函数的基类与其派生类之间的转换,特点是进行运行时检测转换类型是否安全,如果转换失败返回nullptr,依赖于RTTI技术,但是有额外的函数开销。
static_cast 代替隐式转换,基类与派生类的转换,派生类转换成基类是安全的,基类转换成派生类是不安全的。编译期转换。
reinterpret_cast 代替显示转换。

构造函数有那些?

vector是不是线程安全的?

参考

shared_ptr是线程安全的吗?

是,为了满足线程安全需求,引用计数器通常使用std::atomic::fetch_add和std::memory_order_relaxed的等价递增(递减需要更强的顺序来安全地销毁控制块)。

std::map和std::unordered_map的实现原理

std::condition_variable::wait为什么需要一个unique_lock<mutex>的变量lck?

源代码到可执行程序过程

锁的种类有那些?

       std::unique_lock<std::mutex> lk(mtx_sync_);
class spin_mutex {
       std::atomic<bool> flag = ATOMIC_VAR_INIT(false);
public:
       spin_mutex() = default;
       spin_mutex(const spin_mutex&) = delete;
       spin_mutex& operator= (const spin_mutex&) = delete;
       void lock() {
              bool expected = false;
              while (!flag.compare_exchange_strong(expected, true))
                     expected = false;
       }
       void unlock() {
              flag.store(false);
       }
}

C++与Lua的相互调用

Lua和C++ 交互机制的基础在于Lua提供了一个虚拟栈,C++ 和Lua之间的所有类型的数据交换都通过这个栈完成。无论何时C想从Lua中调用一个值,被请求的值将会被压入栈,无论何时C想要传递一个值给Lua,首先将整个值压栈,然后就可以在Lua中调用。

生产者和消费者简易实现

参考

条件变量和互斥锁(C++11)

#include <iostream>           
#include <queue>
#include <thread>             
#include <mutex> 
#include <condition_variable> 
using namespace std;

mutex mtx;
condition_variable produce, consume;  // 条件变量是一种同步机制,要和mutex以及lock一起使用

queue<int> q;     // shared value by producers and consumers, which is the critical section
int maxSize = 20;

void consumer()
{
    while (true)
    {
        this_thread::sleep_for(chrono::milliseconds(1000));
        unique_lock<mutex> lck(mtx);
        consume.wait(lck, [] {return q.size() != 0; });     // wait(block) consumer until q.size() != 0 is true

        cout << "consumer " << this_thread::get_id() << ": ";
        q.pop();
        cout << q.size() << '\n';

        produce.notify_all();                               // nodity(wake up) producer when q.size() != maxSize is true
    }
}

void producer(int id)
{
    while (true)
    {
        this_thread::sleep_for(chrono::milliseconds(1000));      // producer is a little faster than consumer  
        unique_lock<mutex> lck(mtx);
         produce.wait(lck, [] {return q.size() != maxSize; });   // wait(block) producer until q.size() != maxSize is true

        cout << "producer " << this_thread::get_id() << ": ";
        q.push(id);
        cout << q.size() << '\n';

        consume.notify_all();                                   // notify(wake up) consumer when q.size() != 0 is true
    }
}

int main()
{
    thread consumers[2], producers[2];

    // spawn 2 consumers and 2 producers:
    for (int i = 0; i < 2; ++i)
    {
        consumers[i] = thread(consumer);
        producers[i] = thread(producer, i + 1);  //thread:第一个参数是task任务,第二个参数是task函数的参数 
    }

    // join them back: (in this program, never join...)
    for (int i = 0; i < 2; ++i)
    {
        producers[i].join();
        consumers[i].join();
    }

    system("pause");
    return 0;
}

信号量(C++20)

#include <iostream>           
#include <queue>
#include <thread>             
#include <semaphore>
using namespace std;

queue<int> q;     // shared value by producers and consumers, which is the critical section
int maxSize = 20;

counting_semaphore semaphore(0);

void consumer()
{
    while (true)
    {
        this_thread::sleep_for(chrono::milliseconds(1000));
        semaphore.acquire();

        cout << "consumer " << this_thread::get_id() << ":";
        q.pop();
        cout << q.size() << '\n';
    }
}

void producer(int id)
{
    while (true)
    {
        this_thread::sleep_for(chrono::milliseconds(1000));      // producer is a little faster than consumer  
        
        cout << "producer " << this_thread::get_id() << ":";
        q.push(id);
        cout << q.size() << '\n';
        semaphore.release();
    }
}

int main()
{
    thread consumers[2], producers[2];

    // spawn 2 consumers and 2 producers:
    for (int i = 0; i < 2; ++i)
    {
        consumers[i] = thread(consumer);
        producers[i] = thread(producer, i + 1);  //thread:第一个参数是task任务,第二个参数是task函数的参数 
    }

    // join them back: (in this program, never join...)
    for (int i = 0; i < 2; ++i)
    {
        producers[i].join();
        consumers[i].join();
    }

    system("pause");
    return 0;
}

无锁队列

参考

CAS是英文单词Compare and Swap的缩写,翻译过来就是比较并替换。
CAS机制中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。

template偏特化

参考

template偏特化是相对于template全特化而言的,即只特化了部分模板参数。

template <typename T, typename Allocator_T>
class MyVector {
public:
    MyVector() {
        std::cout << "Normal version." << std::endl;
    }
};

template <typename T>
class MyVector<T, DefaultAllocator> {
public:
    MyVector() {
        std::cout << "Partial version." << std::endl;
    }
};

template <typename A, typename B>
void f(A a, B b) {
    std::cout << "Normal version." << std::endl;
}

template <typename A, typename B>
requires std::integral<B>
void f(A a, B b) {
    std::cout << "Partial version." << std::endl;
}

类的函数后面加一个const

意味着只能修改static或mutable成员。

左值和右值

左值:是glvalue但不是xvalue。
右值:是prvalue或者xvalue。

可重入

可重入函数主要用于多任务环境中,一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段代码,而返回控制时不会出现什么错误。

C++17新特性





计算机网络的八股文

OSI七层模型

http和https的区别

https 主要由两部分组成:http + ssl / tls,也就是在 http 上又加了一层处理加密信息的模块。服务端和客户端的信息传输都会通过 tls 进行加密,所以传输的数据都是加密后的数据。

https加密算法

参考

差异点 TCP UDP
包头大小 20字节 8字节
是否基于连接
是否可靠
对系统资源的要求 较多 较少
速度 较慢 较快

TCP如何保证可靠性

TCP三次握手和四次挥手

如何实现tcp拥塞控制?

参考

什么是tcp粘包?如何处理?

网址输入会发生什么?

什么是DNS协议?

Protobuf Union

参考

Protobuf没有提供union类型,如果希望使用union类型,可以采用enum和optional属性定义的方式。

为什么采用Protobuf?

Protobuf Lua 的序列化和反序列化





计算机操作系统的八股文

进程和线程

进程间通信方式

共享内存实例

参考

#include <windows.h>
#include <iostream>
#include <string>
#include <cstring>
using namespace std;

int main()
{
    string strMapName("ShareMemory");                // 内存映射对象名称
    string strComData("This is common data!");        // 共享内存中的数据
    LPVOID pBuffer;                                    // 共享内存指针

    // 首先试图打开一个命名的内存映射文件对象
    HANDLE hMap = ::OpenFileMapping(FILE_MAP_ALL_ACCESS, 0, strMapName.c_str());
    if (NULL == hMap)
    {    // 打开失败,创建之
        hMap = ::CreateFileMapping(INVALID_HANDLE_VALUE,
                                   NULL,
                                   PAGE_READWRITE,
                                   0,
                                   strComData.length()+1,
                                   strMapName.c_str());
        // 映射对象的一个视图,得到指向共享内存的指针,设置里面的数据
        pBuffer = ::MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
        strcpy((char*)pBuffer, strComData.c_str());
        cout << "写入共享内存数据:" << (char *)pBuffer << endl;
    }
    else
    {    // 打开成功,映射对象的一个视图,得到指向共享内存的指针,显示出里面的数据
        pBuffer = ::MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
        cout << "读取共享内存数据:" << (char *)pBuffer << endl;
    }

    getchar();            // 注意,进程关闭后,所有句柄自动关闭,所以要在这里暂停

    // 解除文件映射,关闭内存映射文件对象句柄
    ::UnmapViewOfFile(pBuffer);
    ::CloseHandle(hMap);
    system("pause");
    return 0;
}

参考


#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <vector>
#include <unistd.h>
#include <cstring>

#define KEY_OF_SHM 8888
#define TEXT_SIZE 1024

struct ShmDataStruct {
    int readable_;
    char text_[TEXT_SIZE];
};

using namespace std;

int main(){
    //获取shm_id,在不同进程中这是唯一的,获取和KEY有关
    int shm_id = shmget(KEY_OF_SHM, sizeof(struct ShmDataStruct), 0666 | IPC_CREAT);

    string strComData("This is common data!");        // 共享内存中的数据
    void *addr_to_shm;                                  // 共享内存指针

    struct ShmDataStruct *shm_data;
      //将addr_to_shm连接到系统分配的共享内存,也就是将共享内存(物理地址)挂到当前进程的内存空间(虚拟地址)
    addr_to_shm = shmat(shm_id, (void*)0, 0);

     //将获得的void*类型的转为我们需要的data struct
    shm_data = (struct ShmDataStruct*)addr_to_shm;

    // char buffer[TEXT_SIZE];
    if(shm_data->readable_ == 0){
        shm_data->readable_ = 1;
        strncpy(shm_data->text_, strComData.c_str(), strComData.size());
         cout << "写入共享内存数据:" << (char *)shm_data->text_ << endl;
    }
    else{
        
        cout << "读取共享内存数据:" << (char *)shm_data->text_ << endl;
    }

    shm_data->readable_ = 1;

    getchar();            // 注意,进程关闭后,所有句柄自动关闭,所以要在这里暂停

    //把共享内存从当前进程分离,也就是将其从进程内存空间的页表中除掉
    shmdt(addr_to_shm);

    //删除共享内存
    shmctl(shm_id, IPC_RMID, 0);
    exit(EXIT_SUCCESS);
    return 0;
}

线程间通信方式

参考

多线程的应用场景?

协程

死锁

物理地址和逻辑地址

虚拟内存

作业调度策略

页面置换算法

大端和小端

孤儿进程和僵尸进程

进程响应信号有那些方式?





数据结构与算法的八股文

二叉树的应用场景

用两个小球来判断100层高的楼会被摔坏的最低楼层

最短路径算法





计算机操作系统的八股文

简述CPU执行一条指令的过程

页面分页和分段的不同点

其他

opengl渲染管线

为什么DrawCall多了会影响帧率?

DrawCall:CPU调用图形编程接口。

求P点是否在三角形ABC内?

P = A + u * (C – A) + v * (B - A)
v0 = C - A,v1 = B - A,v2 = P - A。
u = ((v1•v1)(v2•v0)-(v1•v0)(v2•v1)) / ((v0•v0)(v1•v1) - (v0•v1)(v1•v0))
v = ((v0•v0)(v2•v1)-(v0•v1)(v2•v0)) / ((v0•v0)(v1•v1) - (v0•v1)(v1•v0))

未完待续

上一篇 下一篇

猜你喜欢

热点阅读