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

来源:http://www.cnblogs.com/life2refuel/archive/2016/02/15/5189340.html
-Advertisement-
Play Games

為什麼代碼中常有配置文件這個模塊呢.首先認為是全局常量,重要的是支持熱更新. 配置在上層語言中應用的很廣,這裡帶大家手寫一個簡單可用的配置文件庫. 配置的規則 等同於php 的 變數 $heoo = "Hello World!" 這樣.


前言

  最近看到這篇文章,

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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 遲來的年後總結 其實很多時候都想寫兩篇博客來記錄下工作學習中的點點滴滴,不過自從去年10月份後,工作上總是無比的忙,新功能,新項目,需求不明確,需求變動等原因導致在後期長時間的加班和趕工。不過慶幸2015終歸拉下了帷幕。在2015年中雖然累,雖然有段時間無比苦逼,不過也算是學了很多,如 技術上的突破
  • 不管什麼平臺,應用內難免會出現一些消息提示框,下麵就來聊聊我在UWP里用到的消息提示框。 彈窗也可按是否需要用戶操作促發一些邏輯進行分為兩大類。 不需要用戶干涉的一類: MessageDialog:操作簡單,寫起來也省事,想具體瞭解的請參考MSDN 先看看效果 PC上效果: mobile上效果: 再
  • 原文地址:http://blog.sina.com.cn/s/blog_604fb7ae0100x2s7.html 中小企業辦公自動化系統都需要有與微軟辦公軟體連接的功能,如把數據導入到電子錶格、Word等功能。C#.NET在Office方面提供了強大的功能,只要導入 Microsoft.Offic
  • (1/2)類型 普通函數指針不能被賦值為成員函數的地址。 int (*pFunc)(); pFunc是一個函數指針,而int (*)()是類型。 成員函數地址要賦值給成員函數指針。 class Base { public: int func() { return 1; } }; int main()
  • 摘自:http://cmsblogs.com/?p=52 面向對象編程有三大特性:封裝、繼承、多態。 封裝隱藏了類的內部實現機制,可以在不影響使用的情況下改變類的內部結構,同時也保護了數據。對外界而已它的內部細節是隱藏的,暴露給外界的只是它的訪問方法。 繼承是為了重用父類代碼。兩個類若存在IS-A的
  • 摘自:http://cmsblogs.com/?p=48 在講解之前我們先看一個例子,該例子是前篇博文(java提高篇—–理解java的三大特性之封裝)的。 從這裡我們可以看出,Wife、Husband兩個類除了各自的husband、wife外其餘部分全部相同,作為一個想最大限度實現復用代碼的我們是
  • 摘自:http://cmsblogs.com/?p=41 封裝從字面上來理解就是包裝的意思,專業點就是信息隱藏,是指利用抽象數據類型將數據和基於數據的操作封裝在一起,使其構成一個不可分割的獨立實體,數據被保護在抽象數據類型的內部,儘可能地隱藏內部的細節,只保留一些對外介面使之與外部發生聯繫。系統的其
  • package cn.aust.zyw.demo; /** * Created by zyw on 2016/2/9. * insert-sort */ public class Insertion { public static void sort(int [] a){ int N=a.lengt
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...