為什麼代碼中常有配置文件這個模塊呢.首先認為是全局常量,重要的是支持熱更新. 配置在上層語言中應用的很廣,這裡帶大家手寫一個簡單可用的配置文件庫. 配置的規則 等同於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引擎.