“啊……”小P舒服的伸著懶腰,“你知道什么是九三學社么?”
“?”
“就是上午睡到9點,下午睡到3點……舒服……”小P用手拍拍嘴,打了一個哈欠。
“呵呵,睡覺睡到自然醒,真是我們學生一大福利啊……”老C感嘆,“好了,閑話少說,你去開機……我去接水……”
“哦……”
兩人折騰一番后,終于坐在小P桌前。
“我們現在根據程序的模塊將它分成幾部分,我來寫,你看看。”老C開始在電腦鍵盤上扣扣扣扣的敲打起來,“我們的程序大體上分為三個主要部分,一個是
main()函數,這個是程序的進入點和主框架結構,負責算法部分;一個是我們的MY_DEBUG調試宏;再一個是我們的apple
game游戲,提供接口供算法部分調用。根據這樣的劃分,每一個部分的規模和所擔負的責任就很清楚了。”老C一邊說,一邊在工程中添加了新的文件,并改寫
了main.c文件的內容。
mydebug.h:
#if !defined(MY_DEBUG_H_)
#define MY_DEBUG_H_
#include <stdio.h>
#define PRINT_DEBUG_INFO
#if defined(PRINT_DEBUG_INFO)
#define MY_DEBUG(str) printf(str)
#define MY_DEBUG_1(str, par) printf(str, par)
#else
#define MY_DEBUG(str)
#define MY_DEBUG_1(str, par)
#endif /* PRINT_DEBUG_INFO */
#endif /* MY_DEBUG_H_ */
------------------------------------------------------(華麗的分割線)
applegame.h:
#if !defined(APPLE_GAME_H_)
#define APPLE_GAME_H_
#define CHILDREN_NUM 20U
typedef int SEAT_NUM;
typedef enum tagEXISTE_STATE { ABSENT, EXISTED } EXISTE_STATE;
typedef struct tagCHILD
{
SEAT_NUM seatNum_;
EXISTE_STATE existeState_;
}CHILD;
#define QUEUE_LENGTH CHILDREN_NUM
typedef CHILD QUEUE_CONTENT;
typedef struct tagQUEUE
{
int size_;
int index_;
QUEUE_CONTENT queue_[QUEUE_LENGTH];
}QUEUE;
typedef struct tagAPPLE_GAME
{
int currCountNum_;
int childrenRemained_;
QUEUE childrenQueue_;
}APPLE_GAME;
extern void InitAppleGame (APPLE_GAME* game);
extern int IsGameOver (APPLE_GAME* game);
extern void PlayGame (APPLE_GAME* game);
extern int LastChildSeatNum (APPLE_GAME* game);
#endif /* APPLE_GAME_H_ */
------------------------------------------------------(華麗的分割線)
applegame.c:
#include "applegame.h"
#include "mydebug.h"
void InitAppleGame(APPLE_GAME* game)
{
MY_DEBUG("Init the apple game.\n");
}
int IsGameOver(APPLE_GAME* game)
{
static int n = -1;
MY_DEBUG("Only one child?\n");
++n;
return n;
}
void PlayGame(APPLE_GAME* game)
{
MY_DEBUG("Play game...\n");
}
int LastChildSeatNum(APPLE_GAME* game)
{
int n = 1;
MY_DEBUG("Searching last child's seat number\n");
return n;
}
------------------------------------------------------(華麗的分割線)
main.c:
#include <stdio.h>
#include "applegame.h"
int main()
{
APPLE_GAME theGame;
int num;
/* Initialize the game. */
InitAppleGame(&theGame);
/* Play the game, until the last child is found. */
while (!IsGameOver(&theGame))
{
PlayGame(&theGame);
}
/* Search the last child's seat number. */
num = LastChildSeatNum(&theGame);
printf("The last child's seat number is %d.\n", num);
return 0;
}
“編譯……運行……OK,我們的V0.03版本成功了……”老C道,他依照以前的辦法將各個新文件拷貝到AppleGame_V0.03目錄下,又新建了AppleGame_V0.04。
“我們的原則是盡量少的包含依賴關系。每個文件都包含且僅包含它們需要的頭文件,既不能多,也不能少。比如main.c,就算我知道
applegame.h中包含了stdio.h,但是并沒有明顯的線索和強烈的暗示告訴我applegame.h中一定包含stdio.h,所以我還是要
包含stdio.h,這樣就減小了main.c對applegame.h的一些依賴……如果哪天我一高興,決定不再在applegame.h中包含
stdio.h,這樣就可以少修改一些地方……偷懶,是程序員的美德……”
“嗯……”無視了老C的自吹自擂,小P看了幾遍工程中的文件,“嗯,這樣的確比寫在一起清晰很多。然后呢?”
“然后我們就繼續細化我們的代碼,你來接著寫啊。注意編碼的規范性……”老C答道。
“好!”小P開始挽袖子。
“不過在此之前我先教你一個乖。”老C將applegame.c文件打開,“你按一下Ctrl+Tab試試。”
“唔……哦?文本跳轉到了applegame.h?這個……好東東啊。”小P試了試。
“呵呵,你可以修改這個快捷鍵,我記得好像叫toggle head & source,因為某些惡趣味我改成了Alt+O,哈哈……”
“囧……”忽略過老C的傻笑,小P開始完善applegame.c文件。
經過一陣忙碌,小P修改了applegame.h和applegame.c文件。
applegame.h:
#if !defined(APPLE_GAME_H_)
#define APPLE_GAME_H_
#define CHILDREN_NUM 20U
#define KICK_OUT_NUM 7U
typedef int SEAT_NUM;
typedef enum tagEXISTE_STATE { ABSENT, EXISTED } EXISTE_STATE;
typedef struct tagCHILD
{
SEAT_NUM seatNum_;
EXISTE_STATE existeState_;
}CHILD;
#define QUEUE_LENGTH CHILDREN_NUM
typedef CHILD QUEUE_CONTENT;
typedef struct tagQUEUE
{
int size_;
int index_;
QUEUE_CONTENT queue_[QUEUE_LENGTH];
}QUEUE;
typedef struct tagAPPLE_GAME
{
int currCountNum_;
int kickOutNum_;
int childrenRemained_;
QUEUE childrenQueue_;
}APPLE_GAME;
extern void InitAppleGame (APPLE_GAME* game);
extern int IsGameOver (APPLE_GAME* game);
extern void PlayGame (APPLE_GAME* game);
extern int LastChildSeatNum (APPLE_GAME* game);
#endif /* APPLE_GAME_H_ */
------------------------------------------------------(華麗的分割線)
applegame.c:
#include "applegame.h"
#include "mydebug.h"
void InitAppleGame(APPLE_GAME* game)
{
int i;
MY_DEBUG("Init the apple game.\n");
game->currCountNum_ = 0;
game->kickOutNum_ = KICK_OUT_NUM;
game->childrenRemained_ = CHILDREN_NUM;
game->childrenQueue_.size_ = CHILDREN_NUM;
game->childrenQueue_.index_ = 0;
for (i = 0; i < game->childrenQueue_.size_; ++i)
{
game->childrenQueue_.queue_[i].seatNum_ = i + 1;
game->childrenQueue_.queue_[i].existeState_ = EXISTED;
}
}
int IsGameOver(APPLE_GAME* game)
{
MY_DEBUG_1("The children remained %d\n", game->childrenRemained_);
return (1 == game->childrenRemained_);
}
void PlayGame(APPLE_GAME* game)
{
MY_DEBUG("Play game...\n");
/* If the current child is existed in the queue, count on, then check if she will be kicked out. */
if (EXISTED == game->childrenQueue_.queue_[game->childrenQueue_.index_].existeState_)
{
/* Count on. */
game->currCountNum_++;
/* If the child counts kicked out number, then she is kicked out. */
if (game->currCountNum_ == game->kickOutNum_)
{
game->childrenQueue_.queue_[game->childrenQueue_.index_].existeState_ = ABSENT;
game->childrenRemained_--;
game->currCountNum_ = 0;
MY_DEBUG_1("The child kicked out is %d\n", game->childrenQueue_.queue_[game->childrenQueue_.index_].seatNum_);
}
}
game->childrenQueue_.index_++;
game->childrenQueue_.index_ %= game->childrenQueue_.size_;
}
int LastChildSeatNum(APPLE_GAME* game)
{
int i;
MY_DEBUG("Searching last child's seat number\n");
for (i = 0; i < game->childrenQueue_.size_; ++i)
{
if (EXISTED == game->childrenQueue_.queue_[i].existeState_)
{
break;
}
}
return game->childrenQueue_.queue_[i].seatNum_;
}
“呵呵,代碼寫得還可以,但是程序執行結果到底對不對呢?”老C看著代碼問道,“你可以在applegame.h中將CHILDREN_NUM的值改為1U試試看邊界的情況,然后將CHILDREN_NUM的值改為3U,KICK_OUT_NUM的值改為2U,試試看調試輸出的內容是否正確。”
“好啊。”小P試著更改了幾下,看了看調試輸出內容和自己在紙上畫出的內容,“好像沒有什么不對,我又用5個小朋友和數到3被提出試了試,結果也是正確的。”
“OK,這么說我們的代碼經過你的測試是正確的啦,我們終于有了自己的第一個正式版本啦。”說完老C將所有文件拷貝到AppleGame_V0.04,然后又將AppleGame_V0.04重新命名為AppleGame_V1.00,“呵呵,這下你可以對比一下我們的V1.00版本和你原來的版本。”
“唔,現在的版本代碼變得多得多,但是……看起來更容易明白,而且比較容易調試和測試。”小P分析道。
“沒錯!代碼顯得多是因為增加了函數,因為問題的規模很小,所有我們這樣做有些麻煩——但這是練習——等我們熟悉了正規的做法再去進行直截了當的做法吧,
這樣基本功更扎實一些。”老C點頭,“我們再來優化一下applegame.c文件吧,因為里面的結構體點點點的看著實在是煩人。”他又建立了
AppleGame_V1.01目錄。
“唔,看著的確挺煩人的,那么你打算怎么優化么?”小P問。
“呵呵,把煩人的操作放到函數后面,這樣程序的結構和意圖會更加明顯。”老C回答。
“怎么做呢?”
“呵呵,您瞧好咧……”老C又開始敲鍵盤,優化applegame.c文件。
applegame.c:
#include "applegame.h"
#include "mydebug.h"
static void QueInitQueue (QUEUE* childQueue);
static int QueIsChildExisted (QUEUE* childQueue);
static void QueKickOutChild (QUEUE* childQueue);
static void QueMoveToNextChild (QUEUE* childQueue);
static int QueFindRemainedChild (QUEUE* childQueue);
void InitAppleGame(APPLE_GAME* game)
{
MY_DEBUG("Init the apple game.\n");
game->currCountNum_ = 0;
game->kickOutNum_ = KICK_OUT_NUM;
game->childrenRemained_ = CHILDREN_NUM;
QueInitQueue(&(game->childrenQueue_));
}
int IsGameOver(APPLE_GAME* game)
{
MY_DEBUG_1("The children remained %d\n", game->childrenRemained_);
return (1 == game->childrenRemained_);
}
void PlayGame(APPLE_GAME* game)
{
MY_DEBUG("Play game...\n");
/* If the current child is existed in the queue, count on, then check if she will be kicked out. */
if (QueIsChildExisted(&(game->childrenQueue_)))
{
/* Count on. */
game->currCountNum_++;
/* If the child counts kicked out number, then she is kicked out. */
if (game->currCountNum_ == game->kickOutNum_)
{
QueKickOutChild(&(game->childrenQueue_));
game->childrenRemained_--;
game->currCountNum_ = 0;
MY_DEBUG_1("The child kicked out is %d\n", game->childrenQueue_.queue_[game->childrenQueue_.index_].seatNum_);
}
}
QueMoveToNextChild(&(game->childrenQueue_));
}
int LastChildSeatNum(APPLE_GAME* game)
{
int seatNum;
MY_DEBUG("Searching last child's seat number\n");
seatNum = QueFindRemainedChild(&(game->childrenQueue_));
return seatNum;
}
/************************************************************************/
/* Local functions */
/************************************************************************/
static void QueInitQueue(QUEUE* childQueue)
{
int i;
childQueue->size_ = CHILDREN_NUM;
childQueue->index_ = 0;
for (i = 0; i < childQueue->size_; ++i)
{
childQueue->queue_[i].seatNum_ = i + 1;
childQueue->queue_[i].existeState_ = EXISTED;
}
}
static int QueIsChildExisted(QUEUE* childQueue)
{
return (EXISTED == childQueue->queue_[childQueue->index_].existeState_);
}
static void QueKickOutChild(QUEUE* gameQueue)
{
gameQueue->queue_[gameQueue->index_].existeState_ = ABSENT;
}
static void QueMoveToNextChild(QUEUE* gameQueue)
{
gameQueue->index_++;
gameQueue->index_ %= gameQueue->size_;
}
static int QueFindRemainedChild(QUEUE* gameQueue)
{
int i;
for (i = 0; i < gameQueue->size_; ++i)
{
if (EXISTED == gameQueue->queue_[i].existeState_)
{
break;
}
}
return gameQueue->queue_[i].seatNum_;
}
“編譯……運行……ok,我們的V1.01版本也好了。”老C又將所有文件拷貝到AppleGame_V1.01目錄下。
“等等,”小P問道,“我看不出有什么實質性的變化啊,無非就是用一些static函數替換了原來的內容,換湯不換藥啊。”
“呵呵,你看不出區別是因為你熟悉,如果你第一次看代碼,你會覺得是在代碼中看到QueMoveToNextChild(&(game->childrenQueue_))感
覺好些,還是看到一堆鬼畫符似的結構體點點點的感覺好?”老C解釋道,“意圖,這里強調意圖,因為使用了函數你一眼就可以看出程序執行的意圖,而如果是一
堆代碼的話,你還要反應半天;如果明白了意圖,再去看代碼,感覺會好很多——而且你可以根據代碼意圖提出更好的實現方法;同時這樣也減少了代碼中注釋的工
作量——一般在維護代碼的時候人們很少去修改注釋的;最后,如果你的具體實現需要被維護,這樣也給維護代碼的人提供了線索,無需他在源代碼程序中找來找
去……如果不小心還有可能將你的代碼進行錯誤的修改……”他找到水杯,喝了一大口,“總之不要害怕小而短的函數,有時它們對閱讀代碼的人來說是很好的伙
伴……”
“哦,有些道理。那么會不會影響程序執行的效率呢?”
“……會有一些,不過你要理解20-80原則……”
“什么是20-80原則?”小P問。
“就是說影響程序執行效率的代碼只占代碼總量的20%,我們如果要提高效率,需要把80%的經歷投放到這20%的代碼上——一般來說都是一些算法、方案上
的問題。換句話說,除非需要,否則不要進行效率優化——可維護性要高于效率——再說我們這樣做對效率的影響是微乎其微的。”
“哦,”小P點點頭,“為什么你的static函數命名這樣奇特?”
“唔……習慣。因為這些函數作用于Queue這個模塊,所以我使用Que作為前綴,表示函數屬于Queue模塊,然后采用動賓結構進行命名……習慣、習慣而已。”老C笑道。
“模塊?什么是模塊?”小P追問。
“呵呵,這個是我們下來需要討論的問題,和我們的V1.02版本有些關系。”老C說完又新建了一個AppleGame_V1.02的目錄。
兩人抬頭看看天色,已經接近黃昏了,于是老C決定暫停一下,“這個……我們還是先去喂腦袋吧……人是鐵……”
“呵呵,好吧,我知道一個東北菜館還不錯……我請,我請……”小P拍拍口袋。
“呵呵,總讓你請多不好意思,我請,我請……”兩人一邊推讓,一邊向門口走去。
(V1.02還在后面喔)