• C++面试题(基础)


    C++面试题

    文章目录


    1、变量的声明和定义

    • 声明
      • 告诉编译器变量的存在,不分配内存或初始化
      • 它通常发生在头文件或函数参数列表中,以便不同部分的代码可以知道这个变量的存在
    • 定义
      • 为变量分配存储空间并可以赋初值,它在程序中创建了变量的实体,使其可以在运行时存储数据
      • 变量只需要在一个地方定义一次,但可以在多个地方声明

    2、sizeof和strlen的区别

    • sizeof
      • 是一个运算符,它返回一个变量或数据类型的字节数
    • strlen
      • 是一个函数,用于计算字符串中的字符数

    3、volatile关键字

    用于告诉编译器不要对标记为 volatile 的变量进行优化,以确保其值在程序执行期间不会被缓存或重新排序

    volatile还可以告诉编译器不要将变量的值缓存在寄存器中,而是每次都从内存中读取。这对于与硬件通信或多线程编程等情况非常重要

    volatile还可以提醒其他程序员或维护人员,这个变量是特殊的,可能会在不同的地方更改,因此要特别小心处理

    4、数组和链表的区别

    • 数组
      • 数组是一种静态数据结构,其元素在内存中是连续存储的,具有固定的大小,因此它们对于随机访问和遍历元素非常高效。然而,数组的大小在创建时就确定,难以动态扩展或缩小,这限制了其灵活性。
    • 链表
      • 链表是一种动态数据结构,由节点组成,每个节点包含数据和指向下一个节点的指针。链表的元素在内存中不一定是连续存储的,这使得插入和删除元素非常高效,因为只需调整指针,而不需要移动大量数据。链表的大小可以动态增长或缩小,因此更适合用于动态的数据结构。

    5、引用和指针的区别

    1. 语法差异: 引用使用&符号声明,而指针使用*符号。引用在声明时需要初始化,而指针可以稍后初始化或者指向不同的对象。
    2. 空值: 指针可以为空,即指向空(null)地址,而引用必须始终引用某个有效的对象。
    3. 别名 vs 变量: 引用是变量的别名,操作引用等同于操作原始变量。指针是一个独立的变量,它存储另一个变量的地址,需要通过解引用运算符*来访问目标变量。
    4. 多次赋值: 引用一旦初始化,无法重新绑定到另一个对象。指针可以在需要时重新指向不同的对象。
    5. 传递方式: 通常,引用用于传递函数参数,使函数能够修改原始数据。指针也可以用于这一目的,但需要更多的注意来避免悬空指针或内存泄漏。

    6、static关键字

    1. 静态成员变量: 静态成员变量属于类而不是类的实例。它们被共享并存储在类的单一内存位置中,可用于跟踪整个类的信息。
    2. 静态全局变量: 静态全局变量在整个程序的生命周期内存在,但它们仅在定义它们的文件中可见。它们可用于在单个文件内共享数据,而不暴露给其他文件。
    3. 静态局部变量: 静态局部变量在函数内部定义,但仅初始化一次,然后保持其值。它们通常用于避免重复初始化和在多次函数调用之间保持状态。
    4. 静态成员函数: 静态成员函数是属于类而不是类的实例的函数,它可以直接通过类名调用,无需创建类的对象。这些函数通常用于执行与类相关但不需要特定对象实例的任务,如全局配置或实用函数。

    7、const关键字

    1. 常量声明: 通过在变量前加上const关键字,可以声明一个常量,其值在初始化后不可更改,有助于提高代码的可读性和安全性。
    2. 成员函数: 在类中,const可用于声明成员函数,表示该函数不会修改类的成员变量。这有助于确保在调用函数时不会意外地修改对象的状态。
    3. 函数参数: 在函数参数中使用const表示该参数是只读的,不能在函数内部修改。这有助于传递参数时保护其内容。
    4. 返回值: 在函数声明中,const可以表示该函数返回一个常量值,不可用于赋值操作。

    总之,const在C++中用于创建常量、指定不可修改的数据、保护函数参数和防止不必要的数据更改,有助于提高代码的可靠性和维护性。

    const int *a; *//等于 int const \*a;*
    
    int * const a; 
    
    int const * const a; *//等于 const int\* const a;*
    
    以*为分界点, 
    当const在*的左边时,实际物体值不变
    当const在*的右边时,指针不变
    当const在*的两边时,指针和指向的数都不可变
    即, 左物右指
    这样来记比较方便!! 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    8、内存四区

    1. 栈区(Stack): 栈区用于存储局部变量和函数调用的上下文信息。它是一个自动分配和释放内存的区域,通常包含函数参数、局部变量和函数调用的返回地址。栈上的数据在函数调用结束后会自动释放,因此具有较短的生命周期。
    2. 堆区(Heap): 堆区用于存储动态分配的内存,通常由程序员手动分配和释放。堆上的数据的生命周期可以很长,需要程序员负责管理内存的分配和释放,以避免内存泄漏。
    3. 全局/静态区(Global/Static): 全局区用于存储全局变量和静态变量。这些变量在程序启动时创建,直到程序结束时才销毁。全局区中的数据在整个程序执行期间都是可用的。
    4. 常量区(Constant): 常量区用于存储常量数据,如字符串常量。这些数据通常存储在只读内存区域,不能被修改。常量区中的数据对于所有程序实例都是相同的。

    9、struct和union的区别

    • struct(结构体)
      • struct是一种用户自定义的数据结构,每个成员变量在内存中占据独立的空间,结构体的大小等于所有成员变量大小之和。
      • 可以同时访问结构体的所有成员,每个成员都有自己的地址。
      • 每个成员可以独立初始化。
    • union(联合)
      • union也是一种用户自定义的数据结构,联合的内存大小等于最大成员的大小,因为只能存储一个成员的值。
      • 只能同时访问一个成员,因为它们共享相同的内存,访问一个成员可能会影响其他成员的值。
      • 只能初始化一个成员,因为只能存储一个成员的值。

    10、#define和const的区别

    • #define
      • #define创建的常量没有特定类型,它们只是文本替换
      • #define常量在预处理阶段进行替换,可能导致多个重复的值
      • 不能使用指针指向#define常量,因为它们没有明确的内存位置
      • #define可以用于定义简单的宏函数。
    • const
      • const常量有明确定义的类型,可以提供更好的类型安全性
      • const常量在编译时确定其值,只有一个实例,不会有多个拷贝
      • 可以使用指针指向const常量,因为它们在内存中有确定的位置
      • const不用于定义函数,它是用于创建常量变量的关键字

    11、为什么有#define了还要用const

    1. 类型安全性const 创建的常量具有明确定义的数据类型,这提供了类型安全性。如果使用错误的类型,编译器将发出错误。而 #define 创建的常量只是文本替换,没有类型信息,可能导致类型错误。
    2. 调试和可读性const 常量在编译后会保留类型信息,这对调试和代码可读性非常有帮助。#define 常量只是文本替换,不会在调试信息中显示。
    3. 作用域控制const 常量可以具有作用域,可以限定在特定的代码块中,而 #define 常量在整个文件中都有效。
    4. 指针关联:你可以使用指针指向 const 常量的地址,这对于处理常量数据很有帮助。使用 #define 常量时,它们没有确定的内存位置,无法使用指针。

    总之,虽然 #defineconst 都可以用于定义常量,但 const 在类型安全性、调试可读性、作用域控制和指针关联方面提供了更多的优势。因此,现代C++编程中通常更倾向于使用 const 来定义常量。

    12、重载,覆盖,隐藏

    1. 重载(Overload):在同一作用域内定义多个具有相同名称但不同参数列表的函数,编译器根据参数来选择正确的函数。
    2. 覆盖(Override):在派生类中重新定义基类中的虚函数,以提供新的实现。覆盖的函数在基类和派生类中必须具有相同的函数原型,包括函数名称、参数类型和返回类型。
    3. 隐藏(Hide):在派生类中定义一个与基类中函数名称相同但参数列表不同的函数,导致基类函数在派生类中不可见。隐藏的函数在派生类中是一个全新的函数,不要求是虚函数。

    13、new、delete、malloc、free之间的关系

    1. newdelete(C++中的运算符):
      • new 是C++中的运算符,用于在堆上动态分配内存,并返回分配内存的指针。它还会调用对象的构造函数。
      • delete 是C++中的运算符,用于释放通过 new 分配的内存,并调用对象的析构函数。它与 new 配对使用,确保资源的正确释放。
    2. mallocfree(C语言中的函数):
      • malloc 是C语言中的库函数,用于在堆上动态分配内存,返回一个指向分配内存的指针。它不会调用对象的构造函数。
      • free 是C语言中的库函数,用于释放通过 malloc 分配的内存。它与 malloc 配对使用,确保资源的正确释放。

    关键区别:

    • newdelete 是C++特有的,与类和对象相关。它们处理内存和对象的生命周期。
    • mallocfree 是C语言中的标准库函数,用于处理一般的内存分配和释放,不处理对象构造和析构。
    • 在C++中,可以混合使用 newdelete 以及 mallocfree,但要小心避免混淆和内存泄漏。

    在现代C++中,推荐使用 newdelete 来进行内存管理,因为它们更安全,能够正确处理对象的构造和析构。

    14、delete 和 delete[]

    1. delete 用于释放单个对象的内存,而 delete[] 用于释放数组对象的内存。

    2. 如果使用 new 分配了单个对象的内存,应使用 delete 进行释放;如果使用 new[] 分配了数组对象的内存,应使用 delete[] 进行释放。

    15、虚函数、纯虚函数

    1. 虚函数(Virtual Function)
      • 虚函数是在基类中声明为虚函数的函数,其特征是在派生类中可以被覆盖(重写)。
      • 虚函数用 virtual 关键字进行声明,例如:virtual void myFunction() { /* 函数实现 */ }
      • 虚函数允许运行时多态性(动态绑定),即可以使用基类指针或引用来调用派生类的实现。
      • 基类中的虚函数可以有默认实现,但它们通常在派生类中被重写以提供具体的实现。
    2. 纯虚函数(Pure Virtual Function)
      • 纯虚函数是在基类中声明为纯虚函数的函数,它没有默认实现,只有函数原型。
      • 纯虚函数用 virtual 关键字和 = 0 进行声明,例如:virtual void myFunction() = 0;
      • 派生类必须提供对纯虚函数的具体实现,否则派生类也将成为抽象类。
      • 纯虚函数用于定义一个接口,以确保派生类提供必要的实现。

    16、STL库

    STL(Standard Template Library)是C++标准库中的一个重要组成部分,提供了各种通用数据结构和算法,以及用于处理这些数据结构的迭代器等工具。

    • 容器(Containers):

      • 序列式容器(Sequence Containers):这些容器按照元素的线性顺序存储数据,可以在其中插入、删除和访问元素。常见的序列容器包括:
        • vector:动态数组
        • list:双向链表
        • array:固定大小的数组
      • 关联式容器(Associative Containers):这些容器按照键(key)来组织元素,用于高效的查找和检索。常见的关联容器包括:
        • set:有序不重复元素集合
        • multiset:有序可重复元素集合
        • map:有序键-值对映射
    • 算法(Algorithms):

      • sort:对序列进行排序。
      • find:在序列中查找指定元素。
      • for_each:对序列中的每个元素执行指定操作。
      • count:计算序列中指定元素的数量。
      • remove:删除容器中的指定元素。
    • 迭代器(Iterators): 用于遍历容器中的元素,提供了统一的接口。

    • 函数对象(Functors): 可以像函数一样使用的对象,用于自定义算法的行为。

    • 适配器(Adapters): 提供了容器和迭代器之间的适配器,如栈适配器(stack adapter)和队列适配器(queue adapter)。

    17、C++文件编译与执行的四个阶段

    1. 预处理:根据文件中的预处理指令来修改源文件的内容
    2. 编译:编译成汇编代码
    3. 汇编:把汇编代码翻译成目标机器指令
    4. 链接:链接目标代码生成可执行程序

    18、STL常用容器实现原理

    1. vector: vector 是基于动态数组实现的。它在内存中分配一块连续的内存区域,当元素数量达到容量上限时,会重新分配更大的内存块,并将元素复制过去。
    2. list: list 是双向链表的实现。每个元素都包含一个指向前一个元素和后一个元素的指针。
    3. set 和 map: setmap 通常使用平衡二叉搜索树(如红黑树)来实现,以保持元素的有序性和快速查找。
    4. unordered_set 和 unordered_map: unordered_setunordered_map 使用哈希表作为底层数据结构,以提供快速的查找和插入操作。

    19、STL中unordered_map和map的区别

    • map是STL中的一个关联容器,提供键值对的数据管理。底层通过红黑树来实现,实际上是二叉排序树和非严格意义上的二叉平衡树。所以在map内部所有的数据都是有序的,且map的查询、插入、删除操作的时间复杂度都是O(logN),存在重复值。
    • unordered_map和map类似,都是存储key-value对,可以通过key快速索引到value,不同的是unordered_map不会根据key进行排序。unordered_map底层是一个防冗余的哈希表,存储时根据key的hash值判断元素是否相同,即unoredered_map内部是无序的,不存在重复值。

    20、构造函数为什么一般不定义为虚函数?而析构函数一般写成虚函数的原因

    • 为什么构造函数不定义

      • 构造函数的目的是创建对象并初始化其状态,包括分配资源、设置成员变量等。
      • 在对象创建的过程中,虚函数表还没有建立,因此虚函数的机制不适用于构造函数。
    • 为什么析构函数定义

      • 析构函数的目的是在销毁对象时释放资源和清理状态。
      • 当使用基类指针指向派生类对象时,如果基类的析构函数不是虚函数,那么在销毁对象时只会调用基类的析构函数,而不会调用派生类的析构函数。
      • 这可能导致资源泄漏和不正确的清理。通过将析构函数声明为虚函数,可以确保在销毁对象时正确调用派生类的析构函数,以便释放资源和执行清理操作。

    21、拷贝构造函数

    • 拷贝构造函数是一种特殊的构造函数,用于创建一个对象并使其与另一个对象具有相同的数据状态。
    • 它通常用于对象之间的赋值操作和传递对象作为函数参数时,确保新对象与源对象具有相同的数据。
    • 编译器默认生成的拷贝构造函数通常执行浅拷贝,自定义拷贝构造函数可以用于执行深拷贝,以确保对象之间不共享相同的资源。拷贝构造函数对于维护对象独立性和避免资源泄漏非常重要。

    22、野指针

    野指针不是NULL指针,是未初始化或者未清零的指针

    1. 释放后未置空: 当你使用deletefree释放动态分配的内存后,如果不将指针置为nullptr(或NULL),它可能仍然包含之前的内存地址,从而成为野指针。
    2. 超出作用域: 当指针指向的对象超出了其作用域(例如,局部变量在函数结束后),指针将成为野指针。
    3. 指针悬空: 当指针指向的对象已经被销毁或释放,但指针仍然在使用,导致它成为野指针。

    23、vector和list的区别

    • vector
      • vector使用动态数组(array)作为底层数据结构,因此元素在内存中是连续存储的。这使得对元素的随机访问非常高效,但在插入和删除元素时可能涉及数据的移动。
      • 由于vector的元素是连续存储的,它通常会占用更少的内存,因为不需要额外的指针。
    • list
      • list使用双向链表作为底层数据结构,每个元素都包含了指向前一个和后一个元素的指针。这使得插入和删除元素非常高效,但随机访问需要遍历链表,效率相对较低。
      • list的元素需要额外的指针来维护链表结构,因此通常占用更多内存。

    24、友元

    • 友元函数
      • 友元函数是一个不属于类的函数,被声明为某个类的友元,允许访问该类的私有成员。
    • 友元类
      • 友元类是一个类,被声明为另一个类的友元,允许友元类的成员函数访问该类的私有成员。

    这些机制提供了更灵活的访问控制,但需要谨慎使用,以避免破坏类的封装性和安全性。

    25、struct 和 class 区别

    在C++中,structclass都是用于定义用户自定义数据类型的关键字,它们有一些区别,主要涉及到成员的默认访问权限

    1. 默认访问权限
      • struct:成员的默认访问权限是public,即结构体中的成员可以被外部访问。
      • class:成员的默认访问权限是private,即类中的成员默认是私有的,无法被外部直接访问。
    2. 用途
      • struct:通常用于表示一组相关的数据,类似于记录,其成员通常是公有的,用于数据聚合。
      • class:通常用于表示一个抽象的数据类型,其成员可以包含数据和成员函数,用于封装数据和行为。
    3. 继承
      • struct:支持继承,可以派生自其他结构体或类。
      • class:同样支持继承,可以派生自其他类。

    26、include头文件的“”和<>的区别

    • 对于双引号包含的头文件,查找头文件默认为当前头文件目录下查找。

    • 而尖括号查找头文件默认是编译器设置的头文件路径下查找

    27、智能指针

    智能指针是C++中的类模板,用于管理动态分配的内存资源。它们自动处理内存的分配和释放,从而减少内存泄漏的风险。

    C++11引入了std::shared_ptrstd::unique_ptr

    • std::shared_ptr 允许多个智能指针共享同一个对象,维护一个引用计数。当引用计数为零时,对象将被销毁。
    • std::unique_ptr 代表独占所有权,只有一个智能指针可以拥有对象。它更轻量,因为不需要维护引用计数。

    28、智能指针的性能如何

    智能指针通常比原始指针稍微慢一些,因为它们需要额外的开销来管理引用计数等信息。但在现代C++中,这种性能差异通常可以忽略不计。

    29、C++11新特性

    1. 范围for循环: 简化了遍历容器和数组的操作。
    2. Lambda表达式: 允许创建匿名函数,提供更灵活的函数定义方式。
    3. 智能指针: std::shared_ptrstd::unique_ptr 等用于更安全地管理动态内存。
    4. 移动语义和右值引用: 提高了对象的资源管理效率,减少了不必要的拷贝操作。
      • 右值引用: 右值引用是一种新的引用类型,用双引号(&&)表示。它主要用于绑定到临时对象或可以被"移动"的对象,即将要销毁的对象。右值引用允许修改、移动或获取临时对象的内容。
      • 移动语义: 移动语义是一种编程范式,它利用右值引用来实现更有效的资源管理。它允许在对象之间转移资源,而不是复制资源。例如,将一个临时对象的内容(右值)转移到另一个对象,而不是执行深拷贝操作。
        • 标准库中的容器,如std::vector,已经实现了移动语义,使得容器的操作更加高效。

    30、TCP和UDP的区别及使用场景

    TCP:

    • 面向连接:需要建立连接,数据传输完成后关闭连接。
    • 可靠性:保证数据不丢失、不乱序,提供数据完整性。
    • 流量控制:控制数据发送速率,适应接收端处理能力。
    • 面向字节流:数据被视为字节流。
    • 适用于需要可靠传输和顺序传输的应用。

    UDP:

    • 无连接:无需建立连接,直接发送数据包。
    • 不可靠性:不保证数据可靠性,数据可能丢失或乱序。
    • 面向消息:以消息为单位发送和接收数据。
    • 适用于实时性要求高、数据量较小、容忍数据丢失的应用。

    TCP适用于需要可靠传输的应用,而UDP适用于实时性要求高、可以容忍数据丢失的应用。

    31、单例模式

    1. 什么是单例模式?
      • 单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供全局访问点。
    2. 如何实现单例模式?
      • 可以通过将构造函数私有化(private),并提供一个静态方法来获取唯一的实例。
    3. 什么是懒汉式和饿汉式单例?
      • 懒汉式是在首次访问时创建单例实例,而饿汉式是在类加载时立即创建实例。
    4. 如何处理多线程下的单例?
      • 可以使用互斥锁(mutex)来保证线程安全,或者使用双重检查锁(Double-Checked Locking)。

    32、工厂模式

    1. 什么是工厂模式?
      • 工厂模式是一种创建型设计模式,它提供了一种创建对象的接口,但允许子类决定实例化哪个类。
    2. 工厂模式的不同类型是什么?
      • 主要有简单工厂、工厂方法和抽象工厂。简单工厂是通过一个工厂类创建不同类型的对象。工厂方法是将对象创建延迟到子类中。抽象工厂提供一个接口来创建一系列相关对象。
    3. 工厂模式和单例模式之间的关系?
      • 工厂模式通常用于创建多个对象实例,而单例模式用于确保只有一个对象实例。
    4. 举例说明工厂模式的应用场景。
      • 工厂模式常用于创建不同类型的数据库连接、日志记录器、UI控件等。

    33、什么函数不能声明为虚函数

    1. 构造函数和析构函数: 构造函数(包括拷贝构造函数)不能声明为虚函数。虚函数的调用是在对象构造完成后才发生的,而构造函数的执行顺序是固定的,不允许虚函数的动态绑定。
    2. 静态成员函数: 静态成员函数属于类而不是对象,因此它们不能被声明为虚函数。虚函数是与对象实例相关的,而静态成员函数与类关联。
    3. 普通(非成员)函数: 普通的全局函数或类外的非成员函数不能声明为虚函数。虚函数必须属于类,通过类的成员函数来实现。

    34、析构函数可以是纯虚的吗

    • 析构函数可以声明为虚函数,以支持多态性和动态绑定,但不可以声明为纯虚函数。

    35、C++中为什么用模板类

    • 模板类允许编写通用的代码,适用于多种数据类型,提高代码的重用性和通用性。通过参数化类型,可以在编译时实现类型安全。这样可以减少冗余代码,支持STL,自定义数据类型,提高C++代码的灵活性和效率。

    36、三次握手

    三次握手是TCP协议中用于建立连接的过程

    1. 客户端向服务器发送一个连接请求(SYN)。
    2. 服务器收到请求后,回应一个确认(ACK)和自己的连接请求(SYN)。
    3. 客户端再次确认服务器的响应,向服务器发送确认(ACK)。

    37、四次挥手

    四次挥手是TCP协议中用于关闭连接的过程

    1. 客户端向服务器发送连接终止请求(FIN)。
    2. 服务器收到请求后,回应一个确认(ACK),表示已经准备好关闭连接,但仍可向客户端发送数据。
    3. 服务器完成数据发送后,向客户端发送连接终止请求(FIN)。
    4. 客户端收到请求后,回应一个确认(ACK),表示也准备好关闭连接。

    38、extern “C”

    extern "C"是C++中用于与C语言代码进行交互的关键字,它告诉C++编译器按照C语言的规则处理函数名,以确保正确的链接和符号匹配。这通常用于混合编程,以确保C++代码能够与C代码协同工作。

    39、拷贝构造函数在哪几种情况下会被调用

    1. 对象初始化,如用一个对象去初始化另一个对象。
    2. 对象作为函数参数传递给函数。
    3. 对象作为函数的返回值。
    4. 创建临时对象,例如在表达式中。
    5. 值传递,当对象通过值传递给函数时。

    40、Get/Post的区别

    1. GET请求将数据附加到URL的查询字符串中,数据暴露在URL中,不适合传输敏感数据。POST请求将数据放在请求体中,数据不可见于URL,相对安全,适合传输敏感数据。
    2. GET请求对数据长度有限制,通常受浏览器或服务器的限制。POST请求通常没有固定的数据长度限制。
    3. GET请求可以被浏览器缓存,可以提高性能。POST请求通常不被缓存。
    4. GET用于浏览页面、请求资源、搜索等无副作用的操作。POST用于提交表单数据、上传文件、进行数据更新等有副作用的操作。

    41、http和https分别是什么?他们的区别是什么

    HTTP和HTTPS都是用于在网络上传输数据的协议

    1. HTTP(Hypertext Transfer Protocol):
      • 不安全,数据以明文传输,容易被窃听或篡改。
      • 默认端口号是80。
      • 性能较快,适用于一般网站。
    2. HTTPS(Hypertext Transfer Protocol Secure):
      • 安全,数据加密传输,难以被窃听或篡改。
      • 默认端口号是443。
      • 性能较慢,但随着硬件和加密算法的改进,影响逐渐减小。

    42、锁的实现方式

    1. 互斥锁(Mutex):互斥锁是最常见的锁,用于保护共享资源,确保在任何时刻只有一个线程可以进入临界区。它提供了线程安全的访问机制,但可能会引入线程切换的开销。
      • std::mutexstd::mutex 是C++标准库中的互斥锁,最常见的锁之一。它用于实现基本的线程互斥,确保同时只有一个线程可以访问临界区。你可以使用 std::mutex 来保护共享资源,以确保线程安全。
    2. 读写锁(Read-Write Lock):读写锁允许多个线程并发读取共享资源,但在写操作时需要排他性访问。这提高了读取性能,适用于读多写少的场景。
    3. 自旋锁(Spinlock):自旋锁不会将线程挂起,而是会循环等待资源的可用性,适用于短暂的临界区。自旋锁避免了线程切换的开销,但需要小心避免忙等待时间过长,以免影响性能。

    43、线程与进程的区别和联系

    1. 定义:进程是独立的执行实体,拥有独立的内存空间。线程是进程中的一个执行单元,共享相同的内存空间。
    2. 资源开销:进程之间的切换开销较大,线程之间的切换开销较小。
    3. 通信和同步:进程之间的通信需要特殊机制,如消息队列。线程之间可以共享内存,更容易实现通信和同步。
    4. 稳定性:进程之间的崩溃不影响其他进程,线程之间的崩溃可能影响整个进程。

    44、树

    1. 二叉树(Binary Tree):

    • 每个节点最多有两个子节点,通常称为左子树和右子树。

    2. 二叉搜索树(Binary Search Tree,BST):

    • 对于每个节点,其左子树中的所有节点的值都小于它的值。
    • 对于每个节点,其右子树中的所有节点的值都大于或等于它的值。

    3. 平衡二叉树:

    • 平衡二叉树是一种二叉搜索树,它确保左子树和右子树的高度差不超过1。
    • AVL树是一种常见的平衡二叉树,它通过旋转操作来维护平衡。

    4. B树和B+树:

    • B树是一种自平衡的多路搜索树,B树的节点可以包含多个子节点,而不仅限于两个。
    • B+树也是一种自平衡的多路搜索树,B+树中的非叶子节点不存储数据,只存储键值范围,数据只存储在叶子节点。

    45、Twap算法

    • TWAP算法是一种交易算法,用于执行大额交易订单,以平均价格的方式逐渐完成订单,以减少对市场价格的冲击。算法的核心思想是将整个订单分散在一段时间内均匀执行,以获得平均价格。这有助于降低对市场造成的影响,特别是在处理大额订单时。

    46、红黑树和AVL树的定义,特点,以及二者区别

    1. 平衡要求: AVL树要求更加严格的平衡,确保左子树和右子树的高度差不超过1。而红黑树的平衡要求相对宽松一些,只要满足一些基本平衡性质即可。
    2. 节点结构: 红黑树的节点比AVL树的节点多一个颜色属性,通常用红色或黑色表示。这个颜色属性用于维护树的平衡性质。
    3. 旋转操作: 在维护平衡时,AVL树使用更多的旋转操作来保持平衡,而红黑树则使用颜色属性和少量的旋转操作。
    4. 性能: 由于AVL树的平衡要求更严格,在插入和删除操作时需要更多的旋转,因此在某些情况下性能可能略逊于红黑树。红黑树则在插入和删除操作上相对高效。

    总的来说,AVL树提供了更严格的平衡性,但因此可能在某些操作上更慢。红黑树在平衡和性能之间取得了一种平衡,适用于广泛的应用场景。它们都是平衡二叉树。

    47、Socket通信中的粘包和拆包问题是什么

    1. 粘包问题:粘包问题发生时,多个小消息被粘在一起发送,形成一个大消息。接收端需要额外的逻辑来分离和识别这些消息。例如,发送端发送"Hello"和"How are you?“,接收端可能会收到"HelloHow are you?”。接收端必须分解这个消息以获取原来的两个消息。
    2. 拆包问题:拆包问题是相反的情况。发送端发送一个大消息,但接收端以小块的方式接收它,这可能导致接收端无法正确重建原始消息。例如,发送端发送"HelloHow are you?“,接收端可能首先收到"Hello”,然后在稍后收到"How are you?"。接收端必须将这些碎片重组为原始消息。

    48、函数指针,指针函数

    • 函数指针是指向函数的指针,它可以用来动态选择并调用不同的函数。
    • 指针函数是一个函数,其返回类型是指针。指针函数返回一个指针,该指针可以指向某个数据或对象。

    49、指针数组,数组指针

    • 数组指针是一个指向数组的指针。它指向整个数组,而不是数组中的单个元素。

    • 指针数组是一个数组,其中的元素都是指针。

    50、C++为什么用模板类

    • 模板类在C++中用于实现泛型编程,提高了代码的重用性、类型安全性和灵活性。通过模板类,可以编写一次代码,适用于多种不同数据类型,提高了代码的灵活性和性能优化。

    51、如何定义一个只能在堆上创建的类

    • 提供一个静态工厂方法,返回值处提供一个类的指针,通过new关键字在new出一个对象就可以在堆上分配内存了

    52、如何定义一个只能在栈上创建的类

    • 可以通过删除类的 newdelete 运算符或将它们设置为私有来禁用堆上的动态内存分配。此外,在函数中声明和使用该类的对象,以限制其生命周期仅在函数范围内,从而强制将其分配到栈上。这样可以确保对象不会在堆上创建。

    53、内联函数有什么优点?内联函数和宏定义的区别

    1. 内联函数:
      • 内联函数是由C++编译器处理的,提供了一种更安全、类型检查更严格的代码重用方法。
      • 内联函数的代码会被嵌入到调用它的地方,避免了函数调用的开销。
      • 内联函数通常由inline关键字定义
    2. 宏定义:
      • 宏定义是一种在预处理阶段进行文本替换的机制,不由编译器处理。
      • 宏定义只是简单地将文本替换为宏定义的内容,没有类型安全和错误检查。
      • 宏定义通常由#define指令定义。

    总之,内联函数提供了更安全、更可读且更易维护的代码重用方式,而宏定义提供了更灵活但更容易出错的文本替换机制。在C++中,应尽量使用内联函数以提高代码的可维护性和安全性。

    54、常见的消息队列

    1. RabbitMQ: RabbitMQ是一个开源的消息代理软件,实现了高级消息排队、可靠性、弹性和可扩展性等特性。它支持多种协议,包括AMQP,STOMP,MQTT等,适用于多种应用场景。
    2. Kafka: Kafka是一个分布式流处理平台和消息代理,旨在处理高吞吐量、持久性和实时数据流。它常用于日志和事件流处理。
    3. **ActiveMQ:**ActiveMQ是一个基于JMS(Java消息服务)规范的消息代理,支持多种通信协议,包括OpenWire、AMQP、STOMP等。
    4. **RocketMQ:**RocketMQ是一个开源的分布式消息队列系统,特别适用于大规模的数据处理和实时分析。

    55、RabbitMQ

    RabbitMQ是一种消息队列中间件,它用于在分布式系统中实现异步通信和解耦组件之间的通信。

    1. 消息生产者和消费者: 不同的模块需要进行异步通信。使用RabbitMQ来实现消息的发布和订阅机制。生产者将消息发送到队列,而消费者从队列中接收和处理这些消息。
    2. 可靠性和持久性: RabbitMQ支持消息的持久性,这意味着即使在消息代理重启后,消息也不会丢失。确保了消息的可靠性传递,并配置了队列和交换机以确保消息的持久性。
    3. 消息路由和交换机: 使用不同类型的交换机,如直接交换机、主题交换机和扇出交换机,来定义消息的路由规则。这使我们能够将消息发送到多个队列或选择性地让消费者订阅消息。
    4. 集群和负载均衡: 为了确保高可用性和负载均衡,我们设置了RabbitMQ的集群。这确保了即使某个节点出现故障,系统也能继续工作。
    5. 消息序列化: 我们使用了不同的消息序列化格式,如JSON和Protocol Buffers,以满足不同消息的需求。
  • 相关阅读:
    还可以这样实现ARM+ROS(机器人操作系统)
    安装vue vue-server-renderer报错
    PrestoSQL, PrestoDB 和 Trino
    给Docker一个辈分(备份),免得无后...
    融合动态概率阈值和自适应变异的鲸鱼优化算法-附代码
    声纹技术(五):声纹分割聚类技术
    【win10和win11打印机局域网共享】
    项目进度管理
    题解:ABC319C - False Hope
    手机能做静态二维码吗?用手机做二维码的教程
  • 原文地址:https://blog.csdn.net/lufeia/article/details/133888981