首页 > 编程语言 >C/C++ 实现INI配置文件读写 [转载]

C/C++ 实现INI配置文件读写 [转载]

时间:2022-10-02 14:38:35浏览次数:75  
标签:char option 配置文件 section C++ cnf INI key NULL

INI文件是一种标准的Windows平台配置文件,通常这种配置文件用于保存系统软件的一些基本配置参数,如下代码是本人从网络上收集到的一段纯C++编写的配置解析代码,感觉使用起来很方便,所以放到这里分享一下。

INI文件是一种标准的Windows平台配置文件,通常这种配置文件用于保存系统软件的一些基本配置参数,如下代码是本人从网络上收集到的一段纯C++编写的配置解析代码,感觉使用起来很方便,所以放到这里分享一下。

原作者GitHUB地址(感谢作者的无私奉献):​​https://github.com/jan-bar/ConfigIni​

#define  _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>
#include <Windows.h>
#include <string.h>

#define MAX_VALUE 64 /* 定义节字符串最大长度*/
#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))
{
/* 参数不正确,返回false */
return false;
}

/* 让变量p循环遍历data,找到对应section */
Data* p = cnf->data;
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))
{
/* 遍历option,检查key是否已经存在 */
q = q->next;
}

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)
{
/* 读文件错误直接按照错误码退出 */
exit(errno);
}

/* 保存一行数据到字符串 */
char* s, * e, * pLine, sLine[MAX_VALUE];

/* 缓存section,key,value */
char section[MAX_VALUE] = { '\0' }, key[MAX_VALUE], value[MAX_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)
{
/* 找到section */
*e = '\0';
strcpy(section, s);
}
}
else
{
s = strchr(pLine, separator);

/* 找到包含separator的行,且前面行已经找到section */
if (s != NULL && *section != '\0')
{
/* 将分隔符前后分成2个字符串 */
*s = '\0';
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)
{
/* 让变量p循环遍历data,找到对应section */
Data* p = cnf->data;
while (NULL != p && 0 != strcmp(p->section, section))
{
p = p->next;
}

/* 节点Section没有找到 */
if (NULL == p)
{
return false;
}

Option* q = p->option;
while (NULL != q && 0 != strcmp(q->key, key))
{
/* 遍历option,检查key是否已经存在 */
q = q->next;
}

/*节点key没有找到*/
if (NULL == q)
{
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)
{
/* 让变量p循环遍历data,找到对应section */
Data* p = cnf->data;
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))
{
/* 遍历option,检查key是否已经存在 */
q = q->next;
}
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)
{
/* 读文件错误直接按照错误码退出 */
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;

/* 让变量p循环遍历data,找到对应section */
q = p = cnf->data;
while (NULL != p && 0 != strcmp(p->section, section))
{
if (p != q)
{
/* 始终让q处于p的上一个节点 */
q = q->next;
}
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);
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[])
{
// 读取配置文件 注释字符为# 分隔字符为=
Config* cnf = cnf_read_config("d://config.ini", '#', '=');
if (NULL == cnf)
{
return -1;
}

// 新增一些配置项(可用于修改配置项)
cnf_add_option(cnf, "Server", "Address", "192.168.1.1");
cnf_add_option(cnf, "Server", "Port", "99");
cnf_add_option(cnf, "Server", "Enabled", "True");

cnf_add_option(cnf, "Client", "ClientID", "10001");
cnf_add_option(cnf, "Client", "ClientName", "lyshark");
cnf_add_option(cnf, "Client", "ClientEnabled", "False");

// 读入配置项中的值
cnf_get_value(cnf, "Server", "Address");
printf("读入IP地址: %s \n", cnf->re_string);

cnf_get_value(cnf, "Server", "Port");
printf("读入端口: %d \n", cnf->re_int);

cnf_get_value(cnf, "Client", "ClientEnabled");
printf("读入状态: %s \n", cnf->re_string);

// 修改配置项中的值
cnf_add_option(cnf, "Server", "Address", "192.168.1.100");

// 删除配置项
cnf_remove_option(cnf, "Server", "Eanbled"); // 删除Enabled标签
cnf_remove_section(cnf, "Client"); // 删除整个标签

// 写出文件
bool ref = cnf_write_file(cnf, "d://config_new.ini", "配置文件解析 Ver 1.0");
if (ref == 1)
{
std::cout << "已写出" << std::endl;
destroy_config(&cnf);
}

return 0;
}

文章作者:​lyshark​​​ (王瑞)

标签:char,option,配置文件,section,C++,cnf,INI,key,NULL
From: https://blog.51cto.com/lyshark/5729301

相关文章

  • C++实现双向RRT算法
    C++实现双向RRT算法背景介绍RRT(Rapidly-exploringRandomTrees)是StevenM.LaValle和JamesJ.KuffnerJr.提出的一种通过所及构建空间搜索树实现对非凸高维空间快速搜......
  • [ 数据结构 - C++]红黑树RBTree
    在上篇文章我们了解了第一种平衡二叉搜索树AVL树,我们知道AVL树是通过平衡因子来控制左右子树高度差,从而将二叉树变成一颗平衡二叉搜索树。本篇文章我们将要了解另外一种平衡......
  • c++的四种类型转换
    const_cast<> 表示消除const属性static_cast<> 编译时就检查、没鸟用reinterpret_cast<>表示两个没关系的类型转换dynamic_cast<>运行时转换、父转子 总结:没......
  • c++ string类 和c 风格string 的问题梳理
    1.互相转换:c++---> c:           cppstr.c_str()c   ---> c++: stringcppstr=string(cstr)打印时、co......
  • c++ const 总结
    1.基本的定义一个常量 constinta=xxx2.constchar*p,char*constp区别前者表示指向的内容不能用p修改后者表示p不能指向别处const......
  • UE C++教程之接口 UINTERFACE
    我是谁不重要,重要的是,我能做什么。近期笔者在进行UE的开发时,实现多武器的换弹与开火需要用到接口。而笔者以前是做Unity开发的,遂没有使用过UEC++的UINTERFACE,而这个接......
  • C++实现二分法求零点
    ​ 目录 前言题目:一、零点是什么?二、二分法求零点1.二分法2.完整代码总结 前言首先,我们要清楚我们是干嘛的;其次,知道原理;最后,才能明白自己要怎么办。明确:......
  • c++ vector
    创建vectorvector的几个别名:向量、动态数组头文件:#include<vector>记得加上std命名空间,不然会报错usingnamespacestd;创建vectorvector<int>A;//一维动态数组......
  • C++智能指针
    C++智能指针需要头文件<memory>不需要手动释放指针不是所有指针都能封装成智能指针,很多时候原始指针更加方便。std::unique_ptr任何时刻都只能有一个指针管理内存......
  • C++ 编程中常用的英文单词(首字母是A、B、C开头)
    学习编程不一定需要英语水平很高,能记住认识一些常用的英文单词也可以,有看不明白的文档资料也可以使用翻译工具,编写代码时大部分好用的IDE都是有代码提示的。本文主要介绍C+......