C 构造一个 简单配置文件读取库

时间:2023-03-09 19:31:22
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\"
";

我们只需要解析上面数据, 保存在全局区下次直接调用就可以了. 例如

C 构造一个 简单配置文件读取库

运行的结果如上.  对于上面配置 有下面几个规则

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[];
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 = ; //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 ;
}

上面 代码的输出结果是 就是上面截图. 算法比较直白很容易理解. 到这里 写了一个小功能还是很有成就感的. 特别是看那些著名的开源代码库,特别爽.

上面代码完全可以自己练习一下,很有意思,到这里完全没有难度. 后面将从以前的框架角度优化这个代码.

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的内容, 现在文件目录结构如下

C 构造一个 简单配置文件读取库

上面接口使用方式也很简单例如

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+));
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 + );
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 = ; //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 ;
}

运行结果如下

C 构造一个 简单配置文件读取库

其中配置 看 上面 config.ini . 到这里关于简单的配置文件功能我们就完成了. 我想扯一点, 用过较多的语言或库, 还是觉得 高级VS + .Net 库 开发最爽,

拼积木最愉快,什么功能都用,代码设计也很漂亮, 唯一可惜的就是速度有点慢,有点臃肿.个人感觉 开发 C#库都是window 届 C++的*大牛, C#没有推广

出去却把window上C++程序员坑的要死,都转行到 Linux C/C++开发行列中. 更加有意思的是 C#没有因为 微软老爸红了,却因Unity 3D的 干爹火了.

C#+VS写起来确实很优美,  但是 如果你不用VS那就 另说了, 考验一个window程序员功底就是 , 让他离开了vs是否开始那么 销魂.

推荐做为一个程序员还是多学点, 因为都是语言, 学多了以后交流方便.大家觉得呢.

后语

错误是难免的, 欢迎指正, 随着年纪增长愈发觉得自己还很水. 需要学习很多东西,也需要舍弃很多东西. 对于未知的前方,

全当乱走的记录.看开源的项目源码还是很爽的,下次有机会分享手把手写个高效cjson引擎.