C語言讀寫ini文件


碼雲地址

主要是使用了鏈表保存ini文件的內容,在程序運行最初會初始化鏈表,接下來的查詢操作都只是查詢內存,比較快,而且此時也不依賴文件了,同時還支持寫入ini文件。
下面是源代碼,個人感覺還不錯。歡迎大家使用。
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>

#define  MAX_VALUE  1024 /* 讀取文件緩存行最大長度 */
// 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;           /* 對應鍵 */
  char    *value;         /* 對應值 */
  struct  _option *next;  /* 鏈表連接標識 */
}Option;

typedef struct _data {
  char    *section;    /* 保存section值 */
  Option  *option;     /* option鏈表頭  */
  struct  _data *next; /* 鏈表連接標識  */
}Data;

typedef struct {
  char    comment;    /* 表示注釋的符號    */
  char    separator;  /* 表示分隔符        */
  char    *re_string; /* 返回值字符串的值  */
  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); /* 申請內存錯誤 */
    }
    /* 動態申請section內存 */
    ps->section = (char*)malloc(sizeof(char) * (strlen(section)+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); /* 申請內存錯誤 */
    }
    /* 動態申請key內存 */
    q->key = (char*)malloc(sizeof(char) * (strlen(key)+1));
    strcpy(q->key, key);
    q->next = p->option; /*這里p->option可能為NULL,不過也沒關系 */
    p->option = q; /* 頭插法插入鏈表 */
    /* 動態申請value內存 */
    q->value = (char*)malloc(sizeof(char) * (strlen(value)+1));
  } else if (strlen(q->value) < strlen(value)) {
    /* 當新值長度小於舊值,重新申請內存 */
    q->value = (char*)realloc(q->value, sizeof(char) * (strlen(value)+1));
  }
  strcpy(q->value, value); /* 無論如何要把值改了 */
  return true;
}

/**
* 按照參數去除字符串左右空白
**/
char *trim_string(char *string,int mode)
{
  char *left = string;
  if ((mode & TRIM_LIFT) != 0) { // 去除左邊空白字符
    for (;*left != '\0'; left++) {
      if (0 == isspace(*left)) {
        break;
      }
    }
  }
  if ((mode & TRIM_RIGHT) != 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;
  }

  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->key);
  free(p->value);
  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 */
    free(o->key);
    free(o->value);
    ot = o;
    o = o->next;
    free(ot);
  }
  free(p->option); /* 釋放內存 */
  free(p);         /* 釋放刪除的section */
  q = p = NULL;  // 避免野指針

  return true;
}

/**
* 銷毀Config對象
* 刪除所有數據
**/
void destroy_config(Config **cnf)
{
  if (NULL == *cnf) return;

  if (NULL != (*cnf)->data)
  {
    Data *pt, *p = (*cnf)->data;
    Option *qt, *q;
    while (NULL != p) {
      q = p->option;
      while (NULL != q) {
        free(q->key);
        free(q->value); /* 釋放內存 */

        qt = q;
        q = q->next;
        free(qt);
      }
      free(p->section); /* 釋放內存 */

      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
  cnf_remove_option(cnf, "CC","zxc");  // 刪除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", "abc"); // 新增NEW下的new_2的值
  cnf_add_option(cnf, "NEW", "new_2", "123456789"); // 覆蓋之前的值
  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;
}

另外下面是運行的結果。

# .\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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM