实习面试记录

湖南某操作系统中厂(9月)

首先是科研项目相关的问题,然后是其他问题如下:

STL中,vector,map和unorded_map底层是怎么实现的?

在C++标准库中,vector和map都是基于模板类实现的容器。

  1. vector底层实现:
    vector是一个动态数组,它在内存中以连续的块存储元素。当需要添加或删除元素时,vector会根据需要自动重新分配内存空间,以保持元素的连续存储。通常情况下,vector会分配比实际需求大一些的内存空间,以避免频繁的重新分配开销。

  2. map底层实现:
    map是一个键-值对的关联容器,底层使用红黑树(Red-Black Tree)实现。红黑树是一种自平衡二叉搜索树,它具有良好的查找、插入和删除性能。红黑树保持了以下性质:节点要么为红色,要么为黑色;树的根节点和叶子节点(空节点,即NULL)为黑色;如果一个节点是红色的,那么它的子节点必定是黑色的;对于任意节点,其到达其所有后代叶子节点的路径上,黑色节点的数量相同。

STL中,map和unorded_map底层是怎么实现的?区别?

在STL(Standard Template Library)中,map和unordered_map都是关联容器,用于存储键值对的数据结构。它们的底层实现方式不同。

  1. map底层实现:
    map使用红黑树(Red-Black Tree)实现,它是一种自平衡的二叉搜索树。红黑树具有以下性质:每个节点要么是红色,要么是黑色;根节点是黑色;叶子节点(空节点)是黑色;红色节点的两个子节点都是黑色;对于每个节点,从该节点到其所有后代叶子节点的简单路径上,包含相同数目的黑色节点。

  2. unordered_map底层实现:
    unordered_map使用哈希表(Hash Table)实现,也被称为散列表。哈希表通过将键映射到桶(bucket)来实现快速查找。键经过哈希函数计算得到一个索引,然后在该索引处的桶中查找或插入键值对。当多个键被哈希到同一个索引时,通常会使用链表、红黑树或其他方法来解决冲突。

总结起来,map使用红黑树实现,保证了元素的有序性,但查找速度较慢;unordered_map使用哈希表实现,查找速度较快,但元素的顺序是无序的。在使用时,可以根据具体的需求进行选择。

怎么把map清空,内存删除呢?

要清空一个 std::map 容器 ,你可以使用 clear() 函数。调用 clear() 函数会删除容器中的所有键值对,使其变为空容器。这只会清除容器中的数据,不会释放相应的内存。

以下是一个示例代码片段,展示了如何清空 std::map 容器:

std::map<KeyType, ValueType> myMap;

// 添加键值对至 myMap

// 清空容器
myMap.clear();

在调用 clear() 函数后,myMap 将变为空的 std::map 容器,其大小为0,不再包含任何键值对。

请注意,调用 clear() 函数只会删除容器中的数据,而不会直接释放内存。内存的释放是由容器的析构函数负责的,当容器本身被销毁时,与之关联的内存会被自动释放。如果你需要手动释放内存,可以通过销毁容器对象或将其重新赋值为一个新的空容器来实现。例如:

// 销毁容器,释放内存
myMap.~map();

// 或者将其重新赋值为空容器
myMap = std::map<KeyType, ValueType>();

当使用这些方法时,之前 std::map 容器关联的内存将会被释放。使用哪种方法取决于你的具体需求和代码结构。

STL是线程安全的吗?

STL(Standard Template Library)是C++标准库的一部分,其中包含了众多的模板类和函数,用于实现各种数据结构和算法。STL本身并没有提供对多线程的直接支持,因此,并不能说STL是线程安全的。

在多线程环境中使用STL容器和算法时,需要开发人员自行确保线程安全性。多个线程同时访问和修改同一个STL容器的共享数据是一种典型的线程安全问题,如果不采取适当的措施,可能会导致数据竞争和不确定的行为。

为了在多线程环境中安全地使用STL,可以采取以下一些措施:

  1. 互斥锁(Mutex):使用互斥锁来保护访问和修改STL容器的临界区。在每次对STL容器进行读写操作之前,使用互斥锁进行加锁,在操作完成后再进行解锁。这样可以确保多个线程之间的互斥访问。

  2. 读写锁(Read-Write Lock):如果多个线程之间主要是读操作,可以考虑使用读写锁来提高性能。读写锁允许多个读操作同时进行,但会阻塞写操作。

  3. 原子操作(Atomic Operations):STL提供了一些原子操作类型,如std::atomic。原子操作是在单个指令中执行的,不能被中断,因此可以保证在多线程环境下的原子性访问。

  4. 线程安全的容器(Thread-Safe Containers):除了STL容器之外,一些第三方库或框架提供了线程安全的容器,如Intel TBB、Boost等。这些容器封装了线程安全性,可以更方便地在多线程环境中使用。

C++ 14才引入的线程安全吗

C++14并没有引入特定的机制来确保线程安全,但它引入了一些新的标准库组件,以支持多线程编程。这些组件包括std::thread、std::mutex、std::condition_variable等,它们是基于C++11引入的线程支持功能的进一步扩展。

C++11引入了原生的多线程支持,包括std::thread和std::mutex等,但在C++11中没有提供标准库实现的并发数据结构或其他专门用于线程安全的组件。这意味着,在C++11中,开发人员需要自行实现线程安全的代码,使用互斥锁、条件变量等机制来确保线程安全。

C++14在C++11的基础上提供了一些新的标准库组件,如std::shared_mutex、std::atomic_shared_ptr,它们提供了更方便的方式来实现线程安全。而且,C++14引入了一些语法上的改进,如lambda函数的改进、typedef的改进等,使得编写线程安全的代码更加简洁和方便。

总结起来,C++14并没有直接引入线程安全的概念或机制,但它提供了更丰富的标准库组件和语法上的改进,使得在C++中编写线程安全的代码更加便捷和易于理解。然而,确保线程安全仍然是开发人员的责任,需要正确地使用这些组件和机制。

python常见的数据结构,python中哪些数据结构是不可变的?

Python 提供了许多常见的数据结构,以下是一些常见的数据结构及其在 Python 中的表示:

  1. 列表(Lists):有序、可变的数据集合。

    • 示例:my_list = [1, 2, 3, 4, 5]
  2. 元组(Tuples):有序、不可变的数据集合。

    • 示例:my_tuple = (1, 2, 3, 4, 5)
  3. 字典(Dictionaries):存储键-值对的无序集合,通过键进行访问。

    • 示例:my_dict = {'name': 'John', 'age': 30, 'city': 'New York'}
  4. 集合(Sets):存储唯一元素的无序集合。

    • 示例:my_set = {1, 2, 3, 4, 5}
  5. 字符串(Strings):字符的有序集合。

    • 示例:my_string = "Hello, world!"
  6. 数组(Arrays):有序、可变的数据集合,需要导入 array 模块。

    • 示例:import array; my_array = array.array('i', [1, 2, 3, 4, 5])
  7. 堆(Heaps):优先级队列,需要导入 heapq 模块。

    • 示例:import heapq; my_heap = [3, 1, 4, 1, 5]; heapq.heapify(my_heap)
  8. 链表(Linked Lists):通过节点连接的线性数据结构,可以使用自定义类实现。

    • 示例:
      class Node:
          def __init__(self, data):
              self.data = data
              self.next = None
      
      # 创建链表
      head = Node(1)
      second = Node(2)
      third = Node(3)
      
      head.next = second
      second.next = third
      

这只是 Python 中一些常见的数据结构示例,每个数据结构都有其特定的用途和操作。根据实际需求选择合适的数据结构,Python 提供的丰富的数据结构可以满足不同的编程任务。
在 Python 中,有以下几种不可变的数据结构:

  1. 数字类型(Number types):包括整数(int)、浮点数(float)、复数(complex)等。

    • 示例:x = 10
  2. 字符串(Strings):由单个字符组成的有序序列。

    • 示例:name = "Alice"
  3. 元组(Tuples):有序的对象序列,一旦创建就不能修改。

    • 示例:point = (3, 4)
  4. 冻结集合(Frozen Sets):不可变的集合对象。

    • 示例:my_set = frozenset([1, 2, 3])
  5. 字节串(Bytes):存储二进制数据的不可变序列。

    • 示例:data = b"Hello"

这些不可变的数据结构是指一旦创建就无法修改其内容,每次对其进行操作都会创建一个新的对象。这种不可变性有助于确保数据的一致性和安全性,并且使得这些数据结构可以作为字典的键或集合的元素使用。另外,对于数值计算和存储不变数据等场景,使用不可变数据结构也可以提高性能。

Linux常见的命令,Linux命令统计内存占用

以下是一些常见的 Linux 命令:

  1. 文件和目录操作:

    • ls: 列出目录内容。
    • cd: 切换目录。
    • pwd: 显示当前工作目录。
    • mkdir: 创建目录。
    • touch: 创建空文件或更新文件时间戳。
    • cp: 复制文件或目录。
    • mv: 移动或重命名文件或目录。
    • rm: 删除文件或目录。
    • find: 在文件系统中查找文件。
  2. 文件内容查看和编辑:

    • cat: 显示文件内容。
    • more: 分页显示文件内容。
    • less: 分页显示文件内容,支持向前和向后翻页。
    • head: 显示文件开头部分。
    • tail: 显示文件结尾部分。
    • grep: 在文件中查找匹配的文本。
    • vi / vim: 强大的文本编辑器。
  3. 系统信息查看:

    • df: 显示磁盘空间使用情况。
    • du: 显示目录或文件的磁盘使用情况。
    • top: 实时显示系统资源使用情况。
    • free: 显示系统内存使用情况。
    • uname: 显示系统信息(内核版本、操作系统等)。
    • ifconfig: 显示和配置网络接口信息。
  4. 系统管理和进程控制:

    • ps: 显示当前运行的进程。
    • kill: 终止指定进程。
    • shutdown: 关闭系统。
    • reboot: 重启系统。
    • service: 管理系统服务。
    • systemctl: 管理系统服务和日志。
    • sudo: 以超级用户权限运行命令。
  5. 压缩和解压缩:

    • tar: 打包和解包文件。
    • gzip: 压缩文件。
    • gunzip: 解压缩文件。
    • zip: 压缩文件和目录。
    • unzip: 解压缩 Zip 文件。

这只是一小部分常见的 Linux 命令,Linux 提供了丰富的命令行工具和实用程序,以满足各种需求。可以通过 man 命令来查看命令的帮助文档,了解更多详细信息。

python中的推导函数,List和set推导式的区别?

在 Python 中,有两种常见的推导函数,它们分别是列表推导式(List Comprehension)和集合推导式(Set Comprehension)。这些推导函数可以更简洁地创建列表和集合。

  1. 列表推导式(List Comprehension):
    列表推导式允许我们通过对一个可迭代对象中的每个元素应用一个表达式来创建一个新的列表。

例如,如果我们想创建一个包含 1 到 5 的平方的列表,可以使用以下列表推导式:

squares = [x**2 for x in range(1, 6)]
# 输出结果: [1, 4, 9, 16, 25]

列表推导式还可以包含条件语句,以过滤元素。例如,我们可以创建一个包含 1 到 10 中偶数的列表:

even_numbers = [x for x in range(1, 11) if x % 2 == 0]
# 输出结果: [2, 4, 6, 8, 10]
  1. 集合推导式(Set Comprehension):
    集合推导式与列表推导式类似,但创建的是一个集合而不是一个列表。它使用大括号 {} 来表示集合。

例如,我们可以使用集合推导式创建一个包含 1 到 5 的平方的集合:

squares = {x**2 for x in range(1, 6)}
# 输出结果: {1, 4, 9, 16, 25}

与列表推导式类似,集合推导式也可以包含条件语句。例如,我们可以创建一个包含 1 到 10 中偶数的集合:

even_numbers = {x for x in range(1, 11) if x % 2 == 0}
# 输出结果: {2, 4, 6, 8, 10}

这些推导函数提供了一种简洁而直观的方式来创建列表和集合,可以减少代码量并提高可读性。除了列表和集合推导式,Python 还支持字典推导式和生成器表达式等其他形式的推导函数。

在 Python 中,推导式是一种简洁的语法,可以通过对可迭代对象中的元素应用表达式和条件语句来创建新的数据结构。以下是 Python 中常见的推导式类型:

  1. 列表推导式(List Comprehension):
    列表推导式允许我们通过对一个可迭代对象中的每个元素应用一个表达式来创建一个新的列表。语法形式为:[expression for item in iterable if condition]

    示例:

    squares = [x**2 for x in range(1, 6)]
    # 输出结果: [1, 4, 9, 16, 25]
    
  2. 集合推导式(Set Comprehension):
    集合推导式与列表推导式类似,但创建的是一个集合而不是一个列表。语法形式为:{expression for item in iterable if condition}

    示例:

    squares = {x**2 for x in range(1, 6)}
    # 输出结果: {1, 4, 9, 16, 25}
    
  3. 字典推导式(Dictionary Comprehension):
    字典推导式允许我们通过对一个可迭代对象中的每个元素应用表达式来创建一个新的字典。语法形式为:{key_expression: value_expression for item in iterable if condition}

    示例:

    squares = {x: x**2 for x in range(1, 6)}
    # 输出结果: {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
    
  4. 生成器表达式(Generator Expression):
    生成器表达式可以看作是一个简化版的列表推导式,它返回一个生成器对象,而不是创建一个完整的列表。语法形式为:(expression for item in iterable if condition)

    示例:

    squares = (x**2 for x in range(1, 6))
    # 输出结果:  at 0x10aeb0b50>
    

python中set传参和List传参有什么区别

在 Python 中,set 和 list 是常见的数据结构,它们在传参时有一些区别:

  1. 传参方式:

    • set:当你将一个 set 对象作为参数传递给函数时,实际上是将该 set 对象的引用传递给了函数。这意味着函数内部对传递的 set 对象进行的任何修改都会影响到原始的 set 对象。
    • list:与 set 不同,当你将一个 list 对象作为参数传递给函数时,也是将该 list 对象的引用传递给了函数。也就是说,函数内部对传递的 list 对象的修改也会影响到原始的 list 对象。
  2. 可变性:

    • set:set 是可变的数据类型,意味着你可以对其进行添加、删除、更新等操作,而不会创建一个新的 set 对象。因此,如果在函数内部修改了传递的 set 对象,原始的 set 对象也会被修改。
    • list:list 也是可变的数据类型,你可以随意修改其中的元素,添加或删除元素。函数内部对传递的 list 对象进行的修改同样也会影响到原始的 list 对象。

总结而言,set 和 list 在传参时都是通过引用传递,而不是通过值传递。这意味着对于可变的 set 和 list 对象,函数内部的修改都会反映到原始的对象上。需要注意的是,如果你想要在函数内部创建一个独立的 set 或 list 对象,可以使用 set() 或 list() 函数创建一个新的对象,并将原始对象的元素复制给这个新对象。

深拷贝和浅拷贝的区别?

poll和epoll的区别?

“poll” 和 “epoll” 是在 Linux 环境下用于实现事件驱动的 I/O 多路复用的机制,用于同时监听多个文件描述符的可读或可写状态。以下是它们之间的主要区别:

  1. 接口设计:

    • poll:poll 函数使用一个 pollfd 结构数组(包含文件描述符和待监听事件)来传递和存储参数,每次调用需要将所有待监听的文件描述符都传递给内核。
    • epoll:epoll 采用更加简洁的接口设计,通过调用 epoll_create 创建一个 epoll 对象,然后使用 epoll_ctl 函数向 epoll 对象中注册待监听的文件描述符和事件,通过 epoll_wait 获取已就绪的文件描述符。
  2. 数据结构效率:

    • poll:poll 函数需要遍历整个 pollfd 数组来查找已发生的事件,随着待监听的文件描述符数量增加,性能会线性下降。
    • epoll:epoll 使用红黑树和链表来存储待监听的文件描述符,因此在文件描述符数量较多时,性能较好,不会随着数量增加而降低。
  3. 触发模式支持:

    • poll:poll 只支持水平触发(Level Triggered,LT)模式,即当文件描述符就绪时,会一直触发可读或可写事件,直到处理完剩余数据。
    • epoll:epoll 支持水平触发模式(LT)和边缘触发模式(Edge Triggered,ET)。边缘触发模式只在文件描述符状态变化时通知一次,需要消耗更多的系统调用。
  4. 扩展性:

    • poll:poll 的可扩展性受制于每次调用时需要传递所有待监听的文件描述符,适用于文件描述符数量较少的场景。
    • epoll:epoll 可以使用边沿触发模式(ET)以及 epoll_wait 函数的 timeout 参数,保持较好的可扩展性和高性能,适用于大规模并发场景。

总体而言,epoll 在设计和性能上相对于 poll 有更多的优势,尤其是在大规模并发的 I/O 处理中。然而,具体选择使用 poll 还是 epoll 取决于具体的应用场景和需求。

你可能感兴趣的:(复盘,面试,职场和发展,实习)