综合实战:数组与字符串在项目中的应用
讲完了数组和字符串的基础知识,咱们得真刀真枪干一场了。我个人一直觉得,学编程最怕的就是「学了一堆语法,但不知道能干嘛」。所以这一章,我挑了两个非常经典的项目场景——学生成绩管理系统和字符串加密解密,带你看看数组和字符串到底是怎么在真实项目中配合使用的。
项目一:学生成绩管理系统
这个项目,我估计每个学C语言的人都写过。但写得好不好,差别很大。我在公司带新人时,发现很多人会用一堆散乱的变量来存成绩,比如 score1、score2…… 这显然不现实。你想想看,一个班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];
}
}
这个函数既能加密也能解密——因为异或操作是对称的。你传入明文,得到密文;再传入密文,就得到明文。
知识体系总览
下面这张图,把本章涉及的核心知识点串了起来。你可以看到,数组和字符串是如何在项目中配合使用的:
避坑指南
最后,我把自己踩过的坑总结一下,希望能帮你少走弯路:
- 数组越界:C语言不会检查数组下标是否越界。我曾经写了一个成绩管理系统,往
scores[3]里写数据,结果把相邻的average字段给覆盖了,查了两天才发现。记住:数组下标从0开始,最大下标是长度-1。 - 字符串末尾的 '\0':用字符数组存字符串时,一定要留一个位置给结束符。比如名字最长20个字符,数组大小至少是21。我见过有人定义
char name[20],然后输入了20个字符,结果strlen返回了随机值。 - scanf 的缓冲区问题:混合使用
scanf和fgets时,要注意换行符残留。我的习惯是:读字符串一律用fgets,然后手动去掉末尾的换行符。 - 加密算法的选择:如果你只是做练习,凯撒密码和异或加密足够了。但如果是真实项目,请务必使用成熟的加密库。记住:不要自己发明加密算法,这是无数前辈用血泪换来的教训。
核心总结:数组和字符串是C语言中最基础也最实用的两个工具。学生成绩管理系统让你学会用数组组织结构化数据,字符串加密解密让你掌握字符数组的灵活操作。这两个项目做下来,你对数组和字符串的理解会上一个台阶。
公众号:蓝海资料掘金营,微信deep3321