Skip to content

小米C++二面

来源:https://www.nowcoder.com/share/jump/6290577101704445356558

C++

1、指针和引用的区别?

  1. 语法和声明:
    • 指针使用*来声明和使用,例如int* ptr;声明一个指向整数的指针。
    • 引用使用&来声明和使用,例如int& ref = var;声明一个整数引用。
  2. 空值:
    • 指针可以是空指针(nullptr),即指向空地址的指针。
    • 引用必须在声明时初始化,并且不能改变引用的目标,所以不存在空引用。
  3. 赋值和指向对象:
    • 指针可以在运行时被重新赋值,指向不同的对象。
    • 引用在声明时必须初始化,并且不能重新绑定到其他对象,一旦初始化就一直引用同一个对象。
  4. 地址操作符:
    • 指针可以使用取地址操作符&获取变量的地址。
    • 引用在声明时就绑定到了一个对象,不需要使用取地址操作符。
  5. 空间占用:
    • 指针通常占用一定的内存空间来存储目标对象的地址。
    • 引用在编译时会被转换为目标对象的地址,因此不需要额外的空间来存储地址。
  6. 用法和语义:
    • 指针通常用于动态内存分配、数组和函数传递等场景,可以通过指针进行间接访问。
    • 引用通常用于函数参数传递、返回值等,可以看作是目标对象的别名,简化了对目标对象的操作。

2、函数传参什么时候用指针,什么时候用引用?

使用指针的情况

  1. 需要在函数内部修改传入的变量:

    如果函数需要修改传入的变量的值,而不是创建一个新的变量,可以使用指针作为参数,通过指针间接修改原始变量的值。

  2. 传递数组或动态分配的内存:

    当需要传递数组或动态分配的内存时,通常使用指针作为参数,因为指针可以指向数组的首地址,或者指向动态分配的内存块。

  3. 传递大型对象:

    对于大型对象,传递指针可以减少内存消耗和复制开销,因为指针的大小固定(通常为4或8字节),而对象可能很大。

使用引用的情况

  1. 避免拷贝开销:

    当传递参数时需要避免不必要的拷贝开销时,可以使用引用作为参数,因为引用不会创建对象的副本。

  2. 函数返回值为引用:

    当函数的返回值为引用时,可以直接将返回值用作参数传递给其他函数,避免了拷贝构造函数的调用。

  3. 传递基本数据类型:

    对于基本数据类型(如int、double等),通常使用引用进行传递,因为它们的大小较小,不会带来大的性能开销。

  4. 传递对象并希望保持原始对象的语义:

    当传递对象并希望在函数内部修改对象的值,同时希望保持原始对象的语义时,可以使用引用作为参数,因为引用在函数内部可以直接修改原始对象。

使用const修饰的引用

还有一种特殊情况是使用const修饰的引用,它可以用于传递临时变量或者常量,并且保证在函数内部不会修改被引用的变量。这样可以提高代码的安全性和可读性。

3、栈和堆的区别?

栈(Stack):

  1. 特点:

    • 栈是一种后进先出(LIFO)的数据结构,它的操作包括入栈(push)和出栈(pop)。
    • 栈是一种线性结构,通常由操作系统自动管理,用于存储函数的局部变量、函数的调用信息等。
  2. 分配和释放:

    • 栈上的内存空间由编译器自动分配和释放,无需手动管理。
    • 函数的局部变量在函数调用时被分配在栈上,函数返回时自动释放。
  3. 速度:

    • 栈上的内存分配和释放速度较快,因为它是由编译器自动管理的,不需要复杂的内存分配算法。
  4. 生命周期:

    栈上的变量的生命周期与其所在的作用域(通常是函数)相同,当作用域结束时,变量会被自动销毁。

  5. 大小限制:

    栈的大小通常受到限制,一般在几MB到几GB之间,超出栈的大小可能会导致栈溢出(Stack Overflow)。

堆(Heap):

  1. 特点:

    • 堆是一种动态分配内存的方式,它的大小不固定,可以根据需要动态增长或缩小。
    • 堆的分配和释放由程序员手动控制,需要显式地调用分配(如new)和释放(如delete)函数。
  2. 分配和释放:

    • 堆上的内存空间需要手动分配和释放,如果不及时释放可能会导致内存泄漏。
    • 堆上的内存分配和释放速度相对较慢,因为它需要进行内存管理和垃圾回收。
  3. 生命周期:

    堆上的内存生命周期由程序员控制,可以在需要时分配内存,在不需要时释放内存。

  4. 大小限制:

    堆的大小通常受到系统总内存的限制,可以动态增长,但受到系统资源的限制。

总结比较

  • 栈适合存储局部变量和函数调用信息等生命周期短、大小固定的数据,由编译器自动管理。
  • 堆适合存储动态分配的大型数据结构或者需要灵活管理生命周期的数据,需要程序员手动管理。

4、C/C++/Python的区别?

C 语言:

  1. 特点:

    • C 语言是一种过程式编程语言,注重过程和函数的设计。
    • C 语言是一种底层语言,更接近计算机硬件,对内存和计算机底层的控制更直接。
  2. 用途:

    C 语言适合编写操作系统、嵌入式系统、驱动程序等对性能要求高、需要直接操作硬件的场景。

  3. 性能:

    C 语言的性能很高,因为它可以直接操作内存和硬件,没有额外的运行时开销。

  4. 运行环境:

    C 语言的程序需要编译成机器码才能运行,通常不具备跨平台性,需要针对不同的平台进行编译。

C++ 语言:

  1. 特点:

    • C++ 是在 C 语言的基础上发展而来的,具有面向对象编程(OOP)的特性。
    • C++ 同时支持面向过程和面向对象的编程范式,可以更灵活地进行程序设计。
  2. 用途:

    C++ 语言适合编写大型系统、游戏开发、图形界面程序等需要面向对象设计的场景。

  3. 性能:

    C++ 语言的性能也很高,与 C 语言相比没有显著的性能损失。

  4. 运行环境:

    C++ 语言的程序也需要编译成机器码才能运行,与 C 语言类似,通常需要针对不同的平台进行编译。

Python 语言:

  1. 特点:

    • Python 是一种高级编程语言,具有简洁、易读的语法,适合快速开发和原型设计。
    • Python 支持面向对象、函数式编程等多种编程范式,具有很强的灵活性。
  2. 用途:

    Python 语言适合用于数据分析、人工智能、网络编程、Web 开发等领域,也可以用于快速脚本编写。

  3. 性能:

    Python 的性能相对于 C 和 C++ 来说较低,因为它是一种解释型语言,需要在运行时进行解释执行。

  4. 运行环境:

    Python 的程序可以直接在解释器中运行,具有良好的跨平台性,同一份代码可以在不同的平台上运行。

总结比较

  • C 语言适合需要高性能和直接操作硬件的场景,但需要程序员手动管理内存和处理更多的细节。
  • C++ 语言在 C 语言的基础上增加了面向对象的特性,适合大型系统和需要面向对象设计的场景。
  • Python 语言具有简洁易读的语法和丰富的库支持,适合快速开发和各种领域的应用,但性能相对较低。

5、面向对象的三大特性和解释?

  1. 封装
  • 定义:封装是将数据(属性)和操作(方法)封装在对象内部,对外部隐藏对象的内部实现细节,只提供有限的接口与外界交互。
  • 作用****:封装可以保护对象的内部状态,防止外部直接访问和修改对象的内部数据,提高了代码的安全性和可靠性。
  • 示例:在类中将成员变量声明为私有的,并提供公有的方法来访问和修改这些变量,就是一种封装的体现。
  1. 继承
  • 定义:继承是指一个类(子类)可以继承另一个类(父类)的属性和方法,子类可以在不改变父类的情况下扩展自己的功能。
  • 作用:继承可以实现代码的重用和扩展,减少了代码的冗余,提高了代码的可维护性和可扩展性。
  • 示例:子类可以直接使用父类的属性和方法,也可以通过重写(Override)和扩展来实现自己的特定功能。
  1. 多态
  • 定义:多态是指不同的对象对同一消息做出不同的响应,即同一个方法调用可以根据对象的不同而具有不同的行为。
  • 作用:多态可以提高代码的灵活性和可扩展性,使得程序更加通用,易于维护和扩展。
  • 示例:通过方法的重载(Overload)和方法的重写(Override)来实现多态,同一个方法名可以根据参数的不同而执行不同的操作。

6、C++中STL的理解和容器、算法、迭代器?

容器:

  1. 定义:

    • 容器是STL中用来存储数据的类模板,提供了各种数据结构,如数组、链表、栈、队列、集合、映射等。
    • STL中的容器分为序列式容器(如vector、list、deque)、关联式容器(如set、map)和容器适配器(如stack、queue)等。
  2. 特点:

    • 容器提供了高效的数据存储和访问方式,支持动态增长和缩小,具有丰富的成员函数和操作符重载。
    • 不同类型的容器适用于不同的场景,可以根据需要选择合适的容器来存储数据。
  3. 示例:

    使用vector来存储动态数组,使用map来存储键值对,使用stack来实现栈等。

算法

  1. 定义:

    • 算法是STL中用于处理容器数据的函数模板,提供了丰富的数据操作和算法实现,如查找、排序、合并等。
    • STL中的算法包括通用算法(如sort、find、transform)、数值算法(如accumulate、inner_product)、集合算法(如set_union、set_intersection)等。
  2. 特点:

    • 算法可以直接对容器进行操作,提供了丰富的功能,可以简化代码并提高效率。
    • STL的算法通常采用迭代器作为参数,因此可以适用于各种不同类型的容器。
  3. 示例:

    使用sort对数组进行排序,使用find在容器中查找指定元素,使用accumulate对容器中的元素进行累加等。

迭代器

  1. 定义:

    • 迭代器是STL中用于遍历容器元素的类模板,提供了统一的访问接口,可以逐个访问容器中的元素。
    • STL中的迭代器分为输入迭代器、输出迭代器、前向迭代器、双向迭代器和随机访问迭代器等。
  2. 特点:

    • 迭代器提供了对容器元素的统一访问方式,可以像指针一样进行自增、自减等操作。
    • 迭代器可以用于STL算法中,作为算法的参数,用于指定要操作的容器范围。
  3. 示例:

    使用迭代器遍历vector中的元素,使用迭代器作为算法的参数指定要操作的容器范围等。

7、深拷贝和浅拷贝的区别和特点?

浅拷贝

  1. 定义:
    • 浅拷贝是指将一个对象的值复制到另一个对象,如果对象中包含指针类型的成员变量,那么只会复制指针的值,而不会复制指针指向的内存。
    • 也就是说,浅拷贝只是对对象进行了一份简单的拷贝,对于指针成员变量,拷贝后的对象和原对象共享同一块内存。
  2. 特点:
    • 浅拷贝速度较快,不需要复制对象中的动态分配内存的内容。
    • 如果原对象中包含指针类型的成员变量,浅拷贝后的对象和原对象共享同一块内存,可能会导致对象间的相互影响。

深拷贝

  1. 定义:
    • 深拷贝是指将一个对象的值复制到另一个对象,并且对于对象中的指针类型的成员变量,会递归地复制指针指向的内容,而不是简单地复制指针的值。
    • 深拷贝会创建一个新的内存块,拷贝后的对象和原对象完全独立,互不影响。
  2. 特点:
    • 深拷贝速度较慢,需要复制对象中的动态分配内存的内容。
    • 深拷贝后的对象和原对象完全独立,互不影响,可以避免因为共享内存带来的问题。

给个例子说明:

假设有一个类Person,其中包含一个指向字符串的指针成员变量name,进行浅拷贝和深拷贝。

c++
#include <iostream>
#include <cstring>

class Person {
public:
    char* name;

    // 构造函数
    Person(const char* n) {
        name = new char[strlen(n) + 1];
        strcpy(name, n);
    }

    // 拷贝构造函数(浅拷贝)
    Person(const Person& other) {
        name = other.name;  // 浅拷贝,只复制指针的值
    }

    // 拷贝构造函数(深拷贝)
    Person(const Person& other) {
        name = new char[strlen(other.name) + 1];
        strcpy(name, other.name);  // 深拷贝,复制指针指向的内容
    }

    // 析构函数
    ~Person() {
        delete[] name;
    }
};

Person类中包含了一个指向字符串的指针成员变量name,在浅拷贝的构造函数中,只是简单地复制了指针的值,而在深拷贝的构造函数中,创建了一个新的内存块,并将原对象的字符串内容复制到了新的内存块中。

8、内存泄漏 和内存泄露的预防方法?

内存泄漏(Memory Leak)是指程序在动态分配内存后,无法释放已经不再需要的内存空间,导致系统的可用内存持续减少,最终可能导致系统性能下降甚至崩溃。

预防方法

  1. 合理管理内存分配和释放:

在使用newmalloc等动态分配内存的操作后,务必在不再需要使用这块内存时及时调用deletefree等函数进行释放,以确保内存得到正确的释放。

  1. 使用智能指针:

C++11引入的智能指针(如std::shared_ptrstd::unique_ptr)可以自动管理内存的释放,避免手动管理内存带来的繁琐和容易出错的问题。

  1. 避免循环引用:

在使用智能指针时,需要注意避免循环引用的情况,即A对象持有B对象的智能指针,同时B对象也持有A对象的智能指针,这样会导致对象间的引用计数无法减为0,无法释放内存。

  1. 使用RAII(Resource Acquisition Is Initialization)原则:

RAII是一种资源获取即初始化的编程技术,在对象的构造函数中进行资源的获取(如内存分配),在析构函数中进行资源的释放(如内存释放),可以有效避免忘记释放内存的问题。

  1. 使用内存检测工具:

使用内存检测工具(如Valgrind、Dr.Memory等)可以帮助检测程序中的内存泄漏问题,及时发现并解决内存泄漏。

  1. 编码规范和代码审查:

遵循良好的编码规范,编写可读性高、结构清晰的代码,进行代码审查可以帮助发现潜在的内存泄漏问题。

  1. 内存分析和优化:

定期进行内存分析和优化,检查程序中的内存使用情况,及时发现内存泄漏和性能问题,并进行优化和改进。

9、结构体内存对齐 如何进行内存对齐 ?

在C/C++中,结构体的内存对齐(Memory Alignment)是指在分配内存空间时,结构体成员变量在内存中的布局方式。内存对齐是为了提高内存访问的效率和硬件对齐要求而设计的,其主要目的是使得结构体成员变量的起始地址是其大小的整数倍。

内存对齐的原则

  1. 数据对齐:数据的起始地址必须是其大小的整数倍。
  2. 结构体对齐:结构体的起始地址必须是其最大成员大小的整数倍。
  3. 结构体内部对齐:结构体内部成员的偏移量必须是其大小的整数倍。

内存对齐的实现

  1. 默认对齐规则:在C/C++中,默认情况下,结构体的对齐规则是按照最大成员的大小来进行对齐的。比如,如果结构体中包含了一个double类型的成员变量,那么整个结构体的对齐大小就是double的大小。
  2. 手动控制对齐:在一些特殊情况下,可以通过使用特定的编译指令来手动控制结构体的对齐方式。在C++中,可以使用#pragma pack(n)指令来指定对齐方式,其中n表示对齐大小,通常是1、2、4、8等。在一些特定的嵌入式系统或者网络通信中,可能会需要手动控制对齐方式来保证数据在不同平台上的一致性。
  3. 结构体填充:为了满足对齐要求,编译器可能会在结构体的成员之间插入一些填充字节,使得结构体的大小是其最大成员大小的整数倍。这些填充字节在结构体的实际使用中是无效的,只是为了保证对齐要求。

10、智能指针 ?

在C++中,智能指针是一种用于管理动态分配内存的指针类模板,它可以在对象不再需要时自动释放所占用的内存,从而避免内存泄漏的问题。

  1. std::unique_ptr
  • 特点:

    • std::unique_ptr是一种独占式智能指针,即同一时间只能有一个std::unique_ptr指向某块内存。
    • std::unique_ptr被销毁时,它所管理的内存会被自动释放。
    • 不支持拷贝和赋值操作,可以通过std::move来转移所有权。
  • 示例:

    c++
    std::unique_ptr<int> ptr(new int(5));
  1. std::shared_ptr
  • 特点:

    • std::shared_ptr是一种共享式智能指针,可以有多个std::shared_ptr指向同一块内存。
    • 内部使用引用计数来管理内存,当最后一个std::shared_ptr被销毁时,内存会被释放。
    • 支持拷贝和赋值操作。
  • 示例:

    c++
    std::shared_ptr<int> ptr1(new int(5));
    std::shared_ptr<int> ptr2 = ptr1;
  1. std::weak_ptr
  • 特点:

    • std::weak_ptr是一种弱引用智能指针,它指向std::shared_ptr所管理的对象,但不增加引用计数。
    • 可以通过lock()方法获取一个std::shared_ptr,用于访问所管理的对象,但需要注意避免循环引用。
  • 示例:

    c++
    std::shared_ptr<int> ptr1(new int(5));
    std::weak_ptr<int> weakPtr = ptr1;
    std::shared_ptr<int> ptr2 = weakPtr.lock();
  1. std::auto_ptr(C++11之前):
  • 特点:

    • std::auto_ptr是C++11之前的一种智能指针,类似于std::unique_ptr,但具有一些缺陷,不建议在新代码中使用。
    • 在C++11中被弃用,推荐使用std::unique_ptr替代。
  • 示例:

    c++
    std::auto_ptr<int> ptr(new int(5));  // C++11之前的写法

计算机网络

1、TCP和UDP有什么区别?

  1. 连接方式
  • TCP:TCP是面向连接的协议,通信双方在传输数据之前需要先建立连接,然后进行可靠的数据传输,传输完成后再释放连接。
  • UDP:UDP是无连接的协议,通信双方在传输数据时不需要先建立连接,可以直接发送数据包,每个数据包都是独立的,不会像TCP一样进行可靠的数据传输。
  1. 数据可靠性
  • TCP:TCP提供可靠的数据传输,通过序号、确认和重传机制来保证数据的可靠性,可以保证数据的顺序和完整性。
  • UDP:UDP不提供数据传输的可靠性保证,发送的数据包可能会丢失、重复或者乱序,不保证数据的可靠性和顺序性。
  1. 数据报大小
  • TCP:TCP发送的数据没有大小限制,可以根据需要分片发送和接收。
  • UDP:UDP发送的数据报有大小限制,通常被限制在64KB以内,超过这个限制的数据需要进行分片发送。
  1. 连接开销
  • TCP:TCP需要进行三次握手建立连接和四次挥手释放连接,连接的建立和释放会带来一定的开销。
  • UDP:UDP无需建立和释放连接,不需要进行握手和挥手,连接开销较小。
  1. 适用场景
  • TCP:适用于要求数据传输可靠、顺序、不丢失的场景,如文件传输、网页访问等。
  • UDP:适用于实时性要求高、数据量小、丢失一些数据不会影响整体效果的场景,如音视频传输、实时游戏等。

2、三次握手、四次挥手?

三次握手

  1. 第一步(SYN):

    客户端发送一个带有 SYN(同步)标志的数据包给服务器,并进入 SYN_SENT 状态,表示客户端请求建立连接。

  2. 第二步(SYN + ACK):

    服务器收到 SYN 数据包后,如果同意建立连接,则会发送一个带有 SYN/ACK 标志的数据包给客户端,并进入 SYN_RCVD 状态。

  3. 第三步(ACK):

    客户端收到服务器的 SYN/ACK 数据包后,会发送一个带有 ACK 标志的数据包给服务器,表示确认连接建立,双方可以开始通信。

四次挥手

  1. 第一步(FIN):

    客户端发送一个带有 FIN(结束)标志的数据包给服务器,表示客户端希望关闭连接,进入 FIN_WAIT_1 状态。

  2. 第二步(ACK):

    服务器收到客户端的 FIN 数据包后,发送一个带有 ACK 标志的数据包给客户端,表示收到了关闭请求,但仍然允许数据传输,服务器进入 CLOSE_WAIT 状态,客户端进入 FIN_WAIT_2 状态。

  3. 第三步(FIN):

    服务器完成数据传输后,发送一个带有 FIN 标志的数据包给客户端,表示服务器也准备关闭连接,服务器进入 LAST_ACK 状态。

  4. 第四步(ACK):

    客户端收到服务器的 FIN 数据包后,发送一个带有 ACK 标志的数据包给服务器,表示确认收到了关闭请求,客户端进入 TIME_WAIT 状态。

3、TCP拥塞的控制算法?

TCP(Transmission Control Protocol)拥塞控制算法是用于控制网络中出现拥塞时TCP发送方的行为,以避免网络拥塞的发生或者减轻网络拥塞的程度,保证网络的稳定性和公平性。TCP拥塞控制算法主要包括慢启动、拥塞避免、快速重传和快速恢复等机制。

  1. 慢启动
  • 作用:慢启动算法用于在连接刚建立或者网络恢复时,逐渐增加发送窗口(即可发送的数据量),以便测试网络的拥塞程度。
  • 过程:发送方从一个较小的初始窗口大小开始发送数据,每收到一个ACK确认就将发送窗口大小加倍,指数增长,直到达到一个阈值(慢启动阈值)。
  1. 拥塞避免
  • 作用:拥塞避免算法用于在慢启动阶段结束后,维护一个合理的发送窗口大小,以避免网络拥塞。
  • 过程:当发送窗口大小达到慢启动阈值后,切换到拥塞避免算法,每收到一个ACK确认就将发送窗口大小线性增长,直到出现丢包或者超时等拥塞信号。
  1. 快速重传
  • 作用:快速重传算法用于在发送方快速发现丢失的数据包,以便快速进行重传,而不必等待超时。
  • 过程:当发送方连续收到三个重复的ACK确认时,认为有数据包丢失,立即进行快速重传,不等待超时时间。
  1. 快速恢复
  • 作用:快速恢复算法用于在快速重传后,快速恢复发送窗口的大小,以便继续发送数据。
  • 过程:在快速重传后,将慢启动阈值减半,将发送窗口大小设置为慢启动阈值,然后切换到拥塞避免算法。

操作系统

1、进程与线程的区别?

进程

  1. 定义:进程是程序的一次执行过程,是系统资源分配和调度的基本单位,拥有独立的内存空间、地址空间和系统资源。
  2. 特点:
    • 拥有独立的内存空间,每个进程都有自己的地址空间,不同进程之间的内存空间相互隔离。
    • 可以拥有多个线程,多个线程共享进程的资源。
    • 进程之间通信需要使用特定的通信机制,如管道、消息队列、共享内存等。
    • 进程的切换开销比线程大,因为需要切换整个地址空间。
  3. 应用:常见的应用有浏览器、文档编辑器等,每个应用程序通常对应一个进程。

线程

  1. 定义:线程是进程中的一个执行单元,是操作系统能够进行运算调度的最小单位。
  2. 特点:
    • 线程共享所属进程的地址空间和系统资源,每个线程有自己的栈空间。
    • 线程之间的通信更加方便,可以直接读写同一进程的内存。
    • 线程的切换开销比进程小,因为线程之间共享地址空间。
  3. 应用:常见的应用有多线程服务器、图形界面程序等,利用多线程可以提高程序的并发性和响应速度。

区别对比

  • 资源分配:进程拥有独立的地址空间和系统资源,而线程共享所属进程的资源。
  • 通信方式:进程之间通信需要特定的通信机制,而线程可以直接读写同一进程的内存。
  • 切换开销:进程切换的开销比线程大,因为需要切换整个地址空间,而线程切换的开销较小。

2、进程间通信,挑一个详细讲讲?

进程间通信(Inter-Process Communication,IPC)是指不同进程之间进行数据交换和通信的机制,用于在操作系统中实现进程之间的协作和数据共享。在多进程环境下,不同进程之间可能需要进行通信来完成某些任务,如数据传输、同步操作等。

  1. 管道
  • 描述:管道是一种半双工的通信方式,它可以在具有亲缘关系的进程之间进行通信,通常用于父子进程之间或者兄弟进程之间的通信。
  • 特点:管道是单向的,数据在管道中流动的方向是固定的,分为匿名管道和命名管道两种。
  1. 消息队列
  • 描述:消息队列是一种通过消息传递进行通信的方式,可以实现多个进程之间的通信。
  • 特点:消息队列是一种比较灵活的通信方式,发送方可以发送多种类型的消息,接收方可以选择性地接收消息。
  1. 共享内存
  • 描述:共享内存是一种将同一块物理内存映射到多个进程的通信方式,不同进程可以直接访问共享内存,实现数据共享。
  • 特点:共享内存是一种高效的通信方式,但需要考虑数据的同步和互斥问题。
  1. 信号量
  • 描述:信号量是一种用于进程间同步和互斥的机制,可以用于控制对共享资源的访问。
  • 特点:信号量可以用于解决多个进程之间的同步问题,防止竞争条件的发生。
  1. 套接字
  • 描述:套接字是一种用于不同主机之间的进程通信的通信方式,通过网络进行数据交换。
  • 特点:套接字通信是一种跨网络的通信方式,可以实现不同主机上的进程之间的通信。

3、虚拟内存?

虚拟内存的工作原理

  1. 地址映射:虚拟内存通过地址映射机制将应用程序中的逻辑地址(虚拟地址)映射到物理内存中的实际地址。CPU在访问内存时,先将虚拟地址转换为物理地址,然后再进行访问。
  2. 页面机制:虚拟内存将物理内存分成大小相等的页面(通常为4KB),并将逻辑地址空间也分成大小相等的页面。当程序访问一个页面时,如果该页面不在物理内存中,系统会将其从硬盘中调入内存,并进行地址映射。
  3. 页表:虚拟内存使用页表来管理虚拟地址和物理地址之间的映射关系,每个进程都有自己的页表,用于将其虚拟地址映射到物理地址。
  4. 页面置换:当物理内存不足时,系统会根据一定的置换算法(如LRU、FIFO等)将部分页面从物理内存中换出到硬盘上,以释放内存空间。

虚拟内存的优势

  • 更大的地址空间:每个进程可以拥有更大的地址空间,使得程序可以处理更大的数据集。
  • 更高的并发性:虚拟内存可以将多个进程的数据同时加载到内存中,提高了系统的并发处理能力。
  • 更好的内存管理:虚拟内存可以提供更灵活的内存管理机制,如动态分配、动态扩展等。

虚拟内存的缺点

  • 性能开销:虚拟内存需要额外的硬件支持和管理机制,会带来一定的性能开销。
  • 复杂性:虚拟内存的实现较为复杂,需要考虑地址映射、页面置换等问题,容易引入一些难以调试的错误。

4、python内存管理?

Python的内存管理是由其解释器(CPython)负责的,它使用了自动内存管理机制(垃圾回收)来管理内存的分配和释放。Python的内存管理主要包括对象的分配和释放、引用计数、垃圾回收等方面。

  1. 对象的分配和释放
  • 对象分配:当创建一个新的对象(如整数、字符串、列表等)时,Python会在内存中分配一块空间来存储这个对象的数据和相关信息。
  • 对象释放:当一个对象不再被引用时,Python的垃圾回收机制会检测到这个对象,并将其所占用的内存释放,以便重新利用。
  1. 引用计数
  • 引用计数:Python使用引用计数来跟踪每个对象的引用数量,即对象被引用的次数。当一个对象被引用时,其引用计数会加1;当一个对象的引用被释放时,其引用计数会减1。
  • 垃圾回收:当一个对象的引用计数变为0时,Python的垃圾回收机制会将其所占用的内存释放,并将其从内存中删除。
  1. 垃圾回收

垃圾回收机制:Python的垃圾回收机制通过检测引用计数为0的对象来释放内存。此外,Python还使用了一种称为分代回收的机制,将对象根据其存活时间分为不同的代,根据不同的代采用不同的垃圾回收策略,以提高垃圾回收的效率。

  1. 内存池机制

内存池:Python使用了内存池机制来管理小型对象的内存分配,以减少内存碎片和提高内存分配的效率。在Python中,对于小型的对象(如整数、短字符串等),会先从内存池中分配内存,而不是直接调用操作系统的内存分配函数。

  1. 循环引用
  • 循环引用:当存在循环引用时,即两个或多个对象互相引用,导致它们的引用计数都不为0,但它们已经不再被使用时,Python的垃圾回收机制无法正确释放这些对象所占用的内存,可能会导致内存泄漏。
  • 解决方法:Python的垃圾回收机制中包含了针对循环引用的特殊处理,可以通过手动解除循环引用或者使用弱引用来避免循环引用导致的内存泄漏问题。

5、Linux操作系统下开发环境,常用命令?

文件操作

  • ls:列出当前目录下的文件和子目录。
  • cd:切换当前工作目录。
  • mkdir:创建新目录。
  • rm:删除文件或目录。
  • cp:复制文件或目录。
  • mv:移动文件或目录。

文本编辑

  • vi/vim:文本编辑器,用于编辑代码文件等。
  • nano:另一个文本编辑器,相对于vi更加简单易用。

编译调试

  • gcc/g++:C/C++编译器,用于编译源代码。
  • make:自动化构建工具,根据Makefile文件编译程序。
  • gdb:GNU调试器,用于调试程序。

版本控制

  • git:分布式版本控制系统,用于管理代码的版本。
  • svn:集中式版本控制系统,也可用于管理代码的版本。

系统管理

  • ps:显示当前系统的进程状态。
  • top:实时显示系统的进程信息。
  • kill:终止指定进程。
  • chmod:修改文件或目录的权限。

网络相关

  • ping:测试网络连接是否通畅。
  • wget:下载文件或网页。
  • ssh:远程登录工具,用于远程连接Linux服务器。

6、死锁的产生以及避免?

死锁(Deadlock)是在多个进程或线程之间互相持有对方所需要的资源而无法继续执行的一种状态,是多线程或多进程并发编程中常见的问题。

死锁产生的条件

  1. 资源互斥:至少有一个资源是不可共享的,即一次只能被一个进程或线程占用。
  2. 持有和等待:进程或线程持有至少一个资源,并在等待获取其他进程或线程占用的资源。
  3. 不可抢占:已经分配给一个进程或线程的资源不能被强制性地抢占,只能由持有它的进程或线程释放。
  4. 循环等待:存在一个进程或线程的资源等待链,使得每个进程或线程都在等待下一个进程或线程所持有的资源。

死锁的避免方法

  1. 资源一次性分配:尽量避免使用不可共享的资源,或者在使用资源时尽量降低资源的独占性。
  2. 资源有序分配:规定所有的资源必须按照某个全局统一的顺序进行申请,释放资源时按相反的顺序释放。
  3. 资源预先分配:在系统运行之前就确定每个进程或线程所需要的全部资源,并根据资源的使用情况进行分配,避免在运行过程中动态地分配资源。
  4. 资源动态分配:当一个进程或线程请求资源时,系统可以根据当前的资源情况进行动态分配,但要避免出现循环等待的情况。

死锁的解决方法

  1. 死锁检测与解除:通过系统监控和检测,一旦发现死锁的存在就立即进行解除。
  2. 超时机制:设置一个超时时间,在规定时间内未能获取所需资源,则放弃对资源的请求,释放已经持有的资源,从而避免死锁。
  3. 资源剥夺:当发现系统出现死锁时,可以强制性地抢占某些进程或线程所持有的资源,以解除死锁。