【C语言内存函数完全指南】:memcpy、memmove、memset、memcmp 的用法、区别与模拟实现(含代码示例)

✨ 用 清晰易懂的图解 帮你建立直观认知 ,用通俗的 代码语言 帮你落地理解, 让每个知识点都能 轻松get !

🚀 个人主页0xCode小新 · CSDN

🌱 代码仓库0xCode小新· Gitee

📌 专栏系列

💬 座右铭 : " 积跬步,以致千里。"

在C语言编程中,对内存的直接操作是每个开发者必须掌握的核心技能。无论是数据复制、移动、初始化还是比较,都离不开高效且安全的内存函数。本篇内容将以清晰易懂的方式,带你深入理解C语言中四大内存函数------memcpymemmovememsetmemcmp 的使用方法、适用场景,并手把手教你如何模拟实现它们。这篇指南将为你提供实用的知识与代码示例,助你在内存管理中游刃有余。

文章目录

  • [1. [memcpy](https://legacy.cplusplus.com/reference/cstring/memcpy/?kw=memcpy)使用和模拟实现](#1. memcpy使用和模拟实现)
        • [1. 函数是什么?](#1. 函数是什么?)
        • [2. 核心特性与使用场景](#2. 核心特性与使用场景)
        • [3. 实战示例](#3. 实战示例)
        • [4. 动手模拟实现](#4. 动手模拟实现)
        • [5. 重要提醒:什么时候不能用 memcpy?](#5. 重要提醒:什么时候不能用 memcpy?)
  • [2. [memmove](https://legacy.cplusplus.com/reference/cstring/memmove/)使用和模拟实现](#2. memmove使用和模拟实现)
        • [1. 函数是什么?为什么需要它?](#1. 函数是什么?为什么需要它?)
        • [2. 核心问题:什么是内存重叠?为什么 memcpy 处理不了?](#2. 核心问题:什么是内存重叠?为什么 memcpy 处理不了?)
        • [3. memmove 的智能解决方案](#3. memmove 的智能解决方案)
        • [4. 动手模拟实现](#4. 动手模拟实现)
        • [5. 验证实现](#5. 验证实现)
        • [6. 使用建议](#6. 使用建议)
  • [3. [memset](https://legacy.cplusplus.com/reference/cstring/memset/)函数的使用和模拟实现](#3. memset函数的使用和模拟实现)
        • [1. 函数是什么?](#1. 函数是什么?)
        • [2. 核心特性与工作原理](#2. 核心特性与工作原理)
        • [3. 实战示例](#3. 实战示例)
        • [4. 重要注意事项](#4. 重要注意事项)
        • [5. 动手模拟实现](#5. 动手模拟实现)
        • [6. 验证实现](#6. 验证实现)
  • [4. [memcmp](https://legacy.cplusplus.com/reference/cstring/memcmp/)函数的使用和模拟实现](#4. memcmp函数的使用和模拟实现)
        • [1. 函数是什么?](#1. 函数是什么?)
        • [2. 核心特性与工作原理](#2. 核心特性与工作原理)
        • [3. 返回值规则详解](#3. 返回值规则详解)
        • [4. 实战示例](#4. 实战示例)
        • [5. 与 strcmp 的区别](#5. 与 strcmp 的区别)
        • [6. 动手模拟实现](#6. 动手模拟实现)
        • [7. 验证实现](#7. 验证实现)
  • 结语

1. memcpy使用和模拟实现

1. 函数是什么?

memcpy 是 C 语言标准库中一个用于内存拷贝的函数。它的核心任务是将一块内存中的数据,原封不动地复制到另一块内存中。

函数原型

c 复制代码
void *memcpy(void *destination, const void *source, size_t num);
  • destination: 指向目标内存区域的指针,复制的内容将存放于此。
  • source: 指向源内存区域的指针,复制的内容来源于此。
  • num : 要复制的字节数
  • 返回值 : 返回指向 destination 的指针。
2. 核心特性与使用场景
  • 按字节复制memcpy 不关心内存中存储的是什么数据类型(int, char, struct 等),它只负责忠实地、一个字节一个字节地进行复制。
  • 不关心终止符 :与 strcpy 不同,memcpy 遇到 '\0' 并不会停止。它只认准你传入的 num 参数,复制完指定字节数后才会停下。这使得它可以用于复制任何二进制数据,如图片、结构体等。
  • 不处理重叠 :这是 memcpy 最关键的一个限制。如果 sourcedestination 所指向的内存区域有重叠,使用 memcpy 的结果是"未定义的" 。这意味着可能会复制出错,程序崩溃,或者出现各种意想不到的情况。处理重叠内存是 memmove 的任务。
3. 实战示例

让我们通过一个代码示例来看看它的实际效果:

c 复制代码
#include <stdio.h>
#include <string.h> // 包含 memcpy 的头文件

int main() {
    int arr1[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int arr2[10] = {0}; // 目标数组,初始化为0

    // 将 arr1 的前 20 个字节(即前5个int元素)复制到 arr2
    memcpy(arr2, arr1, 20);

    // 打印 arr2 的结果
    for (int i = 0; i < 10; i++) {
        printf("%d ", arr2[i]);
    }
    // 输出:1 2 3 4 5 0 0 0 0 0
    return 0;
}

代码解读:

  • memcpy(arr2, arr1, 20); 这行代码的意思是:从 arr1 的首地址开始,拷贝 20 个字节的数据到 arr2
  • 在我们的系统中,一个 int 类型通常占 4 个字节。因此,20 个字节正好对应 5 个 int 元素。
  • 结果就是 arr2 的前 5 个元素变成了 1, 2, 3, 4, 5,后面的元素保持为 0。
4. 动手模拟实现

理解一个函数最好的方式就是亲手实现它。下面我们来看看如何模拟实现一个自己的 memcpy

c 复制代码
#include <assert.h>

void* my_memcpy(void* dst, const void* src, size_t count) {
    // 1. 保存目标指针的起始位置,用于最后返回
    void* ret = dst;
    
    // 2. 安全检查:确保源指针和目标指针不是空指针
    assert(dst && src);

    // 3. 逐字节拷贝
    while (count--) {
        // 将 void* 强制转换为 char*,因为char类型占1个字节,便于逐字节操作
        *(char*)dst = *(char*)src;
        
        // 移动指针到下一个字节
        dst = (char*)dst + 1;
        src = (char*)src + 1;
    }
    
    // 4. 返回目标内存的起始地址
    return ret;
}

实现要点解析:

  1. void* 指针的妙用void* 是"无类型指针",可以接受任何类型的地址。这赋予了 memcpy 处理任意数据类型的能力。
  2. 类型转换 :在函数内部,我们将 void* 转换为 char*。因为 char 类型的大小是 1 字节,这样 (char*)dst + 1 就正好移动一个字节,实现了逐字节拷贝。
  3. 断言 assert:这是一种防御性编程技巧,确保传入的指针是有效的,避免对空指针进行操作导致程序崩溃。
  4. 返回值 :返回最初的 dst 指针,是为了支持函数的链式调用,例如 printf("%s", (char*)memcpy(dest, src, n))。
5. 重要提醒:什么时候不能用 memcpy?

当源内存和目标内存发生重叠时!

请看这个反面例子:

c 复制代码
int arr[] = {1, 2, 3, 4, 5};
// 将前3个元素复制到从第2个元素开始的位置
my_memcpy(arr+1, arr, 12); // 12字节 = 3个int

期望的结果可能是 {1, 1, 2, 3, 5},但由于我们的 my_memcpy 是从低地址向高地址拷贝,在复制过程中,源数据在被读取之前就被覆盖了,导致结果出错。这种情况下,必须使用 memmove 函数

2. memmove使用和模拟实现

1. 函数是什么?为什么需要它?

memmove 是 C 语言标准库中另一个用于内存拷贝的函数,它与 memcpy 功能相似,但有一个关键的区别:memmove 能够正确处理源内存和目标内存重叠的情况

函数原型:

c 复制代码
void *memmove(void *destination, const void *source, size_t num);
  • 参数与返回值 :与 memcpy 完全相同
  • 核心优势:处理内存重叠时的安全性
2. 核心问题:什么是内存重叠?为什么 memcpy 处理不了?

让我们通过一个例子来理解这个问题:

c 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // 场景:将数组前5个元素复制到从第3个元素开始的位置
    // 这会导致源区域 [0-4] 与目标区域 [2-6] 发生重叠
    memmove(arr + 2, arr, 20); // 20字节 = 5个int元素
    
    for (int i = 0; i < 10; i++) {
        printf("%d ", arr[i]);
    }
    // 输出:1 2 1 2 3 4 5 8 9 10
    return 0;
}

如果用 memcpy 会发生什么?

如果我们错误地使用 memcpy(arr + 2, arr, 20),由于 memcpy 只是简单地从低地址向高地址逐字节拷贝,会发生这样的悲剧:

  1. 先把 arr[0] (1) 拷贝到 arr[2] → 数组变成 [1, 2, 1, 4, 5, ...]
  2. 再把 arr[1] (2) 拷贝到 arr[3] → 数组变成 [1, 2, 1, 2, 5, ...]
  3. 接着把 arr[2] (现在已经是1了!) 拷贝到 arr[4] → 数组变成 [1, 2, 1, 2, 1, ...]

看到问题了吗?源数据在被读取之前就被覆盖了,导致复制结果完全错误!

3. memmove 的智能解决方案

memmove 通过判断内存重叠情况,智能地选择拷贝方向来解决这个问题:

  • 当目标地址在源地址之前,或者两者没有重叠时 :从低地址向高地址拷贝(与 memcpy 相同)
  • 当目标地址在源地址之后,且存在重叠时:从高地址向低地址拷贝(反向拷贝)
4. 动手模拟实现

下面是 memmove 的模拟实现代码,体现了这种智能的拷贝策略:

c 复制代码
#include <assert.h>

void* my_memmove(void* dst, const void* src, size_t count) {
    void* ret = dst;
    
    // 安全检查
    assert(dst && src);
    
    // 情况1:目标地址在源地址之前,或者没有重叠
    // 从低地址向高地址拷贝(正向拷贝)
    if (dst <= src || (char*)dst >= (char*)src + count) {
        while (count--) {
            *(char*)dst = *(char*)src;
            dst = (char*)dst + 1;
            src = (char*)src + 1;
        }
    }
    // 情况2:目标地址在源地址之后,且存在重叠
    // 从高地址向低地址拷贝(反向拷贝)
    else {
        // 将指针移动到内存块的末尾
        dst = (char*)dst + count - 1;
        src = (char*)src + count - 1;
        
        while (count--) {
            *(char*)dst = *(char*)src;
            dst = (char*)dst - 1;
            src = (char*)src - 1;
        }
    }
    
    return ret;
}

实现要点解析:

  1. 重叠判断逻辑
    • dst <= src:目标在源之前,安全正向拷贝
    • (char*)dst >= (char*)src + count:目标在源结束之后,没有重叠,安全正向拷贝
    • 其他情况:目标在源之后且存在重叠,需要反向拷贝
  2. 反向拷贝技巧
    • 先将指针移动到内存块的最后一个字节:dst = (char*)dst + count - 1
    • 然后从后往前逐个字节拷贝
    • 这样确保重叠部分中尚未被读取的数据不会被覆盖
5. 验证实现

让我们用之前的例子来测试:

c 复制代码
int main() {
    int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    printf("原始数组: ");
    for (int i = 0; i < 10; i++) printf("%d ", arr[i]);
    printf("\n");
    
    // 测试重叠拷贝
    my_memmove(arr + 2, arr, 20);
    
    printf("拷贝后数组: ");
    for (int i = 0; i < 10; i++) printf("%d ", arr[i]);
    printf("\n");
    
    return 0;
}

输出结果:

perfect!数据被正确地复制了,没有因为内存重叠而出错。

6. 使用建议
  1. 安全第一 :当你不确定源内存和目标内存是否重叠时,优先使用 memmove
  2. 性能考量 :如果确定两者不重叠,memcpy 可能稍微快一点(因为不需要做重叠判断)。
  3. 适用场景
    • 数组元素的移动
    • 缓冲区内部数据的调整
    • 任何可能涉及内存重叠的拷贝操作

3. memset函数的使用和模拟实现

1. 函数是什么?

memset 是 C 语言标准库中用于内存初始化批量设置的函数。它能够将指定内存区域的每个字节都设置为特定的值。

函数原型:

c 复制代码
void *memset(void *ptr, int value, size_t num);
  • ptr: 指向要设置的内存区域的起始地址
  • value : 要设置的值(以 int 形式传递,但实际使用时会被转换为 unsigned char
  • num : 要设置的字节数
  • 返回值 : 返回指向 ptr 的指针
2. 核心特性与工作原理
  • 按字节设置 :这是 memset 最重要的特性。它不关心内存中存储的是什么数据类型,只是简单地将每个字节都设置为指定的值。
  • 高效批量操作 :相比于使用循环逐个赋值,memset 通常经过高度优化,执行效率更高。
  • 用途广泛:常用于内存初始化、数组清零、字符串填充等场景。
3. 实战示例

让我们通过几个例子来理解它的用法:

示例1:字符串填充

c 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    char str[] = "hello world";
    
    // 将前6个字符设置为'x'
    memset(str, 'x', 6);
    printf("%s\n", str);
    
    return 0;
}	

输出:

示例2:数组清零

c 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    
    printf("清零前: ");
    for (int i = 0; i < 5; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    
    // 将整个数组清零
    memset(arr, 0, sizeof(arr));
    
    printf("清零后: ");
    for (int i = 0; i < 5; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    
    return 0;
}

输出:

4. 重要注意事项

陷阱:不要误解 memset 对整型数组的设置

这是一个常见的错误用法:

c 复制代码
int arr[5];
// 错误:试图将每个元素设置为1
memset(arr, 1, sizeof(arr));

你以为的结果:{1, 1, 1, 1, 1}

实际的结果:每个 int 元素的每个字节都被设置为1

假设 int 占4字节,那么每个 int 元素的值将是:

markdown 复制代码
00000001 00000001 00000001 00000001

转换为十进制就是:16843009,而不是1!

正确用法总结:

  • ✅ 清零:memset(arr, 0, sizeof(arr))
  • ✅ 设置为 -1:memset(arr, -1, sizeof(arr))(因为-1的二进制表示是所有位都是1)
  • ✅ 字符数组/字符串操作
  • ❌ 不要用于将整型数组设置为非0非-1的值
5. 动手模拟实现

让我们自己实现一个 memset 函数来加深理解:

c 复制代码
void* my_memset(void* ptr, int value, size_t num) {
    // 保存原始指针,用于返回
    void* start = ptr;
    
    // 将value转换为unsigned char,确保只取低8位
    unsigned char byte_value = (unsigned char)value;
    
    // 将void*转换为unsigned char*以便逐字节操作
    unsigned char* p = (unsigned char*)ptr;
    
    // 逐字节设置内存
    for (size_t i = 0; i < num; i++) {
        p[i] = byte_value;
    }
    
    return start;
}

更优化的版本(使用指针运算):

c 复制代码
void* my_memset_optimized(void* ptr, int value, size_t num) {
    unsigned char* p = (unsigned char*)ptr;
    unsigned char byte_value = (unsigned char)value;
    
    // 使用指针运算代替数组索引
    while (num--) {
        *p++ = byte_value;
    }
    
    return ptr;
}

实现要点解析:

  1. 类型转换 :将 void* 转换为 unsigned char* 是关键,因为 unsigned char 正好是1字节,便于逐字节操作。
  2. 值处理 :将传入的 int 值转换为 unsigned char,确保只使用低8位,避免意外行为。
  3. 循环方式 :可以使用数组索引 p[i] 或指针运算 *p++,指针通常更加高效。
  4. 返回值:返回原始指针,支持链式调用。
6. 验证实现
c 复制代码
#include <stdio.h>

int main() {
    // 测试1:字符串填充
    char str[] = "hello world";
    my_memset(str, 'A', 5);
    printf("测试1: %s\n", str); // 输出: AAAAA world
    
    // 测试2:数组清零
    int arr[3] = {10, 20, 30};
    my_memset(arr, 0, sizeof(arr));
    printf("测试2: %d %d %d\n", arr[0], arr[1], arr[2]); // 输出: 0 0 0
    
    // 测试3:验证按字节设置的特点
    int test = 0;
    my_memset(&test, 1, sizeof(test));
    printf("测试3: %d (验证按字节设置)\n", test); // 输出: 16843009
    
    return 0;
} 

4. memcmp函数的使用和模拟实现

1. 函数是什么?

memcmp 是 C 语言标准库中用于比较两块内存区域内容的函数。它能够逐字节地比较两个内存块,判断它们是否相等或者哪个更大。

函数原型:

c 复制代码
int memcmp(const void *ptr1, const void *ptr2, size_t num);
  • ptr1: 指向第一个内存块的指针
  • ptr2: 指向第二个内存块的指针
  • num : 要比较的字节数
  • 返回值: 比较结果,遵循特定的规则
2. 核心特性与工作原理
  • 按字节比较memcmp 逐个字节地比较两个内存区域,直到发现不同的字节或比较完所有指定字节
  • 不关心内容类型 :与 memcpy 类似,它不关心内存中存储的是什么数据类型
  • 不依赖终止符 :与 strcmp 不同,memcmp 遇到 '\0' 不会停止,它会比较完所有指定字节
  • 精确比较:能够比较任何二进制数据,包括结构体、数组等
3. 返回值规则详解

memcmp 的返回值规则很明确:

返回值 含义
< 0 第一个不匹配的字节在 ptr1 中的值 < 在 ptr2 中的值(按无符号字符解释)
= 0 两个内存块的内容完全相等
> 0 第一个不匹配的字节在 ptr1 中的值 > 在 ptr2 中的值(按无符号字符解释)

重要提示 :比较时是按照 unsigned char 类型来解释每个字节的!

4. 实战示例

让我们通过几个例子来深入理解:

示例1:基本字符串比较

c 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    char str1[] = "Hello";
    char str2[] = "Hello";
    char str3[] = "Hell0"; // 最后是数字0
    
    int result1 = memcmp(str1, str2, 5);
    int result2 = memcmp(str1, str3, 5);
    
    printf("比较 'Hello' 和 'Hello': %d\n", result1); // 输出: 0
    printf("比较 'Hello' 和 'Hell0': %d\n", result2); // 输出: >0的数字
    
    return 0;
}

示例2:区分大小写的比较

c 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    char buffer1[] = "DWga0tP12df0";
    char buffer2[] = "DWGA0TP12DF0"; 
    
    int n = memcmp(buffer1, buffer2, sizeof(buffer1));
    
    if (n > 0)
        printf("'%s' 大于 '%s'\n", buffer1, buffer2);
    else if (n < 0)
        printf("'%s' 小于 '%s'\n", buffer1, buffer2);
    else
        printf("'%s' 等于 '%s'\n", buffer1, buffer2);
    
    return 0;
}

输出:

这是因为小写字母的 ASCII 值大于对应的大写字母。

示例3:比较结构体

c 复制代码
#include <stdio.h>
#include <string.h>

typedef struct {
    int id;
    char name[20];
    float score;
} Student;

int main() {
    Student s1 = {1, "Alice", 95.5};
    Student s2 = {1, "Alice", 95.5};
    Student s3 = {2, "Bob", 88.0};
    
    // 比较两个相同的学生
    int result1 = memcmp(&s1, &s2, sizeof(Student));
    printf("相同学生比较: %d\n", result1); // 输出: 0
    
    // 比较两个不同的学生
    int result2 = memcmp(&s1, &s3, sizeof(Student));
    printf("不同学生比较: %d\n", result2); // 输出: 非0值
    
    return 0;
}
5. 与 strcmp 的区别

理解 memcmpstrcmp 的区别很重要:

特性 memcmp strcmp
停止条件 比较完指定字节数 遇到 '\0' 终止符
参数 需要指定比较的字节数 自动根据字符串长度比较
适用范围 任何内存数据(结构体、数组等) 仅适用于以 '\0' 结尾的字符串
性能 通常更快(不需要检查终止符) 需要检查终止符
6. 动手模拟实现

让我们自己实现一个 memcmp 函数:

c 复制代码
int my_memcmp(const void* ptr1, const void* ptr2, size_t num) {
    // 转换为 unsigned char* 以便逐字节比较
    const unsigned char* p1 = (const unsigned char*)ptr1;
    const unsigned char* p2 = (const unsigned char*)ptr2;
    
    // 逐字节比较
    for (size_t i = 0; i < num; i++) {
        if (p1[i] != p2[i]) {
			return (int)(p1[i]) - (int)(p2[i]);
        }
    }
    return 0;
}

更优化的指针版本:

c 复制代码
int my_memcmp_optimized(const void* ptr1, const void* ptr2, size_t num) {
    const unsigned char* p1 = ptr1;
    const unsigned char* p2 = ptr2;
    
    while (num-- > 0) {
        if (*p1 != *p2) {
            return (*p1 > *p2) ? 1 : -1;
        }
        p1++;
        p2++;
    }
    return 0;
}

实现要点解析:

  1. 类型转换 :转换为 unsigned char* 确保按字节比较,并且正确处理符号问题
  2. 比较逻辑:发现不同的字节立即返回,否则继续比较
  3. 返回值计算:返回第一个不同字节的差值,确保符合标准规定的正负号
  4. 边界处理 :正确处理 num 为 0 的情况
7. 验证实现
c 复制代码
#include <stdio.h>

void test_comparison(const char* desc, const void* p1, const void* p2, size_t n) {
    int result1 = memcmp(p1, p2, n);
    int result2 = my_memcmp(p1, p2, n);
    
    printf("%s:\n", desc);
    printf("  标准 memcmp: %d\n", result1);
    printf("  我们的实现: %d\n", result2);
    printf("  结果一致: %s\n\n", (result1 == result2) ? "是" : "否");
}

int main() {
    // 测试1:相同字符串
    char str1[] = "Hello";
    char str2[] = "Hello";
    test_comparison("相同字符串", str1, str2, 5);
    
    // 测试2:不同字符串
    char str3[] = "Hello";
    char str4[] = "Hell0";
    test_comparison("不同字符串", str3, str4, 5);
    
    // 测试3:数组比较
    int arr1[] = {1, 2, 3};
    int arr2[] = {1, 2, 4};
    test_comparison("数组比较", arr1, arr2, sizeof(arr1));
    
    // 测试4:部分比较
    test_comparison("部分比较", "ABCDE", "ABCDF", 4); // 只比较前4个字节
    
    return 0;
}

结语

通过本篇文章的学习,我们学习了C语言中四大内存函数:memcpymemmovememsetmemcmp

大家不仅要会使用这些函数,还要理解每个函数的工作原理,自己也可以通过模拟实现来加深理解,并且要时刻注意内存边界和重叠问题,在不确定时优先选择更安全的函数!

那么本篇内容到这里就结束了,希望大家能将所学知识灵活运用到实际编程中,在C语言的道路上稳步前行!

最后分享一段我非常喜欢的话送给大家:

每个优秀的人,都有一段沉默的时光。那段时光,是付出了很多努力,却得不到结果的日子,我们把他叫做扎根。

相关推荐
Elastic 中国社区官方博客2 小时前
如何在 vscode 里配置 MCP 并连接到 Elasticsearch
大数据·人工智能·vscode·elasticsearch·搜索引擎·ai·mcp
hweiyu002 小时前
Linux 命令:scp
linux·运维·服务器
三掌柜6663 小时前
2025三掌柜赠书活动第三十五期 AI辅助React Web应用开发实践:基于React 19和GitHub Copilot
前端·人工智能·react.js
无限进步_3 小时前
【C语言】计算两个整数二进制表示中不同位的个数
c语言·开发语言
特种加菲猫3 小时前
Linux之线程池
linux·笔记
机器之心3 小时前
强强联手!深度求索、寒武纪同步发布DeepSeek-V3.2模型架构和基于vLLM的模型适配源代码
人工智能·openai
机器之心3 小时前
Claude Sonnet 4.5来了!能连续编程30多小时、1.1万行代码
人工智能·openai
8K超高清3 小时前
汇世界迎全运 广州国际社区运动嘉年华举行,BOSMA博冠现场展示并分享与科技全运的故事
运维·服务器·网络·数据库·人工智能·科技
2401_841495643 小时前
【机器学习】朴素贝叶斯法
人工智能·python·数学·算法·机器学习·概率论·朴素贝叶斯法