腾讯面试准备

算法和数据结构

刷题

C++的stl和新特性

常量指针和指针常量

1
const int *p = &a;//常量指针,指针指向常量,指针指向的变量不能通过指针修改,但是指针指向的值可以改变
1
int *const p = &a;//指针常量,是一个常量,不能改变指向的对象(地址),但是可以改变地址的内容

野指针和悬浮指针

野指针是指向已经被释放或者⽆效的内存地址的指针

悬浮指针是指向已经被销毁的对象的引⽤

区别就是一个是指针一个是引用

网络编程

网络编程的基本流程

(1) 服务端代码流程

1
2
3
4
5
6
1. socket()             // 创建一个套接字
2. bind() // 将套接字与特定 IP 地址和端口绑定
3. listen() // 开启监听,等待客户端连接
4. accept() // 相应客户端的连接请求,接受新的连接
5. send()/recv() // 用于和客户端通信(读取请求或者写入响应
6. close() // 关闭连接

(2) 客户端代码流程

1
2
3
4
1. socket()             // 创建一个套接字
2. connect() // 连接到服务端指定的 IP 地址和端口
3. send()/recv() // 用于发送请求或读取服务端的响应
4. close() // 关闭连接

调试命令,gdb/vscode

额外:linux操作系统的内存管理,文件系统,进程和线程调度

进程间的通信机制:信号量,条件变量,生产者消费者

异步通信:回调函数,生产者消费者(promise-future和消息队列)

同步通信:阻塞式调用,文件或网络操作

引用和指针的区别

1. 什么是引用和指针?

  1. 引用(Reference)

    • 引用是某个变量的别名,声明后与该变量绑定在一起,不能再绑定其他变量。
    • 本质上是一个语法糖,用更简洁的方式访问变量。

    示例代码:

    1
    2
    3
    int a = 10;
    int& ref = a; // 引用 `ref` 绑定到变量 `a`
    ref = 20; // 实际修改的是 `a`
  2. 指针(Pointer)

    • 指针是存储变量地址的一种特殊变量,通过指针可以间接访问或操作存储在内存地址上的值。
    • 指针可以指向不同的变量或内存单元。

    示例代码:

    1
    2
    3
    int a = 10;
    int* ptr = &a; // 指针 ptr 存储变量 a 的地址
    *ptr = 20; // 实际修改的是 a

2. 引用和指针的区别

特性 引用 指针
定义方式 使用 & 声明 使用 * 声明
是否可以为空 引用必须绑定到变量 指针可以是 nullptr 或空
绑定后是否可以更改 一旦绑定不能更改 指针可以重新指向其他变量
语法 直接使用,无需解引用符号 需要用 * 解引用
内存布局 编译器实现(可能是指针) 明确存储变量地址
需要初始化 声明时必须初始化 可以声明后再初始化
别名关系 是原变量的别名 独立的变量
运算 不支持运算 可以进行加减运算
灵活性 较低,限定性强 较高,可以动态分配内存

3. 区别详解

(1) 是否必须初始化

  • 引用:

    • 引用在声明的时候必须初始化,否则无法通过编译。
    1
    2
    int a = 10;
    int& ref; // 编译错误,引用必须绑定变量
  • 指针:

    • 指针声明后可以不初始化,但最好将其初始化(例如初始化为 nullptr),否则容易产生未定义行为。
    1
    2
    int* ptr;  // 未初始化,非法操作可能导致未定义行为
    int* ptr = nullptr; // 推荐初始化为 nullptr

(2) 是否可以为空

  • 引用:

    • 引用不能指向空(nullptr),它必须始终绑定到有效的变量。
  • 指针:

    • 指针可以指向空内存区域(nullptr),表示它当前没有指向任何变量。

    示例:

    1
    2
    int* ptr = nullptr;  // 合法
    int& ref = nullptr; // 编译错误,引用必须绑定到变量

(3) 绑定后是否可以更改

  • 引用:

    • 引用一旦绑定到变量,就不能再绑定到其他变量,引用始终是它所绑定变量的别名。
    1
    2
    3
    int a = 10, b = 20;
    int& ref = a; // ref 绑定到 a
    ref = b; // 修改的是 a 的值,而不是重新绑定到 b
  • 指针:

    • 指针可以随时更改指向,可以指向其他变量或内存单元。
    1
    2
    3
    int a = 10, b = 20;
    int* ptr = &a; // ptr 指向 a
    ptr = &b; // ptr 改为指向 b

(4) 使用上的差异

  • 引用:

    • 如果将一变量赋值给引用,引用直接操作变量本身。
    1
    2
    3
    int a = 10;
    int& ref = a;
    ref = 20; // 改变的是 a,a 的值变为 20
  • 指针:

    • 要改变指针指向变量的值,需要解引用(*)。
    1
    2
    3
    4
    int a = 10, b = 20;
    int* ptr = &a; // 指针指向 a
    *ptr = 30; // 修改 a 的值为 30
    ptr = &b; // 改为指向 b

(5) 运算能力

  • 引用:
    • 引用不支持指针的算术运算,例如加减法。
  • 指针:
    • 指针可以进行算术运算,例如指针递增/递减,用于访问数组元素。
    1
    2
    3
    int arr[3] = {1, 2, 3};
    int* ptr = arr; // 指向数组的第一个元素
    ptr++; // 指向下一个元素

(6) 内存特性

  • 引用在编译器实现中,可能会转换为指针,但它对开发者是透明的。
  • 指针本质上是一个变量,存储的是某个地址,并且占用内存。

4. 引用与指针的适用场景

(1) 适用引用

  1. 函数参数传递

    • 使用引用避免拷贝实参,提高性能。
    • 常用于不需要修改参数的地方(const 引用)。
    1
    2
    3
    void print(const int& x) {
    std::cout << x << std::endl;
    }
  2. 返回值

    • 返回引用允许函数直接返回变量本身,而不是拷贝。
    1
    2
    3
    int& getValue(int& a) {
    return a;
    }
  3. 别名

    • 为变量创建更简化的别名。
    1
    2
    int a = 42;
    int& alias = a; // alias 是 a 的别名

(2) 适用指针

  1. 动态内存管理

    • 使用指针分配和释放动态内存。
    1
    2
    int* ptr = new int(10);
    delete ptr; // 手动释放内存
  2. 数组与迭代

    • 指针常用来访问数组元素。
    1
    2
    3
    4
    int arr[3] = {1, 2, 3};
    for (int* ptr = arr; ptr < arr + 3; ++ptr) {
    std::cout << *ptr << " ";
    }
  3. 数据结构

    • 指针是数据结构(如链表、树)的核心基础。
    1
    2
    3
    4
    struct Node {
    int data;
    Node* next;
    };
  4. 需要动态改变指向时

    • 指针可以灵活地改变指向,适合需要频繁切换指向的场景。

5. 总结对比

特征 引用 指针
需要初始化 必须初始化 可以先声明后初始化
是否可以为空 不可以为空 可以是空指针(nullptr
绑定是否可更改 绑定后不可更改 可通过重新赋值更改指向
语法复杂度 更简单 更复杂,需要使用 *
灵活性 受限 更灵活
常见场景 函数传参、别名 动态内存、复杂结构

引用更简单、更安全,适合大多数普通变量操作;指针更灵活,适合动态内存和复杂结构的场景。在实际开发中,应根据场景和需求选择适合的工具。

红黑树

红黑树

基本规则:

隐藏的空节点是黑色

满足Avl树

根节点和叶子节点都是黑色

不能有连续两个红色节点

从根节点到叶子节点的每个路径上黑色节点数量都一致

插入:

插入节点是根节点;直接变黑

插入节点的叔叔是红色;叔父爷爷变色,爷爷变插入节点继续调整

插入节点的叔叔是黑 ;(LL,RL,LR,RL)旋转,然后变色

红黑树 - 定义, 插入, 构建_哔哩哔哩_bilibili

金山C++研发实习生面试准备

金山C++研发实习生面试准备

1.自我介绍
2.拷打项目

3.拷打八股:

4.多态的实现

5.静态多态和动态多态

静态多态是指在编译时多态,动态多态是指运行时多态

静态多态有函数重载(函数名相同,参数不同,返回值相同或不同),函数模板

动态多态(动态绑定):即运行时的多态,在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方法。

6.动态多态怎么实现的

动态多态

通过每个类的虚函数和动态绑定

派生类实现动态绑定

  • 先拷贝基类的虚函数表
  • 如果派生类重写了基类的某个虚函数,就用派生类的虚函数替换虚表同位置的基类虚函数
  • 跟上派生类自己的虚函数

指向基类的指针或引用

动态多态需要通过指向基类的 指针或引用 操作派生类对象。如果直接操作派生对象,则是静态绑定,而非动态绑定。

7.介绍一下虚函数表和虚指针

虚函数表和虚指针是由编译器生成并使用的,它们是实现 动态绑定 和运行时多态的核心。

虚函数表是一种由编译器生成的 隐藏数据结构,它是一个函数指针数组,用来存储类中所有虚函数的地址。简单来说,虚函数表就是一个指针数组,每个元素指向一个虚函数的具体实现。

虚指针是一个 隐藏成员变量,存在于每个包含虚函数的类的对象中。虚指针用于指向当前对象所属类的虚函数表。

8.说一下构造函数和析构函数在父类和子类的执行顺序

  • 构造函数顺序
    父类构造函数成员对象的构造函数子类构造函数
  • 析构函数顺序
    子类析构函数成员对象的析构函数父类析构函数

9.析构函数的作用

析构函数用于释放对象占用的资源(如内存、文件句柄等),在对象生命周期结束时自动调用,确保资源不会泄漏。

10.为什么析构函数通常定义为虚函数;不调用析构函数会怎样

  • 原因
    若父类指针指向子类对象,且父类析构函数非虚,则通过父类指针删除对象时,只会调用父类析构函数,导致子类资源泄漏。
  • 后果
    不调用析构函数会导致资源泄漏(如内存、文件句柄未释放)。

11.结构体为何通常要进行内存对齐;一般是几字节对齐

为了提高访问效率,为了时内存命中访问效率更高

对齐原则

结构体的内存对齐通常遵循以下原则:

  1. 每个成员变量的起始地址必须是其自身大小的整数倍
  2. 结构体本身的大小必须是其最大对齐系数(对齐要求)的整数倍

12.new/delete和malloc/free的区别

特性 new/delete malloc/free
语言特性支持 C++ 专有 C 风格(C 和 C++ 均支持)
类型安全 否(需要手动类型转换)
初始化支持 自动调用构造函数和析构函数 不调用,需要手动处理
异常处理 内存分配失败抛出 std::bad_alloc 内存分配失败返回 NULL
灵活性 适合对象管理和复杂场景 适合简单的POD或跨语言
效率 稍有额外开销(构造/析构函数调用) 相对高效

什么时候选择哪一个?

使用 new/delete

  • 在C++程序中,需要创建具有构造函数和析构函数的复杂对象。
  • 希望利用类型安全和异常处理机制,提高代码的健壮性。
  • 更偏向于面向对象的编程风格。

使用 malloc/free

  • 在C语言程序中,或者需要与 C 的库/代码接口。
  • 需要跨语言兼容时(如 C 和 C++ 混合开发)。
  • 只操作简单的内存(非对象)时,比如使用裸内存块或数组。

总结

  1. newdelete 是C++中的语法糖,功能更强大,更贴合现代编程需求。
  2. mallocfree 是底层内存管理工具,适合裸内存分配。
  3. 在C++开发中,通常推荐优先使用 newdelete,除非有特殊需要才选择 mallocfree

13.什么情况用mmap()

mmap() 是一个 POSIX 标准的内存映射函数,通常用于将文件或设备映射到进程虚拟内存空间中,使得我们可以通过内存访问文件内容,大大提高了 I/O 的效率。它的使用场景主要集中在对文件的高效访问、大文件处理、共享内存以及特殊的内存管理需求等。以下是 mmap() 的典型使用情况、优缺点以及示例解释。

1.高效文件访问,减少内核态和用户态之间的拷贝开销

2.处理大文件,将文件内容按需映射到虚拟内存中

3.共享内存(进程间通信)

  • mmap() 支持使用 MAP_SHARED 标志创建共享内存,多个进程可以操作同一内存区域进行通信。
  • 使用共享内存比使用消息队列、管道等传统 IPC 机制有更高的效率。

4.文件修改或写入(内存作为缓冲区)适用于需要高效更新大文件的场景

5.实现内存映射I/O

6.异步I/O和并行处理

使用 mmap() 的场景主要是权衡性能与开销,以下是具体选择标准:

  1. 适合用 mmap() 的场景
    • 需要频繁随机访问大文件。
    • 文件的内容按需加载,不需要一次性加载到内存。
    • 多进程需要共享文件或内存。
    • 操作设备文件或硬件寄存器。
    • 文件访问频繁,且希望减少用户态与内核态之间频繁的数据拷贝。
  2. 不太适合用 mmap() 的场景
    • 操作小文件,或者一次性读取整个文件,不需要复杂的随机访问。
    • 文件处理逻辑非常简单,而 mmap() 的复用性和管理开销反而适得其反。
    • 跨平台兼容需求非常高的场景。

14.动态链接库用的brk()还是mmap()

特性 brk() mmap()
内存范围 紧跟数据段末尾 任意空闲的虚拟地址空间
按需加载 不支持 支持
分页分配 不支持 支持,以页为单位分配和管理
碎片问题 容易引发堆碎片 避免堆碎片,内存管理灵活
共享内存 不支持 支持共享内存(MAP_SHARED 标志)
动态链接库加载 不适用 是动态链接库的默认选择

因此,动态链接库的加载和运行使用 mmap() 是最佳选择,它为动态链接库的按需加载、多进程共享、独立管理以及性能优化提供了完美支持。同时,brk() 由于其限制,已不适用于动态库加载。

15.动态链接和静态链接的区别

特性 静态链接 动态链接
生成的可执行文件体积 较大,库代码直接嵌入 较小,仅包含对动态库的引用
运行时依赖 不依赖外部库,所有代码都在一个文件中 依赖外部库,运行时需要动态加载
运行时性能 加载速度快,无需加载外部库 加载速度稍慢,需要动态加载符号解析
内存使用 每个程序独立占用一份库的内存 多个程序共享同一个库,节省内存
更新方式 更新程序需要重新编译并链接静态库 可以通过更新库文件,无需重新编译程序
跨平台兼容性 静态库只与编译平台相关,适合封装依赖 动态库可能与运行时环境、平台设置相关
调试难度 相对简单,因为库已集成进可执行文件 调试更复杂,可能需要检查多个动态库
符号冲突问题 不会出现符号冲突,代码已固定 有可能发生符号冲突,需要小心处理
磁盘存储 每个可执行文件都包含完整的库代码 动态库存储独立,节省磁盘空间
维护与分发 更新程序和库需要重新分发完整的可执行文件 库文件可以单独更新,减少分发工作量

16.红黑树特性;有哪些应用

  • 特性
    1. 每个节点是红或黑。
    2. 根节点和叶子节点(NIL)是黑。
    3. 红节点的子节点必为黑。
    4. 从任一节点到其叶子节点的路径包含相同数量的黑节点。
  • 应用
    C++ STL map/set、Java TreeMap、Linux 内核调度器等。

17.死锁的必要条件有哪些

  1. 互斥:资源只能被一个进程占用。
  2. 持有并等待:进程持有资源并等待其他资源。
  3. 不可抢占:资源只能由持有者主动释放。
  4. 循环等待:存在进程资源的环形等待链。

18.线程安全实现方式

  1. 互斥锁(Mutex):保证临界区代码原子性。
  2. 原子操作(Atomic):通过硬件支持的原子指令(如 CAS)。
  3. 无锁编程:如使用无锁数据结构。
  4. 线程局部存储(TLS):避免共享数据。
  5. 读写锁(Read-Write Lock):区分读/写操作。
  6. 条件变量(Condition Variable):线程间同步。

docker上传文件至容器

docker上传文件至容器

拿到容器ID

1
docker ps -a

将本地文件上传到容器的指定目录中

1
docker cp 本地文件路径 ID全称:容器路径
1
docker cp /home/stydent/EdgeGPT /usr/local/lib/python3.11/site-packages
1
sudo docker cp fbf56805d23a:/usr/local/lib/python3.11/site-packages ./site-packages

docker遇到的问题

拉取镜像失败是网络问题,先看网络

docker命令报错error during connect: Get http://2F2F.2Fpipe2Fdocker_engine/v1.36/containers/json: open//.

在清理docker时遇到

1
docker system prune

原因是docker无法挂载到wsl上

因为刚刚执行压缩命令占用了vhdx文件,不能把docker文件关联上

关机重启就好

1
2
3
4
5
6
7
8
9
 docker system prune
WARNING! This will remove:
- all stopped containers
- all networks not used by at least one container
- all dangling images
- unused build cache

Are you sure you want to continue? [y/N] y
Total reclaimed space: 0B

ngnix实现共享文件

ngnix实现共享文件

乱码解决

1
2
server和loaction层都加上
charset utf-8

配置 Nginx

1
sudo vi /etc/nginx/sites-enabled/default

验证配置

1
2
3
sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

这样测试说明 nginx 配置是正确的。

重启启动 nginx 服务

1
sudo service nginx restart

权限问题

要求权限是755才能正常访问

1
chmod -R 755 xmal/

openvpn

openvpn

下载

1
wget -O openvpn.sh https://get.vpnsetup.net/ovpn

安装

1
sudo bash openvpn.sh --auto

启动

1
sudo bash openvpn.sh

system初始化问题

System has not been booted with systemd as init system (PID 1). Can’t operate.
Failed to connect to bus: Host is down

翻译过来就是:“系统尚未以systemd作为初始系统启动”。

问题分析:

1)当你尝试使用 systemd 命令来管理 Linux 系统上的服务的时候,之所以会报错,可能因为系统中根本就没有使用 systemd,而是使用的 SysV init (sysvinit)。

2)如果你是在 windows 中通过 WSL 使用的 Ubuntu 或者 Dibian 系统,默认情况下系统使用的是 SysV 而不是 systemd。