程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> C 構造一個 簡單配置文件讀取庫,構造配置文件讀取庫

C 構造一個 簡單配置文件讀取庫,構造配置文件讀取庫

編輯:關於C語言

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引擎.

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved