C++八股(面试题、手撕题)自用版

目录

面试题: 

1. define inline 在编译的哪个阶段

2. const static

3. 子函数返回结构体有什么问题,返回对象调用了哪些函数

4. volatile关键字

5. 编译器基本原理

6. 预处理、编译、汇编、链接以及他们在操作系统上如何运作的

7. 数组和指针(二维)

8. 指针和引用

9. new 和 malloc

10.万能引用和右值引用

11. 解释中断,以及底层发生的操作细节

12. C++多线程在操作系统上如何运作的

13. 进程、线程间通信方式

14. 信号量在操作系统中如何实现

15. 虚函数

16. 构造函数、析构函数

17. C++内存管理

18. 智能指针

手撕题:

1. 内存池

2. lambda表达式

3. 手写单例模式

4. 遍历二叉树(非递归)

5. 实现一个简易的shared_ptr


面试题: 

1. define inline 在编译的哪个阶段

define: 预编译/预处理

inline: 链接阶段

2. const static

经过static修饰的变量会作为类的属性而不是实体属性存在。它的作用是在编译时期确定,程序运行过程中不会改变。

static 关键字用于声明静态成员变量、静态成员函数和局部静态变量,其作用取决于它所修饰的实体。

  • 静态成员变量:静态成员变量是属于类的,而不是属于类的各个实例的。它的特点是所有类的实例共享同一份静态成员变量。静态成员变量可以通过类名直接访问,也可以通过对象访问。
  • 静态成员函数:静态成员函数是属于类的函数,它不依赖于任何特定的对象。因此,它可以直接通过类名来调用,而不需要创建类的实例
  • 局部静态变量:具有静态生存期,即它在程序运行期间只初始化一次,并且在函数调用结束后仍然存在于内存中。

const

const 关键字用于声明常量,它指定了一个变量在初始化后不能被修改的特性。const 可以用于变量、成员函数和指针。

3. 子函数返回结构体有什么问题,返回对象调用了哪些函数

typedef定义类型,因此返回return mm类似返回int这种基础操作,仅需声明函数时也声明结构体的类型。

也可以用指针

4. volatile关键字

volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。

5. 编译器基本原理

6. 预处理、编译、汇编、链接以及他们在操作系统上如何运作的

1、预处理:

通过gcc -E main.c -o main.i    生成.i文件将进行如下操作:

1、将所有的#define删除,并展开所有的宏定义。

2、处理所有的预编译指令,例如:#if,#elif,#else,#endif等。

3、处理#include预编译指令,将被包含的文件插入到预编译指令的位置。

4、添加行号信息、文件名标识,便于调试。

5、删除所有的注释。

6、保留所有的#pragma编译指令,因为在编写程序的时候,我们经常要用到#pargma指令来设定编译的状态或者是指示编译器完成一些特定的动作。

7、生成.i文件(包括去注释、宏替换、头文件展开、条件编译),编译生成的.i文件不包含任何宏定义,因为宏已经被展开,并且包含的文件已经被插入到.i文件中。

2、编译(C/C++语音 ------> 汇编):

通过gcc -S main.i –o main.s    生成.s文件,需要进行如下操作:

1、扫描、语法分析、语义分析、源代码分析、目标代码生成、目标代码优化。

2、生成汇编代码。

3、汇总符号。

4、生成.s文件。

3、汇编(汇编 ------> 二进制):

通过gcc –c main.s –o main.o   生成.o文件,需要进行如下操作:

1、根据汇编指令和特定平台,把汇编指令翻译成二进制形式。

2、合并各个section,合并符号表。

3、生成.o文件。

4、链接:

链接过程会进行如下操作:

1、合并各个.obj文件的section,合并符号表,进行符号解析。

2、符号地址重定位。

3、生成可执行文件。

7. 数组和指针(二维)

二维数组名是一个二级指针?

8. 指针和引用

指针大小固定,初始化需要分配内存,指针指向的地址可变

引用为变量的别名,需要初始化,初始化后不可改变

存在空值的指针,不存在空值的引用

9. new 和 malloc

new delete 操作符
malloc free 函数

常规八股

要了解new的底层需要熟悉operator操作符

怎样减少内存碎片--->内存池

10.万能引用和右值引用

涉及类型推导的引用,才会是万能引用。

对于万能引用,如果采用右值来初始化,得到的是一个右值引用,如果采用左值来初始化万能引用,那么得到的是一个左值引用。

左值引用能引用左值和右值,右值引用只能引用右值

Move的功能是将一个左值引用强制转化为右值引用,继而可以通过右值引用使用该值,以用于移动语义,从实现原理上讲基本等同一个强制类型转换。

优点:可以将左值变成右值而避免拷贝构造,将对象的状态所有权从一个对象转移到另一个对象,只是转移,没有内存搬迁或者内存拷贝。

11. 解释中断,以及底层发生的操作细节

中断:CPU  中端服务程序

识别中断源、找到中断程序、保存当前任务的各个寄存器状态、进入中断处理程序后的返回等

...

对于外部中断,CPU在执行当前指令的最后一个时钟周期去查询INTR引脚,若查询到中断请求信号有效,同时在系统开中断(即IF=1)的情 况下,CPU向发出中断请求的外设回送一个低电平有效的中断应答信号,作为对中断请求INTR的应答,系统自动进入中断响应周期(即中断响应指令的指令周期)。

不太详细...

12. C++多线程在操作系统上如何运作的

  • 多线程实现:C++标准库提供了对多线程的支持,开发人员可以使用 std::thread 等类来创建和管理线程。C++11引入了对并发编程的支持,包括原子操作、互斥量、条件变量等,使得线程间的同步和通信更加方便。此外,还有一些其他的第三方库,如Boost.Thread等,也提供了丰富的多线程支持。
  • 多进程实现:在C++中,可以使用操作系统提供的系统调用或者一些跨平台的库来创建和管理多个独立的进程。例如,可以使用fork()系统调用来创建一个新的进程,或者使用exec()系列函数来在新的进程中执行其他程序。另外,一些跨平台的库,如Boost.Interprocess等,也提供了一些跨平台的进程间通信的工具,如共享内存、消息队列等。

构造函数作用是创建新线程并指定要执行的函数及其参数。可调用对象可以是以下五个中的任何一个:

  • 函数指针
  • Lambda 表达式
  • 函数对象(仿函数)
  • 非静态成员函数
  • 静态成员函数

13. 进程、线程间通信方式

进程和线程的区别

线程具有许多传统进程所具有的特征,故又称为轻型进程(Light—Weight Process)或进程元;而把传统的进程称为重型进程(Heavy—Weight Process),它相当于只有一个线程的任务。在引入了线程的操作系统中,通常一个进程都有若干个线程,至少包含一个线程。

根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位

资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的

影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。

执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行

进程:

管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。


命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。


消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。


共享存储SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。


信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。


套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。


信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

线程

锁机制:包括互斥锁、条件变量、读写锁

  • 互斥锁提供了以排他方式防止数据结构被并发修改的方法。
  • 读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
  • 条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。

信号量机制(Semaphore):包括无名线程信号量和命名线程信号量

信号机制(Signal):类似进程间的信号处理

线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。

死锁:多个共享资源,每个共享资源由各自的互斥量管理,超过一个线程加锁同一组互斥量时可能发生死锁

忘记释放锁、重复加锁、多线程多锁抢占锁资源

14. 信号量在操作系统中如何实现

15. 虚函数

通过基类访问派生类定义的函数。所谓虚函数就是在基类定义一个未实现的函数名,为了提高程序的可读性,建议后代中虚函数都加上virtual关键字。

其子类重新定义父类的做法这种行为成为覆盖(override),或者为重写。

  • override:保证在派生类中声明的重载函数,与基类的虚函数有相同的签名;
  • final:阻止类的进一步派生 和 虚函数的进一步重写。

在类的继承中,每一个class产生一堆指向virtual function的指针,放在vtbl(虚表)中。对于每一个class object 被添加了一个指针,指向相关的virtual table,这里指针称为vptr(虚指针)。

纯虚函数:

在基类中没有定义,但要求任何派生类都要定义自己的实现方法。作用:提供一个函数接口

virtual void funtion1()=0

含有纯虚函数的类叫做派生类

16. 构造函数、析构函数

析构函数于构造函数相对应,构造函数是对象创建的时候自动调用的,而析构函数就是对象在销毁的时候自动调用的的

当我们在类中声明了一些指针变量时,我们一般就在析构函数中进行释放空间,因为系统并不会释放指针变量指向的空间,我们需要自己来delete,而一般这个delete就放在析构函数里面

构造函数:

       1.构造函数是一种特殊的成员函数,不需要用户来调用,定义对象时被自动执行。

  2.构造函数名字与类名相同,无返回类型。

  3.可以由用户自己定义实现,根据需要设计对数据成员进行初始化,依旧可以设置函数的默认参数。

析构函数:清理工作,系统自动调用

       1.析构函数没有返回值,没有参数;

  2.没有参数,所以不能重载,一个类仅有一个析构函数;

  3.析构函数除了释放工作,还可以做一些用户希望它做的一些工作,比如输出一些信息。

17. C++内存管理

  1.  又叫 堆栈,一般存储 非静态局部变量函数参数返回值等等,向下增长(先使用高地址空间)
  2. 内存映射段 是 高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信
  3.  用于程序运行时 动态内存分配mallocrealloccalloc开辟出的空间即存储在此,向上增长(先使用低地址空间)
  4. 数据段,语言中常称作 静态区 ,存储全局数据和静态数据
  5. 代码段,语言中常称作 常量区 ,存储可执行的代码(二进制代码)、只读常量

18. 智能指针

C/C++面试:什么是智能指针?智能指针有什么作用?分为哪几种?各自有什么样的特点?_什么是智能指针?智能指针有什么作用?分为哪几种?各自有什么样的特点?-CSDN博客

手撕题:

1. 内存池

内存池(Memory Pool)是一种内存分配方式。通常我们习惯直接使用new、malloc等API申请内存,这样做的缺点在于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。

内存池优点:

(1)针对特殊情况,例如需要频繁分配释放固定大小的内存对象时,不需要复杂的分配算法和多线程保护。也不需要维护内存空闲表的额外开销,从而获得较高的性能。

(2)由于开辟一定数量的连续内存空间作为内存池块,因而一定程度上提高了程序局部性,提升了程序性能。

(3)比较容易控制页边界对齐和内存字节对齐,没有内存碎片的问题。

(4)当需要分配管理的内存在100M一下的时候,采用内存池会节省大量的时间,否则会耗费更多的时间。

(5)内存池可以防止更多的内存碎片的产生

(6)更方便于管理内存

经典内存池实现过程
(1)先申请一块连续的内存空间,该段内存空间能够容纳一定数量的对象;
(2)每个对象连同一个指向下一个对象的指针一起构成一个内存节点(Memory Node)。各个空闲的内存节点通过指针形成一个链表,链表的每一个内存节点都是一块可供分配的内存空间;
(3)某个内存节点一旦分配出去,从空闲内存节点链表中去除;
(4)一旦释放了某个内存节点的空间,又将该节点重新加入空闲内存节点链表;
(5)如果一个内存块的所有内存节点分配完毕,若程序继续申请新的对象空间,则会再次申请一个内存块来容纳新的对象。新申请的内存块会加入内存块链表中。

C++ 内存池介绍与经典内存池的实现-CSDN博客

#include <iostream>
#include <cstdlib>

class MemoryPool
{
private:
    void *memoryBlock;
    size_t blockSize;
    size_t numBlocks;
    bool *isOccupied;

public:
    MemoryPool(size_t blockSize, size_t numBlocks) : blockSize(blockSize), numBlocks(numBlocks)
    {
        memoryBlock = std::malloc(blockSize * numBlocks);
        isOccupied = new bool[numBlocks]();
    }

    ~MemoryPool()
    {
        std::free(memoryBlock);
        delete[] isOccupied;
    }

    void *allocate()
    {
        for (size_t i = 0; i < numBlocks; ++i)
        {
            if (!isOccupied[i])
            {
                isOccupied[i] = true;
                return static_cast<char *>(memoryBlock) + i * blockSize;
            }
        }
        return nullptr; // Out of memory
    }

    void deallocate(void *ptr)
    {
        if (ptr)
        {
            size_t index = (static_cast<char *>(ptr) - static_cast<char *>(memoryBlock)) / blockSize;
            isOccupied[index] = false;
        }
    }
};

int main()
{
    const size_t blockSize = 64;
    const size_t numBlocks = 10;

    MemoryPool pool(blockSize, numBlocks);

    void *ptr1 = pool.allocate();
    void *ptr2 = pool.allocate();

    if (ptr1 && ptr2)
    {
        std::cout << "Memory allocated successfully." << std::endl;

        // Use the allocated memory...

        pool.deallocate(ptr1);
        pool.deallocate(ptr2);
    }
    else
    {
        std::cerr << "Failed to allocate memory." << std::endl;
    }

    return 0;
}

2. lambda表达式

手写一个类实现lambda表达式

#include <iostream>

class MyLambda
{
private:
    using FuncType = int (*)(int); // 定义函数指针类型

    FuncType func; // 函数指针成员变量

public:
    // 构造函数,接受Lambda表达式作为参数
    MyLambda(FuncType f) : func(f) {}

    // 调用Lambda表达式的方法
    int operator()(int x) const
    {
        return func(x);
    }
};

int main()
{
    // 使用Lambda表达式创建一个函数,实现将整数加一
    auto lambda = [](int x)
    { return x + 1; };

    // 实例化MyLambda对象,传入Lambda表达式
    MyLambda myLambda(lambda);

    // 调用MyLambda对象,传入参数并输出结果
    std::cout << "Result: " << myLambda(5) << std::endl;

    return 0;
}

3. 手写单例模式

#include <iostream>

class Singleton {
    public: static Singleton &getInstance() {
        static Singleton s;
        return s;
    }

    public: void test() {
        std::cout << "test" << std::endl;
    }
};

int main() {
    Singleton &s = Singleton::getInstance();
    s.test();
    return 0;
}

4. 遍历二叉树(非递归)

5. 实现一个简易的shared_ptr

C++智能指针简单实现(面试常问) - 知乎 (zhihu.com)

再补充.....

还有其他常规C++八股总结:史上最全C/C++面试、C++面经八股文,一文带你彻底搞懂C/C++面试、C++面经!_c++八股-CSDN博客

C++八股文(基础面试题)_c++面试八股文-CSDN博客

参考:C/C++ : 函数返回值为结构体,以及返回指针_c++ 返回结构体-CSDN博客 C++基础重点:static和const关键字 - 知乎 (zhihu.com)C++中static、const、static const修饰变量作用详解_const static-CSDN博客编译的四个过程-预处理、编译、汇编、链接-阿里云开发者社区 (aliyun.com)CPU中断的工作原理,从最底层讲起_用逻辑门实现三选一-CSDN博客C++中的线程:完整指南 - 知乎 (zhihu.com)     进程和线程的区别(超详细)-CSDN博客进程间通讯的7种方式_进程间通信的几种方法-CSDN博客面试官:说说进程间通信和线程间通信的几种方式及区别 - 知乎 (zhihu.com)  C++虚函数详解-CSDN博客        C++ 内存池介绍与经典内存池的实现-CSDN博客  C++构造函数和析构函数详解 - 知乎 (zhihu.com)    析构函数-CSDN博客[C++] 超详细分析 C++内存分布、管理(new - delete)、C 和 C++ 内存管理关系、内存泄漏 - July.cc Blogs (julysblog.cn)c++: 单例模式(Singleton)的最优写法_c++单例模式 成员变量赋值-CSDN博客

八股内容大部分内容直接粘贴过来的

侵删

手撕题后期会收费才能看

  • 20
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在提供的引用内容中,有一个关于STL面试题的代码示例。这个示例展示了如何使用STL中的allocator类来进行内存分配和对象构造销毁的操作。在这个示例中,使用了一个Test类作为示例对象。首先,使用allocator的allocate方法来申请三个单位的Test内存,并将其赋值给指针pt。然后,使用allocator的construct方法来构建三个Test对象,并使用默认值或拷贝构造函数来初始化这些对象。最后,使用allocator的destroy方法来销毁这些对象,并使用deallocate方法释放之前分配的内存。这个示例展示了如何使用allocator来实现自定义内存管理和对象构造销毁的操作。 关于C++ STL面试题,根据提供的引用内容,我无法找到具体的面试题。请提供更具体的问题或者引用内容,以便我能够给出更准确的答案。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [C++ STL程序员面试题](https://download.csdn.net/download/kpxingxing/3697052)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [C++面试题 STL篇](https://blog.csdn.net/qq_31442743/article/details/109575971)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值