Skip to content

腾讯微信部门C++开发实习一面

C++

1、多态靠什么机制实现的?

  1. 静态多态(编译时多态,也称为早绑定或函数重载):静态多态(编译期多态)的主要实现方式有函数重载和模板。函数重载是指在同一作用域内定义多个同名函数,但参数列表不同,由编译器根据参数类型和数量确定调用哪个函数。而模板是在编译时根据模板参数的类型生成具体的代码,实现对不同类型的参数进行相同操作的函数或类。
c++
#include <iostream>

void print(int value) {
    std::cout << "Integer value: " << value << std::endl;
}

void print(double value) {
    std::cout << "Double value: " << value << std::endl;
}

int main() {
    print(10);
    print(3.14);
    return 0;
}
  1. 动态多态(运行时多态,也称为晚绑定或虚函数):在运行时根据对象的实际类型来确定调用的函数或方法。动态多态是通过虚函数(virtual function)和继承(inheritance)实现的。在动态多态中,基类的指针或引用可以指向派生类的对象,并且通过这个指针或引用调用的虚函数会根据对象的实际类型来确定调用哪个版本的函数,这种机制称为动态绑定。动态多态的一个特点是可以实现运行时的多态性,即在运行时才确定调用的函数版本。
c++
#include <iostream>

class Base {
public:
    virtual void print() {
        std::cout << "Base class" << std::endl;
    }
};

class Derived : public Base {
public:
    void print() override {
        std::cout << "Derived class" << std::endl;
    }
};

int main() {
    Base* basePtr = new Derived();
    basePtr->print();  // 输出 "Derived class"
    delete basePtr;
    return 0;
}

2、多态内部通过什么实现的?

参考1。

3、什么是纯虚函数?

纯虚函数是在基类中声明的虚函数,但没有在基类中给出具体的实现,它只是一个接口,需要在派生类中进行实现。纯虚函数通过在函数声明后面加上 = 0 来声明。一个类中只要有一个纯虚函数,就成为了抽象类,抽象类不能实例化对象,只能作为基类被继承。

纯虚函数的作用:

  1. 提供接口规范:通过纯虚函数,基类可以定义一组接口规范,要求派生类必须实现这些接口,从而保证派生类具有某种特定的行为或功能。
  2. 实现多态:纯虚函数和虚函数一样,支持运行时的多态性。在运行时,根据对象的实际类型调用相应的函数。

参考代码:

c++
#include <iostream>

// 抽象类
class Shape {
public:
    // 纯虚函数,提供接口规范
    virtual double area() const = 0;
    virtual void draw() const = 0;
};

// 派生类,实现了 Shape 接口
class Circle : public Shape {
public:
    Circle(double r) : radius(r) {}
    double area() const override {
        return 3.14 * radius * radius;
    }
    void draw() const override {
        std::cout << "Drawing circle" << std::endl;
    }

private:
    double radius;
};

// 派生类,实现了 Shape 接口
class Rectangle : public Shape {
public:
    Rectangle(double w, double h) : width(w), height(h) {}
    double area() const override {
        return width * height;
    }
    void draw() const override {
        std::cout << "Drawing rectangle" << std::endl;
    }

private:
    double width;
    double height;
};

int main() {
    Circle c(5);
    Rectangle r(3, 4);

    // 通过指向基类的指针实现多态
    Shape* shapePtr1 = &c;
    Shape* shapePtr2 = &r;

    std::cout << "Circle area: " << shapePtr1->area() << std::endl;
    std::cout << "Rectangle area: " << shapePtr2->area() << std::endl;

    shapePtr1->draw();
    shapePtr2->draw();

    return 0;
}

4、用过lambda表达式吗? 能否写一个?

Lambda表达式是C++11引入的一种函数对象,可以用来创建匿名函数。它的基本语法如下:

c++
[capture list](parameters) -> return_type { body }

其中:

  • capture list:捕获列表,用来捕获外部变量。可以是值捕获([=])、引用捕获([&])或特定变量的捕获([x, &y])等方式。
  • parameters:参数列表,与普通函数的参数列表类似。
  • return_type:返回类型,可以省略,由编译器推导出来。
  • body:函数体,与普通函数的函数体类似。

给个例子:

c++
#include <iostream>

int main() {
    int a = 5;
    int b = 10;

    // Lambda表达式,求两个数的和
    auto sum = [](int x, int y) -> int {
        return x + y;
    };

    std::cout << "Sum of " << a << " and " << b << " is " << sum(a, b) << std::endl;

    return 0;
}

Lambda表达式也可以捕获外部变量。

c++
#include <iostream>

int main() {
    int a = 5;
    int b = 10;

    // Lambda表达式,捕获外部变量a,求和
    auto sum_with_capture = [a](int x) -> int {
        return a + x;
    };

    std::cout << "Sum of " << a << " and 7 is " << sum_with_capture(7) << std::endl;

    return 0;
}

5、Linux中进程内存布局知道吗?

  1. 栈:栈是用于存储函数的局部变量和函数调用信息的内存区域。每当调用一个函数,系统会为其分配一个栈帧,包含局部变量和函数参数。当函数执行完毕,栈帧被弹出,释放相关内存。栈的分配和释放速度很快,但大小通常受限于系统。
  2. 堆:堆是用于动态内存分配的区域,通常由开发人员手动分配和释放。堆上的内存需要手动管理,因此需要小心避免内存泄漏和悬挂指针问题。堆的大小通常比栈大,但分配和释放速度较慢。
  3. 全局/静态存储区:这是用于存储全局变量和静态变量的区域。全局变量是在程序启动时创建的,持续到程序结束,而静态变量在它们首次访问时初始化,然后保持其值在函数调用之间保持不变。
  4. 自由存储区:就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。
  5. 常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改(当然,你要通过非正当手段也可以修改,而且方法很多)

6、各个分区存放哪些数据?

  1. 栈(Stack):存放函数的局部变量、函数参数值、返回地址等信息。栈是一种后进先出(LIFO)的数据结构,函数调用时会分配栈帧,在函数返回时释放栈帧。因此,栈上的数据的生命周期与函数的调用关系密切。栈空间通常较小,大小受限于系统,但分配和释放速度快。
  2. 堆(Heap):堆用于动态分配内存,存放动态分配的变量和对象。堆上的内存需要手动管理,使用malloc、new等函数分配内存,使用free、delete等函数释放内存。堆的大小通常比栈大,但分配和释放速度较慢。
  3. 全局/静态存储区(Global/Static Storage):全局变量和静态变量存放在这个区域。全局变量在程序启动时创建,持续到程序结束,静态变量在首次访问时初始化,然后保持其值在函数调用之间保持不变。
  4. 自由存储区(Free Store):也是用于动态分配内存的区域,类似于堆。它是通过malloccalloc等函数分配的内存块,需要通过freedelete等函数释放。
  5. 常量存储区(Constant Storage):存放常量数据,如字符串常量、全局常量等。这些数据通常在编译时就确定,并且不允许修改。常量存储区通常位于只读内存中,尝试修改这些数据会导致程序崩溃。

7、临时变量是存在哪里的?

  1. 函数内部的临时变量:在函数内部声明的临时变量通常存储在栈上。当函数调用时,会为这些临时变量分配内存,当函数返回时,这些临时变量所占用的内存会被释放。
  2. 表达式中的临时变量:在表达式计算过程中产生的临时变量通常存储在寄存器中,这样可以提高计算速度。对于无法存储在寄存器中的较大的临时变量,可能会存储在栈上。
  3. 全局临时变量:如果临时变量是全局的,则存储在静态存储区。这些变量在程序运行期间一直存在,不会随着函数的调用而分配和释放。

8、在堆上分配内存,在C++中怎么去申请?

  1. 使用new申请内存:new关键字用于在堆上动态分配内存,语法如下:

    c++
    int* ptr = new int; // 分配一个整型变量的内存空间

    例子中,new int将返回一个指向动态分配的整型变量的指针,并将该指针赋值给ptr变量。如果需要分配的是数组,可以使用如下语法:

    c++
    int* arr = new int[10]; // 分配一个包含10个整型元素的数组的内存空间
  2. 使用delete释放内存:使用new分配的内存需要手动释放,否则会导致内存泄漏。可以使用delete关键字来释放内存,语法如下:

    c++
    delete ptr; // 释放ptr指向的内存空间

    如果分配的是数组,需要使用delete[]释放内存:

    c++
    delete[] arr; // 释放arr指向的数组内存空间

    需要注意的是,释放内存后,指针将变为悬空指针,应该将其设置为nullptr以避免误用。

9、假如现在一个数据量比较大,比如达到兆级,一般放在哪里?

  1. 硬盘: 对于大量数据,最常见的存储位置是硬盘。硬盘有较大的存储容量,可以持久保存数据,但读写速度相对较慢,适合存储不经常访问的数据。
  2. 内存: 如果数据量不是特别大,但又需要快速访问,可以将数据存储在内存中。内存读写速度非常快,适合存储需要频繁访问的数据,但内存容量有限,无法存储过大的数据。
  3. 数据库: 对于需要结构化存储和管理的数据,可以将数据存储在数据库中。数据库可以是关系型数据库(如MySQL、PostgreSQL)或非关系型数据库(如MongoDB、Redis),具体选择根据数据特点和访问需求而定。
  4. 分布式存储系统: 对于大规模数据存储和处理,可以使用分布式存储系统,如Hadoop的HDFS、Apache Cassandra、Amazon S3等。这些系统可以横向扩展,适合存储PB级别甚至更大规模的数据。
  5. 缓存: 对于需要频繁访问的数据,可以使用缓存来提高访问速度。常见的缓存系统包括Redis、Memcached等,它们通常将数据存储在内存中,提供快速的读写访问。

10、C++没有内存回收,C++中分配内存要考虑到什么问题?

  1. 内存泄漏:分配了内存但没有释放,导致程序运行时内存不断累积,最终可能耗尽可用内存。解决方法是在不需要使用内存时及时释放。
  2. 重复释放:对同一块内存多次释放会导致程序崩溃或不可预测的行为。解决方法是避免重复释放同一块内存。
  3. 野指针:指向已释放内存的指针称为野指针,访问野指针会导致程序崩溃或不可预测的行为。解决方法是在释放内存后将指针置为nullptr。
  4. 内存越界:访问超出分配内存范围的内存会导致程序崩溃或不可预测的行为。解决方法是确保访问的内存范围不超出分配的范围。
  5. 内存对齐:某些平台要求内存按照一定的字节对齐方式分配,不正确的内存对齐可能导致性能问题或程序崩溃。解决方法是根据平台要求正确对齐内存。
  6. 内存拷贝:在使用动态内存分配时,需要注意拷贝指针指向的内存而不是指针本身,否则可能导致两个指针指向同一块内存,释放时会出错。
  7. 内存分配效率:频繁的内存分配和释放会影响程序性能,可以通过对象池、内存池等方式提高内存分配效率。
  8. 异常安全:在分配内存时要考虑异常安全,即当内存分配失败时程序能够正常处理,避免资源泄漏。

11、避免内存泄漏的方法有哪些?

  1. 手动释放内存:在动态分配内存后,确保在不再需要使用时及时释放。使用deletedelete[]释放对应的内存。
  2. 使用智能指针:使用智能指针管理动态分配的内存,可以避免忘记释放内存的问题。C++11引入的std::shared_ptrstd::unique_ptr是两种常用的智能指针,它们会在对象不再需要时自动释放内存。
  3. 避免循环引用:如果使用std::shared_ptr管理对象,要注意避免循环引用,即两个对象互相持有对方的shared_ptr,导致对象无法释放。
  4. RAII(资源获取即初始化):使用RAII管理资源,即在对象构造时分配资源,在对象析构时释放资源。通过RAII,可以确保资源在对象不再需要时被正确释放。
  5. 内存检测工具:使用内存检测工具(如Valgrind、Dr.Memory等)来检测内存泄漏问题,及时发现并修复潜在的内存泄漏。

12、在使用过程中主动释放,但是很难避免程序在运行过程中中途退出的情况,内存也会泄漏,那有没有其他更好的方式避免内存泄漏?

  1. 避免使用裸指针:尽量避免使用裸指针进行内存管理,因为裸指针容易导致内存泄漏和悬挂指针问题。可以使用智能指针或其他更安全的内存管理方式代替裸指针。
  2. 使用内存池/对象池:可以使用内存池或对象池来管理对象的内存分配和释放,从而减少动态内存分配和释放的次数,降低内存泄漏的风险。

13、智能指针是用什么方式避免内存泄漏的?

  1. 自动释放内存:智能指针通过在析构函数中释放内存来确保内存被正确释放。当智能指针指向的对象不再被引用时,智能指针对象会自动调用析构函数,从而释放内存。
  2. 引用计数:智能指针通常使用引用计数来跟踪对象的引用次数。每次创建一个指向对象的智能指针时,引用计数加一;每次销毁一个指向对象的智能指针时,引用计数减一。当引用计数为零时,说明没有任何指针指向该对象,此时会自动释放对象的内存。
  3. 避免悬挂指针:智能指针可以防止悬挂指针问题的发生。当对象被释放后,智能指针会自动将指针置为nullptr,从而避免了访问已释放对象的问题。
  4. 管理数组内存:C++11引入的std::unique_ptr和std::shared_ptr可以用于管理动态数组的内存。智能指针可以确保在释放数组内存时调用delete[]而不是delete。
  5. 异常安全性:智能指针提供了异常安全性,即使在发生异常时,智能指针也可以正确地释放内存,避免内存泄漏。

操作系统

1、进程间的通信方式有哪些?

  1. 管道(Pipe):管道是一种半双工的通信方式,用于具有亲缘关系的进程之间的通信。在Linux中,管道可以是匿名管道(通过pipe函数创建)或命名管道(通过mkfifo函数创建)。管道有一定的缓冲区,可以在一端写入数据,在另一端读取数据。
  2. 命名管道(Named Pipe):也称为FIFO(First In First Out),是一种特殊的文件类型,用于不具有亲缘关系的进程之间的通信。命名管道在文件系统中有一个路径名,进程可以通过文件I/O操作来进行通信。
  3. 消息队列(Message Queue):消息队列是一种消息传递机制,允许进程通过将消息发送到队列中来进行通信。消息队列通常具有特定的格式和优先级,可以实现进程间的异步通信。
  4. 信号量(Semaphore):信号量是一种用于控制对共享资源访问的机制。进程可以通过对信号量进行操作来实现对共享资源的互斥访问和同步。
  5. 共享内存(Shared Memory):共享内存是一种高效的进程间通信方式,允许多个进程共享同一块内存区域。通过共享内存,进程可以直接读写共享数据,而无需进行数据拷贝,因此可以实现较高的通信速度。
  6. 套接字(Socket):套接字是一种网络编程接口,可以用于不同主机上的进程间通信,也可以用于同一主机上的进程间通信。套接字通信可以在网络上进行,也可以在同一台计算机上进行本地通信。

2、多个线程间的有哪些通信方式?

  1. 共享内存:多个线程可以访问同一块共享内存区域,通过在共享内存中读写数据来进行通信。共享内存通常需要配合使用互斥锁等同步机制来确保数据的一致性和安全性。
  2. 互斥锁(Mutex):互斥锁用于保护共享资源,确保在同一时间只有一个线程可以访问共享资源。当一个线程获取了互斥锁后,其他线程需要等待该线程释放锁才能访问共享资源。
  3. 条件变量(Condition Variable):条件变量用于在多个线程之间进行条件等待和通知。线程可以在条件变量上等待某个条件成立,当条件成立时,另一个线程可以通过条件变量通知等待线程。
  4. 信号量(Semaphore):信号量是一种用于控制并发访问的同步原语。它可以用来限制同时访问共享资源的线程数量,也可以用于线程间的信号通知。
  5. 屏障(Barrier):屏障用于在多个线程之间进行同步,使得多个线程在达到某个点之前都会阻塞,直到所有线程都到达后才继续执行。
  6. 消息队列(Message Queue):消息队列可以用于线程之间的异步通信,一个线程可以向消息队列中发送消息,另一个线程可以从队列中接收消息。
  7. 线程间的数据传递:线程可以通过共享的全局变量或者函数参数来进行数据传递。但需要注意线程安全性,避免出现数据竞争等问题。

3、Linux下查看Linux服务器的性能指标通过什么查看?比如内存、CPU使用情况?

  1. top:top命令可以实时显示系统中各个进程的资源占用情况,包括CPU占用率、内存占用率等。可以使用top命令查看系统整体的性能情况,按1可以查看各个CPU核心的使用情况。
  2. ps:ps命令可以列出当前系统中的进程信息,包括进程的PID、占用的CPU和内存等信息。常用的选项包括ps auxps -ef
  3. free:free命令用于查看系统的内存使用情况,包括已使用、空闲、缓存和交换空间等信息。常用的选项包括free -h以人类可读的方式显示内存使用情况。
  4. vmstat:vmstat命令用于显示系统的虚拟内存、内存、磁盘、CPU等性能指标。可以使用vmstat命令查看系统的整体性能情况,也可以使用vmstat <interval> <count>实时监控。
  5. sar:sar命令是System Activity Reporter的缩写,用于收集、报告和保存系统的性能数据,包括CPU、内存、磁盘、网络等方面的指标。可以使用sudo apt-get install sysstat安装sar工具。
  6. iostat:iostat命令用于查看系统的磁盘I/O性能指标,包括每个磁盘的读写速度、I/O等待时间等。可以使用iostat命令查看磁盘性能情况。
  7. uptime:uptime命令用于显示系统的运行时间以及平均负载情况。可以使用uptime命令查看系统的运行时间和当前的平均负载。

4、别人作为客户端给Linux服务器上的程序发消息,但是你这边没收到,Linux下面用什么方式命令可以快速知道这个包有没有到你这边?

在Linux系统下,可以使用tcpdump工具来快速检查网络包是否到达服务器。tcpdump是一个强大的网络抓包工具,可以用来捕获和分析网络流量。

  1. 安装tcpdump工具(如果没有安装):

    bash
    sudo apt-get install tcpdump   # Ubuntu/Debian系统
    sudo yum install tcpdump       # CentOS/RHEL系统
  2. 使用tcpdump命令捕获指定端口的网络流量:

    bash
    sudo tcpdump -i eth0 port <port_number>

    其中,-i参数指定网络接口(如eth0),port参数指定要捕获的端口号。如果要捕获所有端口的流量,可以省略port参数。

  3. 在客户端发送消息到服务器后,观察tcpdump的输出。如果服务器成功接收到消息,你应该能在输出中看到相应的网络包。

  4. 如果tcpdump输出中没有看到相应的网络包,可能是由于网络配置、防火墙设置等问题导致消息未能到达服务器。需要进一步排查网络环境和配置是否正确。

5、调试程序有BUG是通过什么方式调试的?

  1. 使用调试器(如gdb、lldb等):
    • 使用调试器可以逐行跟踪程序执行,查看变量的值,检查内存状态等。
    • 可以设置断点,在程序执行到断点处停止,查看当前的执行状态和变量值,帮助定位问题。
    • 可以使用调试器提供的各种命令和功能,如查看堆栈、查看寄存器状态、设置条件断点等。
  2. 打印调试信息:
    • 在程序中插入打印语句,输出关键变量的值,以便观察程序执行过程中的状态。
    • 可以根据打印信息的输出,判断程序执行到哪一步出现问题,帮助定位BUG。
  3. 日志记录:
    • 使用日志记录库(如log4cpp、spdlog等)记录程序运行时的关键信息。
    • 可以在程序中适当地添加日志记录语句,记录程序执行过程中的关键步骤和变量值,便于分析问题。
  4. 单元测试和集成测试:
    • 编写单元测试和集成测试用例,对程序的各个模块进行测试,发现潜在的BUG。
    • 可以使用测试框架(如Google Test、JUnit等)来编写和运行测试用例。
  5. 静态分析工具:
    • 使用静态分析工具(如Cppcheck、PVS-Studio等)分析源代码,查找潜在的编码错误和安全隐患。
  6. 动态分析工具:
    • 使用动态分析工具(如Valgrind、AddressSanitizer等)检测内存泄漏、越界访问等问题。

计算机网络

1、5层网络协议是哪5层?

五层网络协议是指 OSI(Open Systems Interconnection,开放式系统互联)参考模型中的五层网络协议。该模型将网络通信划分为五个抽象层次,每个层次负责特定的功能,从物理传输到应用层,每一层都依赖于下一层的功能。

  1. 物理层(Physical Layer):负责传输比特流,实现数据的物理传输。主要涉及电压、信号、数据传输速率等物理特性。
  2. 数据链路层(Data Link Layer):处理数据帧的传输,确保数据在相邻节点之间的可靠传输。主要包括数据的帧封装、透明传输、差错检测和纠正等功能。
  3. 网络层(Network Layer):负责数据的路由和转发,实现不同网络之间的数据传输。主要功能包括寻址、路由选择、拥塞控制等。
  4. 传输层(Transport Layer):提供端到端的数据传输服务,保证数据可靠性和顺序性。主要功能包括分段、重组、流量控制和差错校验等。
  5. 应用层(Application Layer):为用户提供应用服务,是用户与网络之间的接口。主要功能包括文件传输、电子邮件、远程登录等。

2、TCP属于哪一层?

TCP(Transmission Control Protocol,传输控制协议)属于 OSI 参考模型中的传输层(Transport Layer)。传输层主要负责为两台主机之间的数据传输提供可靠的端到端通信和错误恢复功能。TCP通过提供面向连接的、可靠的数据传输服务,确保数据在通信过程中不会丢失、损坏、重复或无序。

3、数据链路层通过什么来寻址?

数据链路层通过 MAC 地址(Media Access Control Address,媒体访问控制地址)来进行寻址。MAC 地址是一个全球唯一的地址,通常由 48 位二进制数表示,常以 6 个十六进制数对的形式显示,比如 00:1A:2B:3C:4D:5E。在以太网中,每个网络设备都有一个唯一的 MAC 地址,用于标识设备的硬件地址。

数据链路层使用 MAC 地址来确定数据包应该从网络中的哪个设备接收。当一个设备要发送数据包时,它会在数据帧中包含目标设备的 MAC 地址。网络中的交换机或路由器会根据数据帧中的目标 MAC 地址来决定如何转发数据包。

4、网络层通过什么来寻址?

网络层通过 IP 地址(Internet Protocol Address,互联网协议地址)来进行寻址。IP 地址是一个用于识别网络中设备的逻辑地址,它是在网络层使用的地址。IP 地址的格式通常是 IPv4 或 IPv6,IPv4 地址由 32 位二进制数表示,通常以点分十进制的形式显示,如 192.168.1.1;IPv6 地址由 128 位二进制数表示,通常以冒号分隔的十六进制数表示,如 2001:0db8:85a3:0000:0000:8a2e:0370:7334

在网络中,每个设备都有一个唯一的 IP 地址,用于标识该设备在网络中的位置。当一个设备要发送数据包时,它会在数据包的 IP 头部中包含目标设备的 IP 地址。路由器通过检查目标 IP 地址来决定如何转发数据包,将数据包从源设备传输到目标设备。

5、不在同一局域网的两台机器,分别只有IP,在客户端发出消息到服务端收到地址,经过哪些协议?

假设客户端的IP地址为ClientIP,服务端的IP地址为ServerIP:

  1. ARP(地址解析协议):客户端首先要确定服务端的MAC地址(物理地址),因为在发送数据时需要知道目标机器的MAC地址。为了获取目标机器的MAC地址,客户端会发送一个ARP请求广播,请求目标机器的MAC地址。
  2. 路由选择:客户端发送数据时,路由器会根据目标IP地址选择合适的路径进行转发。这一步会涉及到路由表的查找和决策过程。
  3. NAT(网络地址转换):如果客户端和服务端不在同一子网下,通常还会涉及到NAT过程。NAT会将客户端的私有IP地址转换为公有IP地址,以便在公网上进行通信。
  4. IP协议:客户端将数据封装成IP数据包,并通过互联网传输到服务端。
  5. ICMP协议:在数据传输过程中,可能会遇到网络问题,比如路由不可达等。ICMP协议用于在网络中传递错误信息和控制信息,帮助进行网络故障的排除。
  6. TCP/UDP协议:在传输层,客户端和服务端会使用TCP或UDP协议来建立连接和传输数据。TCP协议提供可靠的数据传输,通过三次握手建立连接;UDP协议则是无连接的,不保证数据传输的可靠性。
  7. DNS协议:在通信过程中,客户端需要知道服务端的IP地址。客户端会向DNS服务器发送查询请求,获取服务端的域名对应的IP地址。
  8. 应用层协议:最后,客户端和服务端使用特定的应用层协议进行通信,比如HTTP、FTP等协议。这些协议定义了数据的格式和交换方式,实现了具体的功能。

6、ARP协议的流程是什么?

  1. 主机发送ARP请求:当主机A需要通信到主机B时,首先会检查本地ARP缓存表中是否有目标IP地址对应的MAC地址。如果没有,则主机A会发送一个ARP请求广播包,包含了主机A的MAC地址、IP地址以及需要解析的目标IP地址。
  2. 局域网中其他主机处理ARP请求:局域网中的所有主机都会接收到这个ARP请求包,但只有目标IP地址与自己IP地址相同的主机会处理该请求。其他主机会忽略这个请求。
  3. 目标主机返回ARP响应:如果目标主机B收到了ARP请求并且确定自己是请求的目标,它会向主机A发送一个ARP响应包。这个响应包包含了主机B的MAC地址。
  4. 主机A更新ARP缓存表:主机A收到ARP响应后,会将目标IP地址和MAC地址的对应关系存储到本地的ARP缓存表中。这样,在以后的通信中,主机A就可以直接使用这个对应关系,而不需要再次发送ARP请求。
  5. 数据包发送:有了目标主机的MAC地址,主机A就可以将要发送的数据封装成以太网帧,并通过交换机传输到目标主机B。
  6. 定期刷新:为了保持ARP缓存表中的对应关系是最新的,主机会定期发送ARP请求,以便更新对应关系。

7、socket编程,TCP和UDP在API层面主要有哪些区别?

  1. 连接方式:
    • TCP是面向连接的,需要先建立连接,然后再进行数据传输,通过connect()函数建立连接。
    • UDP是无连接的,每次数据传输都是独立的,不需要建立连接,通过sendto()recvfrom()函数发送和接收数据。
  2. 可靠性:
    • TCP提供可靠的数据传输,保证数据不丢失、不重复、按序到达,如果数据包丢失会进行重传。
    • UDP不保证数据传输的可靠性,数据可能丢失、重复或者乱序到达。
  3. 数据流模式:
    • TCP是基于字节流的,保证数据按照发送顺序到达,应用程序可以直接读取到完整的数据包。
    • UDP是基于数据报的,发送的数据包是独立的,接收端每次接收一个完整的数据包。
  4. 通信方式:
    • TCP是一对一的通信方式,即一个连接只有两个端点,数据传输的目标是确定的。
    • UDP支持一对一、一对多、多对一和多对多的通信方式,可以实现广播和多播。
  5. 错误处理:
    • TCP有内建的错误检测和重传机制,能够自动处理网络传输中的错误。
    • UDP不具备错误处理机制,应用程序需要自行处理丢失或损坏的数据包。
  6. 拥塞控制:
    • TCP具有拥塞控制机制,当网络拥塞时会调整发送数据的速率以避免丢包。
    • UDP没有拥塞控制机制,发送的数据包可能会导致网络拥塞。

8、TCP是可靠传输,我们在编程的时候相比UDP是不是要绑定端口这些?

  1. 绑定端口:在使用TCP时,通常需要绑定端口号以便其他端可以连接到该端口进行通信。这可以通过bind()函数来实现。
  2. 建立连接:TCP是面向连接的协议,通信前需要先建立连接。在服务器端,需要先调用listen()函数监听连接请求,然后调用accept()函数接受客户端的连接请求;在客户端,需要调用connect()函数与服务器建立连接。
  3. 数据传输:TCP保证了数据的可靠传输,但发送和接收数据时需要使用send()recv()等函数,并通过返回值来检查发送和接收的数据量以确保完整传输。
  4. 错误处理:TCP提供了一套完善的错误处理机制,应用程序需要根据需要处理各种可能出现的错误情况,例如连接中断、超时等。
  5. 释放连接:TCP连接是可靠的双向通道,使用完毕后需要正确释放连接资源,通常通过调用close()函数来实现。

9、网络多路复用里,什么是IO多路复用?

IO多路复用是一种通过一种机制实现同时监视多个IO流的方法。在网络编程中,通常指的是通过select、poll、epoll等系统调用来实现同时监听多个socket,当有socket准备好数据时,可以立即对其进行处理,而不需要阻塞等待。这种方式可以提高程序的性能和效率,使得一个进程可以同时处理多个IO操作。

具体来说,IO多路复用利用操作系统提供的系统调用,将多个IO操作封装成一个函数调用,通过一个函数来监听多个IO事件,从而避免了单线程中需要轮询多个IO操作的问题。当有IO事件发生时,操作系统会通知程序哪些IO操作已经就绪,程序可以立即对这些IO进行处理,而不需要等待。

  1. select:
    • select是最古老的IO多路复用机制,它通过一个大的fd_set来保存所有需要监视的文件描述符,然后通过select函数来监听这些文件描述符的IO事件。
    • select的缺点是,每次调用select都需要将所有需要监视的文件描述符集合从用户态拷贝到内核态,造成性能开销,而且fd_set有大小限制。
  2. poll:
    • poll是select的改进版本,它使用一个pollfd数组来保存需要监视的文件描述符和事件,通过poll函数来监听这些文件描述符的IO事件。
    • poll没有fd_set的大小限制问题,但仍然需要将监视的文件描述符集合从用户态拷贝到内核态。
  3. epoll:
    • epoll是Linux特有的高性能IO多路复用机制,它使用一个内核事件表来保存需要监视的文件描述符和事件,通过epoll_ctl来向内核注册文件描述符,然后通过epoll_wait来等待IO事件发生。
    • epoll的优点是,它避免了select和poll中需要将文件描述符集合从用户态拷贝到内核态的问题,而是在epoll_ctl中将文件描述符集合直接传递给内核,减少了性能开销。

10、经常说大端小端,网络通信是属于大端还是小端?

网络通信中使用的是大端(Big Endian)序列化方式。大端序列化方式是指将数据的高字节存储在低地址,低字节存储在高地址。在网络通信中,数据的传输是以字节流的形式进行的,发送方按照大端序列化方式将数据转换成字节流,然后发送给接收方,接收方再按照同样的方式将字节流转换成数据。这样可以保证不同系统之间的数据传输的一致性,因为不同系统的处理器可能使用不同的字节序,使用大端序列化可以规定统一的字节序。

11、HTTP里面的返回码知道吗? 200代表什么?

  • 200 OK:请求成功。服务器成功处理了客户端的请求。
  • 301 Moved Permanently:永久重定向。请求的资源已经被分配了新的 URL,以后应使用新的 URL。
  • 302 Found:临时重定向。请求的资源现在临时从不同的 URL 中获取。
  • 400 Bad Request:客户端请求的语法错误,服务器无法理解。
  • 401 Unauthorized:请求要求身份验证,需要提供有效的身份认证信息。
  • 403 Forbidden:服务器拒绝请求,通常是因为没有权限访问。
  • 404 Not Found:服务器找不到请求的资源。
  • 500 Internal Server Error:服务器内部错误,无法完成请求。
  • 503 Service Unavailable:服务器暂时不可用,通常是由于过载或维护。

数据库

1、数据库中要优化查询性能,最常见的方式是什么?

  1. 索引优化:通过在表的列上创建索引,可以加快查询速度。索引可以减少数据库需要扫描的数据量,从而提高查询效率。但要注意不要过度索引,因为索引会增加插入、更新和删除操作的时间。
  2. 合适的数据类型:选择合适的数据类型可以减小存储空间,提高查询效率。例如,使用整数代替字符串作为主键。
  3. 查询语句优化:编写高效的查询语句,避免使用过多的子查询、不必要的联合查询或者使用复杂的连接条件。
  4. 表结构设计:合理设计表结构,避免过度范式化,减少表之间的关联,以减少连接查询的次数。
  5. 分区和分表:对于大型数据表,可以考虑使用分区或分表技术,将数据按照一定的规则拆分成多个部分,以提高查询效率。
  6. 缓存:使用缓存技术减少数据库的访问次数,例如使用Redis或Memcached缓存查询结果或频繁访问的数据。

2、数据库索引是干什么用的?

用于加快数据库表中数据的检索速度。索引类似于书籍的目录,它提供了一种快速查找数据的方法,可以帮助数据库系统快速定位到需要的数据,而不必扫描整个数据表。

优点:

  1. 提高检索速度:通过在索引列上创建索引,可以减少数据库系统需要扫描的数据量,从而提高检索速度。对于大型数据表,索引可以显著减少查询时间。
  2. 加速排序:如果查询需要对结果进行排序,索引可以加速排序操作,因为数据库系统可以直接使用索引中的排序顺序,而不必对整个结果集进行排序。
  3. 加速连接:当两个表进行连接操作时,如果连接列上有索引,可以加速连接操作,减少连接所需的时间。
  4. 提高数据唯一性:通过在唯一性约束的列上创建唯一索引,可以确保该列中的数据是唯一的,避免数据重复。
  5. 减少磁盘IO:索引可以减少数据库系统需要读取的磁盘数据量,因为数据库系统可以直接定位到需要的数据块,而不必读取整个数据表。

缺点:

  1. 占用存储空间:索引需要占用额外的存储空间,特别是对于大型数据表和复合索引。
  2. 降低插入、更新和删除性能:当对索引列进行插入、更新或删除操作时,数据库系统需要更新索引,可能会导致性能下降。
  3. 不适合于所有查询:并非所有查询都适合使用索引,有些查询可能会因为使用了索引而变慢。因此,在创建索引时需要根据实际情况进行权衡和选择。

3、索引是通过什么数据结构实现的知道吗?

索引通常是通过B+树数据结构来实现的。

  1. 平衡性:B+树是一棵平衡树,所有叶子节点都位于同一层,这样可以保持检索的稳定性,时间复杂度为O(log n)。
  2. 有序性:B+树的所有叶子节点形成一个有序链表,便于范围查找。
  3. 多路搜索:B+树的非叶子节点可以有多个子节点,这样可以减少树的高度,提高检索效率。
  4. 高度平衡:B+树的高度相对较低,树的高度和节点数的对数关系。

4、快排的时间复杂度是多少?

  • 平均情况下,快速排序的时间复杂度为O(nlogn)。这是因为在平均情况下,快速排序将数组分成两半,并对每一半进行递归排序,时间复杂度可以表示为T(n) = 2T(n/2) + O(n),其中O(n)是划分的时间复杂度。根据主定理,这种情况下的时间复杂度为O(nlogn)。
  • 最坏情况下,快速排序的时间复杂度为O(n^2)。最坏情况发生在每次划分都将数组分成大小差不多的两部分,并且每次划分都选取了最大或最小的元素作为基准元素。这种情况下,快速排序退化为类似冒泡排序的时间复杂度。

5、常用的负载均衡协议有了解吗?

  1. Round Robin(轮询):将请求依次分配给后端服务器,每个服务器接收到的请求数量大致相同。
  2. Least Connections(最少连接数):将请求分配给当前连接数最少的服务器,用于避免某些服务器负载过高的情况。
  3. IP Hash(基于IP地址的哈希):根据客户端的IP地址计算哈希值,将相同IP地址的请求分配给同一个后端服务器,可保证同一客户端的请求始终被发送到同一服务器,适用于需要保持会话的应用场景。
  4. Least Response Time(最少响应时间):将请求发送到响应时间最短的服务器,可以提高用户体验。
  5. Least Bandwidth(最少带宽):将请求发送到当前带宽使用最少的服务器,适用于对带宽敏感的场景。
  6. URL Hash(基于URL的哈希):根据请求的URL计算哈希值,将相同URL的请求发送到同一个后端服务器,适用于缓存等场景。

编程题

ATOI函数,将字符串转化成数字?

c++
#include <iostream>
#include <string>

using namespace std;

int myAtoi(string str) {
    // 去除字符串前导空格
    size_t i = 0;
    while (i < str.length() && str[i] == ' ') {
        i++;
    }

    // 判断正负号
    int sign = 1;
    if (i < str.length() && (str[i] == '+' || str[i] == '-')) {
        sign = (str[i++] == '-') ? -1 : 1;
    }

    // 转换数字
    int result = 0;
    while (i < str.length() && isdigit(str[i])) {
        // 检查溢出
        if (result > INT_MAX / 10 || (result == INT_MAX / 10 && str[i] - '0' > INT_MAX % 10)) {
            return (sign == 1) ? INT_MAX : INT_MIN;
        }
        result = result * 10 + (str[i++] - '0');
    }

    return result * sign;
}

int main() {
    string input;
    cout << "输入一个字符串:";
    getline(cin, input);

    int result = myAtoi(input);
    cout << "转换后的整数为:" << result << endl;

    return 0;
}

反问

1.部门具体干什么的? 2.部门员工需要掌握哪些方面的知识?