scjson純c json引擎, 補充了可以在json串或文件中添加註釋的功能. window和linux測試完畢. ...
引言 - 以前那些系列
長活短說, 寫的最終 scjson 純c跨平臺引擎, 希望在合適場景中替代老的csjon引擎, 速度更快, 更輕巧.
下麵也是算一個系列吧. 從cjson 中得到靈感, 外加上很久以前自己寫的json引擎. 具體的可以看看下麵鏈接.
代碼也許有點亂, 但是相信你看 cjson那個源碼, 那就更亂了. 有些代碼不講道理, 好吃的東西都得讓你難受一下. 才有轉折.
本文是為了scjson 手寫引擎添加註釋解析功能. 再在跨平臺上再做一些修改, 最終給出完整的測試demo.
c json實戰引擎四 , 最後❤跳躍 (這就是程式語言設計中自舉例子)
本文最終的資源 test_scjson.zip
前言 - 那就從json文件看起
先看我們需要處理的 goods.json 文件
[ /* * 物品定義處: * 物品名,品質,作用值,加血,加魔,加攻擊,加防禦,加速度,加幸運,價格,\ * 占用包裹,加暴擊,擁有量,最大擁有,可買(1可買,0不可買),可賣(1可賣, 0不可買) */ ["小靈芝", "低級★", 1, 50, 0, 0, 0, 0, 0, 20, 1, 0, 3, 99, 1, 1], ["中靈芝", "中級★★", 1, 100, 0, 0, 0, 0, 0, 40, 2, 0, 1, 99, 1, 1], ["大靈芝", "高級★★★", 1, 200, 0, 0, 0, 0, 0, 80, 3, 0, 1, 99, 1, 1], ["滷肉 ", "初級★", 1, 80, 0, 0, 0, 0, 0, 30, 1, 0, 0, 99, 1, 0], ["小鴨脖", "初級★", 1, 100, 0, 0, 0, 0, 0, 35, 1, 0, 5, 99, 1, 1], ["小藍瓶", "初級★", 1, 0, 50, 0, 0, 0, 0, 20, 1, 0, 0, 99, 1, 1], ["中藍瓶", "中級★★", 1, 0, 100, 0, 0, 0, 0, 40, 2, 0, 0, 99, 1, 1], ["大藍瓶", "高級★★★", 1, 0, 200, 0, 0, 0, 0, 80, 3, 0, 1, 99, 1, 1], ["金鰲 ", "高級★★★", 3, 0, 0, 5, 0, 0, 0, 200, 2, 0, 2, 99, 1, 1], ["野山椒", "中級★★", 3, 0, 0, 2, 0, 0, 0, 80, 2, 0, 1, 99, 1, 1], ["巨蜥肉", "高級★★★", 3, 0, 0, 0, 10, 0, 0, 100, 3, 0, 1, 99, 1, 1], ["龍血 ", "神級★★★★★", 3, 300, 100, 2, 2, 2, 0, 600, 5, 0, 1, 99, 0, 0], ["龍肉 ", "神級★★★★★", 3, 0, 0, 10, 20, 10, 0, 800, 5, 0, 1, 99, 0, 0], ["木劍 ", "初級★", 2, 0, 0, 10, 0, 0, 0, 50, 1, 0, 1, 1, 0, 0], ["木衣 ", "初級★", 2, 0, 0, 0, 10, 0, 0, 50, 1, 0, 1, 1, 0, 0], ["木鞋 ", "初級★", 2, 0, 0, 0, 0, 10, 0, 50, 1, 0, 1, 1, 0, 0], ["屠龍劍", "神級★★★★★", 2, 0, 0, 100, 0, 0, 0, 10000, 0, 0, 0, 1, 1, 1], ["龍皮鎧甲", "神級★★★★★", 2, 0, 0, 0, 200, 0, 0, 10000, 0, 0, 0, 1, 1, 1], ["涅槃丹", "神級★★★★", 3, 100, 100, 100, 100, 20, 10, 5000, 1, 1, 0, 1, 1, 1] ]
扯一點我是用notepad++ 編輯的, 請安裝 JsTool 插件處理json很好用
切入正題我們處理思路是, 在文件讀取的時候, 去掉無效字元和註釋字元. 主要code思路如下
// 從json文件中解析出最簡json數據 static tstr_t _cjson_newfile(const char * path) { char c, n; tstr_t tstr; FILE * txt = fopen(path, "r"); if (NULL == txt) { CERR("fopen r %s is error!", path); return NULL; } //這裡創建文本串對象 tstr = tstr_new(NULL); while ((c = fgetc(txt)) != EOF) { // step 1 : 處理字元串 if (c == '"') { tstr_append(tstr, c); for (n = c; ((c = fgetc(txt)) != EOF) && (c != '"' || n == '\\'); n = c) tstr_append(tstr, c); if (EOF != c) tstr_append(tstr, c); continue; } // step 2 : 處理不可見特殊字元 if (c < '!') continue; if (c == '/') { // step 3 : 處理 // 解析到行末尾 n = fgetc(txt); if (n == '/') { while ((c = fgetc(txt)) != EOF && c != '\n') ; continue; } // step 4 : 處理 /* if (n == '*') { while ((c = fgetc(txt)) != EOF) { if (c == '*') { n = fgetc(txt); if (n == '/') break; ungetc(n, txt); } } continue; } ungetc(n, txt); } // step 5 : 合法數據直接保存 tstr_append(tstr, c); } fclose(txt);//很重要創建了就要釋放,否則會出現隱藏的句柄bug return tstr; }
(請原諒, 這種一言不合就上源碼的套路.) 主要分5類
1. "" 字元串, 不處理直接放入
2. 1-32 空字元直接捨棄, ['!' == 33, 可以查看ascii表]
3. // 註釋直接 捨棄到 \n, 行尾
4. /* 捨棄到 */ 為止
5. 合法字元直接放入
附註ascii碼表如下
挺不錯的, 可以自行查查.
同樣對於json記憶體串也是採用相同的處理思路
/* * 將 jstr中 不需要解析的字元串都去掉,並且紀念mini 比男的還平 * jstr : 待處理的json串 * : 返回壓縮後的json串長度 */ static int _cjson_mini(char * jstr) { char c, *in = jstr, *to = jstr; while ((c = *to)) { // step 1 : 處理字元串 if (c == '"') { *in++ = c; while ((c = *++to) && (c != '"' || to[-1] == '\\')) *in++ = c; if (c) { *in++ = c; ++to; } continue; } // step 2 : 處理不可見特殊字元 if (c < '!') { ++to; continue; } if (c == '/') { // step 3 : 處理 // 解析到行末尾 if (to[1] == '/') { while ((c = *++to) && c != '\n') ; continue; } // step 4 : 處理 /* if (to[1] == '*') { while ((c = *++to) && (c != '*' || to[1] != '/')) ; if (c) to += 2; continue; } } // step 5 : 合法數據直接保存 *in++ = *to++; } *in = '\0'; return in - jstr; }
到這裡我們就為這個json引擎, 添加上了json註釋功能了, 下麵會搭建測試環境.
正文 - 測試環境搭建
跨平臺的 scjson引擎[simple c json] , 跨平臺的, 採用 Ubuntu linux 搭建一下. window上更好搞. 首先得到所有文件附在下麵
schead.h
#ifndef _H_SIMPLEC_SCHEAD #define _H_SIMPLEC_SCHEAD #include <stdio.h> #include <errno.h> #include <string.h> #include <stdint.h> #include <stddef.h> #include <assert.h> #include <stdbool.h> #include <scalloc.h> /* * error => 以後再說 * 跨平臺的醜陋從這裡開始 * __GNUC => linux 平臺特殊操作 * __MSC_VER => window 平臺特殊操作 */ #ifdef __GNUC__ // 下麵是依賴GCC編譯器實現 #include <unistd.h> #include <sys/time.h> #include <termio.h> // 統一的程式睡眠巨集, 單位是毫秒顆粒度 #define SLEEPMS(m) \ usleep(m * 1000) // 屏幕清除巨集, 依賴系統腳本 #define CONSOLECLS() \ system("printf '\ec'") /* * 得到用戶輸入的一個字元 * : 返回得到字元 */ extern int sh_getch(void); #elif _MSC_VER // 下麵是依賴Visual Studio編譯器實現 #include <Windows.h> #include <direct.h> #include <conio.h> // window 刪除目錄巨集 #define rmdir _rmdir // window 上用_getch 替代了getch, 這裡為了讓其回來 #define sh_getch _getch #define CONSOLECLS() \ system("cls") #define SLEEPMS(m) \ Sleep(m) #else #error "error : Currently only supports the Visual Studio and GCC!" #endif /* * 錯誤定義枚舉 用於判斷返回值狀態的狀態碼 RT_*表示返回標誌 * 使用舉例 : int flag = scconf_get("pursue"); if(flag < RT_SuccessBase) { sclog_error("get config %s error! flag = %d.", "pursue", flag); exit(EXIT_FAILURE); } * 這裡是內部 使用的通用返回值 標誌. >=0 表示成功, <0 表示失敗的情況 */ typedef enum { RT_SuccessBase = 00, //結果正確的返回巨集 RT_ErrorBase = -1, //錯誤基類型, 所有錯誤都可用它, 在不清楚的情況下 RT_ErrorParam = -2, //調用的參數錯誤 RT_ErrorMalloc = -3, //記憶體分配錯誤 RT_ErrorFopen = -4, //文件打開失敗 RT_ErrorClose = -5, //文件描述符讀取關閉, 讀取完畢也會返回這個 } flag_e; /* * 定義一些通用的函數指針幫助,主要用於基庫的封裝中 * 有構造函數, 釋放函數, 比較函數等 */ typedef void * (* pnew_f)(); typedef void (* vdel_f)(void * node); // icmp_f 最好 是 int cmp(const void * ln, const void * rn); 標準結構 typedef int (* icmp_f)(); // 迴圈操作函數, arg 外部參數, node 內部節點 typedef flag_e (* each_f)(void * node, void * arg); /* * c 如果是空白字元返回 true, 否則返回false * c : 必須是 int 值,最好是 char 範圍 */ #define sh_isspace(c) \ ((c==' ')||(c>='\t'&&c<='\r')) // 3.0 浮點數據判斷巨集幫助, __開頭表示不希望你使用的巨集 #define __DIFF(x, y) ((x)-(y)) //兩個表達式做差巨集 #define __IF_X(x, z) ((x)<z && (x)>-z) //判斷巨集,z必須是巨集常量 #define EQ(x, y, c) EQ_ZERO(__DIFF(x,y), c) //判斷x和y是否在誤差範圍內相等 // 3.1 float判斷定義的巨集 #define _FLOAT_ZERO (0.000001f) //float 0的誤差判斷值 #define EQ_FLOAT_ZERO(x) __IF_X(x, _FLOAT_ZERO) //float 判斷x是否為零是返回true #define EQ_FLOAT(x, y) EQ(x, y, _FLOAT_ZERO) //判斷表達式x與y是否相等 // 3.2 double判斷定義的巨集 #define _DOUBLE_ZERO (0.000000000001) //double 0誤差判斷值 #define EQ_DOUBLE_ZERO(x) __IF_X(x, _DOUBLE_ZERO) //double 判斷x是否為零是返回true #define EQ_DOUBLE(x,y) EQ(x, y, _DOUBLE_ZERO) //判斷表達式x與y是否相等 // 4.0 控制台列印錯誤信息, fmt必須是雙引號括起來的巨集 #ifndef CERR #define CERR(fmt, ...) \ fprintf(stderr,"[%s:%s:%d][error %d:%s]" fmt "\n",\ __FILE__, __func__, __LINE__, errno, strerror(errno), ##__VA_ARGS__) #endif // !CERR // 4.1 控制台列印錯誤信息並退出, t同樣fmt必須是 ""括起來的字元串常量 #ifndef CERR_EXIT #define CERR_EXIT(fmt,...) \ CERR(fmt, ##__VA_ARGS__),exit(EXIT_FAILURE) #endif // !CERR_EXIT // 4.2 執行後檢測,如果有錯誤直接退出 #ifndef IF_CHECK #define IF_CHECK(code) \ if((code) < 0) \ CERR_EXIT(#code) #endif // !IF_CHECK // 5.0 獲取數組長度,只能是數組類型或""字元串常量,後者包含'\0' #ifndef LEN #define LEN(arr) \ (sizeof(arr) / sizeof(*(arr))) #endif/* !ARRLEN */ // 7.0 置空操作 #ifndef BZERO // v必須是個變數 #define BZERO(v) \ memset(&(v), 0, sizeof(v)) #endif/* !BZERO */ // 9.0 scanf 健壯的 #ifndef SAFETY_SCANF #define _STR_SAFETY_SCANF "Input error, please according to the prompt!" #define SAFETY_SCANF(scanf_code, ...) \ while(printf(__VA_ARGS__), scanf_code){\ while('\n' != getchar()) \ ;\ puts(_STR_SAFETY_SCANF);\ }\ while('\n' != getchar()) #endif /*!SAFETY_SCANF*/ // 簡單的time幫助巨集 #ifndef TIME_PRINT #define _STR_TIME_PRINT "The current code block running time:%lf seconds\n" #define TIME_PRINT(code) \ do{\ clock_t __st, __et;\ __st=clock();\ code\ __et=clock();\ printf(_STR_TIME_PRINT, (0.0 + __et - __st) / CLOCKS_PER_SEC);\ } while(0) #endif // !TIME_PRINT /* * 10.0 這裡是一個 在 DEBUG 模式下的測試巨集 * * 用法 : * DEBUG_CODE({ * puts("debug start..."); * }); */ #ifndef DEBUG_CODE # ifdef _DEBUG # define DEBUG_CODE(code) code # else # define DEBUG_CODE(code) # endif // ! _DEBUG #endif // ! DEBUG_CODE //11.0 等待的巨集 是個單線程沒有加鎖 | "請按任意鍵繼續. . ." #define _STR_PAUSEMSG "Press any key to continue . . ." extern void sh_pause(void); #ifndef INIT_PAUSE # ifdef _DEBUG # define INIT_PAUSE() atexit(sh_pause) # else # define INIT_PAUSE() /* 別說了,都重新開始吧 */ # endif #endif // !INIT_PAUSE //12.0 判斷是大端序還是小端序,大端序返回true extern bool sh_isbig(void); /** * sh_free - 簡單的釋放記憶體函數,對free再封裝了一下 **可以避免野指針 **pobj:指向待釋放記憶體的指針(void*) **/ extern void sh_free(void ** pobj); /* * 比較兩個結構體棧上內容是否相等,相等返回true,不等返回false * a : 第一個結構體值 * b : 第二個結構體值 * : 相等返回true, 否則false */ #define STRUCTCMP(a, b) \ (!memcmp(&a, &b, sizeof(a))) #endif// ! _H_SIMPLEC_SCHEADView Code
schead.c
#include <schead.h> //簡單通用的等待函數 inline void sh_pause(void) { rewind(stdin); printf(_STR_PAUSEMSG); sh_getch(); } //12.0 判斷是大端序還是小端序,大端序返回true inline bool sh_isbig(void) { static union { unsigned short _s; unsigned char _c; } __u = { 1 }; return __u._c == 0; } /** * sh_free - 簡單的釋放記憶體函數,對free再封裝了一下 **可以避免野指針 **@pobj:指向待釋放記憶體的指針(void*) **/ void sh_free(void ** pobj) { if (pobj == NULL || *pobj == NULL) return; free(*pobj); *pobj = NULL; } // 為linux擴展一些功能 #if defined(__GNUC__) /* * 得到用戶輸入的一個字元 * : 返回得到字元 */ int sh_getch(void) { int cr; struct termios nts, ots; if (tcgetattr(0, &ots) < 0) // 得到當前終端(0表示標準輸入)的設置 return EOF; nts = ots; cfmakeraw(&nts); // 設置終端為Raw原始模式,該模式下所有的輸入數據以位元組為單位被處理 if (tcsetattr(0, TCSANOW, &nts) < 0) // 設置上更改之後的設置 return EOF; cr = getchar(); if (tcsetattr(0, TCSANOW, &ots) < 0) // 設置還原成老的模式 return EOF; return cr; } #endifView Code
scalloc.h
#ifndef _H_SIMPLEC_SCALLOC #define _H_SIMPLEC_SCALLOC #include <stdlib.h> // 釋放sm_malloc_和sm_realloc_申請的記憶體, 必須配套使用 void sm_free_(void * ptr, const char * file, int line, const char * func); // 返回申請的一段乾凈的記憶體 void * sm_malloc_(size_t sz, const char * file, int line, const char * func); // 返回重新申請的記憶體, 只能和sm_malloc_配套使用 void * sm_realloc_(void * ptr, size_t sz, const char * file, int line, const char * func); /* * 釋放申請的記憶體 * ptr : 申請的記憶體 */ #define sm_free(ptr) sm_free_(ptr, __FILE__, __LINE__, __func__) /* * 返回申請的記憶體, 並且是填充'\0' * sz : 申請記憶體的長度 */ #define sm_malloc(sz) sm_malloc_(sz, __FILE__, __LINE__, __func__) /* * 返回申請到num*sz長度記憶體, 並且是填充'\0' * num : 申請的數量 * sz : 申請記憶體的長度 */ #define sm_calloc(num, sz) sm_malloc_(num*sz, __FILE__, __LINE__, __func__) /* * 返回重新申請的記憶體 * ptr : 申請的記憶體 * sz : 申請記憶體的長度 */ #define sm_realloc(ptr, sz) sm_realloc_(ptr, sz, __FILE__, __LINE__, __func__) // 定義全局記憶體使用巨集, 替換原有的malloc系列函數 #ifndef _SIMPLEC_ALLOC_CLOSE # define free sm_free # define malloc sm_malloc # define calloc sm_calloc # define realloc sm_realloc #endif #endif // !_H_SIMPLEC_SCALLOCView Code
scalloc.c
#include <stdio.h> #include <stdlib.h> #include <string.h> // 標識枚舉 typedef enum { HF_Alloc, HF_Free } header_e; // 每次申請記憶體的[16-24]位元組額外消耗, 用於記錄記憶體申請情況 struct header { header_e flag; // 當前記憶體使用的標識 int line; // 申請的文件行 const char * file; // 申請的文件名 const char * func; // 申請的函數名 }; // 內部使用的malloc, 返回記憶體會用'\0'初始化 void * sm_malloc_(size_t sz, const char * file, int line, const char * func) { struct header * ptr = malloc(sz + sizeof(struct header)); // 檢查記憶體分配的結果 if(NULL == ptr) { fprintf(stderr, "_header_get >%s:%d:%s< alloc error not enough memory start fail!\n", file, line, func); exit(EXIT_FAILURE); } ptr->flag = HF_Alloc; ptr->line = line; ptr->file = file; ptr->func = func; memset(++ptr, 0, sz); return ptr; } // 得到申請記憶體的開頭部分, 並檢查 static struct header * _header_get(void * ptr, const char * file, int line, const char * func) { struct header * node = (struct header *)ptr - 1; // 正常情況直接返回 if(HF_Alloc != node->flag) { // 異常情況, 記憶體多次釋放, 和記憶體無效釋放 fprintf(stderr, "_header_get free invalid memony flag %d by >%s:%d:%s<\n", node->flag, file, line, func); exit(EXIT_FAILURE); } return node; } // 內部使用的realloc void * sm_realloc_(void * ptr, size_t sz, const char * file, int line, const char * func) { struct header * node , * buf; if(NULL == ptr) return sm_malloc_(sz, file, line, func); // 合理記憶體分割 node = _header_get(ptr, file, line, func); node->flag = HF_Free; // 構造返回記憶體信息 buf = realloc(node, sz + sizeof(struct header)); buf->flag = HF_Alloc; buf->line = line; buf->file = file; buf->func = func; return buf + 1; } // 內部使用的free, 每次釋放都會列印日誌信息 void sm_free_(void * ptr, const char * file, int line, const char * func) { if(NULL != ptr) { // 得到記憶體地址, 並且標識一下, 開始釋放 struct header * node = _header_get(ptr, file, line, func); node->flag = HF_Free; free(node); } }View Code
tstr.h
#ifndef _H_SIMPLEC_TSTR #define _H_SIMPLEC_TSTR #include <schead.h> //------------------------------------------------簡單字元串輔助操作---------------------------------- /* * 主要採用jshash 返回計算後的hash值 * 不衝突率在 80% 左右還可以, 不要傳入NULL */ extern unsigned tstr_hash(const char * str); /* * 這是個不區分大小寫的比較函數 * ls : 左邊比較字元串 * rs : 右邊比較字元串 * : 返回 ls>rs => >0 ; ls = rs => 0 ; ls<rs => <0 */ extern int tstr_icmp(const char * ls, const char * rs); /* * 這個代碼是 對 strdup 的再實現, 調用之後需要free * str : 待複製的源碼內容 * : 返回 複製後的串內容 */ extern char * tstr_dup(const char * str); //------------------------------------------------簡單文本字元串輔助操作---------------------------------- #ifndef _STRUCT_TSTR #define _STRUCT_TSTR //簡單字元串結構,並定義文本字元串類型tstring struct tstr { char * str; //字元串實際保存的內容 int len; //當前字元串大小 int size; //字元池大小 }; typedef struct tstr * tstr_t; #endif // !_STRUCT_TSTR //文本串棧上創建內容,不想用那些技巧了,就這樣吧 #define TSTR_NEW(var) \ struct tstr $__##var = { NULL, 0, 0 }, * var = &$__##var; #define TSTR_DELETE(var) \ sm_free(var->str) /* * tstr_t 的創建函數, 會根據str創建一個 tstr_t 結構的字元串 * str : 待創建的字元串 * : 返回創建好的字元串,如果創建失敗返回NULL */ extern tstr_t tstr_new(const char * str); /* * tstr_t 析構函數 * tstr : tstr_t字元串指針量 */ extern void tstr_delete(tstr_t tstr); /* * 向簡單文本字元串tstr中添加 一個字元c * tstr : 簡單字元串對象 * c : 待添加的字元 */ extern void tstr_append(tstr_t tstr, int c); /* * 向簡單文本串中添加只讀字元串 * tstr : 文本串 * str : 待添加的素材串 */ extern void tstr_appends(tstr_t tstr, const char * str); /* * 複製tstr中內容,得到char *, 需要自己 free釋放 * 假如你要清空tstr_t字元串只需要 設置 len = 0.就可以了 * tstr : 待分配的字元串 * : 返回分配好的字元串首地址 */ extern char * tstr_dupstr(tstr_t tstr); //------------------------------------------------簡單文件輔助操作---------------------------------- /* * 簡單的文件幫助類,會讀取完畢這個文件內容返回,失敗返回NULL. * 需要事後使用 tstr_delete(ret); 銷毀這個字元串對象 * path : 文件路徑 * : 返回創建好的字元串內容,返回NULL表示讀取失敗 */ extern tstr_t tstr_file_readend(const char * path); /* * 文件寫入,沒有好說的, 會返回 RT_SuccessBase | RT_ErrorParam | RT_ErrorFopen * path : 文件路徑 * str : 待寫入的字元串 * : 返回操作的結果 見上面枚舉 */ extern int tstr_file_writes(const char * path, const char * str); /* * 文件追加內容 會返回 RT_SuccessBase | RT_ErrorParam | RT_ErrorFopen * path : 文件路徑 * str : 待寫入的字元串 * : 返回操作的結果 見上面枚舉 */ extern int tstr_file_append(const char * path, const char * str); #endif // !_H_SIMPLEC_TSTRView Code
tstr.c
#include <tstr.h> /* * 主要採用jshash 返回計算後的hash值 * 不衝突率在 80% 左右還可以, 不要傳入NULL */ unsigned tstr_hash(const char * str) { unsigned i, h = (unsigned)strlen(str), sp = (h >> 5) + 1; unsigned char * ptr = (unsigned char *)str; for (i = h; i >= sp; i -= sp) h ^= ((h<<5) + (h>>2) + ptr[i-1]); return h ? h : 1; } /* * 這是個不區分大小寫的比較函數 * ls : 左邊比較字元串 * rs : 右邊比較字元串 * : 返回 ls>rs => >0 ; ls = rs => 0 ; ls<rs => <0 */ int tstr_icmp(const char * ls, const char * rs) {