• 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 != '
    ')
                    c = fgetc(txt);
                continue;
            }
            //2.1 第一个字符是 $ 合法字符, 开头不能是空格,否则也读取完毕
            if ((c=fgetc(txt))!=EOF && isspace(c)) {
                while (c != EOF && c != '
    ') 
                    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] = '';
            puts(str);
    
            //最后读取到行末尾
            while (c != EOF && c != '
    ')
                c = fgetc(txt);
            if (c != '
    ')
                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
    ";
    
    $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 是 [ abc12345 ]这样的结构
    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 != '
    ')
                    c = fgetc(txt);
                continue;
            }
            //2.1 第一个字符是 $ 合法字符, 开头不能是空格,否则也读取完毕
            if ((c = fgetc(txt)) != EOF && isspace(c)) {
                while (c != EOF && c != '
    ')
                    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 = '';
            //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 != '
    ')
                c = fgetc(txt);
            if (c != '
    ')
                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引擎.

  • 相关阅读:
    Python基础——for/while循环
    Python基础——条件判断
    Python基础——字符串
    Python基础——输出[print()]与输入[input()]
    Python编程软件的安装与使用——Windows、Linux和Mac
    Python数据结构之三——dict(字典)
    微信【跳一跳】 opencv视觉识别 + 物理外挂
    tensorflow安装: win10 + RTX2060 + tensorflow1.15.0+ cuda10.0 + VScode
    FreeRTOS 任务与调度器(2)
    FreeRTOS 任务与调度器(1)
  • 原文地址:https://www.cnblogs.com/life2refuel/p/5189340.html
Copyright © 2020-2023  润新知