当前位置: 首页>编程语言>正文

2024最新大厂C++面试真题合集,玩转互联网公司面试!

小米C++

1. 进程和线程的区别

进程是操作系统分配资源和调度的独立单位,拥有自己的地址空间和系统资源。线程是进程内部的执行单元,共享属于相同进程的资源,但是执行切换代价更小。进程间相互独立,稳定性较高;线程间共享内存,创建和切换成本较低,但一个线程的失败可能影响同进程的其他线程。

2.进程通信方式

进程通信方式主要包括:

  1. 管道:允许一个进程和另一个与它有共同祖先的进程之间进行通信。
  2. 消息队列:消息的链表,存储在内核中,由进程间发送接收消息。
  3. 共享内存:允许多个进程访问同一块内存空间,是最快的IPC方式。
  4. 信号量:主要用于解决进程间的同步问题。
  5. 套接字:适用于不同机器间的进程通信。
  6. 信号:用于通知接收进程某个事件已经发生。

3.socket与其他通信方式有什么不同

  1. Socket 可以实现不同主机间的进程通信,适用于网络中跨操作系统通信。
  2. Socket 通常支持全双工通信,即同一时间可以进行数据的双向传输。
  3. Socket 支持面向连接(如TCP协议)和无连接(如UDP协议)的通信方式。
  4. 使用 Socket 进行通信需要创建、配置、使用和关闭套接字,比其他通信方式如管道和信号等有更明确的使用流程。

4.共享内存安全吗,有什么措施保证

共享内存本身没有内建安全措施,通过以下方式确保安全性:

  1. 使用互斥锁或信号量来同步对共享内存的访问。
  2. 实施访问控制,限制哪些进程可以访问共享内存。
  3. 定期检查和清理,避免僵尸进程造成的资源泄露。
  4. 根据需求实现读写锁,允许多读单写的安全访问模式。

5.linux中内存分布有哪些,cpp呢
Linux中内存分布主要包括:

  1. 用户空间:应用程序占用的内存区域。
  2. 内核空间:操作系统内核及其数据结构和模块占用的内存区域。
  3. 栈区:自动分配和释放的本地变量存储区域。
  4. 堆区:动态分配内存区域,需手动分配和释放。
  5. 代码区:存放程序执行代码。
  6. 数据区:存放全局变量和静态变量。

C++中内存分布主要包括:

  1. 栈(Stack):局部变量和函数调用。
  2. 堆(Heap):动态分配的内存(使用new和delete管理)。
  3. 全局/静态存储区:全局变量和静态变量。
  4. 常量存储区:常量字符串和常量值。
  5. 代码区:函数体的二进制代码。

6.多态实现方式
多态在C++中的实现方式通常有两种:

  1. 虚函数(通过类的继承和虚函数表实现)
  2. 函数重载(同一作用域内多个同名函数通过参数列表区分)

7.cpp面向对象的优势

C++面向对象编程(OOP)的优势包括:

  1. 封装:提高代码安全性和复用性,隐藏实现细节。
  2. 继承:提高代码可维护性,通过基类和派生类体系结构重用代码。
  3. 多态:提高代码的可扩展性和灵活性,允许接口多种实现。
  4. 抽象:简化复杂性,只暴露核心操作,底层细节被抽象化。

手撕:数组的部分元素求和

#include <iostream>
#include <vector>
using namespace std;

// 函数用于计算数组中指定范围内元素的和
int sumOfSubArray(const vector<int>& nums, int start, int end) {
    int sum = 0;
    for (int i = start; i <= end; ++i) {
        sum += nums[i];
    }
    return sum;
}

int main() {
    vector<int> nums = {1, 2, 3, 4, 5}; // 示例数组
    int start = 1; // 起始索引
    int end = 3; // 结束索引
    cout << "The sum of elements from index " << start << " to " << end << " is: " << sumOfSubArray(nums, start, end) << endl;
    return 0;
}

招银网络c++

1.c++内存泄漏

C++中内存泄漏是指程序分配的内存未被释放且无法再次被访问,常见原因包括:

  1. 动态分配的内存(使用newmalloc)没有使用deletefree释放。
  2. 使用指针指向新的内存区域,而忘记释放原有内存。
  3. 数据结构中的循环引用导致无法自动释放。

避免内存泄漏的方法包括:

  1. 使用智能指针(如std::unique_ptrstd::shared_ptr)自动管理内存。
  2. 适时使用deletefree释放不再需要的动态分配内存。
  3. 定期使用内存检测工具(如Valgrind)检测和定位内存泄漏问题。

2.智能指针

智能指针是一种在C++中用于自动管理动态分配的内存生命周期的类模板。它们通过提供类似指针的接口,并在适当的时候自动释放所管理的内存,帮助避免内存泄漏和指针错误。

C++标准库中常用的智能指针有:

  1. std::unique_ptr:独占所有权模型,不可复制但可以移动。它保证同一时间只有一个智能指针拥有对某块内存的控制。
  2. std::shared_ptr:共享所有权模型,可以被复制,并通过引用计数来管理内存。当没有任何shared_ptr指向一块内存时,该块内存会被自动释放。
  3. std::weak_ptr:伴随shared_ptr的非拥有型智能指针,用来解决shared_ptr相互引用时可能产生的循环依赖问题。不增加引用计数,因此不影响其指向的对象的生命周期。

3.智能指针有什么缺点

  1. 性能开销:智能指针(尤其是std::shared_ptr)通过引用计数来管理内存,这会增加额外的性能开销,比原始指针慢。
  2. 循环引用问题std::shared_ptr在存在循环引用时会导致内存泄漏,因为引用计数永远不会达到0,除非使用std::weak_ptr打破循环。
  3. 复杂性增加:虽然智能指针有助于内存管理,但不当使用可以增加程序复杂性,误用可能导致难以调试的问题,如悬挂指针或者提前释放等。
  4. 不适用场景:特定场景下(如高性能或者低延迟要求的应用),智能指针所带来的额外开销可能是不可接受的。

4.Sharedptr中引用计数在内存空间的哪里

std::shared_ptr 中的引用计数通常存储在动态分配的内存区域中,这块区域被称为控制块(control block)。控制块与智能指针所管理的对象内存是分离的,但通常是连续分配的,以减少分配次数和提高空间局部性。控制块包括引用计数及可能的其他管理信息,比如弱引用计数和对象的析构器。

5.Http请求怎么发送给服务端的
HTTP请求是通过基于TCP/IP协议的网络连接发送给服务器的。客户端(如Web浏览器)首先建立到服务器指定端口(通常是80或443)的TCP连接。一旦连接建立,客户端会发送一个HTTP请求消息,这个消息遵循HTTP协议格式,包括请求行(例如GET /index.html)、请求头和可选的请求体。然后,服务器接收这个消息,处理请求,并发送一个HTTP响应回客户端。

6.TCP怎么样发送数据
TCP发送数据的过程包括建立连接、数据传输和结束连接三个主要步骤。首先,通过三次握手协议建立一个可靠的连接。在连接建立后,数据被分割成多个TCP段,每个段都有一个序列号用于确保数据的顺序和可靠性。在整个传输过程中,使用了滑动窗口机制进行流量控制和拥塞控制机制以避免网络拥堵。接收方对收到的数据段发送ACK包作为确认。最后,通过四次挥手协议结束连接。这个过程确保了数据的正确顺序和完整性。

7.TCP什么时候会重传

超时重传:如果发送方在设定的超时时间内没有收到接收方的确认(ACK),它会重传那个数据段。
快速重传:如果发送方收到三个或更多的冗余ACK(即对同一个数据段的连续确认),它会在没有等待超时的情况下立即重传那个被认为丢失的数据段。
接收方提示:接收方可以通过ACK中的SACK选项(选择确认),明确指出哪些数据段已收到,哪些未收到,促使发送方仅重传未被确认接收的数据段。

8.HTTP响应怎么返回客户端

HTTP响应返回给客户端的过程包括以下几个步骤:

  1. 状态行:服务器首先发送一个状态行,包含HTTP版本、状态码和状态消息,描述了请求是否成功及失败的原因。
  2. 响应头部:紧随状态行后,服务器发送一系列的响应头部,提供了关于服务器信息、响应体的类型和长度等元数据。
  3. 空行:头部之后,一个空行标志着头部结束和响应体的开始。
  4. 响应体:最后,服务器发送实际的响应数据(如果有的话),这可能是请求的资源(如网页、图片)、错误信息或其他数据。

整个HTTP响应通过建立的TCP连接传输回客户端,客户端接收到响应后将按照响应中的信息进行相应处理。

9.发送给客户端用的什么函数,怎么用的
在C/C++中使用socket编程:

send(client_socket, response, response_length, 0);

10.手撕代码 数组最小和

以下是使用 Kadane 算法找到数组最小子数组和的 C++ 代码示例:

#include <iostream>
#include <vector>
#include <climits> // For INT_MAX

int minSubArraySum(const std::vector<int>& nums) {
    int currentMin = nums[0];
    int totalMin = nums[0];
    
    for (size_t i = 1; i < nums.size(); ++i) {
        if (currentMin > 0) {
            currentMin = nums[i];
        } else {
            currentMin += nums[i];
        }
        totalMin = std::min(totalMin, currentMin);
    }
    
    return totalMin;
}

int main() {
    std::vector<int> nums = {1, -2, 3, 10, -4, 7, 2, -5};
    std::cout << "Minimum subarray sum is: " << minSubArraySum(nums) << std::endl;
    return 0;
}

快手C++

1、muduo网络库有什么改进的地方

2、如何实现高并发的,IO 线程和业务线程为什么分开,压测过吗

实现高并发

  1. Reactor或Proactor模式:使用非阻塞IO和IO多路复用技术(如epoll或kqueue),在单个或少数几个线程中管理多个网络连接。
  2. 线程池:创建线程池来处理业务逻辑,这样可以重用线程并减少创建和销毁线程的开销。

IO线程和业务线程分开的原因:

  • 避免复杂性:分离IO和业务逻辑可以使系统结构更清晰,代码更容易维护。
  • 提高效率:IO线程专注于数据的读写,业务线程专注于处理逻辑,可以避免一个耗时的业务操作阻塞了其他客户端的IO处理。
  • 更好的可伸缩性:可以独立地根据需要增加IO线程或业务线程,提高系统的响应能力和吞吐量。

3、重构点在哪,thread 类底层如何创建线程,thread_local 了解吗

线程的创建:
C++的std::thread类是在底层使用POSIX pthreads或类似的线程库来创建和管理线程的。当创建std::thread对象时,实际会在底层创建一个系统线程,并启动指定的函数或者可调用对象在新线程中运行。

thread_local:
关键词thread_local用来声明线程局部变量。这些变量对于每一个线程都有各自的副本,线程之间互不影响。当线程结束时,其线程局部变量也会被销毁。这对于那些需要在多线程代码中保持各自状态的场景非常有用,比如,随机数生成器、线程池中的计数器等。

4、协程了解吗

协程,又称为微线程或轻量线程,是程序组件化和异步编程的一种方式,允许函数、方法或者操作挂起和恢复,而非使用传统的线程阻塞方式。协程提供了比线程更轻量级的并行执行单位,它们在用户态进行调度,切换开销小。协程的优点包括提高程序的并发性能,简化异步编程模型,以及减少资源消耗。在C++20中,引入了对协程的支持关键字co_yield, co_return, 和 co_await

5、 智能指针说一下

智能指针的主要类型包括:

  1. std::unique_ptr:独占所指对象的智能指针。它不允许复制,确保一个对象只能被一个unique_ptr管理,可以用于管理资源的独占所有权。
  2. std::shared_ptr:允许多个shared_ptr共享同一个对象。当最后一个引用该对象的shared_ptr被销毁或重置时,对象会被删除。内部通过引用计数来实现。
  3. std::weak_ptr:设计为与shared_ptr协同工作的智能指针,不会增加对象的引用计数。主要用于解决shared_ptr可能引起的循环引用问题。

6、map 和 unordered_map 区别

  1. 内部实现
  • std::map是基于红黑树实现的,支持有序性。这意味着其中的元素按照键排序,并且可以进行快速查找、插入和删除操作。
  • std::unordered_map基于哈希表实现,不保证元素的顺序。它能提供平均情况下常数时间的查找、插入和删除。
  1. 性能
  • std::map中的操作通常是对数时间复杂度,适用于需要按顺序遍历键值对的场景。
  • std::unordered_map由于是哈希表实现,大多数情况下可以提供更快的访问速度,特别是当键值对数量非常大时。

7、算法:[162. 寻找峰值]

峰值元素是指其值严格大于左右相邻值的元素。

给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。

你可以假设 nums[-1] = nums[n] = -∞

你必须实现时间复杂度为 O(log n) 的算法来解决此问题。

示例 1:

输入:nums = [1,2,3,1]
输出:2
解释:3 是峰值元素,你的函数应该返回其索引 2。

示例 2:

输入:nums = [1,2,1,3,5,6,4]
输出:1 或 5 
解释:你的函数可以返回索引 1,其峰值元素为 2;
     或者返回索引 5, 其峰值元素为 6。

提示:

  • 1 <= nums.length <= 1000
  • -231 <= nums[i] <= 231 - 1
  • 对于所有有效的 i 都有 nums[i] != nums[i + 1]

以下是解决这个问题的步骤:

  1. 初始化左边界left为0,右边界right为数组长度减一。
  2. left小于right时执行循环:
    a. 计算中间索引midleftright的平均值。
    b. 比较midmid + 1的元素:
  • 如果nums[mid] < nums[mid + 1],说明峰值在mid的右边,因此设置left = mid + 1
  • 否则,峰值在mid的左边(含mid自身),因此设置right = mid
  1. left等于right时,循环结束,left(或right)就是所求的峰值元素的索引。

这个算法的前提是,我们假设nums[-1]nums[n]是负无穷,这使得边界上的元素也可以是峰值。如果数组是递增或递减的,那么第一个或最后一个元素就会是峰值。

地平线一面

1. C++11 的新特性都有哪些?

  1. 自动类型推断(auto)。
  2. 范围for循环。
  3. Lambda表达式和函数闭包。
  4. 右值引用和移动语义。
  5. 可变参数模板。
  6. 初始化列表。
  7. 强类型枚举。
  8. 智能指针如std::unique_ptr和std::shared_ptr。
  9. 空指针关键字(nullptr)。
  10. 线程库支持。
  11. 新容器如std::array和std::unordered_map。

2. 了解什么设计模式?单例了解吗?

单例模式是一种设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。在C++中实现单例模式通常需要以下步骤:

  1. 私有化构造函数和析构函数防止外部创建和销毁。
  2. 在类内部提供一个私有的静态指针变量指向唯一实例。
  3. 提供一个公共的静态方法用于获取这个唯一的实例。

3. TCP和UDP的区别?

  1. 连接:
  • TCP是面向连接的协议,进行数据传输前需要建立连接。
  • UDP是无连接的协议,不需要建立连接就可以直接发送数据。
  1. 可靠性:
  • TCP提供可靠的数据传输,确保数据完整性和顺序。
  • UDP提供不可靠的数据传输,可能出现丢包,不保证数据顺序。
  1. 速度:
  • TCP相对较慢,因为它需要确认机制和错误校正。
  • UDP传输速度更快,没有确认机制,适用于对速度要求高的场合。
  1. 数据流:
  • TCP提供字节流服务,通过数据流的方式发送数据。
  • UDP以数据报文的形式发送信息,发送独立的消息。

4. 左值和右值引用?

左值引用是对可寻址的、可重复使用的对象(左值)的引用。它使用传统的单个&符号。右值引用是对临时对象(右值)的引用,使用双&&符号。右值引用允许实现移动语义和完美转发,它可以将资源从一个(临时的)对象转移到另一个对象,提高效率,避免不必要的复制。

5. 能说说static关键字吗?

static关键字有几个用途:

  1. 在类的数据成员前使用static,表示该成员属于类本身,而非单个实例,所有实例共享这一成员。
  2. 在类的成员函数前使用static,可以在不创建类实例的情况下直接调用该函数。
  3. 在函数内部使用static定义局部变量,使得该变量的值在函数调用间持久存在,而不是每次调用时都重新创建。
  4. 在文件、函数或区域前使用static,可以限制该变量或函数的链接可见性,令其只在定义它的文件或作用域内部可见。

6. 用过什么容器,map和unordered_map了解吗?vector的底层原理是什么?Vector的扩容机制了解多少?map的底层原理能说说吗?

mapunordered_map:

  • map:
  • 底层实现是红黑树,一个自平衡的二叉搜索树。
  • 元素根据键值自动排序。
  • 插入、删除和查找操作的时间复杂度为O(log n)。
  • unordered_map:
  • 底层实现是哈希表。
  • 元素不会自动排序。
  • 平均情况下插入、删除和查找操作的时间复杂度为O(1),最坏情况下为O(n)。

vector的底层原理和扩容机制:

  • 底层原理:
  • vector是基于动态数组实现的,支持随机访问。
  • 在连续的内存空间中存储元素,允许快速访问。
  • 扩容机制:
  • 当向vector添加元素超过其当前容量时,它会创建一个更大的动态数组,并将所有现有元素复制到新数组中,释放旧数组的内存。
  • 新容量通常是当前容量的两倍,不过这可能因实现而异。

map的底层原理:

  • map的底层是基于红黑树实现的。
  • 红黑树是一种自平衡二叉搜索树,它能保证基本操作(如查找、插入、删除)的时间复杂度为O(log n),确保树的高度保持在对数级别。
  • 通过键值对自动排序和高效操作维持了其数据结构的稳定性和效率。

7. std::move???

std::move 是一个标准库函数,将其参数转换为右值引用。这允许程序利用移动语义,将资源从一个对象转移到另一个对象,而不进行复制。

8. C++转型操作符?

  1. static_cast:用于基本数据类型转换,以及向上转型(将派生类对象或指针转换为基类表示)。
  2. dynamic_cast:用于类的层次结构中的安全向下转型和运行时类型检查,需要运行时RTTI(Run-Time Type Information)支持。
  3. const_cast:用于移除对象的constvolatile属性。
  4. reinterpret_cast:用于低级转换,重新解释底层位模式,可以将一个指针转换为任何其他类型的指针。

9. 智能指针了解吗?weak_ptr?

weak_ptr是C++标准库中的一种智能指针,它设计用来解决智能指针之间的循环引用问题。它不拥有它指向的对象,因此它的存在不会增加对象的引用计数。这意味着weak_ptr不会影响对象的生命周期。当你需要访问weak_ptr指向的对象时,你可以通过调用lock()方法来获得一个可用的shared_ptr,这个shared_ptr会临时拥有对象并增加引用计数。如果原始的shared_ptr已经被销毁,weak_ptr所代表的资源可能已经被释放,那么lock()会返回一个空的shared_ptr

进程间通信的方式有哪些?线程间的数据交互?

进程间通信方式包括:

  1. 管道(Pipes)
  2. 命名管道(FIFO)
  3. 信号(Signals)
  4. 消息队列(Message Queues)
  5. 共享内存(Shared Memory)
  6. 信号量(Semaphores)
  7. 套接字(Sockets)
  8. 内存映射文件(Memory-mapped files)

线程间的数据交互可以通过:

  1. 锁(如互斥锁Mutex)
  2. 条件变量(Condition Variables)
  3. 信号量(Semaphores)
  4. 线程局部存储(Thread-local Storage,TLS)
  5. 全局变量(通过使用同步机制以避免并发访问问题)


https://www.xamrdz.com/lan/56r1962452.html

相关文章: