小米C++
1. 进程和线程的区别
进程是操作系统分配资源和调度的独立单位,拥有自己的地址空间和系统资源。线程是进程内部的执行单元,共享属于相同进程的资源,但是执行切换代价更小。进程间相互独立,稳定性较高;线程间共享内存,创建和切换成本较低,但一个线程的失败可能影响同进程的其他线程。
2.进程通信方式
进程通信方式主要包括:
- 管道:允许一个进程和另一个与它有共同祖先的进程之间进行通信。
- 消息队列:消息的链表,存储在内核中,由进程间发送接收消息。
- 共享内存:允许多个进程访问同一块内存空间,是最快的IPC方式。
- 信号量:主要用于解决进程间的同步问题。
- 套接字:适用于不同机器间的进程通信。
- 信号:用于通知接收进程某个事件已经发生。
3.socket与其他通信方式有什么不同
- Socket 可以实现不同主机间的进程通信,适用于网络中跨操作系统通信。
- Socket 通常支持全双工通信,即同一时间可以进行数据的双向传输。
- Socket 支持面向连接(如TCP协议)和无连接(如UDP协议)的通信方式。
- 使用 Socket 进行通信需要创建、配置、使用和关闭套接字,比其他通信方式如管道和信号等有更明确的使用流程。
4.共享内存安全吗,有什么措施保证
共享内存本身没有内建安全措施,通过以下方式确保安全性:
- 使用互斥锁或信号量来同步对共享内存的访问。
- 实施访问控制,限制哪些进程可以访问共享内存。
- 定期检查和清理,避免僵尸进程造成的资源泄露。
- 根据需求实现读写锁,允许多读单写的安全访问模式。
5.linux中内存分布有哪些,cpp呢
Linux中内存分布主要包括:
- 用户空间:应用程序占用的内存区域。
- 内核空间:操作系统内核及其数据结构和模块占用的内存区域。
- 栈区:自动分配和释放的本地变量存储区域。
- 堆区:动态分配内存区域,需手动分配和释放。
- 代码区:存放程序执行代码。
- 数据区:存放全局变量和静态变量。
C++中内存分布主要包括:
- 栈(Stack):局部变量和函数调用。
- 堆(Heap):动态分配的内存(使用new和delete管理)。
- 全局/静态存储区:全局变量和静态变量。
- 常量存储区:常量字符串和常量值。
- 代码区:函数体的二进制代码。
6.多态实现方式
多态在C++中的实现方式通常有两种:
- 虚函数(通过类的继承和虚函数表实现)
- 函数重载(同一作用域内多个同名函数通过参数列表区分)
7.cpp面向对象的优势
C++面向对象编程(OOP)的优势包括:
- 封装:提高代码安全性和复用性,隐藏实现细节。
- 继承:提高代码可维护性,通过基类和派生类体系结构重用代码。
- 多态:提高代码的可扩展性和灵活性,允许接口多种实现。
- 抽象:简化复杂性,只暴露核心操作,底层细节被抽象化。
手撕:数组的部分元素求和
#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++中内存泄漏是指程序分配的内存未被释放且无法再次被访问,常见原因包括:
- 动态分配的内存(使用
new
或malloc
)没有使用delete
或free
释放。 - 使用指针指向新的内存区域,而忘记释放原有内存。
- 数据结构中的循环引用导致无法自动释放。
避免内存泄漏的方法包括:
- 使用智能指针(如
std::unique_ptr
和std::shared_ptr
)自动管理内存。 - 适时使用
delete
或free
释放不再需要的动态分配内存。 - 定期使用内存检测工具(如Valgrind)检测和定位内存泄漏问题。
2.智能指针
智能指针是一种在C++中用于自动管理动态分配的内存生命周期的类模板。它们通过提供类似指针的接口,并在适当的时候自动释放所管理的内存,帮助避免内存泄漏和指针错误。
C++标准库中常用的智能指针有:
-
std::unique_ptr
:独占所有权模型,不可复制但可以移动。它保证同一时间只有一个智能指针拥有对某块内存的控制。 -
std::shared_ptr
:共享所有权模型,可以被复制,并通过引用计数来管理内存。当没有任何shared_ptr
指向一块内存时,该块内存会被自动释放。 -
std::weak_ptr
:伴随shared_ptr
的非拥有型智能指针,用来解决shared_ptr
相互引用时可能产生的循环依赖问题。不增加引用计数,因此不影响其指向的对象的生命周期。
3.智能指针有什么缺点
- 性能开销:智能指针(尤其是
std::shared_ptr
)通过引用计数来管理内存,这会增加额外的性能开销,比原始指针慢。 - 循环引用问题:
std::shared_ptr
在存在循环引用时会导致内存泄漏,因为引用计数永远不会达到0,除非使用std::weak_ptr
打破循环。 - 复杂性增加:虽然智能指针有助于内存管理,但不当使用可以增加程序复杂性,误用可能导致难以调试的问题,如悬挂指针或者提前释放等。
- 不适用场景:特定场景下(如高性能或者低延迟要求的应用),智能指针所带来的额外开销可能是不可接受的。
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响应返回给客户端的过程包括以下几个步骤:
- 状态行:服务器首先发送一个状态行,包含HTTP版本、状态码和状态消息,描述了请求是否成功及失败的原因。
- 响应头部:紧随状态行后,服务器发送一系列的响应头部,提供了关于服务器信息、响应体的类型和长度等元数据。
- 空行:头部之后,一个空行标志着头部结束和响应体的开始。
- 响应体:最后,服务器发送实际的响应数据(如果有的话),这可能是请求的资源(如网页、图片)、错误信息或其他数据。
整个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 线程和业务线程为什么分开,压测过吗
实现高并发
- Reactor或Proactor模式:使用非阻塞IO和IO多路复用技术(如epoll或kqueue),在单个或少数几个线程中管理多个网络连接。
- 线程池:创建线程池来处理业务逻辑,这样可以重用线程并减少创建和销毁线程的开销。
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、 智能指针说一下
智能指针的主要类型包括:
- std::unique_ptr:独占所指对象的智能指针。它不允许复制,确保一个对象只能被一个unique_ptr管理,可以用于管理资源的独占所有权。
- std::shared_ptr:允许多个shared_ptr共享同一个对象。当最后一个引用该对象的shared_ptr被销毁或重置时,对象会被删除。内部通过引用计数来实现。
- std::weak_ptr:设计为与shared_ptr协同工作的智能指针,不会增加对象的引用计数。主要用于解决shared_ptr可能引起的循环引用问题。
6、map 和 unordered_map 区别
- 内部实现:
-
std::map
是基于红黑树实现的,支持有序性。这意味着其中的元素按照键排序,并且可以进行快速查找、插入和删除操作。 -
std::unordered_map
基于哈希表实现,不保证元素的顺序。它能提供平均情况下常数时间的查找、插入和删除。
- 性能:
-
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]
以下是解决这个问题的步骤:
- 初始化左边界
left
为0,右边界right
为数组长度减一。 - 当
left
小于right
时执行循环:
a. 计算中间索引mid
为left
和right
的平均值。
b. 比较mid
和mid + 1
的元素:
- 如果
nums[mid] < nums[mid + 1]
,说明峰值在mid
的右边,因此设置left = mid + 1
。 - 否则,峰值在
mid
的左边(含mid
自身),因此设置right = mid
。
- 当
left
等于right
时,循环结束,left
(或right
)就是所求的峰值元素的索引。
这个算法的前提是,我们假设nums[-1]
和nums[n]
是负无穷,这使得边界上的元素也可以是峰值。如果数组是递增或递减的,那么第一个或最后一个元素就会是峰值。
地平线一面
1. C++11 的新特性都有哪些?
- 自动类型推断(auto)。
- 范围for循环。
- Lambda表达式和函数闭包。
- 右值引用和移动语义。
- 可变参数模板。
- 初始化列表。
- 强类型枚举。
- 智能指针如std::unique_ptr和std::shared_ptr。
- 空指针关键字(nullptr)。
- 线程库支持。
- 新容器如std::array和std::unordered_map。
2. 了解什么设计模式?单例了解吗?
单例模式是一种设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。在C++中实现单例模式通常需要以下步骤:
- 私有化构造函数和析构函数防止外部创建和销毁。
- 在类内部提供一个私有的静态指针变量指向唯一实例。
- 提供一个公共的静态方法用于获取这个唯一的实例。
3. TCP和UDP的区别?
- 连接:
- TCP是面向连接的协议,进行数据传输前需要建立连接。
- UDP是无连接的协议,不需要建立连接就可以直接发送数据。
- 可靠性:
- TCP提供可靠的数据传输,确保数据完整性和顺序。
- UDP提供不可靠的数据传输,可能出现丢包,不保证数据顺序。
- 速度:
- TCP相对较慢,因为它需要确认机制和错误校正。
- UDP传输速度更快,没有确认机制,适用于对速度要求高的场合。
- 数据流:
- TCP提供字节流服务,通过数据流的方式发送数据。
- UDP以数据报文的形式发送信息,发送独立的消息。
4. 左值和右值引用?
左值引用是对可寻址的、可重复使用的对象(左值)的引用。它使用传统的单个&
符号。右值引用是对临时对象(右值)的引用,使用双&&
符号。右值引用允许实现移动语义和完美转发,它可以将资源从一个(临时的)对象转移到另一个对象,提高效率,避免不必要的复制。
5. 能说说static关键字吗?
static
关键字有几个用途:
- 在类的数据成员前使用
static
,表示该成员属于类本身,而非单个实例,所有实例共享这一成员。 - 在类的成员函数前使用
static
,可以在不创建类实例的情况下直接调用该函数。 - 在函数内部使用
static
定义局部变量,使得该变量的值在函数调用间持久存在,而不是每次调用时都重新创建。 - 在文件、函数或区域前使用
static
,可以限制该变量或函数的链接可见性,令其只在定义它的文件或作用域内部可见。
6. 用过什么容器,map和unordered_map了解吗?vector的底层原理是什么?Vector的扩容机制了解多少?map的底层原理能说说吗?
map
和 unordered_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++转型操作符?
-
static_cast
:用于基本数据类型转换,以及向上转型(将派生类对象或指针转换为基类表示)。 -
dynamic_cast
:用于类的层次结构中的安全向下转型和运行时类型检查,需要运行时RTTI(Run-Time Type Information)支持。 -
const_cast
:用于移除对象的const
或volatile
属性。 -
reinterpret_cast
:用于低级转换,重新解释底层位模式,可以将一个指针转换为任何其他类型的指针。
9. 智能指针了解吗?weak_ptr?
weak_ptr
是C++标准库中的一种智能指针,它设计用来解决智能指针之间的循环引用问题。它不拥有它指向的对象,因此它的存在不会增加对象的引用计数。这意味着weak_ptr
不会影响对象的生命周期。当你需要访问weak_ptr
指向的对象时,你可以通过调用lock()
方法来获得一个可用的shared_ptr
,这个shared_ptr
会临时拥有对象并增加引用计数。如果原始的shared_ptr
已经被销毁,weak_ptr
所代表的资源可能已经被释放,那么lock()
会返回一个空的shared_ptr
。
进程间通信的方式有哪些?线程间的数据交互?
进程间通信方式包括:
- 管道(Pipes)
- 命名管道(FIFO)
- 信号(Signals)
- 消息队列(Message Queues)
- 共享内存(Shared Memory)
- 信号量(Semaphores)
- 套接字(Sockets)
- 内存映射文件(Memory-mapped files)
线程间的数据交互可以通过:
- 锁(如互斥锁Mutex)
- 条件变量(Condition Variables)
- 信号量(Semaphores)
- 线程局部存储(Thread-local Storage,TLS)
- 全局变量(通过使用同步机制以避免并发访问问题)