综合实战:数组与字符串在项目中的应用

讲完了数组和字符串的基础知识,咱们得真刀真枪干一场了。我个人一直觉得,学编程最怕的就是「学了一堆语法,但不知道能干嘛」。所以这一章,我挑了两个非常经典的项目场景——学生成绩管理系统字符串加密解密,带你看看数组和字符串到底是怎么在真实项目中配合使用的。

项目一:学生成绩管理系统

这个项目,我估计每个学C语言的人都写过。但写得好不好,差别很大。我在公司带新人时,发现很多人会用一堆散乱的变量来存成绩,比如 score1score2…… 这显然不现实。你想想看,一个班50个人,难道要定义50个变量?

正确的做法,就是用数组来组织数据。

核心数据结构设计

我们先定义一个结构体,把学生的信息打包在一起:

#define MAX_NAME 20
#define MAX_STUDENTS 50

typedef struct {
    int id;                 // 学号
    char name[MAX_NAME];    // 姓名
    float scores[3];        // 三门课的成绩
    float average;          // 平均分
} Student;

这里有个细节我想强调一下:name 用的是字符数组,而不是指针。为什么?因为我们要存储具体的名字,而不是指向某个字符串的地址。我在项目中见过有人用 char *name,结果赋值时直接崩溃了——因为没分配内存。

核心功能实现

接下来,我们实现几个关键函数。先看录入成绩:

void inputScores(Student students[], int count) {
    for (int i = 0; i < count; i++) {
        printf("请输入第%d个学生的学号:", i + 1);
        scanf("%d", &students[i].id);
        printf("请输入姓名:");
        scanf("%s", students[i].name);
        printf("请输入三门课的成绩:");
        for (int j = 0; j < 3; j++) {
            scanf("%f", &students[i].scores[j]);
        }
        // 计算平均分
        float sum = 0;
        for (int j = 0; j < 3; j++) {
            sum += students[i].scores[j];
        }
        students[i].average = sum / 3.0;
    }
}

嗯,这里要注意:scanf 读字符串时,如果名字中间有空格就会出问题。我曾经在做一个考勤系统时,有个学生叫「张 三」,结果程序直接跳过了后面的输入。后来我改用 fgets 才解决。

排序与查找

按平均分排序,用冒泡排序就够了:

void sortByAverage(Student students[], int count) {
    for (int i = 0; i < count - 1; i++) {
        for (int j = 0; j < count - 1 - i; j++) {
            if (students[j].average < students[j + 1].average) {
                Student temp = students[j];
                students[j] = students[j + 1];
                students[j + 1] = temp;
            }
        }
    }
}

查找某个学生,可以用学号或姓名。用姓名查找时,就要用到字符串比较:

int findByName(Student students[], int count, const char *target) {
    for (int i = 0; i < count; i++) {
        if (strcmp(students[i].name, target) == 0) {
            return i;  // 返回下标
        }
    }
    return -1;  // 没找到
}
小技巧:strcmp 比较字符串时,返回0表示相等。很多人刚学时会写成 if (strcmp(a, b)),这其实是判断「不相等」,因为非0为真。我建议你养成习惯:if (strcmp(a, b) == 0) 这样写,一眼就能看出是在判断相等。

项目二:字符串加密解密

这个项目更有意思。说白了,就是利用字符数组的索引操作,把一段明文变成密文,再变回来。

凯撒密码实现

最简单的加密方式就是凯撒密码——每个字母向后移动固定位数。比如移动3位,'A' 变成 'D','B' 变成 'E'。

void caesarEncrypt(char *text, int shift) {
    for (int i = 0; text[i] != '\0'; i++) {
        char c = text[i];
        if (c >= 'A' && c <= 'Z') {
            text[i] = ((c - 'A' + shift) % 26) + 'A';
        } else if (c >= 'a' && c <= 'z') {
            text[i] = ((c - 'a' + shift) % 26) + 'a';
        }
        // 其他字符保持不变
    }
}

解密就是反向操作:

void caesarDecrypt(char *text, int shift) {
    caesarEncrypt(text, 26 - shift);  // 反向移动
}

你可能会问:为什么要用 % 26?因为字母表只有26个字母,移动超过26位就循环回来了。我记得有一次,我写了一个加密工具,忘了取模,结果 'Z' 移动3位变成了 ']',解密时怎么都还原不回来……排查了半天才发现这个低级错误。

异或加密(更安全)

凯撒密码太容易被破解了。实际项目中,我们常用异或加密。它的原理是:同一个数异或两次,会回到原值。

void xorEncryptDecrypt(char *text, const char *key) {
    int keyLen = strlen(key);
    for (int i = 0; text[i] != '\0'; i++) {
        text[i] ^= key[i % keyLen];
    }
}

这个函数既能加密也能解密——因为异或操作是对称的。你传入明文,得到密文;再传入密文,就得到明文。

注意:异或加密虽然比凯撒密码强,但依然不是真正的安全加密。它只能防「不小心看到」的人,防不了专业攻击。如果你要做真正的加密,请用 AES、RSA 等标准算法。我在做物联网项目时,就吃过这个亏——用异或加密传输数据,结果被中间人轻松破解了。

知识体系总览

下面这张图,把本章涉及的核心知识点串了起来。你可以看到,数组和字符串是如何在项目中配合使用的:

数组与字符串综合实战知识体系 学生成绩管理系统 字符串加密解密 结构体数组 成绩录入 排序与查找 凯撒密码 异或加密 Student 结构体 scanf / fgets strcmp / 冒泡排序 取模运算 % 26 异或 ^ 运算 核心:数组组织数据 + 字符串处理逻辑

避坑指南

最后,我把自己踩过的坑总结一下,希望能帮你少走弯路:

  • 数组越界:C语言不会检查数组下标是否越界。我曾经写了一个成绩管理系统,往 scores[3] 里写数据,结果把相邻的 average 字段给覆盖了,查了两天才发现。记住:数组下标从0开始,最大下标是 长度-1
  • 字符串末尾的 '\0':用字符数组存字符串时,一定要留一个位置给结束符。比如名字最长20个字符,数组大小至少是21。我见过有人定义 char name[20],然后输入了20个字符,结果 strlen 返回了随机值。
  • scanf 的缓冲区问题:混合使用 scanffgets 时,要注意换行符残留。我的习惯是:读字符串一律用 fgets,然后手动去掉末尾的换行符。
  • 加密算法的选择:如果你只是做练习,凯撒密码和异或加密足够了。但如果是真实项目,请务必使用成熟的加密库。记住:不要自己发明加密算法,这是无数前辈用血泪换来的教训。

核心总结:数组和字符串是C语言中最基础也最实用的两个工具。学生成绩管理系统让你学会用数组组织结构化数据,字符串加密解密让你掌握字符数组的灵活操作。这两个项目做下来,你对数组和字符串的理解会上一个台阶。


公众号:蓝海资料掘金营,微信deep3321