C/C++ 实现INI配置文件读写

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

#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;
}
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: INIInitial Input)配置文件是一种常用的配置文件格式,用于存储和读取程序的配置信息。以下是读INI配置文件的步骤: 1. 读取INI配置文件:首先,我们需要打开INI配置文件,并逐行读取文件内容。可以使用适当的函数或库来实现文件读取操作。在读取过程中,我们需要解析每一行内容,并提取所需的配置信息。 2. 解析INI配置文件:在解析每一行配置信息之前,我们还需要进行一些校验和处理。首先,我们需要忽略注释行(行首以分号 “;” 或井号 “#” 开头的行),然后我们需要忽略空行。接下来,我们可以使用适当的函数或算法来解析每一行的配置信息。一般来说,每一行配置信息的格式为“键=值”的形式,我们需要提取出键和对应的值。 3. 存储INI配置文件:如果需要修改INI配置文件或添加新的配置项,我们需要打开INI配置文件,并入新的配置信息。首先,我们需要读取原有的配置信息,并将其保存到内存中。然后,我们可以使用合适的函数或库来修改配置信息或添加新的配置项。最后,我们将修改后的配置信息INI配置文件。 4. 错误处理:在读INI配置文件的过程中,如果遇到错误,例如文件不存在、文件权限不足、文件格式错误等,我们需要进行适当的错误处理。可以根据实际情况输出错误信息或进行其他操作。 总结:读INI配置文件是一种常见的配置文件操作方法,需要按照一定的步骤进行。通过逐行读取和解析配置文件内容,可以实现读取INI配置文件的功能。通过修改和添加配置信息,并将其配置文件,可以实现存储INI配置文件的功能。在进行读操作时,还需要注意错误处理,以保证程序的稳定性和可靠性。 ### 回答2: INI配置文件是一种常见的配置文件格式,用于存储程序的配置信息。C语言提供了一些函数库用于读INI文件,包括<Windows.h>头文件中的GetPrivateProfileString、GetPrivateProfileInt、WritePrivateProfileString等函数。 首先,要读取INI配置文件中的值,可以使用GetPrivateProfileString函数。该函数的参数包括INI文件的路径、节名和键名,同时可以传入一个缓冲区,函数将根据配置文件中的键值将对应的值复制到缓冲区中。 另外,如果配置文件中的值是整数类型的,可以使用GetPrivateProfileInt函数,该函数会直接返回键值对应的整数值。 如果需要INI配置文件,可以使用WritePrivateProfileString函数。该函数的参数包括INI文件的路径、节名、键名和值。调用该函数后,程序会将键值对按照指定的格式配置文件中。 在使用这些函数前,需要先加载Windows.h头文件。同时,在使用INI配置文件时,需要注意配置文件的路径是否正确,以及节名和键名是否正确。读取INI文件时,还要判断读取的值是否为空,以避免出现错误。 总结起来,C语言提供了一些函数用于读INI配置文件,这些函数可以方便地读取和配置信息。通过合理的使用这些函数,可以更好地管理程序的配置,提升程序的可配置性和可维护性。 ### 回答3: INIInitialization)是一种常见的配置文件格式,用于存储应用程序的设置和参数。在C语言中,可以通过读INI配置文件实现配置文件的加载和保存。 在读取INI配置文件时,可以使用标准库函数fopen()来打开文件,然后通过fgets()函数逐行读取文件内容。读取到的每一行都可以通过字符串处理函数来分割成键值对。常用的字符串处理函数包括strtok()、strstr()和sscanf()等。使用时需要根据INI配置文件的具体格式和规范来进行相应的处理。 在INI配置文件时,可以使用标准库函数fopen()来创建或打开文件,然后使用fprintf()函数来向文件入内容。需要注意的是,INI配置文件时需要按照一定的格式和规范来进行入,以保证配置文件的正确性和可读性。 读INI配置文件的步骤主要包括以下几个方面: 1. 打开INI配置文件:使用fopen()函数打开INI配置文件。 2. 读取INI配置文件内容:使用fgets()函数逐行读取配置文件内容,并使用字符串处理函数处理每一行的内容。 3. 解析INI配置文件内容:根据INI配置文件的格式和规范,将每一行的内容解析成键值对,并存储到相应的数据结构中。 4. 关闭INI配置文件:使用fclose()函数关闭INI配置文件,释放资源。 5. 根据需要进行数据操作:根据存储的键值对,进行相应的数据操作,如设置应用程序的参数、初始化等。 6. INI配置文件:使用fprintf()函数向INI配置文件入数据,按照INI配置文件的格式和规范进行入。 总结:通过使用C语言提供的标准库函数,可以实现INI配置文件的读取和入。读取时需要逐行读取并解析每一行的内容,入时需要按照INI配置文件的格式进行入。读INI配置文件是一种常见的配置文件处理方式,具有简单、灵活的优点,适用于各种应用程序的配置管理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值