用驴子拖宝马——怎样滥用结构体
买了一辆宝马,但不知道如何正确使用,找头驴子拖着宝马满世界兜风。这情景多半会让人感到很滑稽。不正确地使用结构体,代码同样会产生一种滑稽的喜感。
只有按照合适的方式使用一件物品才能得到恰如其分的效果,而滥用一件物品只能起到适得其反的作用。
/*
题目:
输入两个学生的学号、姓名和成绩,输出成绩较高的学生的学号、姓名和成绩。
*/
#include <stdio.h>
int main()
{struct Student
{ int num;
char name[20];
float score;
}student1,student2;
scanf("%d%s%f",&student1.num,student1.name,&student1.score);
scanf("%d%s%f",&student2.num,student2.name,&student2.score);
printf("The higher score is:\n");
if(student1.score>student2.score)
printf("%d %s %6.2f\n",student1.num,student1.name,student1.score);
else if(student1.score<student2.score)
printf("%d %s %6.2f\n",student2.num,student2.name,student2.score);
else
{printf("%d %s %6.2f\n",student1.num,student1.name,student1.score);
printf("%d %s %6.2f\n",student2.num,student2.name,student2.score);
}
return 0;
}
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p299
首先,这段代码把结构体的类型声明写在了main()之内,这是让人感到惊异的。因为,根据标识符的作用域原则,这意味着struct Student是一种“局部”的类型,也就是说只能在main()只能使用这种结构体。
这样使用结构体其实反倒不如不使用结构体,因为基本上这样的写法只收获了使用结构体的麻烦:定义结构体类型,臃长复杂的成员引用,……,但是,没有得到任何好处。对比一下不用结构体的代码就清楚了:
#include <stdio.h> int main( void ) { int num1 ,num2; char name1[20] ,name2[20]; float score1 ,score2; scanf ( "%d%s%f" ,&num1,name1,&score1); scanf ( "%d%s%f" ,&num2,name2,&score2); printf ( "The higher score is:\n" ); if ( score1 > score2) printf ( "%d %s %6.2f\n" ,num1,name1,score1); else if ( score1 < score2) printf ( "%d %s %6.2f\n" ,num2,name2,score2); else { printf ( "%d %s %6.2f\n" ,num1,name1,score1); printf ( "%d %s %6.2f\n" ,num2,name2,score2); } return 0; } |
不难看出,后者与前者功能一致,但是更加清爽,代码也更简洁。既然如此,又有什么必要舍近求远地使用结构体呢?可见样本代码使用结构体完全是一种吃力不讨好的行为。之所以吃力不讨好,是因为样本代码并非是正确使用结构体的方式,而是一种错误方式——只使用了结构体的短处,而没有发挥结构体的任何优势。
当然,后来的这段代码也并非没有缺点:几句形式上完全一致但却有些臃长的printf()函数调用缺乏视觉美感(两个scanf()函数调用也有同样的感觉)。可以把这些臃肿的内容各自用一个函数来实现:
#include <stdio.h> void output( int , char [], float ); void input( int *, char *, float *); int main( void ) { int num1 ,num2; char name1[20],name2[20]; float score1 ,score2; puts ( "请输入两个学生的学号、姓名和成绩" ); input(&num1,name1,&score1); input(&num2,name2,&score2); puts ( "The higher score is:" ); if (score1 > score2) output(num1,name1,score1); else if (score1<score2) output(num2,name2,score2); else { output(num1,name1,score1); output(num2,name1,score2); } return 0; } void input( int *p_num, char *p_name, float *p_ score) { scanf ( "%d%s%f" ,p_num,p_name,p_ score); } void output( int num , char name[] , float score ) { printf ( "%d %s %6.2f\n" , num , name , score ); } |
这段代码首先在视觉上比前一个要更富于美感,这种美感并不是一种装饰,而是有着更为实惠的利益,那就是整洁优美的代码更不容易出错,即使出错也比看起来烂糟糟的代码更容易查找和得到改正。
这段代码的另一个优势在于,如果现在需要修改输出的格式,比如把%6.2f改为%6.0f,现在只需要修改一处而对main()函数部分的代码毫无影响,而前两个代码则需要修改4处,且修改之处一起纠结于main()函数之内。俗话说,常在河边走难免不湿鞋。修改的地方多,工作量大不说,出错的可能性也大。
现在,你可能会觉得传递的参数也忒多了些。这个代码中传递的参数是3个,如果是7、8个呢?你可能会感到非常烦躁。并且,我在前一个代码中悄悄的特意留下了一个小BUG(某个name2被写成了name1),你可能根本就没有察觉。查找这类错误往往非常困难,比那种“比眼力找差别”的智力游戏还要困难。
那么,应该如何回避这些问题呢?答案显然是避免分散地传递这些参数。既然这些数据在逻辑上从属于一个主体,最好把它们都捏合在一起。就像电工布线时是把两个电线拧在一起一样。你没见过把火线和地线分别铺设的电工吧?
在代码中怎样才能把几个在逻辑上从属于一个主体的数据捏在一起呢?显然就是结构体。结构体的作用之一就是简化函数之间的参数传递。既然如此,就意味着不只一个函数会使用这种结构体类型,所以绝对不应该把结构体的类型声明放在某个函数内部。现在你总应该懂得为什么把结构体类型声明放main()里是一种愚蠢的、似是而非的写法了吧?
在使用结构体的前提下对前一个代码继续改进:
#include <stdio.h> struct Student { int num; char name[20]; float score; }; void output ( struct Student ); void input ( struct Student * ) ; int main( void ) { struct Student student1,student2; printf ( "请输入两个学生的学号、姓名和成绩\n" ); input( &student1 ); input( &student2 ); printf ( "The higher score is:\n" ); if ( student1.score >= student2.score ) //按 王爱学志 网友意见修改,在此致谢。 output( student1 ); if ( student1.score <= student2.score ) output( student2 ); return 0; } void input( struct Student *p ) { scanf ( "%d%s%f" , &p->num , p->name , &p->score); } void output( struct Student std ) { printf ( "%d %s %6.2f\n" , std.num , std.name , std.score ); } |
现在,只需要传递一个参数,传递多个参数时个别实参写错的可能性大大降低,代码也更加简洁了。并且,由于结构体各个参数在逻辑上从属于一个整体,代码更具有概括力,函数的意义也更为清晰。
总之,结构体的意义在于其“整体性”,如果建立了结构体,而只是对其进行了成员运算,那么结构体的意义如果不能说是全部丧失,恐怕也可以说是几乎全部丧失。
此外要补充一点的是,在结构体尺寸较大的情况下,结构体的赋值可能比较费时,这时可以用传递指向结构体指针的办法来解决。如前面的output()函数,可以写为
void output( struct Student const * const p_std ) { printf ( "%d %s %6.2f\n" , p_std ->num , p_std ->name , p_std ->score ); } |
形参中的第一个const表示p_std指针所指向的结构体对象不应该被函数改变,第二个const表示p_std这个指针本身也不应该在函数中被改变。这种写法在必须提供给函数一个结构体对象的备份时并不适用。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 智能桌面机器人:用.NET IoT库控制舵机并多方法播放表情
· Linux glibc自带哈希表的用例及性能测试
· 深入理解 Mybatis 分库分表执行原理
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
· 手把手教你在本地部署DeepSeek R1,搭建web-ui ,建议收藏!
· 新年开篇:在本地部署DeepSeek大模型实现联网增强的AI应用
· 程序员常用高效实用工具推荐,办公效率提升利器!
· Janus Pro:DeepSeek 开源革新,多模态 AI 的未来
· 【译】WinForms:分析一下(我用 Visual Basic 写的)
2011-01-20 滥用变量综合症