
本例受裘宗燕老師《從問題到程序——程序設計與C語言引論》啟發。
任務:輸出200以內的完全平方數(一個數如果是另一個整數的完全平方,那么我們就稱這個數為完全平方數,也叫做平方數),要求每隔5個數據要輸出一個換行。
解決方案及點評 對于這個簡單任務,我們在一個main函數中完成了任務。程序如方案1:
//方案1:內聚性較高的單模塊實現方案#includeint main(){int m, num=0;for (m = 1; m * m <= 200; m++){printf("%d ", m * m);num++;if (num%5==0)printf("\n");}return 0;}
由于任務本身簡單,將之在一個main函數中實現后,這個函數的內聚程度接近功能內聚,已經相當高了,就任務本身,不需再進行分解。為使讀者能深入理解模塊質量方面的技術,我們將試圖將內聚程序再提高一些,然后考察耦合程度不同的各種解決方案。
要提高上面解決方案中函數(僅main一個函數)的內聚程度,我們考察程度的功能“找出完全平方數并輸出”——“找出完全平方數”和“輸出”這本身就是兩個功能,再細分輸出時還有“要求5個數據在一行”的要求,這些功能的實現細節都在一個函數當中,可見是有余地再提高內聚程度的。
在實現的應用中,幾乎所有的處理都可以分解為“輸入-計算-輸出”的模式,優秀的解決方案往往至少要將這三個模塊都獨立出來,對于“計算”模塊而言,其內部不再包括輸入輸出,專門接受輸入的數據,計算完成后返回結果即可。當然,對于復雜的問題,在各個環節上可能還需要再做分解。
下面,我們探討將“找出完全平方數輸出”和“每5個數據后換行”分開實現的方案。這樣的分解有助于提高內聚性,與此同時,分解后的兩個模塊間的耦合程度,成為我們要關注的焦點。
現在將“找出完全平方數并輸出”的功能仍放在main函數中(獨立成為單獨的函數也可以,但不必要了),而“每5個數據后換行”的功能,設計一個名稱為format的函數,它每調用一次就輸出一個空格作為兩個完全平方數間的分隔,而每調用到第5次時,輸出的是一個換行。
這兩個模塊之間,需要有一個“現在是第幾次調用”的信息需要傳遞,不可能用耦合程度最松散的非直接耦合.我們考慮數據耦合,用簡單形式參數傳值,得到方案2。
//方案2:一個耦合度低,但不能完成功能要求的解決方案void format(int);int main(){int m, num=0;for (m = 1; m * m <= 200; m++){printf("%d", m * m);format(num);}return 0;}void format(int n){n++;if (n%5==0)printf("\n");elseprintf(" ");return;}
在這個程序結構中,format與main函數的耦合程度為數據耦合。在main中定義了局部變量num,在一次都未輸出時,置初值為0是合理的。在調用format時,將num傳遞來的表示第幾次輸出(第幾個完全平方數)的形式參數n,n自增1,然后再控制輸出空格或換行。
然而分析和運行程序發現,“每隔5個數據輸出一個換行”的功能并未實現。因為形式參數n在函數format內的改變對應的實在參數num占不同的內存空間,n++修改的結果,對num無任何的影響,導致了在下一次調用時,丟失了“輸出的是第幾個”的重要信息。
一個補救的方法,是由format將變化后的n值作為返回值,再傳回給main函數,得到如下方案3的程序:
//方案3:利用了返回值使耦合度增大,但功能得以實現的方案int format(int);int main(){int m, num=0;for (m = 1; m * m <= 200; m++){printf("%d", m * m);num = format(num);}return 0;}int format(int n){n++;if (n%5==0)printf("\n");elseprintf(" ");return n;}
維持原函數返回值為void,而將參數改為傳地址,得到下面的方案4。這個方案的耦合度更高一些,但功能還是能夠實現的。
//方案4:傳地址實現功能的方案,耦合度更大#includevoid format(int*);int main(){int m, num=0;for (m = 1; m * m <= 200; m++){printf("%d", m * m);format(&num);}return 0;}void format(int *p){(*p)++;if ((*p)%5==0)printf("\n");elseprintf(" ");return;}
一定有人想到了用全局變量的解決方案。這樣,可以將num定義為全局變量,num的生存周期不再依賴于函數調用,其值也能在函數的調用之間保持不變(只要其間沒有另外給它賦值),從而可以完成傳遞信息的任務。這時,format因為無需參數傳遞,可以設計為無參函數,得到如下方案5的程序:
//方案5:耦合度最高的全局變量方案#includevoid format();int num=0;int main(){int m ;for (m = 1; m * m <= 200; m++){printf("%d", m * m);format();}return 0;}void format(){num++;if (num%5==0)printf("\n");elseprintf(" ");return;}
這是解決這個問題的耦合程度最高的一個方案。將num定義為外部變量,意味著如果還有其他函數,num是可以被任何函數修改的,當發 format 計數錯誤時,尋找錯誤困難,而修改后又可能會帶來其他地方的錯誤。在這么一個短小的程序中,這種方案可能尚可接受,當程度的規模稍變大,可能帶來的問題必須高度重視。因此,在實際應用中,強調全局變量要慎用(不是不用)。
考慮到num是在format中應用的私用數據——只有format才關心這到底是第幾個數據,main本來都不用關心的。這樣,可以考慮將num定義為format中的局部靜態變量,得到方案6的程序:
//方案6:用靜態局部變量,耦合度偏高但封裝性最好的方案#includevoid format();int main(){int m ;for (m = 1; m * m <= 200; m++){printf("%d", m * m);format();}return 0;}void format(){static int num=0;num++;if (num%5==0)printf("\n");elseprintf(" ");return;}
在這里,靜態局部變量num的作用域是局部的,定義在函數體里,封裝性在所有方案里是最好的,從而能保證信息的隱蔽性,避免其他函數無意的越權訪問;
不過,num的生存期是全局的,可以跨越函數的不同次調用,在兩次調用間傳遞信息,耦合程度(自己和自己的耦合)要高一些,
但使main函數和format函數的耦合達到了最理想的程度,既保證了功能的正確,又保證了局部數據的安全性,表現出靜態局部變量的優勢。
綜上所述,在解決一個問題時,存在著諸多的方案。
方案1可以接受,但希望提高內聚性而做出改進;方案2用簡單的參數傳值方式實現耦合程度低,但很可惜不能完成功能;在其他方案中,對于這個問題,選擇的優先順序是:
方案6、方案3 > 方案4 > 方案5
建議讀者回顧前面的內容,想一想這樣排序的理由。
在上述探討各個方案的過程中,我們應該體會到在程序設計能力提高的過程中,不斷地學習新的技術,懂得新的評判標準,這也就是一個不斷拓寬眼蜀的過程。
在稍后的練習中,不妨多想一些方案,也能夠從專業的角度評判方案的優劣,最終做到的,就是出手就是最佳方案的專業水平。
原文:https://helijian.blog.csdn.net/article/details/79401703
文章來源于網絡,版權歸原作者所有,如有侵權,請聯系刪除。

關注【一起學嵌入式】,回復“加群”進技術交流群。
覺得文章不錯,點擊“分享”、“贊”、“在看” 唄!