从考场到实战:用吉大C语言期末题教你写出更专业的代码
很多同学在学C语言时都有这样的困惑:明明期末考试能拿高分,题目也都会做,可一旦自己动手写个小项目,代码就变得一团糟,连自己过几天都看不懂。这背后反映的,其实是“应试思维”与“工程思维”的巨大鸿沟。考试追求的是功能实现和标准答案,而真实世界的软件开发,追求的是可读性、可维护性、健壮性和团队协作。今天,我们就以吉林大学计算机系《高级语言程序设计》的几道经典期末题目为蓝本,进行一次彻底的“代码手术”。我们将一起动手,把那些为了应付考试而写的“学生版”代码,重构为能在真实项目中使用的“工程版”代码。这个过程,远比学会一个新语法更有价值。
1. 告别“一次性”代码:从变量命名与函数设计开始
我们先来看一道经典的字符串排序题。题目要求编写函数 void sort(char st[][10], int n),对n个字符串按字典序排序。一个典型的“学生版”实现,往往只关注算法逻辑本身。
// 学生版代码片段
void sortone(char *s) {
int i, j, len=0;
i=0;
while(s[i]!='\0') { len++; i++; }
for(i=0; i<len; i++) {
bool change=false;
for(j=0; j<len-i-1; j++) {
if(s[j] > s[j+1]) {
change=true;
int temp=s[j];
s[j]=s[j+1];
s[j+1]=temp;
}
}
if(change==false) break;
}
}
这段代码功能上完全正确,使用了冒泡排序,甚至加入了change标志来优化。但从工程角度看,它存在几个典型问题:
- 变量命名随意:
i,j,temp,change,这些名字除了程序员自己,别人很难一眼看懂其确切含义。 - 函数职责不清:
sortone函数名暗示它对单个字符串排序,但实际做的是字符串内部的字符排序(这更像是“字符串内字符排序”),与题目要求的“多字符串间排序”容易混淆。 - 魔法数字:没有解释为什么是
len-i-1,虽然熟悉冒泡排序的人知道,但对于维护者是个认知负担。 - 缺乏防御:函数直接操作指针
s,但没有检查s是否为NULL。
让我们重构它:
// 工程版代码片段
/**
* @brief 对字符串中的字符进行升序排序(冒泡排序优化版)
* @param str 待排序的字符串,必须以空字符结尾。函数会直接修改此字符串。
* @return void
* @note 如果传入空指针,函数行为是未定义的。调用者需确保指针有效。
*/
void sort_characters_within_string(char* str) {
if (str == NULL) {
// 在实际项目中,这里应该记录错误日志或使用断言
return;
}
size_t length = 0;
// 计算字符串长度
for (const char* p = str; *p != '\0'; ++p) {
++length;
}
// 优化冒泡排序:记录上一轮是否发生交换
bool swapped_in_previous_pass = false;
for (size_t pass_index = 0; pass_index < length - 1; ++pass_index) {
swapped_in_previous_pass = false;
// 每次循环后,最大的元素已“冒泡”到末尾,所以内循环范围递减
size_t comparison_limit = length - pass_index - 1;
for (size_t char_index = 0; char_index < comparison_limit; ++char_index) {
if (str[char_index] > str[char_index + 1]) {
// 交换相邻的逆序字符
char temporary_holder = str[char_index];
str[char_index] = str[char_index + 1];
str[char_index + 1] = temporary_holder;
swapped_in_previous_pass = true;
}
}
// 如果上一轮没有交换,说明数组已有序,提前终止
if (!swapped_in_previous_pass) {
break;
}
}
}
重构要点解析:
- 命名是代码的注释:
length比len更完整,pass_index和char_index清晰地表明了循环的层次,temporary_holder比temp更明确。swapped_in_previous_pass这个长名字虽然打字麻烦,但读起来一目了然,消除了change的歧义。 - 函数签名清晰:函数名
sort_characters_within_string精确描述了功能,避免了与外部字符串排序的混淆。 - 添加文档注释:使用
/** ... */格式的注释,说明函数功能、参数、返回值和注意事项。这是团队协作的基石。 - 引入
size_t:对于表示大小的变量,使用size_t类型比int更安全,因为它保证能容纳任何数组的大小。 - 显式声明循环变量作用域:在C99及以上,可以在
for循环内声明size_t i,限制其作用域。这里为了兼容性,在循环外声明但赋予了更明确的名字。 - 空指针检查:虽然题目可能不要求,但工程代码必须考虑输入有效性。这里进行了简单的检查。
注意:在严格的嵌入式或高性能场景,空指针检查可能因性能原因被省略,转而依赖前置条件或断言。但在大多数应用层代码中,这是一个好习惯。
仅仅一个简单的排序函数,就能看出思维方式的差异。学生代码是“写给我自己(和编译器)看的”,而工程代码是“写给我未来的同事、维护者,以及六个月后可能忘记细节的我自己看的”。
2. 模块化与接口设计:二分查找的优雅实现
第二道题是递归实现二分查找。学生版代码通常直接递归,边界处理可能隐藏在逻辑中。我们来看如何将其模块化。

778

被折叠的 条评论
为什么被折叠?



