本文之前由于技术不到位,写的比较挫,最近花了大半天时间写了一个高级点的版本。

这里是我写的代码,已经上传到github了。跳转到github

主要是使用了链表保存ini文件的内容,在程序运行最初会初始化链表,接下来的查询操作都只是查询内存,比较快,而且此时也不依赖文件了。最大的改变就是自己对编程的熟悉和了解,再也不会写挫逼代码咯。

下面是源代码,个人感觉还不错。欢迎大家使用。

#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h> #define MAX_VALUE 64 /* 定义section,key,value字符串最大长度 */
// printf("File = %s\nLine = %d\nFunc=%s\nDate=%s\nTime=%s\n", __FILE__, __LINE__, __FUNCTION__, __DATE__, __TIME__);
#define PRINT_ERRMSG(STR) fprintf(stderr,"line:%d,msg:%s,eMsg:%s\n", __LINE__, STR, strerror(errno)) #define TRIM_LIFT 1 /* 去除左边空白字符 */
#define TRIM_RIGHT 2 /* 去除右边空白字符 */
#define TRIM_SPACE 3 /* 去除两边空白字符 */ typedef struct _option {
char key[MAX_VALUE]; /* 对应键 */
char value[MAX_VALUE]; /* 对应值 */
struct _option *next; /* 链表连接标识 */
}Option; typedef struct _data {
char section[MAX_VALUE]; /* 保存section值 */
Option *option; /* option链表头 */
struct _data *next; /* 链表连接标识 */
}Data; typedef struct {
char comment; /* 表示注释的符号 */
char separator; /* 表示分隔符 */
char re_string[MAX_VALUE]; /* 返回值字符串的值 */
int re_int; /* 返回int的值 */
bool re_bool; /* 返回bool的值 */
double re_double ; /* 返回double类型 */
Data *data; /* 保存数据的头 */
}Config; /**
* 判断字符串是否为空
* 为空返回true,不为空返回false
**/
bool str_empty(const char *string)
{
return NULL == string || '\0' == *string;
} /**
* 向链表添加section,key,value
* 如果添加时不存在section则新增一个
* 如果对应section的key不存在则新增一个
* 如果section已存在则不会重复创建
* 如果对应section的key已存在则只会覆盖key的值
**/
bool cnf_add_option(Config *cnf, const char *section, const char *key, const char *value)
{
if (NULL == cnf || str_empty(section) || str_empty(key) || str_empty(value)) {
return false; /* 参数不正确,返回false */
} Data *p = cnf->data; /* 让变量p循环遍历data,找到对应section */
while (NULL != p && 0 != strcmp(p->section, section)) {
p = p->next;
} if (NULL == p) { /* 说明没有找到section,需要加一个 */
Data *ps = (Data*)malloc(sizeof(Data));
if (NULL == ps) {
exit(-1); /* 申请内存错误 */
}
strcpy(ps->section, section);
ps->option = NULL; /* 初始的option要为空 */
ps->next = cnf->data; /* cnf->data可能为NULL */
cnf->data = p = ps; /* 头插法插入链表 */
} Option *q = p->option;
while (NULL != q && 0 != strcmp(q->key, key)) {
q = q->next; /* 遍历option,检查key是否已经存在 */
} if (NULL == q) { /* 不存在option,则新建一个 */
q = (Option*)malloc(sizeof(Option));
if (NULL == q) {
exit(-1); /* 申请内存错误 */
}
strcpy(q->key, key);
q->next = p->option; /*这里p->option可能为NULL,不过也没关系 */
p->option = q; /* 头插法插入链表 */
}
strcpy(q->value, value); /* 无论如何要把值改了 */ return true;
} /**
* 按照参数去除字符串左右空白
**/
char *trim_string(char *string,int mode)
{
char *left = string;
if ((mode & 1) != 0) { // 去除左边空白字符
for (;*left != '\0'; left++) {
if (0 == isspace(*left)) {
break;
}
}
}
if ((mode & 2) != 0) { // 去除右边空白字符
char *right = string - 1 + strlen(string);
for (;right >= left; right--) {
if (0 == isspace(*right)) {
*(right+1) = '\0';
break;
}
}
}
return left;
} /**
* 传递配置文件路径
* 参数有文件路径,注释字符,分隔符
* 返回Config结构体
**/
Config *cnf_read_config(const char *filename, char comment, char separator)
{
Config *cnf = (Config*)malloc(sizeof(Config));
if (NULL == cnf) {
exit(-1); /* 申请内存错误 */
}
cnf->comment = comment; /* 每一行该字符及以后的字符将丢弃 */
cnf->separator = separator; /* 用来分隔Section 和 数据 */
cnf->data = NULL; /* 初始数据为空 */ if (str_empty(filename)) {
return cnf; /* 空字符串则直接返回对象 */
} FILE *fp = fopen(filename, "r");
if(NULL == fp) {
PRINT_ERRMSG("fopen");
exit(errno); /* 读文件错误直接按照错误码退出 */
} char *s, *e, *pLine, sLine[MAX_VALUE]; /* 保存一行数据到字符串 */
char section[MAX_VALUE] = {'\0'}, key[MAX_VALUE], value[MAX_VALUE]; /* 缓存section,key,value */
while (NULL != fgets(sLine, MAX_VALUE, fp)) {
pLine = trim_string(sLine, TRIM_SPACE); /* 去掉一行两边的空白字符 */
if (*pLine == '\0' || *pLine == comment) {
continue; /* 空行或注释行跳过 */
}
s = strchr(pLine, comment);
if (s != NULL) {
*s = '\0'; /* 忽略本行注释后的字符 */
} s = strchr(pLine, '[');
if (s != NULL) {
e = strchr(++s, ']');
if (e != NULL) {
*e = '\0'; /* 找到section */
strcpy(section, s);
}
} else {
s = strchr(pLine, separator);
if (s != NULL && *section != '\0') { /* 找到包含separator的行,且前面行已经找到section */
*s = '\0'; /* 将分隔符前后分成2个字符串 */
strcpy(key, trim_string(pLine, TRIM_RIGHT)); /* 赋值key */
strcpy(value, trim_string(s+1, TRIM_LIFT)); /* 赋值value */
cnf_add_option(cnf, section, key, value); /* 添加section,key,value */
}
}
} /* end while */
fclose(fp);
return cnf;
} /**
* 获取指定类型的值
* 根据不同类型会赋值给对应值
* 本方法需要注意,int和double的转换,不满足就是0
* 需要自己写代码时判断好
**/
bool cnf_get_value(Config *cnf, const char *section, const char *key)
{
Data *p = cnf->data; /* 让变量p循环遍历data,找到对应section */
while (NULL != p && 0 != strcmp(p->section, section)) {
p = p->next;
} if (NULL == p) {
PRINT_ERRMSG("section not find!");
return false;
} Option *q = p->option;
while (NULL != q && 0 != strcmp(q->key, key)) {
q = q->next; /* 遍历option,检查key是否已经存在 */
} if (NULL == q) {
PRINT_ERRMSG("key not find!");
return false;
} strcpy(cnf->re_string, q->value); /* 将结果字符串赋值 */
cnf->re_int = atoi(cnf->re_string); /* 转换为整形 */
cnf->re_bool = 0 == strcmp ("true", cnf->re_string); /* 转换为bool型 */
cnf->re_double = atof(cnf->re_string); /* 转换为double型 */ return true;
} /**
* 判断section是否存在
* 不存在返回空指针
* 存在则返回包含那个section的Data指针
**/
Data *cnf_has_section(Config *cnf, const char *section)
{
Data *p = cnf->data; /* 让变量p循环遍历data,找到对应section */
while (NULL != p && 0 != strcmp(p->section, section)) {
p = p->next;
} if (NULL == p) { /* 没找到则不存在 */
return NULL;
} return p;
} /**
* 判断指定option是否存在
* 不存在返回空指针
* 存在则返回包含那个section下key的Option指针
**/
Option *cnf_has_option(Config *cnf, const char *section, const char *key)
{
Data *p = cnf_has_section(cnf, section);
if (NULL == p) { /* 没找到则不存在 */
return NULL;
} Option *q = p->option;
while (NULL != q && 0 != strcmp(q->key, key)) {
q = q->next; /* 遍历option,检查key是否已经存在 */
}
if (NULL == q) { /* 没找到则不存在 */
return NULL;
} return q;
} /**
* 将Config对象写入指定文件中
* header表示在文件开头加一句注释
* 写入成功则返回true
**/
bool cnf_write_file(Config *cnf, const char *filename, const char *header)
{
FILE *fp = fopen(filename, "w");
if(NULL == fp) {
PRINT_ERRMSG("fopen");
exit(errno); /* 读文件错误直接按照错误码退出 */
} if (!str_empty(header)) { /* 文件注释不为空,则写注释到文件 */
fprintf(fp, "%c %s\n\n", cnf->comment, header);
} Option *q;
Data *p = cnf->data;
while (NULL != p) {
fprintf(fp, "[%s]\n", p->section);
q = p->option;
while (NULL != q) {
fprintf(fp, "%s %c %s\n", q->key, cnf->separator, q->value);
q = q->next;
}
p = p->next;
} fclose(fp);
return true;
} /**
* 删除option
**/
bool cnf_remove_option(Config *cnf, const char *section, const char *key)
{
Data *ps = cnf_has_section(cnf, section);
if (NULL == ps) { /* 没找到则不存在 */
return false;
} Option *p, *q;
q = p = ps->option;
while (NULL != p && 0 != strcmp(p->key, key)) {
if (p != q) { q = q->next; } /* 始终让q处于p的上一个节点 */
p = p->next;
} if (NULL == p) { /* 没找到则不存在 */
return false;
} if (p == q) { /* 第一个option就匹配了 */
ps->option = p->next;
} else {
q->next = p->next;
} free(p);
q = p = NULL; // 避免野指针 return true;
} /**
* 删除section
**/
bool cnf_remove_section(Config *cnf, const char *section)
{
if (str_empty(section)) {
return false;
} Data *p, *q;
q = p = cnf->data; /* 让变量p循环遍历data,找到对应section */
while (NULL != p && 0 != strcmp(p->section, section)) {
if (p != q) { q = q->next; } /* 始终让q处于p的上一个节点 */
p = p->next;
} if (NULL == p) { /* 没有找到section */
return false;
} if (p == q) { /* 这里表示第一个section,因此链表头位置改变 */
cnf->data = p->next;
} else { /* 此时是中间或尾部节点 */
q->next = p->next;
} Option *ot,*o = p->option;
while (NULL != o) { /* 循环释放所有option */
ot = o;
o = o->next;
free(ot);
}
p->option = NULL; // 避免野指针
free(p); /* 释放删除的section */
q = p = NULL; // 避免野指针 return true;
} /**
* 销毁Config对象
* 删除所有数据
**/
void destroy_config(Config **cnf)
{
if (NULL != *cnf)
{
if (NULL != (*cnf)->data)
{
Data *pt,*p = (*cnf)->data;
Option *qt,*q;
while (NULL != p) {
q = p->option;
while (NULL != q) {
qt = q;
q = q->next;
free(qt);
}
pt = p;
p = p->next;
free(pt);
}
}
free(*cnf);
*cnf = NULL;
}
} /**
* 打印当前Config对象
**/
void print_config(Config *cnf)
{
Data *p = cnf->data; // 循环打印结果
while (NULL != p) {
printf("[%s]\n",p->section); Option *q = p->option;
while (NULL != q) {
printf("%s%c%s\n", q->key, cnf->separator, q->value);
q = q->next;
}
p = p->next;
}
} /**
* 主程序,放在最底下
* 避免重复声明其他函数
**/
int main(int argc, char *argv[])
{
// 读取配置文件cnf.ini,注释字符为#,分隔键值字符为=
Config *cnf = cnf_read_config("cnf.ini", '#', '=');
if (NULL == cnf) {
return -1; /* 创建对象失败 */
} printf("-------------- After Read File --------------\n");
print_config(cnf); // 打印cnf对象
cnf_remove_section(cnf,"AAA"); // 删除AAA的section
cnf_remove_option(cnf, "CC","df"); // 删除CC下的df
printf("-------------- After remove --------------\n");
print_config(cnf); // 打印cnf对象
cnf_add_option(cnf, "NEW", "new_1", "true"); // 新增NEW下的new_1的值
cnf_add_option(cnf, "NEW", "new_2", "asdas"); // 新增NEW下的new_2的值
cnf_add_option(cnf, "NEW1", "new_2", "true");
printf("-------------- After add --------------\n");
print_config(cnf); // 打印cnf对象 cnf_get_value(cnf, "NEW1", "new_2"); // 获取NEW1下的new_2值
printf("cnf_get_value:%s,%d,%d,%f\n",cnf->re_string,cnf->re_int,cnf->re_bool,cnf->re_double); cnf->separator = ':'; // 将分隔符改成 : ,冒号
cnf_write_file(cnf, "cnf_new.ini", "write a new ini file!"); // 将对象写入cnf_new.ini文件
destroy_config(&cnf); // 销毁Config对象
return 0;
}

另外下面是运行的结果,至于读取文件和写入文件可以到我的github查看。

# .\a.exe
-------------- After Read File --------------
[DDD]
cv = 12
[CC]
df = wer
mnxcv = true
zxc = 34
[BB]
xcv = 124x
[AAA]
bvcv = 5464
dbc = 123
-------------- After remove --------------
[DDD]
cv = 12
[CC]
mnxcv = true
zxc = 34
[BB]
xcv = 124x
-------------- After add --------------
[NEW1]
new_2 = true
[NEW]
new_2 = asdas
new_1 = true
[DDD]
cv = 12
[CC]
mnxcv = true
zxc = 34
[BB]
xcv = 124x
cnf_get_value:true,0,1,0.000000 E:\study\c\IniConfig (master)
# cat cnf_new.ini
# write a new ini file! [NEW1]
new_2 : true
[NEW]
new_2 : asdas
new_1 : true
[DDD]
cv : 12
[CC]
mnxcv : true
zxc : 34
[BB]
xcv : 124x

C语言实现和 *.ini文件。的更多相关文章

  1. 【WPS】表格使用VBA宏编程写入ini文件实现软件多语言

    前言:公司软件最近在做多语言版本,而又来一个西班牙文版本的,之前已经做过中文版本,英文版本和法文版本,之前是同事做的,现在安排我做,之前的做法,使用wps表格,翻译好,然后一个一个复制粘贴到ini文件 ...

  2. INI文件的读取(C语言:GetPrivateProfileString/GetPrivateProfileInt)

    INI文件格式说明 /********************************************* ini文件说明 ini文件是文本文件,由节点(Section)和键值对(key=val ...

  3. C语言实现<读取>和<写入> *.ini文件(转)

    原地址:https://blog.csdn.net/niha1993825jian/article/details/41086403 #include <stdio.h> #include ...

  4. C语言实现 读取写入ini文件实现(转)

    #include <stdio.h> #include <string.h> /* * 函数名: GetIniKeyString * 入口参数: title * 配置文件中一组 ...

  5. 创建Unicode格式的INI文件

    前段时间由于开发一个软件,需要调用别人的接口,虽然我的软件是Unicode编码,对方的模块也是Unicode编码,但是对方提供的接口却是Ansi接口,在非中文系统下,由于涉及到中文路径,导致Ansi和 ...

  6. 【vbs】vbs写ini文件

    这两天在折腾给一个项目打安装包,第一次接触软件打包,用的Advanced Installer(以下简称AI),应该说如果安装过程没有特殊动作(常规动作指释放文件.写注册表.建快捷方式等)的话,倒挺傻瓜 ...

  7. C#读写ini文件操作

    ini文件,是windows操作系统下的配置文件,ini文件是一种按照特点方式排列的文本文件,它的构成分为三部分,结构如下: [Section1] key 1 = value2 key 1 = val ...

  8. MySQL数据库安装,配置My.ini文件

    最近在做项目开发时用到了MySql数据库,在看了一些有关MySql的文章后,很快就上手使用了.在使用的过程中还是出现了一些问题,因为使用的是绿色免安装版的MySql所以在配置的时候出现了一些问题,该篇 ...

  9. C#实现.ini文件读写操作

    1.ini文件是什么?        见百度百科:https://baike.baidu.com/item/ini%E6%96%87%E4%BB%B6/9718973?fr=aladdin 2.C#语 ...

随机推荐

  1. leetcode刷题-64最小路径和

    题目 给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小. 说明:每次只能向下或者向右移动一步. 示例: 输入:[  [1,3,1],  [1,5, ...

  2. 记一次奇怪的cookie丢失

    .net给Image控件设置一个空图片路径的时候出现丢失cookie 比如说, img_path.ImageUrl ="../"+  ds.Tables[0].Rows[0][&q ...

  3. SpringCloud-config分布式配置

    为什么要统一管理微服务配置? 随着微服务不断的增多,每个微服务都有自己对应的配置文件.在研发过程中有测试环境.UAT环境.生产环境,因此每个微服务又对应至少三个不同环境的配置文件.这么多的配置文件,如 ...

  4. 在 Flutter 中使用 TensorFlow Lite 插件实现文字分类

    如果您希望能有一种简单.高效且灵活的方式把 TensorFlow 模型集成到 Flutter 应用里,那请您一定不要错过我们今天介绍的这个全新插件 tflite_flutter.这个插件的开发者是 G ...

  5. Linux实战(20):Docker部署EKL入门环境记录文档

    安装环境: centos7 ,EKL全套为7.5.2版本 前期工作 拉取已下三个镜像 docker.io/logstash 7.5.2 b6518c95ed2f 6 months ago 805 MB ...

  6. java学习1day

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  7. win10 系统出现“你不能访问此共享文件夹,因为你组织的安全策略阻止未经身份验证的来宾访问。”

    公司无法访问共享 \192.168.7.101.win+R 输入gpedit.msc2.计算机配置-管理模板–网络-lanman 工作站-找到"启用不安全的来宾登录"选择 已启用. ...

  8. MySQL的共享锁阻塞会话案例浅析输入日志标题

        这是问题是一个网友遇到的问题:一个UPDATE语句产生的共享锁阻塞了其他会话的案例,对于这个案例,我进一步分析.总结和衍化了相关问题.下面分析如有不对的地方,敬请指正.下面是初始化环境和数据的 ...

  9. 基于bellman-ford算法使用队列优化的spfa求最短路O(m),最坏O(n*m)

    acwing851-spfa求最短路 #include<iostream> #include<cstring> #include<algorithm> #inclu ...

  10. 一分钟理解Redo Undo

    数据库中有一种特殊的"日志文件"叫 Redo(重做) Undo(撤销),传统意义上的日志文件是记录系统运行状态的,主要用于系统工程师或者程序员排错.而 Reod/Undo 文件是数 ...