C语言读写ini配置文件

news2024/11/17 8:33:45

环境

  • windows 10 64bit

  • Clion 2023.1

ini简介

ini 文件格式是一种用于保存配置信息的简单文本格式。它通常由多个节(section)组成,每个节包含多个键值对(key-value pair)。

下面是 ini 文件的基本语法规则

  • 一个ini文件由多个节组成,每个节用方括号([])括起来

  • 节后面可以跟一个或多个键值对,每个键值对占一行,格式为key=value

  • 键值对中,键(key)和值(value)之间用等号(=)分隔,键和值之间可以有空格。键和值都是字符串,可以包含任何字符,但是如果键或值中包含等号(=)或分号(;)这样的特殊字符,需要进行转义

  • 注释以井号(#)或分号(;)开头,注释可以出现在任何位置,包括行首、行尾或键值对之间

下面是一个 ini 示例文件,后面代码也会去解析它

# 服务器配置
[server]
ip = 192.168.1.100
port = 8080

;数据库配置
[database]
host = localhost
port = 3306
username = username
password = password

在这个示例文件中,有两个节:serverdatabaseserver 包含2个键值对:ipport,而 database 包含4个键值对,hostportusernamepassword。最后,还有2行注释,一行以 # 开头,另一行以 ; 开头。

代码示例

这里使用 Clion 集成开发环境,编译器使用自带的 MinGW,首先创建一个 C 可执行项目,命名为 Demo

创建完成后,添加一个头文件 ini_parser.h,内容如下

#ifndef DEMO_INI_PARSER_H
#define DEMO_INI_PARSER_H

#include <stdio.h>
#include <string.h>

// 获取key对应的值
int GetIniKeyString(char *section,char *key,char *filename,char *buf);

// 修改key对应的值
int PutIniKeyString(char *section,char *key,char *val,char *filename);

#endif //DEMO_INI_PARSER_H

接着新建一个 C 源码文件 ini_parser.c,内容如下

#include <stdio.h>
#include <string.h>
#include "errno.h"

/*
* 参数:
* section:  配置文件中的节sectin
* key:      配置项的标识
* filename: ini配置文件路径
*
* 返回值:    找到需要的值返回结果0,否则返回-1
*/
int GetIniKeyString(char *section, char *key, char *filename, char *buf)
{
    FILE *fp;

    // 用来标记是否找到section
    int flag = 0;
    char sSection[64], *wTmp;
    char sLine[1024];

    // 节section字符串
    sprintf(sSection, "[%s]", section);

    if (NULL == (fp = fopen(filename, "r")))
    {
        printf("open %s failed.\n", filename);
        return -1;
    }

    // 读取ini中的每一行
    while (NULL != fgets(sLine, 1024, fp))
    {
        // 处理ini文件中的注释行
        if ('#' == sLine[0])
            continue;

        if (';' == sLine[0])
            continue;

        // 定位=的位置
        wTmp = strchr(sLine, '=');
        if ((NULL != wTmp) && (1 == flag))
        {
            if (0 == strncmp(key, sLine, strlen(key)))
            {
                sLine[strlen(sLine) - 1] = '\0';

                while (*(wTmp + 1) == ' ')
                {
                    wTmp++;
                }

                // 获取key对应的value
                strcpy(buf, wTmp + 1);

                fclose(fp);
                return 0;
            }
        }
        else
        {
            if (0 == strncmp(sSection, sLine, strlen(sSection)))
            {
                // 不存在键值对的情况下,标记flag
                flag = 1;
            }
        }
    }

    fclose(fp);
    return -1;
}


/*
* 参数:
* section:  配置文件中的节sectin
* key:      配置项的标识
* val:      配置项标识对应的值
* filename: ini配置文件路径
*
* 返回值:    成功返回结果0,否则返回-1
*/
int PutIniKeyString(char *section, char *key, char *val, char *filename)
{
    FILE *fpr, *fpw;
    int flag = 0;
    int ret;
    char sLine[1024], sSection[32], *wTmp;

    sprintf(sSection, "[%s]", section);

    if (NULL == (fpr = fopen(filename, "r")))
        return -1;

    // 临时文件名
    sprintf(sLine, "%s.tmp", filename);

    fpw = fopen(sLine, "w");
    if (NULL == fpw)
        return -1;

    while (NULL != fgets(sLine, 1024, fpr))
    {
        if (2 != flag)
        {
            wTmp = strchr(sLine, '=');
            if ((NULL != wTmp) && (1 == flag))
            {
                if (0 == strncmp(key, sLine, strlen(key)))
                {
                    // 找到对应的key
                    flag = 2;
                    sprintf(wTmp + 1, " %s\n", val);
                }
            }
            else
            {
                if (0 == strncmp(sSection, sLine, strlen(sSection)))
                {
                    // 找到section的位置
                    flag = 1;
                }
            }
        }

        // 写入临时文件
        fputs(sLine, fpw);
    }

    fclose(fpr);
    fclose(fpw);

    sprintf(sLine, "%s.tmp", filename);

    // rename函数在windows上和linux上表现有差异,看文章中的备注
    ret = rename(sLine, filename);
    if (ret != 0)
    {
        if (errno == EEXIST)
        {
            // 如果目标文件已经存在,需要先删除,再重命名
            if (remove(filename) == 0)
            {
                if (rename(sLine, filename) == 0)
                {
                    // printf("File %s has been renamed to %s\n", sLine, filename);
                    return 0;
                }
            }
        }
    }

    return ret;
}

最后编辑工程入口文件 main.c

#include <stdio.h>
#include "ini_parser.h"

int main(int argc, char const *argv[]) {
    char buff[128];
    int ret;

    ret = GetIniKeyString("server", "ip", "config.ini", buff);
    printf("get ret:%d,value: %s\n", ret, buff);

    memset(buff, 0, sizeof(buff));
    ret = GetIniKeyString("database", "db", "config.ini", buff);
    printf("get ret:%d, value: %s\n", ret, buff);

    ret = PutIniKeyString("server", "port", "80", "config.ini");
    printf("put ret:%d\n", ret);

    memset(buff, 0, sizeof(buff));
    ret = GetIniKeyString("server", "port", "config.ini", buff);
    printf("get ret:%d, value: %s\n", ret, buff);
    return 0;
}

使用 IDE 的好处就是,在添加各个文件时,它会帮你修改 CMakeLists.txt,不需要自己手动去添加

cmake_minimum_required(VERSION 3.25)
project(Demo C)

set(CMAKE_C_STANDARD 99)

add_executable(Demo main.c ini_parser.h ini_parser.c)

然后,就可以编译整个工程了,选中 IDE 顶部菜单栏中的 Build --> Build Project,完成后,会在目录 cmake-build-debug 下生成可执行文件 Demo.exe,在运行之前,还需要将配置 config.ini(也就是上文提到的ini示例文件) 也放到 cmake-build-debug

最后,按下快捷键 Shift+F10 来运行一下

2699646c46df2a99fe1c7c0f355b4c08.jpeg

ini in c lanuage

备注

rename 方法在 windowslinux 上的表现不一样,如果目标文件存在,linux 可以直接覆盖。但是,在 windows 上,返回值就是-1,errno 提示 File Exists

参考资料

  • https://www.w3resource.com/c-programming/stdio/c_library_method_rename.php

c07aa484e4fc5331cb2c5216539de200.jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/661483.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《Java黑皮书基础篇第10版》 第17章【笔记】

第十七章 二进制I/O 17.1 引言 文件可以不严谨的分类为文本文件和二进制文件。文本文件指的是可以用文件编辑器进行查看和修改的&#xff0c;二进制文件则不可以使用文本编辑器查看和修改。 例如&#xff0c;Test.java文件储存在文本文件中&#xff0c;因此可以用文本编辑器…

MapReduce程序基本架构

MapReduce程序是以&#xff08;键/值&#xff09;对的形式来处理数据的&#xff0c;即可以通过以下的形式来表示&#xff1a; map: (K1,V1) ➞ list(K2,V2) reduce: (K2,list(V2)) ➞ list(K3,V3) 不令人惊奇的是&#xff0c;这是一种超越一般数据的数据流表示形式。在本文中…

使用PyMC进行时间序列分层建模

在统计建模领域&#xff0c;理解总体趋势的同时解释群体差异的一个强大方法是分层(或多层)建模。这种方法允许参数随组而变化&#xff0c;并捕获组内和组间的变化。在时间序列数据中&#xff0c;这些特定于组的参数可以表示不同组随时间的不同模式。 今天&#xff0c;我们将深…

ood的5C解题法(1)----管理类面试对象设计

管理类 概念 可以模拟/代替管理员日常工作的系统 下面用停车场系统做演示 答题流程 Clarify What&#xff1a;除题目中的名词外&#xff0c;从管理的名词考虑 parking lot是什么类型的&#xff1f;如果楼有多层&#xff0c;停车位也是多层&#xff0c;则parking lot->pa…

Windows Server 2019 OVF, updated Jun 2023 (sysin) - VMware 虚拟机模板

Windows Server 2019 OVF, updated Jun 2023 (sysin) - VMware 虚拟机模板 2023 年 6 月版本更新&#xff0c;现在自动运行 sysprep&#xff0c;支持 ESXi Host Client 部署 请访问原文链接&#xff1a;https://sysin.org/blog/windows-server-2019-ovf/&#xff0c;查看最新…

5、产品经理的工作职责OR主要工作技能和工具

1、产品经理的工作职责 我们通过一个案例来了解产品经理的工作职责。 老板让你给他点餐&#xff0c;你应该怎么做&#xff1f;你需要考虑哪一些方面的问题&#xff1f; 例如&#xff1a;你预算多少&#xff0c;预算是十块钱还是100块还是1000块。有没有忌口&#xff0c;口味…

【MYSQL篇】Update语句原理详解

文章目录 前言缓冲池Buffer PoolInnoDB 内存结构redo logundo logBinlog 总结 前言 前面的文章我们已经对MySQL的查询语句的执行流程进行了说明&#xff0c;感兴趣的可以去看看&#xff1a; 【MySQL篇】Select语句原理详解 本篇文章我们来聊聊 MySQL更新语句的执行原理。更新…

Win7系统提示Windows Defender无法扫描选定的文件解决方法

Win7 64位系统提示“Windows Defender无法扫描选定的文件”怎么办呢?使用Windows Defender扫描文件,结果弹出如下图窗口,该怎么解决呢,参考下文,一起来解决Win7系统提示“Windows Defender无法扫描选定的文件”的解决方法。 原因分析: 这是因为开启Defender扫描压…

java的序列化注解Serial、序列化版本号serialVersionUID

例如&#xff0c;jdk源码NTLMException类的定义&#xff0c;其中涉及到了序列化注解Serial和序列化版本号字段serialVersionUID&#xff1a; 序列化注解java.io.Serial&#xff1a; 序列化注解java.io.Serial是在javaSE-14版本引入的。通常注解实现了序列化类的序列化相关的函…

【JUC进阶】02. volatile关键字

目录 1、回顾JMM 1.1、可见性&#xff08;Visibility&#xff09; 1.2、原子性&#xff08;Atomicity&#xff09; 1.3、有序性&#xff08;Ordering&#xff09; 2、volatile 2.1、保证可见性 2.2、不保证原子性 2.3、防止指令重排 2.4、什么时候使用volatile 3、小…

微服务中「组件」集成

有品&#xff1a;There is no silver bullet&#xff1b; 一、简介 在微服务工程的技术选型中&#xff0c;会涉及到很多组件的集成&#xff0c;最常用包括&#xff1a;缓存、消息队列、搜索、定时任务、存储等几个方面&#xff1b; 如果工程是单服务&#xff0c;对于集成组件…

有趣的数学 关于自然常数e

一、e的值 自然常数&#xff08;也称欧拉数&#xff09;e是数学中最重要的数字之一。 2.7182818284590452353602874713527...... 二、从复利理解e 设想你在一家银行有一个银行账户&#xff0c;该银行付给你一个慷慨的利息年利率12%,一年计一次复利&#xff0e;你将一笔初始存款…

测试(二)

1.软件测试的生命周期 需求分析→测试计划→ 测试设计→ 测试开发→ 测试执行→ 测试评估 2.如何描述一个Bug 3.Bug的优先级 1、Blocker&#xff08;崩溃&#xff09;&#xff1a; 阻碍开发或测试工作的问题&#xff1b;造成系统崩溃、死机、死循环&#xff0c;导致数据库数…

Windows Server 2016 OVF, updated Jun 2023 (sysin) - VMware 虚拟机模板

2023 年 6 月版本更新&#xff0c;现在自动运行 sysprep&#xff0c;支持 ESXi Host Client 部署 请访问原文链接&#xff1a;https://sysin.org/blog/windows-server-2016-ovf/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org…

Kubernetes 纯理论 贼干篇

Kubernetes理论 docker 容器引擎 docker compose 单机编排工具 docker swarm Docker容器多机编排工具&#xff0c;实现Docker容器的集群管理调度的工具 k8s 容器多机编排工具&#xff0c;占据80%以上的市场份额 mesos marathon mesos:分布式资管管理框架&#xff0c;可以对…

2019年全国硕士研究生入学统一考试管理类专业学位联考写作试题

写作:第56&#xff5e;57小题&#xff0c;共65分。其中论证有效性分析30分&#xff0c;论说文35分。 56&#xff0e;论证有效性分析 分析下述论述中存在的缺陷和漏洞&#xff0c;选择若干要点&#xff0c;写一篇600字左右的文章&#xff0c;对论证的有效性进行分析和评论。(论…

Linux终端与进程的关系 ( 1 ) -【Linux通信架构系列】

系列文章目录 C技能系列 Linux通信架构系列 C高性能优化编程系列 深入理解软件架构设计系列 高级C并发线程编程 期待你的关注哦&#xff01;&#xff01;&#xff01; 现在的一切都是为将来的梦想编织翅膀&#xff0c;让梦想在现实中展翅高飞。 Now everything is for the…

案例:从定性原因分析上升到定量原因分析

在定量原因分析时&#xff0c;主要是有四种定量思考的方法&#xff1a; 1、数据的居中趋势与离散程度分析&#xff1a;均值、标准差 2、 80-20分析&#xff1a;在所有的构成成分中&#xff0c;哪个成分占比最大 3、数据的相关性分析&#xff1a;是否存在强相关 4、敏感性分…

[进阶]Java:文件字符输入流、文件字符输出流

问&#xff1a;字节流读取中文输出可能会存在什么问题&#xff1f; 会乱码。或者内存溢出。 读取中文输出&#xff0c;哪个流更合适&#xff0c;为什么&#xff1f; 字符流更合适&#xff0c;最小单位是按照单个字符读取的。 代码演示如下&#xff1a; public class FileR…

[C++]vs2019运行c++报错:错误 C1075 “{”: 未找到匹配令牌

源码是从git拉下来的&#xff0c;但是我并没有改任何东西&#xff0c;结果报错超过100个&#xff0c;这个很明显不是代码问题&#xff0c;最后发现需要把LF换成CRLF&#xff0c;修改方法很简单&#xff0c;就是VS2019打开源代码右下角切换即可。如图 错误原因就是github下载的源…