買了一輛寶馬,但不知道如何正確使用,找頭驢子拖着寶馬滿世界兜風。這情景多半會讓人感到很滑稽。不正確地使用結構體,代碼同樣會產生一種滑稽的喜感。
只有按照合適的方式使用一件物品才能得到恰如其分的效果,而濫用一件物品只能起到適得其反的作用。
/*
題目:
輸入兩個學生的學號、姓名和成績,輸出成績較高的學生的學號、姓名和成績。
*/
#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這個指針本身也不應該在函數中被改變。這種寫法在必須提供給函數一個結構體對象的備份時並不適用。