Contents
完善MVC的DEMO,闲话MAKEFILE(下)
话说随心所欲,但该规范的还是要规范。针对我们的设计目标,就是不写内容,还是要把架子搭起来。因此model.c的代码需要调整如下:
#include <stdio.h>
#include <setjmp.h>
#include "value.h"
#include "define_attr.h"
#include "view.h"
jmp_buf context_buf;
typedef void F_V_V(void);
enum {
MODEL_SET_UP_TRIANGLE,//is right angle
MODEL_SET_DOWN_TRIANGLE,//is right angle
MODEL_SET_ISOSCELES_TRIANGE,
MODEL_SET_MAX_NUM
};
typedef void (*MODEL_SET_FUNC)(int ,int);
static void model_set_mode0(int height ,int width);
static void model_set_mode1(int height ,int width);
static void model_set_mode2(int height ,int width);
MODEL_SET_FUNC model_set_done[MODEL_SET_MAX_NUM] = {model_set_mode0,model_set_mode1,model_set_mode2};
typedef struct{
int mode;
int height;
int width;
char **ppbuf;
}MODEL_S;
static MODEL_S s_pmodel[1] = 0;
static void model_init(void);
static void model_done(void);
static void model_exit(void);
#define MODEL_ENTRY_NUM 4
static F_V_V *model_entry[MODEL_ENTRY_NUM] = {model_init,model_done,model_exit,model_exit};
static char s_model_buf[5][200];
static char *s_pmodel_buf[5] = {(char *)&(s_model_buf[0]),
(char *)&(s_model_buf[1]),
(char *)&(s_model_buf[2]),
(char *)&(s_model_buf[3]),
(char *)&(s_model_buf[4])};
static void model_init(void){
int t;
__PRINT_FUNC();
if ( (t = setjmp(context_buf)) >= 10){
printf("my god ,i escape! %d\n",t);
model_exit();
}
s_pmodel->mode = MODEL_SET_UP_TRIANGLE;
s_pmodel->height = 3;
s_pmodel->width = 5;
s_pmodel->ppbuf = (char **)&(s_pmodel_buf[0]);
return;
}
static void model_set_mode0(int height ,int width){
int i,j;
char **pbuf = s_pmodel->ppbuf;
__PRINT_FUNC();
for (i = 0; i < height ; i++){
for (j = 0 ; j < width* (i + 1) ; j++){
pbuf[i][j] = '*';
}
pbuf[i][j] = 0;
}
view(_VIEW_REFLASH,pbuf);
view(_VIEW_SET_OUTPUT_INFO,(void*)(long long int)height);
view(_VIEW_OUTPUT,stdout);
}
static void model_set_mode1(int height,int width){
__PRINT_FUNC();
printf("error ! not complete!\n");
}
static void model_set_mode2(int height,int width){
__PRINT_FUNC();
printf("error ! not complete!\n");
}
static void model_done(void){
__PRINT_FUNC();
if ((s_pmodel->ppbuf != 0) && (s_pmodel->ppbuf[0] != 0)){
model_set_done[s_pmodel->mode](s_pmodel->height,s_pmodel->width);
}else{
printf("s_pmodel->ppbuf error !\n");
}
return;
}
static void model_exit(void){
__PRINT_FUNC();
return;
}
void model(int status){
__PRINT_FUNC();
model_done();
#if 0
status = (status >= MODEL_ENTRY_NUM ) ? MODEL_ENTRY_NUM-1: status;
model_entry[status]();
#endif
}
编译链接,运行。。。OK。你会发现,输出有问题。啥*也没打印,但是出来一个 s_pmodel->ppbuf error !
很明确,是在model_done中间,对指针进行判断,由此发现ppbuf没有被初始化。而实际初始化代码,我们放到了model_init里面。仔细看一下 __PRINT_FUNC();对应的输出。确实这个函数没调用,疏忽了,疏忽了,我们得先调用一下。那么下一步该怎么做?你有两种做法,要么符合系统设计方式,从外部,调用model这个主模块入口函数进行调用,要么显示的直接在model_done();调用前增加个model_init。
鬼话:记得我的口头禅,把简单的事情搞复杂,有个目的就是我们可以把看似简单,但混乱不堪的东西,拆细了,让我们的工作落在局部进行。
因此,我更建议采用第二种,目前的设计工作不是把model_init的调用完善。而是把各种计算处理的实现函数的架子先搭起来。也正是这个原因,我们所有的改动均在model.c里实现,没有扩展到哪怕是model.h中。
鬼话:动的越多,错的越多,这是我的经验,如果你认为你和我野鬼一样,只是个平庸之辈,不妨听我的劝,别为了点个烟而到处煽风点火。抽烟就抽烟了,自己HIGH就可以,何必搞的满山野火,浓烟滚滚。
现在就上面的代码改动做如下讨论:
- 我们增加了几个typedef ,对于函数指针类型,函数数组等等,是为了搭架子,规范函数调用流程。我们将model_set_modeX系列作为一批计算函数,也即model模块中,处理子模块的可选项。
- 真的没必要,对诸如model_set_modeX系列函数的函数名,用英文准确描述操作含义,有这个功夫,不如写写函数说明注释,这样更加明确清晰(我此出没写),但在enum处,应该比较明确出对应函数指针数组的每个存储单元所指向的函数的意义,这对诸如
s_pmodel->mode = MODEL_SET_UP_TRIANGLE;
的代码,可以提升阅读性 。提升代码的质量(方面阅读和理解本身就是代码质量的一部分) 同时对于一些太长的名词,可以做一定缩写,而后续做一定注释。以需求名词描述准确度和代码词汇简洁之间的折衷。 static MODEL_S s_pmodel[1] = 0;
不是我脑袋进水了。这是一个比较常用的工程设计技巧。因为诸如宏定义操作时,通常使用结构体类型的指针操作,诸如 ->,为了保障能使用一致性的访问方式,我们对实际结构体类型的存储空间的声明,采用[1]的方式,这样s_pmodel即有真实的存储空间,和MODEL_S s_model;一致,同时也可以利用数组可采用指针方式索引访问的规则,对实际结构体的成员存储空间,使用->的方式实现。s_pmodel->mode = MODEL_SET_UP_TRIANGLE; s_pmodel->height = 3; s_pmodel->width = 5; s_pmodel->ppbuf = (char **)&(s_pmodel_buf[0]);
这些放在model_init内并没有错。但是我们直接做了常量初始化,甚至3,5都不是哪来的。还是哪句话,写 代码,始终要有测试的冲动,我们现在不在完善model_init,因此别顾及那么多,这里先做了辅助测试代码。
static void model_set_mode1(int height,int width)
的接口,你完全可以设计成
static void model_set_mode1(void){
int height = s_pmodel->height;
int width = s_pmodel->width;
....
}
这和我的写法本质上没有任何区别,无非s_pmodel->height ,s_pmodel->width是在model_done函数中预先读取到寄存器中,还是在实际函数中进行读取。但从另一个角度,模块化编程思想的角度来看,是有区别的。上述新的写法,会导致一个问题,实际计算处理的代码逻辑嵌套了实际的数据结构。这个和面向对象的诸如 this.height,this.width很像。而面向模块讲究模块和处理过程尽可能的或数据结构剥离,包括模块与模块之间也存在一个降耦合的设计目标。
上述的实现逻辑很简单,如果是一个复杂的数值计算逻辑设计,当你关联了一堆数据结构,甚至还有malloc free之类的资源类操作,你对这个模块的可再利用性就大为降低,你很难整体COPY到另一个工程里使用,或被另一个逻辑组合调用。这如同一个主任医师,在手术时,先把病人肚子上划拉一刀,然后打开门问病人家属“对了,你这有老虎钳吗?”一样的道理。估计病人家属会有点郁闷,如果这个主任医师过一会,有问“你带锤子了吗?”如果病人家属会崩溃。无非是主任医师,为了操刀动手术,不得不在手术中,中断,找点工具,把一些瓶瓶罐罐给打开。
这是他该做的吗?边上的小护士难道只是花瓶吗?各做各的工作,才能把工作做的更好。因此,model_done的ppbuf的检测,就是咱们那可爱的小护士。再你准备手术前,会帮你做好预先体检等准备工作。
而对应model_set_mode1,model_set_mode2,我们可以根据当期任务把架子拉起来。并通过printf("error ! not complete!\n");提醒我们尚未完成工作,但无非只是填写内容的工作。
现在这一步做完,你会发现,打印的三角形这叫一个丑啊。如果此时你向身边的女同事炫耀自己的工作成功,或许你会开始懂得“讨厌”这两个字怎么写,而且我相信这不是反话,后面估计会跟一句“你怎么和野鬼一样没有素养,层次,和品味”。 但为了证明你有素养前,先回到具体工作。我们需要将model和其他模块开始连通。你改变不了别人对你“憎恨”的主观态度,还是安心做好本职工作。 仍然是那几步工作。 1. 由数据的流向,将一些定义拿出去。 2. 先保证输出的对接,输入以及数据存储空间的东西我们可以通过代码静态的模拟,但是输出还是需要view部分的显示,因此control模块,value的文件不是这一步的关注点。
我们将 model_set_mode0修改如下。拿掉测试点。此时,主任医师终于解脱了。
static void model_set_mode0(int height ,int width){
int i,j;
char **pbuf = s_pmodel->ppbuf;
__PRINT_FUNC();
for (i = 0; i < height ; i++){
for (j = 0 ; j < width*(i+1) ; j++){
pbuf[i][j] = '*';
}
pbuf[i][j] = 0;
}
}
调整model_init,这里存在一个view的配置调用,你可以发现,此处存在了关联,height在model中和view中有了配置,而且他们需要保证是同一个值,没错,后续我们将会在control中,统一进行配置,通过某个函数,将两个模块进行衔接。此时我们先仅在model_init中实现这个功能
static void model_init(void){
int t;
__PRINT_FUNC();
if ( (t = setjmp(context_buf)) >= 10){
printf("my god ,i escape! %d\n",t);
model_exit();
}
s_pmodel->mode = MODEL_SET_UP_TRIANGLE;
s_pmodel->height = 3;
s_pmodel->width = 5;
s_pmodel->ppbuf = (char **)&(s_pmodel_buf[0]);
view(_VIEW_SET_OUTPUT_INFO,(void*)(long long int)s_pmodel->height);
return;
}
调整model_done这个函数,将数据刷新在model_set_done后发生。这里也存在一个数据的关联,ppbuf,为什么我们不如height一样在init中实现呢?我仍然要强调,配置信息,和数据缓冲区是两个类型的数据。
- 前者更多是同步的,需要根据特定外部触发事件,引发新配置动作,如果这个配置内容是根据实际数据内容而变动,那么是和数据相关,不和系统相关(系统组成,模块调用方式)也就不能称为配置信息,而是数据的生成信息而已。
- 数据缓冲区完成的不同模块之间数据的交接。真正的交接动作,需要在数据源已经对数据完成操作后才能启动,否则针对数据源的操作,(相对view,model的输出数据就是数据源)就失去了保障和意义,没计算完,也可以丢给下一步操作,而且也被认可,那么这些操作又有什么存在价值呢?
模块化设计,数据的交接很多情况下,是根据数据完成的进度来触发,相对系统状态而言,更是异步执行,如同你显示一个动态画面,实际刷屏就刷,迟来的,早到的,刷屏动作不在乎,有则刷之,无?怎么办?亦刷之。刷的动作和你数据是否准备完毕没关系。这才是好程序,这样才能保证模块各尽其力,各司其职,由你统筹规划,由用户灵活调度。
你还听不明白?上鬼话。
鬼话:家里领导让你做饭,点啥,烧啥,是配置,开火,揭锅是操作,油米酱醋、菜地瓜是数据。领导一声喊,冲奶粉,记得始终和领导保持同步,需要立刻记录吩咐。奶粉,开水是数据,在家中柜子里还是商店的柜台里另谈,配置命令先保存。至于转去冲奶,导致菜做砸了,没办法,你活该,资源不足而已,毕竟数据操作是异步,它有它自己的流程和执行步骤,接受批评再教育时,你还可以有机会苦瓜脸的反抗一句“您老看我长的像螃蟹吗?”。如果你混淆数据和配置信息,烧饭做菜时,听不进最新指示,跟不上最新意见精神!如同一个程序傻乎乎的无法响应你的配置、控制信息,非得等待数据处理完毕,才能吭一句“啊?你说啥?”,我只能说,这个代码是个缺乏质量的问题,而你做人,是个缺乏觉悟的深层次的问题。
此时你也可以看做view(_VIEW_REFLASH,s_pmodel->ppbuf);是model模块对view模块的一次数据推送,但不代表是model对view的控制触发,控制控制,自然需要在control模块里操作。
static void model_done(void){
__PRINT_FUNC();
if ((s_pmodel->ppbuf != 0) && (s_pmodel->ppbuf[0] != 0)){
model_set_done[s_pmodel->mode](s_pmodel->height,s_pmodel->width);
view(_VIEW_REFLASH,s_pmodel->ppbuf);
}else{
printf("s_pmodel->ppbuf error !\n");
}
return;
}
现在编译链接,运行。屏幕打印出来了。不过别急着准备向美女炫耀。我们现在需要关联control模块。为了能将model和view通过control模块进行关联,需要注意,model和view自身的关联已经处理完毕,哪?前面不说了嘛,数据推送 view(_VIEW_REFLASH,s_pmodel->ppbuf);
在做control模块前,我们先把外围环境整理一下。对attr的main函数代码,我们去掉原先的一些存储空间(变量),和操作,保证整体流程能让control正确的实现外部数据的输入,以下给出attr.c的清单。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include "view.h"
#include "control.h"
#include "model.h"
#include "value.h"
#include <string.h>
int main(int argc ,char *argv[]){
FILE *f = 0;
//int view_mode = 0;
int control_mode = 0;
if (init_all() == 0){
{
printf("init not finish! system return ...\n");
return 1;
}
}else{
atexit(free_all);
}
#if 0
model(0);
view(_VIEW_OUTPUT,stdout);
return 0;
#endif
if (argc < 2){
printf("please enter the pathname !\n");
return 1;
}
control_mode = ((f = fopen(argv[1],"rt")) != 0);
if (control_mode){
strcpy(filename,argv[1]);
fclose(f);
// view_mode = 0;
}else{
filename[0] = 0;
// view_mode = 1;
// strcpy(v_param,argv[1]);
}
while (1){
if (g_status == 0){
break;
}else{
control(control_mode);
#if 0
model(0);
view(_VIEW_OUTPUT,stdout);
#endif
g_status = 0;
}
sleep(1);
}
return 0;
}
注意,这里有两种注释,#if 0 #endif 和 //。后者表示永久删除掉的内容。 #if 0 #endif表示暂时先关闭的代码。另外,while (1)中的#if 0 #endif 修正为上一步,调用测试model和view模块的操作方式。
先说一下,while 之前的代码,仍然使用#if 0 #endif的注释的目的,即便在while循环内已经复制了一份。
鬼话:一步一个脚印,在你后续第N步没有彻底设计和测试完毕前,至少N-1步的测试代码保留,哪怕没有因果关系。虽然“举棋不定”会被别人看作是你犹豫不决,但这总比“落子无悔”而你大声解释,“不是这样的,我不想走这步”要好很多。意思是说,保留上一步骤正确的结果,当本步骤测试出错时,你甚至可以推倒重来(仅是这一个步骤的设计),简单的事情做复杂,那么每一步的设计重新规划的成本会小很多,重头开始,并不会影响你前期的设计成果,不过每次开始前,还是要再次测试N-1步,以确保当前步骤设计的起步基础。
由于我们现在存在一定跨越,从model,view的互联,转到control模块的介入,因此,冲冲冲,立刻测试。编译,运行,执行。看看前面已经做的control模块是否还正确。
鬼话:代码存在磁盘上,没人改动,以前测试过的,还是测试过。但如同每次你出门,锁门再带一下一样。一个看似没价值的小动作,实际上是个良好的习惯,谁能肯定不会有个不知原因的故事发生呢?
看来不错。运行和以前一样,我们在分析文本系统。下面我们着手control的改动。
注意运行时,有个函数被调用,print_param。至少证明在这个函数中,所有输入配置被正确的解析出来。那么与其你先去找print_param在哪调用,不妨就先在print_param里进行model模块和view模块的参数配置。但此处有个问题。不是control的问题,是model的入口问题。我们的model_init是个内部函数。外部模块需要通过model的入口函数进行调用。
鬼话:代码可以回头,架构设计,切记回头,我们已经对model的架构进行了规范,model_init属于内部函数,则正常应该需要入口函数进行调用,如果你尝试直接开放model_init,让control直接调用,打乱了model的内部架构流程,那么你前期为model已经完成的正确的架构设计的测试工作都要重新再来。好马不吃回头草,架构好了不能倒。。。。
因此,为了control的正确处理。我们又要回头调整model的架构,另model_init能按照规范进行调用。同时为了保证model和view的设计类似:
- 我们将model_done这个作为model的函数指针数组。而将原先model_done的函数名修改为model_run。
- 修正入口函数指针类型名为 MODEL_FUNC。
- 我们对model入口函数增加pparam参数,对MODEL_FUNC同样处理。使得有个不确定的参数存储空间的指针能传入。
- 我们也取消了MODEL_ENTRY_NUM的定义,采用架构设计的方法,用对应enum最后增加一个MODEL_SET_MAX_NUM来实现。
model的修改如下
#include <stdio.h>
#include <setjmp.h>
#include "value.h"
#include "define_attr.h"
#include "view.h"
jmp_buf context_buf;
enum {
_MODEL_INIT,
_MODEL_RUN,
_MODEL_EXIT,
_MODEL_MAX_TYPE_SIZE
};
typedef void (*MODEL_FUNC)(void *);
enum {
MODEL_SET_UP_TRIANGLE,//is right angle
MODEL_SET_DOWN_TRIANGLE,//is right angle
MODEL_SET_ISOSCELES_TRIANGE,
MODEL_SET_MAX_NUM
};
typedef void (*MODEL_SET_FUNC)(int ,int);
static void model_set_mode0(int height ,int width);
static void model_set_mode1(int height ,int width);
static void model_set_mode2(int height ,int width);
MODEL_SET_FUNC model_set_done[MODEL_SET_MAX_NUM] = {model_set_mode0,model_set_mode1,model_set_mode2};
typedef struct{
int mode;
int height;
int width;
char **ppbuf;
}MODEL_S;
static MODEL_S s_pmodel[1] = 0;
static void model_init(void *p);
static void model_run(void *p);
static void model_exit(void *p);
static MODEL_FUNC model_done[_MODEL_MAX_TYPE_SIZE] = {model_init,model_run,model_exit};
static char s_model_buf[5][200];
static char *s_pmodel_buf[5] = {(char *)&(s_model_buf[0]),
(char *)&(s_model_buf[1]),
(char *)&(s_model_buf[2]),
(char *)&(s_model_buf[3]),
(char *)&(s_model_buf[4])};
static void model_init(void *p){
int t;
__PRINT_FUNC();
if ( (t = setjmp(context_buf)) >= 10){
printf("my god ,i escape! %d\n",t);
model_exit(0);
}
s_pmodel->mode = MODEL_SET_UP_TRIANGLE;
s_pmodel->height = 3;
s_pmodel->width = 5;
s_pmodel->ppbuf = (char **)&(s_pmodel_buf[0]);
view(_VIEW_SET_OUTPUT_INFO,(void*)(long long int)s_pmodel->height);
return;
}
static void model_set_mode0(int height ,int width){
int i,j;
char **pbuf = s_pmodel->ppbuf;
// int k;
__PRINT_FUNC();
for (i = 0; i < height ; i++){
for (j = 0 ; j < width*(i+1) ; j++){//
pbuf[i][j] = '*';
}
pbuf[i][j] = 0;
}
}
static void model_set_mode1(int height,int width){
__PRINT_FUNC();
printf("error ! not complete!\n");
}
static void model_set_mode2(int height,int width){
__PRINT_FUNC();
printf("error ! not complete!\n");
}
static void model_run(void *p){
__PRINT_FUNC();
if ((s_pmodel->ppbuf != 0) && (s_pmodel->ppbuf[0] != 0)){
model_set_done[s_pmodel->mode](s_pmodel->height,s_pmodel->width);
view(_VIEW_REFLASH,s_pmodel->ppbuf);
}else{
printf("s_pmodel->ppbuf error !\n");
}
return;
}
static void model_exit(void *p){
__PRINT_FUNC();
return;
}
void model(int flag,void *pparam){
__PRINT_FUNC();
model_done[_MODEL_INIT](pparam);
model_done[_MODEL_RUN](pparam);
#if 0
status = (status >= MODEL_ENTRY_NUM ) ? MODEL_ENTRY_NUM-1: status;
model_entry[status]();
#endif
}
对应model.h修改如下:
#ifndef _MODEL_H_
#define _MODEL_H_
void model(int,void *);
extern int a;
#endif
对应attr.c的代码修改如下:
int main(int argc ,char *argv[]){
FILE *f = 0;
//int view_mode = 0;
int control_mode = 0;
if (init_all() == 0){
{
printf("init not finish! system return ...\n");
return 1;
}
}else{
atexit(free_all);
}
#if 1
model(0,0);
view(_VIEW_OUTPUT,stdout);
return 0;
#endif
我们编译,链接,运行。。。怎么样?第N-1步又正确了吧。
鬼话:我还是那句,听不听我的随便。如果你在处理control改动时,发现需要对原先model进行再调整。至少要能保证model的调整能正确。而这个正确并不是新增逻辑的正确。而是修改符合模块化实现方式的调整。如果你前面那步把第一个#if 0框定的内容直接删除,我只想说,快跑的兔子会撞树,慢爬的乌龟不迷路。
估计你要说我了,折腾model的改动就折腾了,忽悠大家先折腾一下control,又绕回来一圈。那不妨我给个设计建议。 我们设计一个系统,肯定是从无到有,逐步完成。但是,第一步做什么,第二步做什么,其实只要做了,做对了,就没有关系。但是同样是对model的设计,有两种,一种是自身的逻辑实现,一种是接口的规范,如果自身的逻辑实现已经查不多而接口的规范,继续闷头写,那么你又怎么知道,这个接口现在必须优先写完?
鬼话:新手,总是很积极,什么事情都想做,结果?什么都做不好。老鸟,老油条,总是该做的才做,不到证明非做不可时,不动。不是偷懒,老鸟知道,做的越多,错的越多,必要性的证明,通常是也是设计的约束和规范,兜一圈,看似无意义,但却另众多看似可随意实施的设计,有序有计划有步骤的完成。
你仍然可以发现,我到目前位置
enum {
_MODEL_INIT,
_MODEL_RUN,
_MODEL_EXIT,
_MODEL_MAX_TYPE_SIZE
};
typedef void (*MODEL_FUNC)(void *);
enum {
MODEL_SET_UP_TRIANGLE,//is right angle
MODEL_SET_DOWN_TRIANGLE,//is right angle
MODEL_SET_ISOSCELES_TRIANGE,
MODEL_SET_MAX_NUM
};
typedef void (*MODEL_SET_FUNC)(int ,int);
均没有拿到model.h文件中去。
鬼话:谁说typedef ,enum一定要放头文件呢?除非必要,否则别动,记得这个原则,对于你少出错,是有帮助的。还是那句,做的越多,错的越多。唯一不出错的事情就是,天天做办公室,干瞪眼,等工资。当然通常什么也不做,到月底,经理也只会对你干瞪眼。
现在我们需要将control的参数读取的结果,传递到model中,同时传递到view中,我们先不考虑view。毕竟model里面会对view进行配置。如果能先测试通过control对model的配置,那么我们自然后续可以依葫芦画瓢的处理control对view的配置。
这里有个值得讨论的地方。就是control将参数读取上来,究竟是在control模块里,去配置model的参数还是在model里面,根据control的参数存储结构,去读取参数?
独立的看,两个方式都行。但我提示一下,control可是要为model ,view两个模块处理的。你如果觉得,在control这一个模块中,去实现N多模块的不同参数结构的识别,比每个模块分别识别同一个control参数结构要好,你大可以按照你的想法。但我仍然认为,一个认知内容,在不同模块中重复设计,比多个认知内容在一个模块中散列,要更好,理由是,当你设计一个新的模块时,再里面增加你已经正确验证的control参数的分析代码,此时你可以集中精力解决新模块与control的对接工作。反之,你即要理解新模块的参数结构,还要兼顾control模块的内部逻辑在新增代码时的一致性。
由此,我们需要将control中 P_PARAM_S 的参数表,开放出来。control.h的内容如下:
#ifndef _CONTROL_H_
#define _CONTROL_H_
#define CONTROL_INPUT_SIZE 4096
void control(int);
typedef struct{
double vec;
int height;
int width;
int mode;
float testf;
}PARAM_S, *P_PARAM_S;
extern char filename[];
#endif
记得对应control.c里的PARAM_S的结构体定义去除。我们将printf_param也做修改。model_init的代码清单如下:
static void model_init(void *p){
int t;
P_PARAM_S *pps = (P_PARAM_S *)p;
__PRINT_FUNC();
if ( (t = setjmp(context_buf)) >= 10){
printf("my god ,i escape! %d\n",t);
model_exit(0);
}
s_pmodel->mode = pps->mode;//MODEL_SET_UP_TRIANGLE;
s_pmodel->height = pps->height;
s_pmodel->width = pps->width;
s_pmodel->ppbuf = (char **)&(s_pmodel_buf[0]);
view(_VIEW_SET_OUTPUT_INFO,(void*)(long long int)s_pmodel->height);
return;
}
control.c的print_param函数清单如下:
static int print_param(void){
__PRINT_FUNC();
printf("s_param.height -> %d\n",s_param.height);
printf("s_param.width -> %d\n",s_param.width);
printf("s_param.mode -> %d\n",s_param.mode);
printf("s_param.testf -> %f\n",s_param.testf);
printf("s_param.vec -> %f\n",s_param.vec);
model(_MODEL_INIT,(void *)&s_param);
return 0;
}
attr.c的main函数清单如下:
int main(int argc ,char *argv[]){
FILE *f = 0;
//int view_mode = 0;
int control_mode = 0;
if (init_all() == 0){
{
printf("init not finish! system return ...\n");
return 1;
}
}else{
atexit(free_all);
}
#if 0
model(0,0);
view(_VIEW_OUTPUT,stdout);
return 0;
#endif
if (argc < 2){
printf("please enter the pathname !\n");
return 1;
}
control_mode = ((f = fopen(argv[1],"rt")) != 0);
if (control_mode){
strcpy(filename,argv[1]);
fclose(f);
// view_mode = 0;
}else{
filename[0] = 0;
// view_mode = 1;
// strcpy(v_param,argv[1]);
}
while (1){
if (g_status == 0){
break;
}else{
control(control_mode);
#if 1
model(_MODEL_RUN,0);
view(_VIEW_OUTPUT,stdout);
#endif
g_status = 0;
}
sleep(1);
}
return 0;
}
注意model的调用参数已经开始有了变化。while中,只进行_MODEL_RUN的动作。因此model模块的入口函数进行调整如下:
void model(int flag,void *pparam){
__PRINT_FUNC();
if (flag >= _MODEL_MAX_TYPE_SIZE){
printf("error flag!\n");return;
}
model_done[flag](pparam);
return;
}
其实上面的设计很简单,你将view函数COPY过来,对应的view单词替换成model就行。这是规范的力量。不过记得在control.c和model.c中#include "model.h"。
编译,链接,运行。。。。。 奇怪了,怎么没有*的三角形。。。。
鬼话:正常的C语言代码在正确的硬件设备和稳定良好的操作系统上,如果使用DEBUGG工具例如GDB及其衍生的IDE里的工具,看寄存器,看存储空间,这是最低级的手段,准确说是最低能的手段。可能有些所谓的“高手”会向你展示这是多么好的分析工具。且不谈有效率性,你先想想,你在组织逻辑,而测试方法确实查看存储空间的具体值,你实在怀疑编译器还是在怀疑计算机硬件?特别是后者,不会因为你debug,一步步执行,而会有错误发生。检测错误的最高手段是在设计时入手,规范错误,而不是发生错误,修复。此高级的手段是通过逻辑调用顺序和输出信息来判断错误。
现在不用一头汗,先看下输出。输出中有这么一句: error ! not complete! 再看上一句 model_set_mode1 func! 怎么调用这个函数了,这个函数没实现呢。参数传递有问题? 再向上看。 s_param.mode -> 1 在control里确实是1,参数传递没问题。哦,忘了,config_attr里面的输入有问题。打开config_attr,将mode = 1修改为model = 0在运行试试?
你还可以测试一下model = 2,但是一定要注意model = 2可以不测试,model = 20一定要测试。
鬼话:一定会有事故发生。因为什么?因为我们的小护士不够。我们没有在函数指针调用函数时做检测。
记得立刻增补model的代码。那么在model_run中增补,还是在model_init调用?
鬼话:我给个个人经验,模块化设计,配置参数的判断,一定尽早,尽早的拒绝,提醒,无视,回避。数据验证,尽可能靠后,书到用时方恨少,是非经过不知难。且不谈书和数据有啥联系,至少数据是否有效,正确,不做怎么知道对错呢?提早做,又有什么意义呢?不过这仅是工作经验,我可不是学院派,出个考题,做成公理,你不当教条一样应用在每个场合,就一枪崩了你放解恨。
于是立刻调整model_init如下:
static void model_init(void *p){
int t;
P_PARAM_S pps = (P_PARAM_S )p;
__PRINT_FUNC();
if ( (t = setjmp(context_buf)) >= 10){
printf("my god ,i escape! %d\n",t);
model_exit(0);
}
if (pps->mode >= MODEL_SET_MAX_NUM){
printf("error model func mode set !\n");
return ;
}
s_pmodel->mode = pps->mode;//MODEL_SET_UP_TRIANGLE;
s_pmodel->height = pps->height;
s_pmodel->width = pps->width;
s_pmodel->ppbuf = (char **)&(s_pmodel_buf[0]);
view(_VIEW_SET_OUTPUT_INFO,(void*)(long long int)s_pmodel->height);
return;
}
鬼话:其实我本应该反复强调,碰到通过函数指针数组来调用函数的方式,一定要注意对下标,诸如s_pmodel->mode的判断。但我说千万次,不如你自己摔跟头,脑袋上长个犄角有效。我更希望你在没有调整前,故意将mode设置为20,出此段错误,来学习。如果身边再有个美女对你说句“你以为你是小龙人吗?头上长犄角?“,或许可以令你更容易理解,函数指针数组的边界判断的重要性。
下面我们要调整control内部的调用,毕竟print_param里面折腾不合适。记得我们还有默认规则。此时你需要查一下,目前位置,我们print_param是哪调用的。由此我们需要对control.c的代码做调整。
还是那句话,少做,少出错。最简单的做法如下: 将所有print_param的地方,替换为 set_param。
现在调整view。 如果你有足够的信息不想输出太多内容,其实现在确实验证了正确性。我们可以如下:
static int set_param(void){
__PRINT_FUNC();
#if 0
printf("s_param.height -> %d\n",s_param.height);
printf("s_param.width -> %d\n",s_param.width);
printf("s_param.mode -> %d\n",s_param.mode);
printf("s_param.testf -> %f\n",s_param.testf);
printf("s_param.vec -> %f\n",s_param.vec);
#endif
model(_MODEL_INIT,(void *)&s_param);
view(_VIEW_INIT,(void *)&s_param);
return 0;
}
对于model_init的函数修改如下:
static void model_init(void *p){
int t;
P_PARAM_S pps = (P_PARAM_S )p;
__PRINT_FUNC();
if ( (t = setjmp(context_buf)) >= 10){
printf("my god ,i escape! %d\n",t);
model_exit(0);
}
if (pps->mode >= MODEL_SET_MAX_NUM){
printf("error model func mode set !\n");
return ;
}
s_pmodel->mode = pps->mode;//MODEL_SET_UP_TRIANGLE;
s_pmodel->height = pps->height;
s_pmodel->width = pps->width;
s_pmodel->ppbuf = (char **)&(s_pmodel_buf[0]);
// view(_VIEW_SET_OUTPUT_INFO,(void*)(long long int)s_pmodel->height);
return;
}
对于view_set_output_info的函数修改如下
static int view_set_output_info(void *pdata){
P_PARAM_S pps = (P_PARAM_S )p;
__PRINT_FUNC();
s_poutput->lines =pps->height;
return 0;
}
我们进行编译,根据出错和警告提示,我们#include对应的头文件。
鬼话:头文件的增加,不要想成习惯。更多的根据编译提示来有针对的性的增加,还是那个字,写代码一定要有“懒”的作风,不到必要不折腾。
现在编译,链接,运行。试试。
怎么样,关联起来了。通过这些步骤,你是否发现,一步步的实现,每步都有测试,实际更放心很多?
鬼话:做乌龟有什么不好?我就是个缩头乌龟。看着那兔子跳来跳去,我会缩着头嘿嘿一笑,蹦达吧,蹦达吧,兔子尾巴长不了,回头终点见。
虽然所谓的关联起来了,也仅是初步,这算模块之间的控制流程的关联,由此可以确定数据流向,但实际数据尚为完全关联完毕。model里面的数据存储空间仍然处于测试状态。下面需要将model的空间由value内进行接入。
我们可以回顾一下,在设计view时,我们已经通过value进行了处理,并且归入init_view的函数内 。但考虑到这个显示空间的内容需要由model模块进行数据处理和装载,此时一定会引发争吵。
model说,数据不好,给你也没用,所以空间得是我获取,回头我推送给你 。
view说,我这显示工作,始终需要空间,回头你撤腿了,让我乱跑指针啊。
怎么办?谁也别争,我们还是让value统一管理无非一次申请两个空间,同一个时刻,model,view各有一个。然后我们在value中增加一个切换工作,model要给view推送数据,转由这个工作完成。
因此,value文件清单如下:
#include "value.h"
#include <stdlib.h>
#include "control.h"
#include "view.h"
#include "model.h"
#define dMODEL_VIEW_BUF_NUM 2
#define FREE_S_POINT(s) do {if (s) {free(s); s = 0 ; } }while (0)
#define FREE_S_G_POINT(s,g) do {if (s) {free(s); s = 0; g = 0;} }while (0)
static int init_flag;
static char *s_pmodel = 0;
static char *s_pcontrol_input = 0;
static char **s_pview_output;
static char **s_pMVbuf[dMODEL_VIEW_BUF_NUM] = {0,0};
static int MVbuf_flag = 0;
static int init_control(void){
if (s_pcontrol_input == 0){
s_pcontrol_input = g_pcontrol_input = (char *)malloc(sizeof(char)*CONTROL_INPUT_SIZE);
return 1;
}
return 0;
}
static void free_control(void){
FREE_S_G_POINT(s_pcontrol_input,g_pcontrol_input);
}
static int init_model(void ){
return 1;
}
static int init_view(void){
return 1;
}
static int init_MVbuf(void){
int i;
for (i = 0 ; i < dMODEL_VIEW_BUF_NUM ;i++){
if (s_pMVbuf[i] == 0){
int h;
char *p;
s_pMVbuf[i] = (char **)malloc(sizeof(char)*dVIEW_HEIGHT*dVIEW_WIDTH + sizeof(char *)*dVIEW_HEIGHT);
if (s_pMVbuf[i] == 0) return 0;
p = (char *)&(s_pMVbuf[dVIEW_HEIGHT]);
for (h = 0; h < dVIEW_HEIGHT;h++ ,p+= dVIEW_WIDTH){
s_pMVbuf[i][h] = p;
}
}
}
view(_VIEW_REFLASH,s_pMVbuf[MVbuf_flag]);
model(_MODEL_REFLASH,s_pMVbuf[!MVbuf_flag]);
return 1;
}
static void free_MVbuf(void){
int i;
for (i = 0 ; i < dMODEL_VIEW_BUF_NUM ;i++){
FREE_S_POINT(s_pMVbuf[i]);
}
}
static void free_view(void){
// FREE_S_POINT(s_pview_output);
}
static void free_model(void){
FREE_S_G_POINT(s_pmodel,g_pmodel);
}
static int init_status(void){
g_status = 1;
return 1;
}
static void free_status(void){
return;
}
int init_all(void){
if (init_flag == 1) {return init_flag;}
init_flag = 1;
init_flag = init_flag & init_status();
init_flag = init_flag & init_model();
init_flag = init_flag & init_control();
init_flag = init_flag & init_view();
init_flag = init_flag & init_MVbuf();
return init_flag;
}
int get_init_status(void){
return init_flag;
}
void free_all(void){
if (init_flag == 0) {return;}
init_flag = 0;
free_MVbuf();
free_view();
free_control();
free_model();
free_status();
return;
}
void switch_MVbuf(void){
MVbuf_flag = !MVbuf_flag;
view(_VIEW_REFLASH,s_pMVbuf[MVbuf_flag]);
model(_MODEL_REFLASH,s_pMVbuf[!MVbuf_flag]);
}
int g_status;
char *g_pmodel ;
char *g_pcontrol_input;
对应的我们需要在value.h里增加 void switch_MVbuf(void); 对于model.c如下:
#include <stdio.h>
#include <setjmp.h>
#include "value.h"
#include "define_attr.h"
#include "view.h"
#include "control.h"
#include "model.h"
jmp_buf context_buf;
typedef void (*MODEL_FUNC)(void *);
enum {
MODEL_SET_UP_TRIANGLE,//is right angle
MODEL_SET_DOWN_TRIANGLE,//is right angle
MODEL_SET_ISOSCELES_TRIANGE,
MODEL_SET_MAX_NUM
};
typedef void (*MODEL_SET_FUNC)(int ,int);
static void model_set_mode0(int height ,int width);
static void model_set_mode1(int height ,int width);
static void model_set_mode2(int height ,int width);
MODEL_SET_FUNC model_set_done[MODEL_SET_MAX_NUM] = {model_set_mode0,model_set_mode1,model_set_mode2};
typedef struct{
int mode;
int height;
int width;
char **ppbuf;
}MODEL_S;
static MODEL_S s_pmodel[1] = 0;
static void model_init(void *p);
static void model_run(void *p);
static void model_exit(void *p);
static void model_reflash(void*p);
static MODEL_FUNC model_done[_MODEL_MAX_TYPE_SIZE] = {model_init,model_run,model_exit,model_reflash};
#if 0
static char s_model_buf[5][200];
static char *s_pmodel_buf[5] = {(char *)&(s_model_buf[0]),
(char *)&(s_model_buf[1]),
(char *)&(s_model_buf[2]),
(char *)&(s_model_buf[3]),
(char *)&(s_model_buf[4])};
#endif
static void model_reflash(void *p){
s_pmodel->ppbuf = (char **)p;
}
static void model_init(void *p){
int t;
P_PARAM_S pps = (P_PARAM_S )p;
__PRINT_FUNC();
if ( (t = setjmp(context_buf)) >= 10){
printf("my god ,i escape! %d\n",t);
model_exit(0);
}
if (pps->mode >= MODEL_SET_MAX_NUM){
printf("error model func mode set !\n");
return ;
}
s_pmodel->mode = pps->mode;//MODEL_SET_UP_TRIANGLE;
s_pmodel->height = pps->height;
s_pmodel->width = pps->width;
// view(_VIEW_SET_OUTPUT_INFO,(void*)(long long int)s_pmodel->height);
return;
}
static void model_set_mode0(int height ,int width){
int i,j;
char **pbuf = s_pmodel->ppbuf;
// int k;
__PRINT_FUNC();
for (i = 0; i < height ; i++){
for (j = 0 ; j < width*(i+1) ; j++){//
pbuf[i][j] = '*';
}
pbuf[i][j] = 0;
}
}
static void model_set_mode1(int height,int width){
__PRINT_FUNC();
printf("error ! not complete!\n");
}
static void model_set_mode2(int height,int width){
__PRINT_FUNC();
printf("error ! not complete!\n");
}
static void model_run(void *p){
__PRINT_FUNC();
if ((s_pmodel->ppbuf != 0) && (s_pmodel->ppbuf[0] != 0)){
model_set_done[s_pmodel->mode](s_pmodel->height,s_pmodel->width);
switch_MVbuf();//view(_VIEW_REFLASH,s_pmodel->ppbuf);
}else{
printf("s_pmodel->ppbuf error !\n");
}
return;
}
static void model_exit(void *p){
__PRINT_FUNC();
return;
}
void model(int flag,void *pparam){
__PRINT_FUNC();
if (flag >= _MODEL_MAX_TYPE_SIZE){
printf("error flag!\n");return;
}
model_done[flag](pparam);
return;
}
对于model.h有如下修改:
enum {
_MODEL_INIT,
_MODEL_RUN,
_MODEL_EXIT,
_MODEL_REFLASH,
_MODEL_MAX_TYPE_SIZE
};
编译,链接,运行。
这里展开几个讨论。
你可以对照上次版本的代码发现,特别是model.c文件,我们的改动非常小。要么是新增一个函数,要么是替换一个推送操作,余下都是屏蔽代码。这是模块化编程的优势。特别是当你把一个系统切割清楚后,对于一个工作,可能关联不同模块,你所要考虑的是数据对接,和对接方式。特别是当数据对接已经完毕后,你的对接方式的修改动作会变的很小,而且代码设计量很低。
当架构稳定时,你的代码设计量较大的,更多是面向业务或者模块自身实现目标的局部设计。架构的一个非常有价值的作用就是,能将你一个设计任务,区分成架构本身或各个局部模块,并且可以分步骤,分阶段的实施。这是一个保障系统设计质量的好方法。
鬼话:很多高级语言,JAVA是个典型,恨不得保姆式的对你全方位照顾,这样人性化的考虑有一个很好的作用,让你集中面对业务,快速实现系统。但这个往往被误解为可以提升代码质量,或者降低代码BUG数,或者提升系统设计效率。我个人认为,除了让你不要重复做很多底层事务外,对代码设计工作本身没有任何提升。该出错的主,一样出错,无非更牛的错。另一个负面影响是,淡化了程序设计方法的重要性。
现在。MVC基本处理完毕。在第二部分,我们会依托这个架构,设计一个网络小游戏,对linux下的应用编程做学习,同时,让你感受到模块化编程的优势,姑且不谈精髓,这不是我能表述的,而是需要你自己领悟的。
回到make的相关讨论上来。恩。又有什么问题?你应该理解我们的代码使用了很多标准库。通过<>头文件的方式,将对应代码链接到程序里,形成实际有效的可执行文件。
库是个好东西。那么库又是什么?我的理解,就是大大的大包裹。什么玩意都望里塞一塞,算个整体,以后但凡需要取的时候,直接指向这个大包裹,链接器会自动在这个包裹以及一些默认的包裹里进行查找。省去了你罗列一堆函数所在对象文件的工作。
假设,你有1000个.o文件,其中有20个.o文件里的函数有一定类型的特性,你的工程里,经常会用到他们其中的一个或部分,甚至整体的函数,一个简单的方法是,你不需要每次都关注,当前这个函数究竟在哪个.o文件里,你也不许要将所有.o文件名都罗列在gcc的命令参数里。
鬼话:如果你不觉得有啥好处,那么我只好说,知道什么叫分类吗?分类是用于方便区分差异性,并对相似对象进行一致性认知,降低你识别判断对象的成本。你去喝水,你会寻找杯子,先不谈你有什么指定偏好,至少是杯子,你就会用,但你通常不会直接埋头在马桶里喝,虽然它也盛着水。库的作用即方便你寻找对应函数,也方便里归类区分。毕竟马桶是用来方便的。
当然,库有两种,静态库和动态库,注意这可不是C标准明确约束的内容。不同的编译器系统有不同的对应方式。我们这里权且仅考虑linux下gcc的情况。
首先,谈下静态和动态之分。很多教科书都有非常标注的答案。我给个简单的例子。液晶电视和笔记本。液晶电视可以接机顶盒,录像播放机(这玩意还有吗?),DVD机。组合其一,如果有信号,总能看到内容。不过液晶电视这个大屏幕可不是出厂的时候,就拖了一堆设备。你想用它做什么,自己插插线,调调台就可以搞定。而笔记本电脑,那液晶屏幕可是一致连着主机的,这卖给你的时候就有。我们可以说,前者是动态链接后使用,后者是静态链接后使用。
静态链接,出厂前就给你搞定,也即,你在连接成可执行文件时,对应静态库中所需要的函数已经装在了执行文件里。这可有好处,走哪都能看。动态链接,出厂后自己组装,相对程序而言,由系统在程序执行时,进行组装。这可就有个麻烦事了。说个故事。
读研究生时,同学需要一个个上去演示自己的程序。可惜教室的电脑并没有装VC的环境,缺乏对应动态库。而很多同学使用debug模式,此时使用动态库链接,在自己机器上,当然没问题,可到了教室演示,10个有6个,发现程序无法运行,缺少库。这就是动态库的麻烦。你无法如静态链接那样,把所有调用的函数都设置在执行文件中,因此你无法简单COPY这个执行文件,到处运行。
小鬼话:啊我知道,JAVA一次编译,到处运行,是因为静态链接方式。
鬼话:走开,谁把这动物放出来了?哦,不好意思,你认错了,谁让你长的那么像猴子。这里是编程语言低级班,说的是C,不是高级班说的是JAVA。你可别把这里的到处运行和JAVA的到处运行联系起来。实际JAVA的到处运行,依赖虚拟机的环境,一次编译,并不彻底,和此处说的彻底编译后的静态、动态链接没有关系。
那么为什么要动态链接库?当然也有好处。如果有很多程序,都使用同一个库里的函数。每个函数在每个程序里都有一段执行代码的COPY版本,这样,系统负载太大了。动态库多好啊,什么时候用到对应函数的执行代码了,再组装,再使用。一个动态库可以为多个程序服务。不过这不是最大的好处。C语言讲究模块化编程。如果你的一个大系统,有多个独立的子系统(执行程序),都使用相同的模块,如果这个模块有个BUG。静态库就悲摧了,对应所有使用到这个模块的执行程序都要重新来过。动态库就不存在问题。通常你换同尺寸的笔记本不会是因为屏幕小了,而是因为硬盘小了,CPU速度下降了。而你想把VCD升级到DVD,并不需要你重新购买液晶电视。
上面说了,静态、动态库的运行机制(一个在链接时,把执行代码复制出一个,并处理后放到执行文件中,而另一个是执行程序执行时,发现需要调用,再进行装载配置使用)的区别,和优劣好坏。
下面说下方法。先说个gcc的命名的潜规则。
无论什么库,记得lib打头就是了。后续在紧跟这个库名。 而静态库,是.a后缀,动态库是so。 为什么要使用lib打头 后续介绍ar时讨论。
这里需要罗列出gnu的很多工具命令。你可以在参考文献2中找到。
下一篇:暂无