第6篇 指针数组字符串(下)

中山野鬼 发布于 2012/11/08 04:08
阅读 2K+
收藏 48

    有了指针,数组,字符串的概念,总算可以继续说说 MVC了。前面说过。模块化的设计,接口参数针对模式,而数据是通过缓冲来传递的。那么对于MVC 的 C ,control我们也存在缓冲。这里先说一下,control,控制,侠义的控制,如同你的游戏机手柄,或者你的鼠标。但其实还有另外一种控制。例如警察针对嫌疑犯,前者对后者的控制,其实这个控制包含了另一种解释,即,约束和掌握。
    因此说,广义的控制,应该是存在主从关系的由主对从的影响力的表现。这话说的有点大和杂了,无非希望你别认为,只有触发和通过你的手才叫控制。当A,使用某种信息,可以影响到时,这其实就是控制。
    我们先不说键盘怎么操作,这个很无聊,有两个无聊点:
    1、各种键盘甚至游戏柄都是外设,而不同操作系统下,对这个外设的接口通常有各自的方式。介绍这类问题会把讨论,谈歪了。
    2、很多教科书,如同推销自己的传家宝一样,推销scanf这个函数,那么试想,如果你尝试测试一个软件,需要很多方式的组合,是不是你也手工一个个键盘按一遍?何必呢?把对应的情况,依次传入文件,再有程序读取这些文件,一样。
鬼话:很多设备我们都可以看作流文件,键盘如此,你依次读取的文件内容也可以 。
    因此,先说个广义上的控制。这还不是我们傻乎乎的把键盘的按键写到文件里。而是我们读取程序运行的参数。现在重新调整第3篇 MVC模块化编程中,设计到的入口参数(注意,这不是接口参数,而是针对程序的给入参数)。假设我们把
    height = 3
    width = 5
    mode = 1
    如上文本保存到名为config_attr这个文件名。我们的control模块的目标就是能正确识别到有3个参数。为了能完成这个目标,我们分析一下需要的基本功能,如下散列(散列的意思是内容没有前后因果逻辑,这比较适合开发小组一开始进行头脑风暴,确认大体的设计任务内容):
    1、我们需要对文本文件进行读取操作。
    2、我们能区分行。
    3、我们能从行中区分单词。
    4、我们能比较单词。
    5、我们能将单词转换为数字。
    6、检测多个单词是否合法
    6、我们能将对应的数字和对应的正确的单词所指向的参数进行存储。
    当然我们还能测试。
    下面一个工作任务就是对上述工作进行函数名的设计。不过先得听我两句,
鬼话1:有些新手此时会脑袋有点蒙,如果换个任务,我该怎么细分动作?这事,还真没法教你。因为任何一个任务,都可以有不同的细分方式,而不同的任务,也很难有一个套路来细分。这是经验,或者说是阅历,不是什么军规,规则就可以解决的。
鬼话2:书上有的,记得不用背,这是和教学,考试非常相反的思想。有就查,不然你花钱买书做什么。而书上没有的,恰恰是需要你通过反复的实践和练习靠自己沉淀才能获得的。即便是你获得了葵花宝典,认为看了就神功了,我仍然认为,疼痛忍耐力这种神功,还是需要你漫长的忍耐才能练成。
    所以,记得多练。我们继续。
    read_param_from_file 这个已经存在。目标是把一个文件的内容读取出来。
    split_line 目标区分行
    split_token 目标区分单词
    //比较不用了吧,咱们用strcmp得了。有标准函数不用,是傻瓜。
    //单词转数字?其实你可以参考文献1,查查atol之类的函数,对应还有strtol等,你可以对比一下他们的区别,为了统一期间,我们使用atol等函数。
    check_grammar 检测每行的数据是否合规
    set_param 设置参数
    好的,我们开始写代码,先把control.c的代码如下设计
#include <stdio.h>
char filename[1024];
static int split_line(void){
    return 1;
}
static int split_token(void){
    return 1;
}
static int check_grammar(void){
    return 1;
}
static int set_param(void){
    return1;
}
static void read_param_default(void){
    printf("read_param_default func !\n");
    return;
}

static int read_param_from_file(char * filename){
    printf("read_param_from_file func !\n");
    return 0;
}

void control(int flag){
    if (flag){
        read_param_from_file(filename);
    }else{
        read_param_default();
    }
    return;
} 

    以上是我们设计代码的第一版。这里有几个鬼话要说。
鬼话1:设计、设计,那么就应该对整体任务有个规划,有个逻辑流程,有个切割细分的工作,可能切割成各个小模块,小函数的内部实现你还没有理清楚,但至少,通常你会一次性的设计出几个函数。
鬼话2:对于这些函数,需要一批次写入,先别在意里面怎么写。先把接口框定好。这如同你请美女去吃饭,美女想吃什么,得看心情,所以你通常是先电话预定个位置,而不是把菜都点了。
鬼话3:如果你不清楚这个函数的细节,那么预定位置,我的习惯是返回int ,而入口参数是void,记得默认返回一个错误值。我的习惯是内部函数,错误按照 1来处理,正确按照0来处理。这样可以通过很多非0的返回,来表示不同的错误类型,正确就一种可以了,你的代码能返回更清晰的错误类型,对你的整体模块的稳定性有帮助。
鬼话4:原则上,一个C文件,先写入口函数,如同void control(int flag) 一样,这个入口函数,我的习惯,并不会返回,而其他函数,刚预定位置时,先static。谁知道你吃饭时是否想私密一下,包厢少,先预定咯。而模块(C文件是编译的单位嘛,所以算一个编译器可以看到的最小模块)的返回和入口信息越少,越容易降低模块与模块的耦合性。降低模块的耦合性的意义是,提高模块的适用性,也就是复用。
    上面写完,我们发现几个问题。
    1、函数没有加测试点。
    2、函数之间没有流程调用关系。
    3、函数接口不明。
    先解决1,2。测试点,我们在model.c中有个define。恩。为什么不考虑让这个define服务大家呢。因此我们现在可以考虑把它放到一个头文件里。这样,不同的C文件都#include这个头文件,岂不方便?放model.h里?难道model.h需要所有C文件使用?或许control.h要争了。凭什么不放它那?谁被引用的多 ,好像谁值钱一样,就是一破头文件,又不是论文。
    所以我们干脆,使用一个新的文件如下
    #ifndef _DEFINE_ATTR_H_
    #define _DEFINE_ATTR_H_
    #define __PRINT_FUNC() do {printf("%s func!\n",__func__);}while(0)
    #endif
    保存为define_attr.h
    这个头文件的意思是,只要是我这个attr的模块的C文件,都有可能需要的定义,那么就都放这里。于是乎,你现在需要对,所有包含函数测试打印点的C文件,都#include “define_attr.h"。并对应的将诸如 printf("read_param_from_file func !\n"); 的代码,替换为 _PRINT_FUNC();
    为什么我们不使用define.h 。算了,这么好的名字,很容易被别人抢注,何必争呢,多写个_attr也不费事,而且方便以后对不同的C语言对应文件名理解(不是C文件名)。这里只给出 control.c和model.c的代码如下
   
#include <stdio.h>
#include "define_attr.h"
char filename[1024];
static int split_line(void){
    __PRINT_FUNC();
    return 0;
}
static int split_token(void){
    __PRINT_FUNC();
    return 0;
}
static int check_grammar(void){
    __PRINT_FUNC();
    return 0;
}
static int set_param(void){
    __PRINT_FUNC();
    return 0;
}
static void read_param_default(void){
    __PRINT_FUNC();
    return;
}

static int read_param_from_file(char * filename){
    __PRINT_FUNC();
    return 0;
}

void control(int flag){
    if (flag){
        read_param_from_file(filename);
    }else{
        read_param_default();
    }
    return;
} 

model.c的代码如下,此处__PRINT_FUNC()的定义已经被拿掉了。换成了#include "define_attr.h"。
#include <stdio.h>
#include <setjmp.h>
#include "value.h"
#include "define_attr.h"


jmp_buf context_buf;
typedef void F_V_V(void);
static int test = 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 void model_init(void){
    int t;
    __PRINT_FUNC();
    if ( (t = setjmp(context_buf)) >= 10){
        printf("my god ,i escape! %d\n",t);
        model_exit();
    }
    return;
}
static void model_done(void){
    int i;
    __PRINT_FUNC();
    for (i = 0 ; i < 100; i++){
        printf("touch the dog! \n");
        if (test >= 10){
            printf("the officers come ! run away!\n");
            longjmp(context_buf,test);
        }
        test += 1;
    }
    return;
}

static void model_exit(void){

    
    __PRINT_FUNC();

    return;
}

void model(int status){
    
    status = (status >= MODEL_ENTRY_NUM ) ? MODEL_ENTRY_NUM-1: status;
    model_entry[status]();
 } 


编译、连接、运行,看
model_init func!
read_param_from_file func!
model_done func!
这三行是否存在?不错吧,现在或许你知道.h文件的价值了。一次书写,到处复制。记住,一个头文件被一个C文件#include,则表示头文件的内容被插入到对应的C文件里。那么对应的被多个C文件#include,你可以理解为,复制了N份,每个#include这个头文件的C文件,都被插入了一份。当然你完全可以头文件里#include头文件,含义一样,后者完全的被COPY了一份在前者对应位置。
注意,你在编译时会有4个warning被提示,说虽然写了但没有被使用。那么你尝试,去掉上述4个函数中的某个函数的static,例如int split_token(void),再编译。
啥情况?少了一个警告。而是连接,也试试?还是没有警告说有4个函数。因为外部函数,现在不被使用,不代表不能被其他模块所使用,或许编译好了后,对应的obj文件,被另一个模块调用,连接器又怎么能回绝这种要求呢?既然连接器可能需要,编译器自然不能说,一个没有被调用的外部函数,有问题了。
但内部(局部)函数,static强制后,不好意思,所有可以调用该函数的,必须在同名C文件里,编译器当然可以直接站出来说,啊!!写了不用,太浪费了,你就是做外包,也不能这么骗钱!
鬼话:这是一个方法。什么方法?利用C语言的规则,为你提示你的工作情况。通过编译器的warning,可以提醒你,还有些函数没有被调用过,你小子,是不是忘记这部分工作了。还是那句,你可以不听我野鬼的,因为这些都不是标准,都不是考试题目,都不是教学内容。但,站在我这一边的,就是一句话,解决BUG的最好手段是习惯,良好的设计习惯,思维习惯。
    我们先把上面4个warning放一边。没错,野鬼第一次开始容忍warning了?非也,不是容忍,而是时刻提醒我们,还有事情没做完。
鬼话:通常,一个团队,分工设计,最好是当前的新增函数,分配到不同的人中,当天解决他们的逻辑调用。而不是隔夜。你有意见?那你今天的晚饭是不是可以隔夜再吃?当然这不属于本部分的内容。在第四部分,C语言的项目开发管理中,会展开讨论。
    现在解决第2个问题。就是上述没有被调用的函数直接调用的流程问题。
    我们先看下 split_line,既然是split_line,则我们需要将一个param的文件读取上来,然后再操作。先看个弱弱的做法。至于学院派的那种介绍,我们如何测定一个文件的长度,并根据文件长度,malloc空间,我还是那句
鬼话:malloc,free集中防止。该申请的一次申请够。
    因此我们一次性申请4K的BUF,那么如果大于4K的文件长度呢?现在先弱弱的告诉用户,我不伺候。由此我们多出个全局变量。让init_value中,malloc到,在free_value中释放掉。因此value.c的文件修改如下:
 
 #include "value.h"
#include <stdlib.h>
#include "control.h"
#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 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){
    if (s_pmodel == 0){

        s_pmodel = g_pmodel = (char *)malloc(sizeof(char)*100);
        
        return 1;
    }
    return 0;
}
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();
  return init_flag;
}
int get_init_status(void){
    return init_flag;
}
void free_all(void){
    if (init_flag == 0) {return;}
    init_flag = 0;
    free_control();
 free_model();
    free_status();
    return;
}
int g_status;
char *g_pmodel ;
char *g_pcontrol_input; 

对应的value.h 为
#ifndef _VALUE_H_
#define _VALUE_H_
extern int g_status;
// used for model module
extern char *g_pmodel;
//used for control module
extern char *g_pcontrol_input;
int init_all(void);
void free_all(void);
#endif 

对应的control.h为
#ifndef _CONTROL_H_
#define _CONTROL_H_
#define CONTROL_INPUT_SIZE 4096
void control(int);
extern char filename[];
#endif 

    这里有几个要注意的。
    1、value模块既然为本模块中所有C文件(子模块)服务,那么#include 各个模块的头文件很正常。同时,你将CONTROL_INPUT_SIZE
    放到value.c或value.h里定义,显然逻辑上错误。
    2、注意我多了一个#define FREE_S_G_POINT(s,g),这样做是因为不同的函数中间,有相同的代码片,而且逻辑一致,无非是操作的存储空间有差异。虽然你可以写成同一个函数,但你仍然会有 free_model free_control这样的逻辑和必须存在的函数(与init_model init_control函数对应嘛),因此这种代码片,我们可以使用宏。
    3、但对于申请部分我并没有这么做。虽然他们的代码片是相同的。
鬼话:你完全可以不要听我的,但是我仍然拒绝在#define的代码片中,拥有return,或者goto的内容。问我为什么?代码描述逻辑不清楚,只会导致2个结果,设计出错,抓BUG痛苦。反正我不干这事,累狠了。

未完,待续。。。。

加载中
0
景德真人
景德真人
传说中的第一排沙发。
ABCA的A
ABCA的A
蹭沙发。。。看鬼篇。。。
0
泡不烂的凉粉
泡不烂的凉粉
好吧,板凳也不错.    
0
jeffsui
jeffsui
我只能天花板了
0
业余编程人士
业余编程人士
我来挤一个空位儿
0
taaaa
taaaa
额的神,现在还论指针呢,都射门年代了
0
魔君
魔君
我发现我快成野鬼的粉丝了
0
叶知秋
叶知秋
先占个地,在看看文章。嘿嘿
0
Ryan-瑞恩
Ryan-瑞恩
占地,看文章!
0
中山野鬼
中山野鬼

引用来自“景德真人”的答案

传说中的第一排沙发。
哇塞你比我睡的还晚。。。。。周末会多补点内容上来。正好出差,蛋疼。
景德真人
景德真人
@中山野鬼 个人习惯。哈哈。
中山野鬼
中山野鬼
@景德真人 ....无语了。。。。
景德真人
景德真人
小倩,我早睡早起。这个点我起床了。
返回顶部
顶部