← C 语言 学习路线(共 8 章)

第 5 章 数组与字符串

red wenzi · 2026-09-15 · 编程语言 · C 语言 · 📖 预计阅读 11 分钟 · 共 2 道练习
🎯 本章你会学到:数组的内存布局、越界为什么危险、C 的字符串为什么要靠 '\0' 收尾。建议边读边敲,每节练习先自己做,再展开答案对照。

数组是一段连续的内存。这一章的两个重点:一是数组下标不做边界检查,二是 C 没有字符串类型,只有“以 \0 结尾的字符数组”。

数组:连续内存 + 下标越界不报错

数组求最大值与平均值
#include <stdio.h>

#define MAXN 100

int main(void) {
    int n = 0;
    if (scanf("%d", &n) != 1 || n <= 0 || n > MAXN) {
        return 1;                      /* 先校验规模,再开数组 */
    }
    int a[MAXN];
    for (int i = 0; i < n; ++i) {
        if (scanf("%d", &a[i]) != 1) {
            return 1;
        }
    }

    int maxv = a[0];
    long long sum = 0;
    for (int i = 0; i < n; ++i) {
        if (a[i] > maxv) {
            maxv = a[i];
        }
        sum += a[i];
    }
    printf("max=%d\n", maxv);
    printf("avg=%.2f\n", (double)sum / n);
    return 0;
}
输入
5
3 9 4 9 2
运行结果
max=9
avg=5.40

输入 53 9 4 9 2,输出 max=9avg=5.40。注意 sumlong long:数组很大时 int 会溢出。

字符串:字符数组 + 结尾的 '\0'

字符串长度与复制
#include <stdio.h>
#include <string.h>

int main(void) {
    char buf[64];
    if (scanf("%63s", buf) != 1) {     /* 63:给结尾的 '\0' 留一个位置 */
        return 1;
    }

    char copy[64];
    strcpy(copy, buf);                 /* 复制(目标数组必须够大) */

    printf("len=%zu\n", strlen(copy)); /* 长度不含 '\0' */
    printf("first=%c\n", copy[0]);
    return 0;
}
输入
hello
运行结果
len=5
first=h

输入 hello 输出 len=5first=hstrlen 是一个字符一个字符数到 \0 为止,所以它不保存长度、每次调用都是 O(n)。

函数作用注意
strlen(s)求长度(不含 '\0')O(n)
strcpy(dst, src)复制字符串dst 必须够大,否则缓冲区溢出
strcmp(a, b)比较(返回负/0/正)不能用 == 比较字符串
strcat(dst, src)拼接同样要保证 dst 够大
fgets(buf, n, stdin)读一整行(含空格)会保留换行符,需要自己删
⚠️ 易错点 数组越界不报错:int a[5]; a[5] = 1; 编译通过、多数时候也能跑,但它改的是别人的内存,属于未定义行为。
⚠️ 易错点 用 %s 读入一定要限宽(%63s)。不限宽时,输入比缓冲区长就会溢出——这是 C 程序最经典的安全漏洞。
⚠️ 易错点 把数组传给函数后,sizeof 拿到的是指针大小(8),不是数组长度。长度必须另外传进去。

✍️ 本节练习

5.1必做数组反转输出

读入 n 和 n 个整数,按相反顺序输出,数字之间用空格分隔,末尾换行。

输入 第一行 n;第二行 n 个整数。

输出 一行:反转后的序列。

样例输入
5
1 2 3 4 5
样例输出
5 4 3 2 1

💡 提示 从 n - 1 倒着循环,注意最后一个数字后面不要再输出空格。

✅ 查看参考答案与解析
#include <stdio.h>

#define MAXN 1000

int main(void) {
    int n = 0;
    if (scanf("%d", &n) != 1 || n <= 0 || n > MAXN) {
        return 1;
    }
    int a[MAXN];
    for (int i = 0; i < n; ++i) {
        if (scanf("%d", &a[i]) != 1) {
            return 1;
        }
    }
    for (int i = n - 1; i >= 0; --i) {
        printf("%d%c", a[i], i == 0 ? '\n' : ' ');
    }
    return 0;
}

解析 用三元运算符决定“输出空格还是换行”,可以避免末尾多一个空格——在线评测系统对格式很敏感。

5.2挑战统计一行的长度和单词数

fgets 读入一整行(可能含空格),输出这行的字符数(不含换行)和单词数(以空格分隔)。

输入 一行文字(长度 ≤ 250)。

输出 两行:len=字符数words=单词数

样例输入
hello world
样例输出
len=11
words=2

💡 提示 先删掉行尾的换行符,再用一个 in_word 标志遍历统计。

✅ 查看参考答案与解析
#include <stdio.h>
#include <string.h>

int main(void) {
    char line[256];
    if (fgets(line, sizeof line, stdin) == NULL) {
        return 0;
    }

    size_t len = strlen(line);
    while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) {
        line[--len] = '\0';            /* 去掉行尾换行 */
    }

    int words = 0, in_word = 0;
    for (size_t i = 0; i < len; ++i) {
        if (line[i] != ' ' && !in_word) {
            ++words;
            in_word = 1;
        } else if (line[i] == ' ') {
            in_word = 0;
        }
    }
    printf("len=%zu\n", len);
    printf("words=%d\n", words);
    return 0;
}

解析 fgets 会把换行也读进来,所以长度要先减掉它;in_word 标志保证连续空格不会被重复计数。

📚 本文概念都在知识大全:

C 语言 · GCC 编译器 · 指针 · 动态内存 malloc/free · C 字符串 · 结构体 · 预处理 · 编译与链接