C++高频知识点(十九)

发布于:2025-08-11 ⋅ 阅读:(19) ⋅ 点赞:(0)

91. TCP断开连接的时候为什么必须4次而不是3次?

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

92. 为什么要区分用户态和内核态?

在这里插入图片描述
在这里插入图片描述

93. 说说编写socket套接字的步骤

在这里插入图片描述
编写一个基于套接字(socket)的网络程序通常包括以下步骤,无论是客户端还是服务器都需要遵循这些步骤。下面分别说明服务器和客户端编写的步骤,这些是简单的代码示例,仅仅帮助大家去理解这个过程。

1. 服务器端编写步骤

1.1 创建套接字

使用 socket() 函数创建一个套接字。这个函数返回一个套接字描述符,用于后续的操作。

int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
    perror("socket failed");
    exit(EXIT_FAILURE);
}

1.2 绑定套接字

将创建的套接字绑定到指定的 IP 地址和端口号上,使用 bind() 函数。

struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);

if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
    perror("bind failed");
    close(server_fd);
    exit(EXIT_FAILURE);
}

网络字节序是大端模式
在这里插入图片描述
address.sin_addr.s_addr = INADDR_ANY;指绑定端口到本地所有网络接口
在这里插入图片描述

1.3 监听连接

使用 listen() 函数使套接字进入监听状态,等待客户端连接请求。

if (listen(server_fd, 3) < 0) {
    perror("listen");
    close(server_fd);
    exit(EXIT_FAILURE);
}

1.4 接受连接

使用 accept() 函数接受客户端的连接请求,返回一个新的套接字描述符,用于与客户端通信。

int new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen);
if (new_socket < 0) {
    perror("accept");
    close(server_fd);
    exit(EXIT_FAILURE);
}

1.5 数据传输

使用 read() 和 write() 函数(或 recv() 和 send() 函数)进行数据的接收和发送。

char buffer[1024] = {0};
read(new_socket, buffer, 1024);
printf("Message from client: %s\n", buffer);
send(new_socket, "Hello from server", strlen("Hello from server"), 0);

1.6 关闭套接字

完成通信后,使用 close() 函数关闭套接字。

close(new_socket);
close(server_fd);

2. 客户端编写步骤

2.1 创建套接字

与服务器端类似,使用 socket() 函数创建一个套接字。

int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
    perror("socket failed");
    exit(EXIT_FAILURE);
}

2.2 连接服务器

使用 connect() 函数将套接字连接到服务器端的指定 IP 地址和端口号。

struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);

if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
    perror("Invalid address/ Address not supported");
    close(sock);
    exit(EXIT_FAILURE);
}

if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
    perror("Connection Failed");
    close(sock);
    exit(EXIT_FAILURE);
}

2.3 数据传输

同样使用 read() 和 write() 或 recv() 和 send() 进行数据的发送和接收。

send(sock, "Hello from client", strlen("Hello from client"), 0);
char buffer[1024] = {0};
read(sock, buffer, 1024);
printf("Message from server: %s\n", buffer);

2.4 关闭套接字

完成通信后,使用 close() 函数关闭套接字。

close(sock);

94. 什么是大小端模式,编写代码区分大小端

在这里插入图片描述
在这里插入图片描述

如何检查自己的电脑 是大端还是小端?

第一种方法:

#include <iostream>  
  
bool isLittleEndian() {  
    int num = 1;  
    // 数值 1 的 32 位表示是:0x00 00 00 01(从高到低 4 个字节)。
    // 大端(big-endian)把最高有效字节放在最低地址,所以内存从低地址到高地址是:
    // 00 00 00 01
    // 于是 reinterpret_cast<char*>(&num) 指向的第一个字节就是 0x00
    // 小端(little-endian)相反,把最低有效字节放在最低地址,所以是:
    // 01 00 00 00
    // 这时第一个字节是 0x01

    // 换个更形象的例子:如果 num = 0x12 34 56 78,
    // 大端内存排布(低→高地址):12 34 56 78
    // 小端内存排布(低→高地址):78 56 34 12
    char *c = reinterpret_cast<char*>(&num);  
    return *c == 1; // 如果最低有效字节在最低地址处,则为小端字节序  
}  
  
int main() {  
    if (isLittleEndian()) {  
        std::cout << "This is a little-endian system." << std::endl;  
    } else {  
        std::cout << "This is a big-endian system." << std::endl;  
    }  
    return 0;  
}

在这里插入图片描述

第二种方法:

利用数据类型的存储方式来判断当前系统的字节序。常见的实现方法是使用 union 联合体来共享内存,并通过访问不同的成员来检查数据的存储顺序。

#include <iostream>
#include <cstdint>

// 定义一个联合体,包含一个整数和一个字符数组
// 联合体(union)的所有成员共享同一段内存;写一个成员,换个成员读,能看到相同内存里的原始字节
union {
    // uint8_t:精确 8 位 无符号整数,范围 0 ~ 255。
    // uint32_t:精确 32 位 无符号整数,范围 0 ~ 4,294,967,295。
    uint32_t i;      // 32 位整数
    uint8_t c[4];    // 4 字节字符数组
} test;

int main() {
    test.i = 0x12345678;  // 将一个已知的 32 位整数存入联合体中

    // 根据第一个字节的值判断大小端
    if (test.c[0] == 0x78) {
        std::cout << "小端模式 (Little-endian)" << std::endl;
    } else if (test.c[0] == 0x12) {
        std::cout << "大端模式 (Big-endian)" << std::endl;
    } else {
        std::cout << "无法确定字节序" << std::endl;
    }

    return 0;
}

在这里插入图片描述

在这里插入图片描述

95. 代码实现:实现简单的智能指针

下面是一个简单的智能指针实现的例子,用于管理动态分配的内存,避免内存泄漏。这个示例实现了一个类似于 std::shared_ptr 的简单智能指针,叫做 SimpleSmartPointer,它使用引用计数来管理对象的生命周期。

是在定义一个新对象 sp2,带着一个“初始值”sp1。
在 C++ 里,带初始值的定义叫“拷贝初始化(copy-initialization)”,它会调用拷贝构造函数(或能匹配的移动构造),不会调用赋值运算符。

赋值运算符只在对象已经存在之后再用 = 给它“换内容”时才会被调用。

SimpleSmartPointer<int> a(new int(10));

// 1) 拷贝初始化:调用拷贝构造函数
SimpleSmartPointer<int> b = a;     // == SimpleSmartPointer<int> b(a);

// 2) 直接初始化:也调用拷贝构造函数
SimpleSmartPointer<int> c(a);

// 3) 先默认构造一个对象,再赋值:调用赋值运算符 operator=
SimpleSmartPointer<int> d;         // 等价于 SimpleSmartPointer<int> d(nullptr);
d = a;                             // 这里才会走你的 operator=
#include <iostream>

// 简单智能指针类
template<typename T>
class SimpleSmartPointer {
private:
    T* ptr;          // 原生指针
    unsigned* count; // 引用计数

public:
    // 构造函数,接受一个原生指针
    //这个explicit 主要目的是防止隐式类型转换。提高代码可读性和安全性
    explicit SimpleSmartPointer(T* p = nullptr) : ptr(p) {
        if (p) {
            count = new unsigned(1); // 初始化引用计数为1
        } else {
            count = nullptr;
        }
    }

    // 拷贝构造函数
    SimpleSmartPointer(const SimpleSmartPointer<T>& sp) : ptr(sp.ptr), count(sp.count) {
        if (count) {
            (*count)++; // 增加引用计数
        }
    }

    // 赋值运算符重载
    SimpleSmartPointer<T>& operator=(const SimpleSmartPointer<T>& sp) {
        if (this == &sp) {
            return *this; // 防止自我赋值
        }

        // 释放当前资源
        //--(*count):对 *count(引用计数值)进行自减操作,表示当前对象不再使用该资源
        //如果 *count 为 0,说明已经没有其他智能指针对象在使用这个资源了,此时需要释放资源
        if (count && --(*count) == 0) {
            delete ptr;
            delete count;
        }

        // delete ptr; 释放的是 ptr 指向的那块堆内存(并调用析构),并不会“删掉变量 ptr 本身”
        // 赋值新资源
        ptr = sp.ptr;
        count = sp.count;
        if (count) {
            (*count)++;
        }

        return *this;
    }

    // 解引用运算符重载
    // 返回类型:对 T 的引用。有了引用返回,*sp 就是一个可当左值用的对象(能读也能改)。
    // 函数名是个特殊运算符函数:重载“一元解引用运算符 *”。
    // 调用方式:*sp 等价于 sp.operator*()
    // operator* 是运算符重载函数,重载的是“一元解引用运算符 *”(注意不是乘法;乘法是二元 *)。
    // 这是一个成员函数,当你写 *sp 时,编译器会把它当作:
    // sp.operator*()   // 调用你这个函数
    // ptr 是你类里存的裸指针(T*)。
    // *ptr 是对这个裸指针的解引用,得到“那个 T 对象本身”。结合返回类型 T&,就把“托管对象”的引用交给了调用者。
    T& operator*() const {
        return *ptr;
    }

    // 指针访问运算符重载
    // T* operator->() const { return ptr; }
    // 把内部的裸指针 ptr(类型 Point*)拿出来,然后再用普通指针的 -> 去调用 print() / move() / 访问 x、y。所以 p->成员 就能像真指针那样用起来了
    // 前提是裸指针对应的数据结构里面有定义成员变量或者成员函数
    // operator->() 只有在 T 有成员时才有用;你现在用的是 SimpleSmartPointer<int>,int 没成员,所以用不上。给它换个有成员的类型,比如 Point,就能直接写 sp->成员/方法 了。
    T* operator->() const {
        return ptr;
    }

    // 返回当前共享计数;空指针时按惯例返回 0
    unsigned use_count() const {
        return count ? *count : 0;
    }

    // 获取原生指针
    T* get() const {
        return ptr;
    }

    // 析构函数 RAII
    ~SimpleSmartPointer() {
        if (count && --(*count) == 0) {
            delete ptr;
            delete count;
        }
    }
};

// 测试函数
void testSimpleSmartPointer() {
    // sp1.ptr 指向“int(10)”;
    // sp1.count 指向一块 unsigned,其值 *count == 1
    SimpleSmartPointer<int> sp1(new int(10));  // 创建一个智能指针,管理整数10
    std::cout << "sp1: " << *sp1 << std::endl; // 输出sp1所指向的值
    std::cout << "sp1.use_count() = " << sp1.use_count() << "\n";

    {
        // 拷贝构造函数  ← 就是被 “SimpleSmartPointer<int> sp2 = sp1;” 调用的这个
        SimpleSmartPointer<int> sp2 = sp1;    // sp2与sp1共享同一块内存
        *sp2 = 33;
        std::cout << "sp2: " << *sp2 << std::endl; // 输出sp2所指向的值
        std::cout << "sp2.use_count() = " << sp2.use_count() << "\n";
        std::cout << "sp1.use_count() = " << sp1.use_count() << "\n";
    } // sp2超出作用域,引用计数减1

    // 再次输出sp1所指向的值
    std::cout << "sp1: " << *sp1 << std::endl;
    std::cout << "sp1.use_count() = " << sp1.use_count() << "\n";
}

int main() {
    testSimpleSmartPointer();
    return 0;
}

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

之后我会持续更新,如果喜欢我的文章,请记得一键三连哦,点赞关注收藏,你的每一个赞每一份关注每一次收藏都将是我前进路上的无限动力 !!!↖(▔▽▔)↗感谢支持!


网站公告

今日签到

点亮在社区的每一天
去签到