“啊……”小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还在后面喔)