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[];
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的内容, 现在文件目录结构如下
上面接口使用方式也很简单例如
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 ;
}
运行结果如下
其中配置 看 上面 config.ini . 到这里关于简单的配置文件功能我们就完成了. 我想扯一点, 用过较多的语言或库, 还是觉得 高级VS + .Net 库 开发最爽,
拼积木最愉快,什么功能都用,代码设计也很漂亮, 唯一可惜的就是速度有点慢,有点臃肿.个人感觉 开发 C#库都是window 届 C++的顶级大牛, C#没有推广
出去却把window上C++程序员坑的要死,都转行到 Linux C/C++开发行列中. 更加有意思的是 C#没有因为 微软老爸红了,却因Unity 3D的 干爹火了.
C#+VS写起来确实很优美, 但是 如果你不用VS那就 另说了, 考验一个window程序员功底就是 , 让他离开了vs是否开始那么 销魂.
推荐做为一个程序员还是多学点, 因为都是语言, 学多了以后交流方便.大家觉得呢.
后语
错误是难免的, 欢迎指正, 随着年纪增长愈发觉得自己还很水. 需要学习很多东西,也需要舍弃很多东西. 对于未知的前方,
全当乱走的记录.看开源的项目源码还是很爽的,下次有机会分享手把手写个高效cjson引擎.
C 构造一个 简单配置文件读取库的更多相关文章
- C 封装一个简单二叉树基库
引文 今天分享一个喜欢佩服的伟人,应该算人类文明极大突破者.收藏过一张纸币类型如下 那我们继续科普一段关于他的简介 '高斯有些孤傲,但令人惊奇的是,他春风得意地度过了中产阶级的一生,而 没有遭受到冷 ...
- Linux内核设计第三周——构造一个简单的Linux系统
Linux内核设计第三周 ——构造一个简单的Linux系统 一.知识点总结 计算机三个法宝: 存储程序计算机 函数调用堆栈 中断 操作系统两把宝剑: 中断上下文的切换 进程上下文的切换 linux内核 ...
- 第三节 构造一个简单的Linux系统MenuOS——20135203齐岳
第三节 构造一个简单的Linux系统MenuOS By 20135203齐岳 Linux内核源代码 arch/ 支持不同cpu的源代码 Documentations/ 文档存储 init/ 内核启动相 ...
- Linux内核分析第三周学习总结:构造一个简单的Linux系统MenuOS
韩玉琪 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.Linux内 ...
- 如何构造一个简单的USB过滤驱动程序
本文分三部分来介绍如何构造一个简单的USB过滤驱动程序,包括"基本原理"."程序的实现"."使用INF安装".此文的目的在于希望读者了解基本 ...
- 20135202闫佳歆--week3 构造一个简单的Linux系统MenuOs--学习笔记
此为个人学习笔记存档 week 3 构造一个简单的Linux系统MenuOs 复习: 计算机有三个法宝:存储程序计算机,函数调用堆栈,中断 操作系统有两把剑: 1.中断上下文的切换,保存现场和恢复现场 ...
- Linux内核分析-构造一个简单的Linux系统MenuOS
构造一个简单的Linux系统MenuOS linux内核目录结构 arch目录包括了所有和体系结构相关的核心代码.它下面的每一个子目录都代表一种Linux支持的体系结构,例如i386就是Intel C ...
- Linux内核分析 笔记三 构造一个简单的Linux系统MenuOS ——by王玥
一.知识点总结 (一)Linux源代码简介 arch/x86目录下的代码是我们重点关注的 内核启动相关代码都在init目录下 start_kernel函数相当于普通C程序的main函数 linux的核 ...
- 第三周 构造一个简单的Linux系统MenuOS
一. Linux内核源代码简介 稳定版内核:Linux-3.18.6 Linux内核源代码的目录结构: arch目录:在Linux内核源代码里占有的比重很大,因为Linux内核支持很多的体系结构, ...
随机推荐
- Ant 脚本打印系统属性变量、ant内置属性
Ant 脚本打印系统属性变量.ant内置属性 作用 编写ant脚本的时候,经常会引用到系统属性,本脚本用于打印系统常用属性(System.getProperties)与环境变量(Environment ...
- Perlin Noise 及其应用
Perlin Noise 可以用来表现自然界中无法用简单形状来表达的物体的形态,比如火焰.烟雾.表面纹路等.要生成 Perlin Noise 可以使用工具离线生成,也可以使用代码运行时生成.最简单常用 ...
- 【PL/SQL练习】函数
1.必须返回一个值2.只能在表达式调用 SQL> create or replace function fun1 return number is v_sum_sal emp.sal%type; ...
- Android控件大全(三)——RecyclerView
是时候用RecyclerView来替换ListView和GridView了 好处就不多说了,百度一搜一大把,来介绍下用法 先定义个适配器: public class BottomSheetAdapte ...
- nagios架构及windows,linux客户端配置
Linux下Nagios的安装与配置 一.Nagios简介 Nagios是一款开源的电脑系统和网络监视工具,能有效监控Windows.Linux和Unix的主机状态,交换机路由器等网络设置,打印机等. ...
- jQ复制按钮的插件zclip
zclip官网:http://www.steamdev.com/zclip/ swf文件国内下载:ZeroClipboard.swf jQuery-zclip是一个复制内容到剪贴板的jQuery插件, ...
- ibatis 存储过程写法
<?xml version=) ORDER BY a.DepId) AS row_n,a.DepId,a.DepName,a.ParentDepId,a.DepCode,a.CustomerS ...
- ajax 清除缓存
$.ajax({ url : actionUrl , beforeSend :function(xmlHttp){ // deforeSend 是请求前清除缓存 ,如果没有缓存也不使用before ...
- 利用SecondaryNameNode文件恢复Namenode-实践可行
二. namenode故障恢复(importCheckpoint) *注意事项: (1) 为了便于将随便一台datanode临时用作namenode,datanode和namenode配置需要一模一样 ...
- Android IOS WebRTC 音视频开发总结(三八)-- tx help
本文主要介绍帮一个程序员解决webrtc疑问的过程,文章来自博客园RTC.Blacker,支持原创,转载请说明出处(www.rtc.help) 这篇文章内容主要来自邮件,为什么我会特别整理到随笔里面来 ...