C学习教程,从入门到精通,C语言数组 —— 知识点详解(8)

C语言数组 ------ 知识点详解


一、一维数组

1.1 一维数组的定义

语法格式:

复制代码
数据类型 数组名[常量表达式];

说明:

项目 说明
数据类型 数组中每个元素的数据类型,如 int、float、char、double 等
数组名 遵循C语言标识符命名规则,如 arr、score、num 等
常量表达式 表示数组的长度(元素个数),必须是常量 或常量表达式,不能是变量
方括号 [] 这是数组的标志符号,必不可少

关键规则:

  • C语言中数组下标从0开始 ,即长度为 n 的数组,下标范围是 0 到 n-1。
  • 定义时方括号内必须是整型常量 或整型常量表达式,不能使用变量(C99标准之前)。
  • 编译器根据"数据类型 × 元素个数"来分配连续的内存空间。
  • 数组名代表数组首元素的地址,是一个地址常量,不能被赋值。

示例:

c 复制代码
int a[5];          // 定义了一个包含5个int元素的数组,下标0~4
float score[10];   // 定义了一个包含10个float元素的数组
#define N 20
char name[N];      // 用宏常量指定数组长度,合法
// int n = 10; int b[n];  // C89标准中非法,C99的VLA特性允许

内存分配原理:

假设系统为 int 类型分配4字节,则 int a[5] 在内存中分配了连续的 5×4=20 字节空间:

复制代码
地址:  1000   1004   1008   1012   1016
      +------+------+------+------+------+
      | a[0] | a[1] | a[2] | a[3] | a[4] |
      +------+------+------+------+------+

1.2 一维数组元素的引用

语法格式:

复制代码
数组名[下标表达式]

说明:

  • 下标可以是整型常量、整型变量或整型表达式 ,如 a[0]、a[i]、a[i+1]。
  • 下标的值必须在合法范围内:0 到 数组长度-1。超出范围会导致数组越界,属于未定义行为。
  • 数组元素的使用方式与同类型的普通变量完全相同,可以赋值、参与运算、作为函数参数等。

常见错误:

c 复制代码
int a[5];
a[5] = 10;    // ❌ 错误!下标越界,a的合法下标是0~4
a[0] = 10;    // ✅ 正确
a[2+1] = 20;  // ✅ 正确,等价于a[3]=20

入门案例:数组元素的基本引用

c 复制代码
#include <stdio.h>  // 包含标准输入输出头文件

int main()
{
    int a[5];       // 定义一个包含5个整型元素的数组a
    int i;          // 定义循环变量i

    // 通过循环为数组的每个元素赋值
    for (i = 0; i < 5; i++)  // i从0循环到4,恰好覆盖所有下标
    {
        a[i] = i * 10;       // 将i乘以10的结果赋给a[i]
                             // a[0]=0, a[1]=10, a[2]=20, a[3]=30, a[4]=40
    }

    // 通过循环输出数组的每个元素
    for (i = 0; i < 5; i++)  // 遍历数组所有元素
    {
        printf("a[%d] = %d\n", i, a[i]);  // 输出下标和对应元素的值
    }

    return 0;  // 程序正常结束
}

运行结果:

复制代码
a[0] = 0
a[1] = 10
a[2] = 20
a[3] = 30
a[4] = 40

1.3 一维数组的初始化

初始化是在定义数组的同时给数组元素赋初值。

(1)完全初始化:给每个元素都赋初值

c 复制代码
int a[5] = {10, 20, 30, 40, 50};
// a[0]=10, a[1]=20, a[2]=30, a[3]=40, a[4]=50

(2)部分初始化:只给部分元素赋初值,其余自动赋0

c 复制代码
int a[5] = {10, 20};
// a[0]=10, a[1]=20, a[2]=0, a[3]=0, a[4]=0

(3)全部初始化为0

c 复制代码
int a[5] = {0};
// a[0]=0, a[1]=0, a[2]=0, a[3]=0, a[4]=0

(4)省略长度:由初始化列表自动确定数组长度

c 复制代码
int a[] = {1, 2, 3, 4, 5};
// 编译器自动确定数组长度为5,等价于 int a[5] = {1,2,3,4,5};

(5)指定下标初始化(C99支持的"指定初始化器")

c 复制代码
int a[10] = {[0] = 1, [5] = 6, [9] = 10};
// a[0]=1, a[5]=6, a[9]=10,其余元素为0

重要注意事项:

场景 是否合法 说明
int a[5] = {1,2,3,4,5}; 合法 完全初始化
int a[5] = {1,2,3,4,5,6}; ❌ 初始化值个数超过数组长度
int a[] = {1,2,3}; 合法 自动确定长度为3
int a[5]; a = {1,2,3,4,5}; ❌ 定义之后不能用这种方式赋值,数组名是常量
int a[5]; a[0]=1; a[1]=2; 合法 逐个元素单独赋值

入门案例:一维数组初始化的多种方式

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

int main()
{
    // 方式1:完全初始化
    int a[5] = {1, 2, 3, 4, 5};  // 为所有5个元素指定初值

    // 方式2:部分初始化
    int b[5] = {10, 20};          // 只初始化前两个元素,其余自动为0

    // 方式3:全部初始化为0
    int c[5] = {0};               // 5个元素全部为0

    // 方式4:省略长度
    int d[] = {100, 200, 300};    // 自动确定长度为3

    int i;  // 循环变量

    printf("数组a(完全初始化):");
    for (i = 0; i < 5; i++)       // 遍历数组a的5个元素
        printf("%d ", a[i]);      // 输出每个元素的值
    printf("\n");                  // 输出换行

    printf("数组b(部分初始化):");
    for (i = 0; i < 5; i++)       // 遍历数组b的5个元素
        printf("%d ", b[i]);      // 前两个为10和20,后三个为0
    printf("\n");

    printf("数组c(全部为0):");
    for (i = 0; i < 5; i++)       // 遍历数组c的5个元素
        printf("%d ", c[i]);      // 全部输出0
    printf("\n");

    printf("数组d(省略长度,长度为%d):", sizeof(d)/sizeof(d[0]));
                                  // sizeof(d)是数组总字节数,sizeof(d[0])是一个元素的字节数
                                  // 相除得到数组元素个数
    for (i = 0; i < 3; i++)       // 遍历数组d的3个元素
        printf("%d ", d[i]);      // 输出100 200 300
    printf("\n");

    return 0;
}

运行结果:

复制代码
数组a(完全初始化):1 2 3 4 5
数组b(部分初始化):10 20 0 0 0
数组c(全部为0):0 0 0 0 0
数组d(省略长度,长度为3):100 200 300

1.4 一维数组的应用

入门案例:求一维数组中的最大值和最小值

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

int main()
{
    int a[10] = {23, 45, 12, 67, 34, 89, 56, 78, 90, 11};  // 定义并初始化一个有10个元素的数组
    int max, min;  // 定义最大值和最小值变量
    int i;         // 循环变量

    max = a[0];    // 先假设第一个元素是最大值
    min = a[0];    // 先假设第一个元素是最小值

    for (i = 1; i < 10; i++)  // 从第二个元素开始遍历(下标从1开始)
    {
        if (a[i] > max)       // 如果当前元素大于max
            max = a[i];       // 更新max为当前元素的值

        if (a[i] < min)       // 如果当前元素小于min
            min = a[i];       // 更新min为当前元素的值
    }

    printf("最大值为:%d\n", max);  // 输出最大值
    printf("最小值为:%d\n", min);  // 输出最小值

    return 0;
}

运行结果:

复制代码
最大值为:90
最小值为:11

入门案例:计算数组元素的平均值

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

int main()
{
    int score[5];       // 定义一个能存储5个成绩的数组
    int i;              // 循环变量
    float sum = 0;      // 定义累加和变量,初始化为0,使用float以存储小数

    printf("请输入5个成绩:\n");  // 提示用户输入
    for (i = 0; i < 5; i++)      // 循环5次读取5个成绩
    {
        scanf("%d", &score[i]);   // 读取用户输入,存入score[i]
    }

    for (i = 0; i < 5; i++)      // 再次循环遍历数组
    {
        sum += score[i];          // 将每个元素累加到sum中
                                  // 等价于 sum = sum + score[i]
    }

    printf("平均分为:%.2f\n", sum / 5);  // 计算并输出平均值,保留两位小数

    return 0;
}

运行结果(示例):

复制代码
请输入5个成绩:
80 90 85 75 95
平均分为:85.00

二、二维数组

2.1 二维数组的定义

语法格式:

复制代码
数据类型 数组名[行数][列数];

说明:

  • 二维数组可以理解为"数组的数组",即一个一维数组,其中每个元素又是一个一维数组。
  • 行数和列数都必须是整型常量或常量表达式。
  • 二维数组在内存中仍然是按行连续存储的(行优先存储)。

示例:

c 复制代码
int a[3][4];    // 定义一个3行4列的二维数组,共12个元素
float b[2][5];  // 定义一个2行5列的二维数组,共10个元素

内存存储方式(以 int a[3][4] 为例):

复制代码
逻辑结构(3行4列的表格):
         列0    列1    列2    列3
行0  | a[0][0] a[0][1] a[0][2] a[0][3]
行1  | a[1][0] a[1][1] a[1][2] a[1][3]
行2  | a[2][0] a[2][1] a[2][2] a[2][3]

内存中实际存储(按行连续排列):
a[0][0] → a[0][1] → a[0][2] → a[0][3] → a[1][0] → a[1][1] → ... → a[2][3]

2.2 二维数组元素的引用

语法格式:

复制代码
数组名[行下标][列下标]
  • 行下标范围:0 到 行数-1
  • 列下标范围:0 到 列数-1

示例:

c 复制代码
int a[3][4];     // 3行4列
a[0][0] = 1;     // ✅ 第0行第0列
a[2][3] = 10;    // ✅ 第2行第3列(最后一行最后一列)
a[3][0] = 5;     // ❌ 行下标越界(最大是2)
a[0][4] = 5;     // ❌ 列下标越界(最大是3)

2.3 二维数组的初始化

(1)按行完全初始化

c 复制代码
int a[3][4] = {
    {1, 2, 3, 4},     // 第0行
    {5, 6, 7, 8},     // 第1行
    {9, 10, 11, 12}   // 第2行
};

(2)不分行的连续初始化(按行优先顺序依次赋值)

c 复制代码
int a[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
// 等价于按行分配:第0行为1,2,3,4;第1行为5,6,7,8;第2行为9,10,11,12

(3)部分初始化

c 复制代码
int a[3][4] = {{1}, {5}, {9}};
// 第0行为 1,0,0,0;第1行为 5,0,0,0;第2行为 9,0,0,0

int a[3][4] = {1, 2, 3, 4, 5};
// 按行优先填入:第0行为1,2,3,4;第1行为5,0,0,0;第2行为0,0,0,0

(4)省略第一维(行数),由编译器自动推断

c 复制代码
int a[][4] = {1, 2, 3, 4, 5, 6, 7, 8};
// 12个元素 / 每行4列 = 3行 → 等价于 int a[3][4]

注意:只能省略第一维(行数),不能省略第二维(列数)。

c 复制代码
int a[][4] = {...};   // ✅ 合法,列数为4
// int a[3][] = {...}; // ❌ 非法,必须指定列数

入门案例:二维数组的初始化和遍历

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

int main()
{
    // 定义并初始化一个3行4列的二维数组
    int a[3][4] = {
        {1,  2,  3,  4},    // 第0行的4个元素
        {5,  6,  7,  8},    // 第1行的4个元素
        {9, 10, 11, 12}     // 第2行的4个元素
    };

    int i, j;  // i为行循环变量,j为列循环变量

    printf("二维数组的元素为:\n");
    for (i = 0; i < 3; i++)       // 外层循环控制行,i从0到2
    {
        for (j = 0; j < 4; j++)   // 内层循环控制列,j从0到3
        {
            printf("%4d", a[i][j]);  // 输出第i行第j列的元素,%4d表示占4个字符宽度
        }
        printf("\n");  // 每输出一行后换行
    }

    return 0;
}

运行结果:

复制代码
二维数组的元素为:
   1   2   3   4
   5   6   7   8
   9  10  11  12

2.4 二维数组的应用

入门案例:矩阵转置(行变列,列变行)

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

int main()
{
    int a[2][3] = {{1, 2, 3}, {4, 5, 6}};  // 定义2行3列的原始矩阵
    int b[3][2];  // 定义3行2列的转置矩阵(行和列互换)
    int i, j;     // 循环变量

    // 进行转置操作
    for (i = 0; i < 2; i++)       // 遍历原矩阵的行
    {
        for (j = 0; j < 3; j++)   // 遍历原矩阵的列
        {
            b[j][i] = a[i][j];    // 将a的第i行第j列赋给b的第j行第i列
                                  // 实现行列互换
        }
    }

    // 输出原矩阵
    printf("原矩阵a(2x3):\n");
    for (i = 0; i < 2; i++)       // 遍历原矩阵的行
    {
        for (j = 0; j < 3; j++)   // 遍历原矩阵的列
        {
            printf("%4d", a[i][j]);  // 输出原矩阵元素
        }
        printf("\n");  // 换行
    }

    // 输出转置后的矩阵
    printf("转置矩阵b(3x2):\n");
    for (i = 0; i < 3; i++)       // 遍历转置矩阵的行
    {
        for (j = 0; j < 2; j++)   // 遍历转置矩阵的列
        {
            printf("%4d", b[i][j]);  // 输出转置矩阵元素
        }
        printf("\n");  // 换行
    }

    return 0;
}

运行结果:

复制代码
原矩阵a(2x3):
   1   2   3
   4   5   6
转置矩阵b(3x2):
   1   4
   2   5
   3   6

入门案例:求二维数组中每行的最大值

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

int main()
{
    int a[3][4] = {                // 定义3行4列的二维数组
        {12, 34, 56, 23},
        {45, 67, 89, 11},
        {33, 55, 77, 99}
    };
    int i, j;                      // 循环变量
    int max;                       // 用于存储每行的最大值

    for (i = 0; i < 3; i++)        // 外层循环:逐行遍历
    {
        max = a[i][0];             // 假设当前行的第一个元素是最大值
        for (j = 1; j < 4; j++)    // 内层循环:从当前行的第二个元素开始比较
        {
            if (a[i][j] > max)     // 如果当前元素大于max
                max = a[i][j];     // 更新max
        }
        printf("第%d行的最大值为:%d\n", i, max);  // 输出当前行的最大值
    }

    return 0;
}

运行结果:

复制代码
第0行的最大值为:56
第1行的最大值为:89
第2行的最大值为:99

三、字符数组

3.1 字符数组的定义

字符数组是元素类型为 char 的数组,主要用于存储字符串。

语法格式:

c 复制代码
char 数组名[常量表达式];

示例:

c 复制代码
char str[20];   // 定义一个能存储20个字符的字符数组
char ch[10];    // 定义一个能存储10个字符的字符数组

3.2 字符数组的初始化

(1)逐个字符初始化

c 复制代码
char str[5] = {'H', 'e', 'l', 'l', 'o'};
// str[0]='H', str[1]='e', str[2]='l', str[3]='l', str[4]='o'

(2)用字符串常量初始化(推荐)

c 复制代码
char str[] = "Hello";
// 等价于 char str[] = {'H', 'e', 'l', 'l', 'o', '\0'};
// 数组长度为6(5个可见字符 + 1个'\0'结束符)

(3)部分初始化

c 复制代码
char str[10] = "Hello";
// str[0]='H', str[1]='e', str[2]='l', str[3]='l', str[4]='o', str[5]='\0'
// str[6]~str[9] 全部为'\0'

(4)逐字符初始化时不加'\0'的注意事项

c 复制代码
char s1[] = {'H', 'i'};      // 长度为2,没有'\0',不是合法的字符串
char s2[] = {'H', 'i', '\0'}; // 长度为3,有'\0',是合法的字符串
char s3[] = "Hi";              // 长度为3,自动包含'\0',是合法的字符串

3.3 字符数组的结束标志

核心概念:'\0'(空字符,ASCII值为0)

  • C语言中,字符串是以 '\0' 结尾的字符数组。
  • '\0' 是字符串的结束标志,不是字符串的内容。
  • 用 sizeof 运算符求得的是数组占用的内存字节数 (包含 '\0')。
  • 用 strlen() 函数求得的是字符串的有效字符个数 (不包含 '\0')。

示例说明:

c 复制代码
char str[] = "Hello";
// sizeof(str) = 6  (H, e, l, l, o, \0 共6个字节)
// strlen(str) = 5  (有效字符为 H, e, l, l, o 共5个)

入门案例:sizeof 与 strlen 的区别

c 复制代码
#include <stdio.h>
#include <string.h>  // 使用strlen函数需要包含此头文件

int main()
{
    char str1[] = "Hello";                     // 用字符串初始化,自动包含'\0'
    char str2[] = {'H', 'e', 'l', 'l', 'o'};  // 逐字符初始化,没有'\0'
    char str3[] = {'H', 'e', 'l', 'l', 'o', '\0'};  // 逐字符初始化,手动加'\0'

    printf("str1: sizeof = %lu, strlen = %lu\n", sizeof(str1), strlen(str1));
    // sizeof(str1)=6(含\0),strlen(str1)=5(不含\0)

    printf("str2: sizeof = %lu, strlen = %lu\n", sizeof(str2), strlen(str2));
    // sizeof(str2)=5(没有\0),strlen(str2)的结果不确定(找不到\0,会继续读内存)
    // 这是一个危险的行为!

    printf("str3: sizeof = %lu, strlen = %lu\n", sizeof(str3), strlen(str3));
    // sizeof(str3)=6(含\0),strlen(str3)=5(不含\0)

    return 0;
}

注意: str2 没有 '\0' 结束标志,strlen(str2) 会继续往后读取内存直到遇到 '\0' 为止,结果是不确定的 ,属于未定义行为。实际编程中一定要确保字符数组以 '\0' 结尾。


3.4 字符数组的输入和输出

(1)使用 scanf 和 printf

c 复制代码
char str[100];
scanf("%s", str);      // 注意:str本身是地址,不需要加&
printf("%s", str);      // 输出字符串,遇到'\0'停止

注意: scanf("%s", ...) 遇到空格、Tab或回车就会停止读入,因此不能读取包含空格的字符串。

(2)使用 gets 和 puts

c 复制代码
char str[100];
gets(str);              // 读取一整行,可以包含空格,遇到回车结束
puts(str);              // 输出字符串并自动换行

(3)逐字符输入输出

c 复制代码
char ch;
ch = getchar();         // 读取一个字符
putchar(ch);            // 输出一个字符

入门案例:字符串的输入和输出

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

int main()
{
    char str[100];  // 定义一个能存储100个字符的字符数组

    printf("请输入一个单词(不含空格):");
    scanf("%s", str);               // 用scanf读取一个单词
    printf("scanf读取到的内容:%s\n", str);  // 输出结果

    // 清除输入缓冲区中的换行符(重要!)
    getchar();  // 吃掉scanf留在缓冲区中的回车符

    printf("请输入一句话(可以含空格):");
    gets(str);                       // 用gets读取一整行(含空格)
    printf("gets读取到的内容:%s\n", str);    // 输出结果

    return 0;
}

运行结果(示例):

复制代码
请输入一个单词(不含空格):Hello World
scanf读取到的内容:Hello
请输入一句话(可以含空格):Hello World
gets读取到的内容:Hello World

可以看到,scanf("%s", ...) 在遇到空格时就停止了,只读到了 "Hello";而 gets 能读取整行内容。


3.5 字符数组的应用

入门案例:统计字符串中大写字母、小写字母、数字和其他字符的个数

c 复制代码
#include <stdio.h>
#include <string.h>  // 使用strlen函数

int main()
{
    char str[200];   // 存储输入的字符串
    int upper = 0;   // 大写字母计数器
    int lower = 0;   // 小写字母计数器
    int digit = 0;   // 数字计数器
    int other = 0;   // 其他字符计数器
    int i;           // 循环变量

    printf("请输入一个字符串:");
    gets(str);       // 读取一行字符串(可以包含空格)

    for (i = 0; i < strlen(str); i++)  // 遍历字符串中的每个字符
    {
        if (str[i] >= 'A' && str[i] <= 'Z')       // 如果是大写字母
            upper++;                                // 大写计数器加1
        else if (str[i] >= 'a' && str[i] <= 'z')  // 如果是小写字母
            lower++;                                // 小写计数器加1
        else if (str[i] >= '0' && str[i] <= '9')  // 如果是数字
            digit++;                                // 数字计数器加1
        else                                       // 其他字符
            other++;                                // 其他字符计数器加1
    }

    printf("大写字母:%d个\n", upper);
    printf("小写字母:%d个\n", lower);
    printf("数字:%d个\n", digit);
    printf("其他字符:%d个\n", other);

    return 0;
}

运行结果(示例):

复制代码
请输入一个字符串:Hello World 123!
大写字母:2个
小写字母:8个
数字:3个
其他字符:3个

四、多维数组

4.1 多维数组的概念

C语言允许定义多维数组。二维以上的数组称为多维数组。最常见的是三维数组。

语法格式:

c 复制代码
数据类型 数组名[维度1][维度2][维度3]...;

示例:

c 复制代码
int a[2][3][4];  // 定义一个三维数组:2层×3行×4列,共24个元素

三维数组的逻辑结构:

可以想象成"一本书":

  • 第1维(2):2页

  • 第2维(3):每页3行

  • 第3维(4):每行4列

    第0页: 第1页:
    列0 列1 列2 列3 列0 列1 列2 列3
    行0: a[0][0][0~3] a[1][0][0~3]
    行1: a[0][1][0~3] a[1][1][0~3]
    行2: a[0][2][0~3] a[1][2][0~3]

多维数组在内存中仍然是线性连续存储的 ,存储顺序为:最右边的下标变化最快(即 a[0][0][0] → a[0][0][1] → a[0][0][2] → ...)。

入门案例:三维数组的初始化和遍历

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

int main()
{
    // 定义并初始化一个2层3行4列的三维数组
    int a[2][3][4] = {
        {   // 第0层
            {1, 2, 3, 4},      // 第0层第0行
            {5, 6, 7, 8},      // 第0层第1行
            {9, 10, 11, 12}    // 第0层第2行
        },
        {   // 第1层
            {13, 14, 15, 16},  // 第1层第0行
            {17, 18, 19, 20},  // 第1层第1行
            {21, 22, 23, 24}   // 第1层第2行
        }
    };

    int i, j, k;  // 三维循环变量

    // 三层循环遍历三维数组
    for (i = 0; i < 2; i++)         // 第1维:层(从第0层到第1层)
    {
        printf("第%d层:\n", i);    // 输出当前层号
        for (j = 0; j < 3; j++)     // 第2维:行(从第0行到第2行)
        {
            for (k = 0; k < 4; k++) // 第3维:列(从第0列到第3列)
            {
                printf("%4d", a[i][j][k]);  // 输出第i层第j行第k列的元素
            }
            printf("\n");  // 每行输出后换行
        }
        printf("\n");  // 每层输出后空一行
    }

    return 0;
}

运行结果:

复制代码
第0层:
   1   2   3   4
   5   6   7   8
   9  10  11  12

第1层:
  13  14  15  16
  17  18  19  20
  21  22  23  24

五、数组的排序算法

排序是将一组数据按照从小到大(升序) 或从大到小(降序) 的顺序重新排列。以下是几种经典排序算法。

5.1 选择排序

基本思想:

每一轮从待排序的数据中选出最小(或最大)的元素,将其放到已排序序列的末尾。

算法步骤(以升序为例):

  1. 第1轮:从 a[0]~a[n-1] 中找到最小值,与 a[0] 交换。
  2. 第2轮:从 a[1]~a[n-1] 中找到最小值,与 a[1] 交换。
  3. 第3轮:从 a[2]~a[n-1] 中找到最小值,与 a[2] 交换。
  4. ......直到只剩最后一个元素,排序完成。

图解示例(对 {64, 25, 12, 22, 11} 排序):

复制代码
初始:[64, 25, 12, 22, 11]
第1轮:找到最小值11,与a[0]=64交换 → [11, 25, 12, 22, 64]
第2轮:在[25,12,22,64]中找到最小值12,与a[1]=25交换 → [11, 12, 25, 22, 64]
第3轮:在[25,22,64]中找到最小值22,与a[2]=25交换 → [11, 12, 22, 25, 64]
第4轮:在[25,64]中找到最小值25,已经就位 → [11, 12, 22, 25, 64]
排序完成!

入门案例:选择排序

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

int main()
{
    int a[5] = {64, 25, 12, 22, 11};  // 定义并初始化待排序的数组
    int i, j;                          // i控制轮次,j用于遍历剩余元素
    int minIndex;                      // 记录最小值元素的下标
    int temp;                          // 用于交换的临时变量

    int n = 5;  // 数组长度

    // 选择排序:外层循环控制排序轮次
    for (i = 0; i < n - 1; i++)  // 共需n-1轮(最后一轮只剩一个元素,无需排序)
    {
        minIndex = i;  // 假设当前位置i的元素就是最小的

        // 内层循环:从i+1开始,寻找比a[minIndex]更小的元素
        for (j = i + 1; j < n; j++)
        {
            if (a[j] < a[minIndex])  // 如果找到更小的元素
            {
                minIndex = j;         // 更新最小值的下标
            }
        }

        // 如果最小值不在位置i,则交换
        if (minIndex != i)
        {
            temp = a[i];          // 将a[i]存入临时变量
            a[i] = a[minIndex];   // 将最小值放到位置i
            a[minIndex] = temp;   // 将原来的a[i]放到最小值原来的位置
        }
    }

    // 输出排序后的结果
    printf("选择排序后的结果:");
    for (i = 0; i < n; i++)
    {
        printf("%d ", a[i]);
    }
    printf("\n");

    return 0;
}

运行结果:

复制代码
选择排序后的结果:11 12 22 25 64

时间复杂度: O(n²),空间复杂度:O(1)。


5.2 冒泡排序

基本思想:

相邻两个元素两两比较,如果前面的比后面的大(升序),就交换它们。每一轮把最大的元素"冒泡"到末尾。

算法步骤(以升序为例):

  1. 第1轮:从 a[0] 到 a[n-1],依次比较相邻元素,把最大值冒泡到最后。
  2. 第2轮:从 a[0] 到 a[n-2],把次大值冒泡到倒数第二位。
  3. ......直到所有元素有序。

图解示例(对 {64, 25, 12, 22, 11} 排序):

复制代码
初始:[64, 25, 12, 22, 11]

第1轮:
  64>25 → 交换 → [25, 64, 12, 22, 11]
  64>12 → 交换 → [25, 12, 64, 22, 11]
  64>22 → 交换 → [25, 12, 22, 64, 11]
  64>11 → 交换 → [25, 12, 22, 11, 64]  ← 64就位

第2轮:
  25>12 → 交换 → [12, 25, 22, 11, 64]
  25>22 → 交换 → [12, 22, 25, 11, 64]
  25>11 → 交换 → [12, 22, 11, 25, 64]  ← 25就位

第3轮:
  12<22 → 不换 → [12, 22, 11, 25, 64]
  22>11 → 交换 → [12, 11, 22, 25, 64]  ← 22就位

第4轮:
  12>11 → 交换 → [11, 12, 22, 25, 64]  ← 排序完成

入门案例:冒泡排序

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

int main()
{
    int a[5] = {64, 25, 12, 22, 11};  // 定义并初始化待排序数组
    int i, j;                          // i控制轮次,j用于相邻元素比较
    int temp;                          // 交换用的临时变量
    int n = 5;                         // 数组长度

    // 冒泡排序:外层循环控制轮次
    for (i = 0; i < n - 1; i++)  // 共需n-1轮
    {
        // 内层循环:比较相邻元素
        for (j = 0; j < n - 1 - i; j++)  // 每轮减少一个已排好的元素
                                          // 第1轮比较n-1次,第2轮比较n-2次...
        {
            if (a[j] > a[j + 1])  // 如果前面的元素比后面的大
            {
                temp = a[j];      // 交换两个元素
                a[j] = a[j + 1];
                a[j + 1] = temp;
            }
        }
    }

    // 输出排序结果
    printf("冒泡排序后的结果:");
    for (i = 0; i < n; i++)
    {
        printf("%d ", a[i]);
    }
    printf("\n");

    return 0;
}

运行结果:

复制代码
冒泡排序后的结果:11 12 22 25 64

时间复杂度: O(n²),空间复杂度:O(1)。


5.3 交换排序

基本思想:

交换排序是一个广义概念,核心思想是两两比较,必要时交换。冒泡排序和快速排序都属于交换排序。此处介绍一种简单交换排序(又称"鸡尾酒排序"的简化版):

每次用当前元素和后面所有元素逐一比较,如果逆序就交换。

入门案例:简单交换排序

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

int main()
{
    int a[5] = {64, 25, 12, 22, 11};  // 定义并初始化待排序数组
    int i, j;                          // 循环变量
    int temp;                          // 交换用的临时变量
    int n = 5;                         // 数组长度

    // 简单交换排序
    for (i = 0; i < n - 1; i++)        // 外层循环:控制起始位置
    {
        for (j = i + 1; j < n; j++)    // 内层循环:将a[i]与后面所有元素比较
        {
            if (a[i] > a[j])           // 如果a[i]比后面的元素大(升序)
            {
                temp = a[i];           // 交换a[i]和a[j]
                a[i] = a[j];
                a[j] = temp;
            }
        }
    }

    printf("交换排序后的结果:");
    for (i = 0; i < n; i++)
    {
        printf("%d ", a[i]);
    }
    printf("\n");

    return 0;
}

运行结果:

复制代码
交换排序后的结果:11 12 22 25 64

时间复杂度: O(n²),空间复杂度:O(1)。


5.4 插入排序

基本思想:

将数组分为"已排序"和"未排序"两部分。每次从未排序部分取出一个元素,插入到已排序部分的正确位置。

算法步骤(以升序为例):

  1. 从第2个元素开始(下标1),将当前元素暂存到 temp。
  2. 与已排序部分从后往前逐个比较。
  3. 比 temp 大的元素后移一位。
  4. 找到合适位置后,将 temp 插入。

图解示例(对 {64, 25, 12, 22, 11} 排序):

复制代码
初始:[64, 25, 12, 22, 11]

取25插入:25<64 → 64后移 → 插入 → [25, 64, 12, 22, 11]
取12插入:12<64 → 64后移,12<25 → 25后移 → 插入 → [12, 25, 64, 22, 11]
取22插入:22<64 → 64后移,22<25 → 25后移,22>12 → 插入 → [12, 22, 25, 64, 11]
取11插入:依次后移 → 插入 → [11, 12, 22, 25, 64]

入门案例:插入排序

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

int main()
{
    int a[5] = {64, 25, 12, 22, 11};  // 定义并初始化待排序数组
    int i, j;                          // i为当前待插入元素的下标,j为比较指针
    int temp;                          // 暂存当前待插入的元素
    int n = 5;                         // 数组长度

    // 插入排序:从第2个元素(下标1)开始
    for (i = 1; i < n; i++)  // i从1到n-1,逐个取出待插入元素
    {
        temp = a[i];  // 将当前元素暂存到temp中,腾出位置

        // 从已排序部分的最后一个元素开始,向前逐个比较
        j = i - 1;    // j指向已排序部分的最后一个元素

        while (j >= 0 && a[j] > temp)  // 如果j合法且a[j]比temp大
        {
            a[j + 1] = a[j];  // 将a[j]后移一位,为temp腾出位置
            j--;               // j前移,继续比较
        }

        a[j + 1] = temp;  // 将temp插入到正确的位置
    }

    printf("插入排序后的结果:");
    for (i = 0; i < n; i++)
    {
        printf("%d ", a[i]);
    }
    printf("\n");

    return 0;
}

运行结果:

复制代码
插入排序后的结果:11 12 22 25 64

时间复杂度: 最好 O(n),最坏 O(n²),空间复杂度:O(1)。


5.5 折半排序(折半插入排序)

基本思想:

折半插入排序是插入排序的改进版。在寻找插入位置时,不再从后往前逐个比较,而是利用折半查找(二分查找) 来确定插入位置,从而减少比较次数。

算法步骤:

  1. 从第2个元素开始,将当前元素暂存到 temp。
  2. 在已排序部分用折半查找确定 temp 应该插入的位置。
  3. 将该位置及之后的元素后移一位。
  4. 将 temp 插入到确定的位置。

入门案例:折半插入排序

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

int main()
{
    int a[6] = {64, 25, 12, 22, 11, 0};  // 数组(第0个元素先不动)
                                           // 我们从下标1开始排序
    int n = 5;                             // 实际待排序元素个数
    int i, j;                              // 循环变量
    int temp;                              // 暂存待插入元素
    int low, high, mid;                    // 折半查找用的变量

    // 从第2个元素开始,逐个插入
    for (i = 2; i <= n; i++)  // 用下标1~n,a[0]作为哨兵暂存
    {
        temp = a[i];  // 暂存当前待插入元素

        // 在a[1]到a[i-1](已排序部分)中用折半查找确定插入位置
        low = 1;          // 已排序部分的起始位置
        high = i - 1;     // 已排序部分的结束位置

        while (low <= high)  // 折半查找
        {
            mid = (low + high) / 2;  // 取中间位置
            if (temp < a[mid])       // 如果temp小于中间元素
                high = mid - 1;      // 在左半部分继续查找
            else                     // 如果temp大于等于中间元素
                low = mid + 1;       // 在右半部分继续查找
        }

        // high+1 就是插入位置,将该位置及之后的元素后移
        for (j = i - 1; j >= high + 1; j--)
        {
            a[j + 1] = a[j];  // 元素后移一位
        }

        a[high + 1] = temp;  // 插入temp到正确位置
    }

    printf("折半插入排序后的结果:");
    for (i = 1; i <= n; i++)  // 从下标1开始输出
    {
        printf("%d ", a[i]);
    }
    printf("\n");

    return 0;
}

运行结果:

复制代码
折半插入排序后的结果:11 12 22 25 64

说明: 折半插入排序减少了比较次数 (利用折半查找从 O(n) 降到 O(log n)),但移动次数不变,总时间复杂度仍为 O(n²)。


5.6 排序算法的比较

排序算法 最好时间复杂度 最坏时间复杂度 平均时间复杂度 空间复杂度 是否稳定
选择排序 O(n²) O(n²) O(n²) O(1) 不稳定
冒泡排序 O(n) O(n²) O(n²) O(1) 稳定
交换排序 O(n²) O(n²) O(n²) O(1) 不稳定
插入排序 O(n) O(n²) O(n²) O(1) 稳定
折半插入排序 O(n log n) O(n²) O(n²) O(1) 稳定

名词解释:

  • 稳定排序:如果两个相等的元素在排序前后的相对顺序不变,则称该排序算法是稳定的。
  • 时间复杂度:描述算法运行时间随数据规模增长的趋势。
  • 空间复杂度:描述算法所需额外内存空间的大小。

选择建议:

  • 数据量小(n < 50):插入排序效果好,实现简单。
  • 数据量中等:冒泡排序或选择排序。
  • 数据量大:应使用更高级的排序算法(如快速排序、归并排序,后续章节学习)。

六、数组应用

6.1 反转输出字符串

基本思想:

将字符串的第一个字符与最后一个字符交换,第二个与倒数第二个交换,依此类推,直到中间位置。

入门案例:反转字符串

c 复制代码
#include <stdio.h>
#include <string.h>  // 使用strlen函数

int main()
{
    char str[100];   // 存储输入的字符串
    int len;         // 字符串长度
    int i;           // 循环变量
    char temp;       // 交换用的临时变量

    printf("请输入一个字符串:");
    gets(str);       // 读取一行字符串

    len = strlen(str);  // 计算字符串的有效长度(不含'\0')

    // 反转操作:首尾对应交换
    for (i = 0; i < len / 2; i++)  // 只需交换前一半
    {
        temp = str[i];                // 将第i个字符暂存
        str[i] = str[len - 1 - i];   // 将倒数第i+1个字符放到第i个位置
        str[len - 1 - i] = temp;     // 将暂存的字符放到倒数第i+1个位置
    }

    printf("反转后的字符串:%s\n", str);  // 输出反转后的结果

    return 0;
}

运行结果(示例):

复制代码
请输入一个字符串:Hello World
反转后的字符串:dlroW olleH

6.2 输出系统日期和时间

基本思想:

使用C标准库中 <time.h> 提供的时间函数获取系统时间,并格式化输出。

入门案例:输出当前系统日期和时间

c 复制代码
#include <stdio.h>
#include <time.h>  // 包含时间处理头文件

int main()
{
    time_t rawtime;      // 定义time_t类型的变量,用于存储时间戳
    struct tm *timeinfo; // 定义tm结构体指针,用于存储分解后的时间信息

    time(&rawtime);                // 获取当前系统时间(时间戳形式),存入rawtime
    timeinfo = localtime(&rawtime); // 将时间戳转换为本地时间的结构体

    // 输出年、月、日
    printf("当前日期:%d年%02d月%02d日\n",
           timeinfo->tm_year + 1900,  // tm_year表示从1900年起的年数,需加1900
           timeinfo->tm_mon + 1,      // tm_mon范围是0~11,需加1
           timeinfo->tm_mday);        // tm_mday是本月的日期(1~31)

    // 输出时、分、秒
    printf("当前时间:%02d时%02d分%02d秒\n",
           timeinfo->tm_hour,  // tm_hour是小时(0~23)
           timeinfo->tm_min,   // tm_min是分钟(0~59)
           timeinfo->tm_sec);  // tm_sec是秒(0~59)

    // 输出星期几
    printf("星期");
    switch (timeinfo->tm_wday)  // tm_wday是星期几(0=周日,1=周一,...,6=周六)
    {
        case 0: printf("日\n"); break;
        case 1: printf("一\n"); break;
        case 2: printf("二\n"); break;
        case 3: printf("三\n"); break;
        case 4: printf("四\n"); break;
        case 5: printf("五\n"); break;
        case 6: printf("六\n"); break;
    }

    return 0;
}

运行结果(示例,取决于运行时的系统时间):

复制代码
当前日期:2024年01月15日
当前时间:14时30分25秒
星期一

6.3 字符串的加密和解密

基本思想:

对字符串中的每个字符进行变换,实现加密。解密是加密的逆过程。

常用方法:

  • 凯撒密码:每个字符的ASCII码加上一个固定偏移量。
  • 异或加密 :每个字符与一个密钥进行异或运算。异或运算的特性:(A ^ key) ^ key = A,即加密和解密使用相同的操作。

入门案例:凯撒密码加密与解密

c 复制代码
#include <stdio.h>
#include <string.h>  // 使用strlen函数

int main()
{
    char str[200];     // 存储原始字符串
    char encrypted[200];  // 存储加密后的字符串
    char decrypted[200];  // 存储解密后的字符串
    int key = 3;       // 加密密钥(偏移量为3)
    int len;           // 字符串长度
    int i;             // 循环变量

    printf("请输入要加密的字符串:");
    gets(str);         // 读取原始字符串

    len = strlen(str); // 获取字符串长度

    // ===== 加密过程 =====
    for (i = 0; i < len; i++)
    {
        // 对字母字符进行偏移加密
        if (str[i] >= 'a' && str[i] <= 'z')  // 小写字母
        {
            // 偏移3位,如果超过'z'则循环回'a'
            encrypted[i] = (str[i] - 'a' + key) % 26 + 'a';
            // 例如:'x' → (120-97+3)%26+97 = 26%26+97 = 0+97 = 97 = 'a'
        }
        else if (str[i] >= 'A' && str[i] <= 'Z')  // 大写字母
        {
            encrypted[i] = (str[i] - 'A' + key) % 26 + 'A';
        }
        else  // 非字母字符保持不变
        {
            encrypted[i] = str[i];
        }
    }
    encrypted[len] = '\0';  // 添加字符串结束标志

    printf("加密后的字符串:%s\n", encrypted);  // 输出加密结果

    // ===== 解密过程 =====
    for (i = 0; i < len; i++)
    {
        if (encrypted[i] >= 'a' && encrypted[i] <= 'z')  // 小写字母
        {
            // 减去偏移量,如果低于'a'则循环回'z'
            decrypted[i] = (encrypted[i] - 'a' - key + 26) % 26 + 'a';
            // 加26是为了防止出现负数
        }
        else if (encrypted[i] >= 'A' && encrypted[i] <= 'Z')  // 大写字母
        {
            decrypted[i] = (encrypted[i] - 'A' - key + 26) % 26 + 'A';
        }
        else  // 非字母字符保持不变
        {
            decrypted[i] = encrypted[i];
        }
    }
    decrypted[len] = '\0';  // 添加字符串结束标志

    printf("解密后的字符串:%s\n", decrypted);  // 输出解密结果

    return 0;
}

运行结果(示例):

复制代码
请输入要加密的字符串:Hello World
加密后的字符串:Khoor Zruog
解密后的字符串:Hello World

入门案例:异或加密与解密

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

int main()
{
    char str[200];          // 存储原始字符串
    char result[200];       // 存储加密/解密后的字符串
    char key = 'K';         // 密钥字符(可以任意指定)
    int len;                // 字符串长度
    int i;                  // 循环变量

    printf("请输入要加密的字符串:");
    gets(str);              // 读取原始字符串

    len = strlen(str);      // 获取字符串长度

    // ===== 加密:每个字符与密钥进行异或运算 =====
    for (i = 0; i < len; i++)
    {
        result[i] = str[i] ^ key;  // 异或加密
                                    // 例:'H'(72) ^ 'K'(75) = 11(不可见字符)
    }
    result[len] = '\0';     // 添加结束标志

    printf("加密后的ASCII值:");
    for (i = 0; i < len; i++)
    {
        printf("%d ", (unsigned char)result[i]);  // 输出加密后的ASCII码值
    }
    printf("\n");

    // ===== 解密:再次异或相同的密钥即可还原 =====
    for (i = 0; i < len; i++)
    {
        result[i] = result[i] ^ key;  // 再次异或,还原为原始字符
                                       // 因为 (A ^ key) ^ key = A
    }
    result[len] = '\0';

    printf("解密后的字符串:%s\n", result);  // 输出解密结果,应与原文相同

    return 0;
}

运行结果(示例):

复制代码
请输入要加密的字符串:Hello
加密后的ASCII值:11 45 16 16 27
解密后的字符串:Hello

七、实践与练习

练习1:求数组中的第二大元素

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

int main()
{
    int a[10] = {45, 12, 78, 34, 90, 56, 23, 67, 89, 11};  // 定义数组
    int first, second;  // first存最大值,second存第二大值
    int i;

    // 初始化最大值和第二大值
    if (a[0] > a[1])          // 比较前两个元素
    {
        first = a[0];          // a[0]较大,作为最大值
        second = a[1];         // a[1]较小,作为第二大值
    }
    else
    {
        first = a[1];          // a[1]较大,作为最大值
        second = a[0];         // a[0]较小,作为第二大值
    }

    // 从第3个元素开始遍历
    for (i = 2; i < 10; i++)
    {
        if (a[i] > first)      // 如果当前元素比最大值还大
        {
            second = first;    // 原来的最大值变成第二大
            first = a[i];      // 更新最大值
        }
        else if (a[i] > second && a[i] != first)  
                               // 如果当前元素比第二大值大且不等于最大值
        {
            second = a[i];     // 更新第二大值
        }
    }

    printf("最大值为:%d\n", first);     // 输出最大值
    printf("第二大值为:%d\n", second);  // 输出第二大值

    return 0;
}

运行结果:

复制代码
最大值为:90
第二大值为:89

练习2:用冒泡排序将字符串按ASCII码从小到大排序

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

int main()
{
    char str[100];   // 存储输入的字符串
    int len;         // 字符串长度
    int i, j;        // 循环变量
    char temp;       // 交换用的临时变量

    printf("请输入一个字符串:");
    gets(str);       // 读取字符串

    len = strlen(str);  // 获取长度

    // 用冒泡排序对字符排序
    for (i = 0; i < len - 1; i++)       // 外层循环:n-1轮
    {
        for (j = 0; j < len - 1 - i; j++)  // 内层循环:两两比较
        {
            if (str[j] > str[j + 1])    // 如果前一个字符的ASCII码大于后一个
            {
                temp = str[j];          // 交换两个字符
                str[j] = str[j + 1];
                str[j + 1] = temp;
            }
        }
    }

    printf("排序后的字符串:%s\n", str);  // 输出排序结果

    return 0;
}

运行结果(示例):

复制代码
请输入一个一个字符串:HelloWorld
排序后的字符串:HWdelllloo

练习3:计算矩阵的对角线元素之和

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

int main()
{
    int a[3][3] = {          // 定义一个3x3的矩阵
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };
    int sum_main = 0;        // 主对角线元素之和(左上到右下)
    int sum_anti = 0;        // 副对角线元素之和(右上到左下)
    int i, j;                // 循环变量

    // 遍历矩阵
    for (i = 0; i < 3; i++)
    {
        for (j = 0; j < 3; j++)
        {
            if (i == j)              // 主对角线条件:行号等于列号
            {
                sum_main += a[i][j]; // 累加主对角线元素
            }
            if (i + j == 2)          // 副对角线条件:行号+列号=n-1=2
            {
                sum_anti += a[i][j]; // 累加副对角线元素
            }
        }
    }

    // 输出矩阵
    printf("矩阵为:\n");
    for (i = 0; i < 3; i++)
    {
        for (j = 0; j < 3; j++)
            printf("%4d", a[i][j]);
        printf("\n");
    }

    printf("主对角线元素之和:%d\n", sum_main);  // 1+5+9=15
    printf("副对角线元素之和:%d\n", sum_anti);  // 3+5+7=15

    return 0;
}

运行结果:

复制代码
矩阵为:
   1   2   3
   4   5   6
   7   8   9
主对角线元素之和:15
副对角线元素之和:15

练习4:删除字符串中的指定字符

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

int main()
{
    char str[200];      // 原始字符串
    char ch;            // 要删除的字符
    int i, j;           // 循环变量(i读指针,j写指针)

    printf("请输入一个字符串:");
    gets(str);          // 读取原始字符串

    printf("请输入要删除的字符:");
    ch = getchar();     // 读取要删除的字符

    j = 0;  // j是"写指针",指向新字符串的当前位置

    // 遍历原始字符串的每个字符
    for (i = 0; str[i] != '\0'; i++)  // 从头到尾遍历
    {
        if (str[i] != ch)    // 如果当前字符不是要删除的字符
        {
            str[j] = str[i]; // 将该字符复制到j的位置
            j++;             // 写指针后移
        }
        // 如果是要删除的字符,则跳过(不复制,j不动)
    }
    str[j] = '\0';  // 在新字符串末尾添加结束标志

    printf("删除后的字符串:%s\n", str);  // 输出结果

    return 0;
}

运行结果(示例):

复制代码
请输入一个字符串:Hello World
请输入要删除的字符:l
删除后的字符串:Heo Word

八、总结

知识点 核心要点
一维数组定义 类型 数组名[常量];下标从0开始
一维数组初始化 完全初始化、部分初始化、省略长度、全0初始化
二维数组定义 类型 数组名[行][列];内存按行连续存储
二维数组初始化 按行初始化、连续初始化、省略行数
字符数组与字符串 字符串以 '\0' 结尾;sizeof 含 '\0',strlen 不含
字符串输入输出 scanf("%s") 不读空格;gets() 读整行
排序算法 选择、冒泡、交换、插入、折半插入排序的原理和实现
数组应用 字符串反转、时间获取、字符串加密解密

学习建议: 数组是C语言中最重要的数据结构基础之一。建议先掌握一维数组和字符数组的基本操作,再学习二维数组和排序算法。每个知识点都要通过编写代码来加深理解,注意数组越界和字符串结束标志这两个最常见的错误。

相关推荐
hetao17338371 小时前
联盟场-提高组-R2 & ZYZSC-S-Round 5
c++·算法
卿卿的产品经理日记1 小时前
【AI产品经理实战】Day 34|计算机科学速成②:从水流开关到 Unicode,把整个电脑拆成一长串 0 和 1
人工智能·笔记·科技·学习·产品经理
lhbweilai1 小时前
分析FreeRTOS内核对象API的源码实现(三)
c语言·stm32·单片机
其实防守也摸鱼1 小时前
Codex 下载与本地部署实战指南
linux·服务器·前端·数据库·算法·ai
南极企鹅1 小时前
生产环境gc文件分析
java·jvm·算法
希洛兔1 小时前
原码、反码、补码:一张图讲透计算机里的负数表示
算法·面试
X北辰北1 小时前
C++ 进阶第二阶段总结|STL容器全家桶、迭代器失效、STL算法、序列化入门(工程必备)
开发语言·c++·算法
过河卒_zh15667662 小时前
再更新!人工智能安全治理指引性文件第三版发布!
人工智能·算法·aigc·生成式人工智能·算法备案
星恒随风2 小时前
Linux进程信号(二):深入理解信号阻塞、未决、sigaction、中断机制、可重入函数与SIGCHLD
linux·运维·服务器·笔记·学习