C 构造一个 简单配置文件读取库

前言

  最近看到这篇文章,

      json引擎性能对比报告 http://www.oschina.net/news/61942/cpp-json-compare?utm_source=tuicool

感觉技术真是坑好多, 显露的高山也很多. 自己原先也 对着

     json 标准定义 http://www.json.org/json-zh.html

写过一般json解析器, 1000行后面跟上面一对比, 真是弱鸡. 后面就看了其中吹得特别掉几个源码,确实有过人之处,深感

自己不足. 下载一些也在研究,发现看懂会用和会设计开发是两码事.

    对于json设计主要基础点是在 结构设计和语法解析 . 继续扯一点对于一个框架的封装在于套路,套路明确,设计就能糅合

在一起. 不管怎样,只要学了,总会优化的. 下面 我们分享的比较简单, 但也是围绕结构设计 和 语法解析方面, 给C框架来个 配置

读取的能力.

 

正文

1.解析文件说明

  这里先展示配置文件的直观展示如下 test.php

<?php

// 这里是简单测试 php 变量解析

$abc = "123456";

$str = "1231212121212121212
21222222222
2121212\"
";

我们只需要解析上面数据, 保存在全局区下次直接调用就可以了. 例如

运行的结果如上.  对于上面配置 有下面几个规则

a. $后面跟变量名

b.中间用 = 分割

c.变量内容用 ""包裹, 需要用" 使用\"

上面就是配置的语法规则.下面我们逐渐讲解 语法解析内容 

 

2.简单的核心代码,语法解析测试

   具体的扫描算法如下

a.跳过开头空白字符 找到$字符

b.如果$后面是空白字符,那么直接 读取完毕这行

c.扫描到 = 字符,否则读取完毕这行

d扫描到 " 字符

e扫描到最后"字符,必须 前一个字符不是 \ , 否则读取这行完毕.

上面就是 处理的算法思路,比较容易理解, 具体测试代码如下

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>


#define _STR_PATH "test.php"

/*
 * 这里处理文件内容,并输出解析的文件内容
 */
int main(int argc, char* argv[])
{
    int c, n;
    char str[1024];
    int idx;
    FILE* txt = fopen(_STR_PATH, "rb");
    if (NULL == txt) {
        puts("fopen is error!");
        exit(EXIT_FAILURE);
    }

    //这里处理读取问题
    while ((c = fgetc(txt))!=EOF){
        //1.0 先跳过空白字符
        while (c != EOF && isspace(c))
            c = fgetc(txt);
        //2.0 如果遇到第一个字符不是 '$'
        if (c != '$') { //将这一行读取完毕
            while (c != EOF && c != '\n')
                c = fgetc(txt);
            continue;
        }
        //2.1 第一个字符是 $ 合法字符, 开头不能是空格,否则也读取完毕
        if ((c=fgetc(txt))!=EOF && isspace(c)) {
            while (c != EOF && c != '\n') 
                c = fgetc(txt);
            continue;
        }
        //开始记录了
        idx = 0;

        //3.0 找到第一个等号 
        while (c!=EOF && c != '=')
            str[idx++]=c, c = fgetc(txt);
        if (c != '=') //无效的解析直接结束
            break;


        //4.0 找到 第一个 "
        while (c != EOF && c !='\"')
            str[idx++] = c, c = fgetc(txt);
        if (c != '\"') //无效的解析直接结束
            break;

        //4.1 寻找第二个等号
        do {
            n = str[idx++] = c;
            c = fgetc(txt);
        } while (c != EOF && c != '\"' && n != '\\');
        if (c != '\"') //无效的解析直接结束
            break;

        str[idx] = '\0';
        puts(str);

        //最后读取到行末尾
        while (c != EOF && c != '\n')
            c = fgetc(txt);
        if (c != '\n')
            break;
    }

    fclose(txt);

    system("pause");
    return 0;
}

上面 代码的输出结果是 就是上面截图. 算法比较直白很容易理解. 到这里 写了一个小功能还是很有成就感的. 特别是看那些著名的开源代码库,特别爽.

上面代码完全可以自己练习一下,很有意思,到这里完全没有难度. 后面将从以前的框架角度优化这个代码.

 

3.最后定稿的接口说明

到这里我们将上面的思路用到实战上, 首先看 scconf.h 接口内容如下

#ifndef _H_SCCONF
#define _H_SCCONF

/**
 *  这里是配置文件读取接口,
 * 写配置,读取配置,需要配置开始的指向路径 _STR_SCPATH
 */
#define _STR_SCCONF_PATH "module/schead/config/config.ini"

/*
 * 启动这个配置读取功能,或者重启也行
 */
extern void sc_start(void);

/*
 * 获取配置相应键的值,通过key
 * key        : 配置中名字
 *            : 返回对应的键值,如果没有返回NULL,并打印日志
 */
extern const char* sc_get(const char* key);

#endif // !_H_SCCONF

接口只有两个,启用这个配置读取库,获取指定key的内容, 现在文件目录结构如下

上面接口使用方式也很简单例如

sc_start();
puts(sc_get("heoo"));

其中 config.ini 配置内容如下

/*
 *  这里等同于php 定义变量那样形式,定义 
 *后面可以通过,配置文件读取出来. sc_get("heoo") => "你好!" 
 */

$heoo = "Hello World\n";

$yexu = "\"你好吗\",
我很好.谢谢!";

$end = "coding future 123 runing, ";

后面就直接介绍具体实现内容了.

 

4.融于当前开发库中具体实现

 首先看scconf.c 实现的代码

#include <scconf.h>
#include <scatom.h>
#include <tree.h>
#include <tstring.h>
#include <sclog.h>

//简单二叉树结构
struct dict {
    _TREE_HEAD;
    char* key;
    char* value;
};

// 函数创建函数, kv 是 [ abc\012345 ]这样的结构
static void* __dict_new(tstring tstr)
{
    char* ptr; //临时用的变量
    struct dict* nd = malloc(sizeof(struct dict) + sizeof(char)*(tstr->len+1));
    if (NULL == nd) {
        SL_NOTICE("malloc struct dict is error!");
        exit(EXIT_FAILURE);
    }

    nd->__tn.lc = NULL;
    nd->__tn.rc = NULL;
    // 多读书, 剩下的就是伤感, 1% ,不是我,
    nd->key = ptr = (char*)nd + sizeof(struct dict);
    memcpy(ptr, tstr->str, tstr->len + 1);
    while (*ptr++)
        ;
    nd->value = ptr;

    return nd;
}

// 开始添加
static inline int __dict_acmp(tstring tstr, struct dict* rnode)
{
    return strcmp(tstr->str, rnode->key);
}
//查找和删除
static inline int __dict_gdcmp(const char* lstr, struct dict* rnode)
{
    return strcmp(lstr, rnode->key);
}

//删除方法
static inline void __dict_del(void* arg)
{
    free(arg);
}

//前戏太长,还没有结束, 人生前戏太长了,最后 ...
static tree_t __tds; //保存字典 默认值为NULL
//默认的 __tds 销毁函数
static inline void __tds_destroy(void)
{
    tree_destroy(&__tds);
}

static int __lock; //加锁用的,默认值为 0 

static void __analysis_start(FILE* txt, tree_t* proot)
{
    char c, n;
    TSTRING_CREATE(tstr);

    //这里处理读取问题
    while ((c = fgetc(txt)) != EOF) {
        //1.0 先跳过空白字符
        while (c != EOF && isspace(c))
            c = fgetc(txt);
        //2.0 如果遇到第一个字符不是 '$'
        if (c != '$') { //将这一行读取完毕
            while (c != EOF && c != '\n')
                c = fgetc(txt);
            continue;
        }
        //2.1 第一个字符是 $ 合法字符, 开头不能是空格,否则也读取完毕
        if ((c = fgetc(txt)) != EOF && isspace(c)) {
            while (c != EOF && c != '\n')
                c = fgetc(txt);
            continue;
        }
        //开始记录了
        tstr.len = 0;

        //3.0 找到第一个等号 
        while (c != EOF && c != '=') {
            if(!isspace(c))
                tstring_append(&tstr, c);
            c = fgetc(txt);
        }
        if (c != '=') //无效的解析直接结束
            break;

        c = '\0';
        //4.0 找到 第一个 "
        while (c != EOF && c != '\"') {
            if (!isspace(c))
                tstring_append(&tstr, c);
            c = fgetc(txt);
        }
        if (c != '\"') //无效的解析直接结束
            break;

        //4.1 寻找第二个等号
        for (n = c; (c = fgetc(txt)) != EOF; n = c) {
            if (c == '\"' && n != '\\')
                break;
            tstring_append(&tstr, c);
        }
        if (c != '\"') //无效的解析直接结束
            break;

        //这里就是合法字符了,开始检测 了, 
        tree_add(proot, &tstr);

        //最后读取到行末尾
        while (c != EOF && c != '\n')
            c = fgetc(txt);
        if (c != '\n')
            break;
    }

    TSTRING_DESTROY(tstr);
}

/*
* 启动这个配置读取功能,或者重启也行
*/
void 
sc_start(void)
{
    FILE* txt = fopen(_STR_SCCONF_PATH, "r");
    if (NULL == txt) {
        SL_NOTICE("fopen " _STR_SCCONF_PATH " r is error!");
        return;
    }

    ATOM_LOCK(__lock);
    //先释放 这个 __tds, 这个__tds内存同程序周期
    __tds_destroy();
    //这个底层库,内存不足是直接退出的
    __tds = tree_create(__dict_new, __dict_acmp, __dict_gdcmp, __dict_del);

    //下面是解析读取内容了
    __analysis_start(txt, &__tds);

    ATOM_UNLOCK(__lock);

    fclose(txt);
}

/*
* 获取配置相应键的值,通过key
* key        : 配置中名字
*            : 返回对应的键值,如果没有返回NULL,并打印日志
*/
inline const char* 
sc_get(const char* key)
{
    struct dict* kv;
    if ((!key) || (!*key) || !(kv = tree_get(__tds, key, NULL)))
        return NULL;

    return kv->value;
}

数据结构采用的通用的 tree.h 二叉查找树结构. 锁采用的 scatom.h 原子锁, 保存内容采用的 tstring.h 字符串内存管理

语法解析 是 按照上面的读取思路 解析字符

static void __analysis_start(FILE* txt, tree_t* proot);

数据结构是

//简单二叉树结构
struct dict {
    _TREE_HEAD;
    char* key;
    char* value;
};

简单带key的二叉树结构.

 

5.使用展示 

  我们来测试一下上面库, 首先测试代码如下 test_scconf.c

#include <schead.h>
#include <scconf.h>

// 写完了,又能怎样,一个人
int main(int argc, char* argv[])
{
    const char* value;
    sc_start();

    //简单测试 配置读取内容
    value = sc_get("heoo");
    puts(value);

    value = sc_get("heoo2");
    if (value)
        puts(value);
    else
        puts("heoo2不存在");

    value = sc_get("yexu");
    puts(value);

    value = sc_get("end");
    puts(value);

    system("pause");
    return 0;
}

运行结果如下

其中配置 看 上面 config.ini . 到这里关于简单的配置文件功能我们就完成了. 我想扯一点, 用过较多的语言或库, 还是觉得 高级VS + .Net 库 开发最爽,

拼积木最愉快,什么功能都用,代码设计也很漂亮, 唯一可惜的就是速度有点慢,有点臃肿.个人感觉 开发 C#库都是window 届 C++的顶级大牛, C#没有推广

出去却把window上C++程序员坑的要死,都转行到 Linux C/C++开发行列中. 更加有意思的是 C#没有因为 微软老爸红了,却因Unity 3D的 干爹火了.

C#+VS写起来确实很优美,  但是 如果你不用VS那就 另说了, 考验一个window程序员功底就是 , 让他离开了vs是否开始那么 销魂.

推荐做为一个程序员还是多学点, 因为都是语言, 学多了以后交流方便.大家觉得呢.

 

后语

       错误是难免的, 欢迎指正, 随着年纪增长愈发觉得自己还很水. 需要学习很多东西,也需要舍弃很多东西. 对于未知的前方,

全当乱走的记录.看开源的项目源码还是很爽的,下次有机会分享手把手写个高效cjson引擎.

posted on 2016-02-15 21:39  喜欢兰花山丘  阅读(1492)  评论(0编辑  收藏  举报