当前位置:   article > 正文

[C++]C语言读取写入CSV文件 [三] 进阶篇读取CSV文件_c++读取csv文件数据

c++读取csv文件数据

本系列介绍了如何利用C语言读取写入CSV文件,本篇是系列的第二篇,介绍了利用C语言读取CSV文件的进阶内容,包括如何处理读取得到的数据、利用结构体保存数据、识别被包裹的字段、处理字段开头和结尾处的空格和制表符、应对其他分隔符、和介绍了现有的一个支持读取CSV的库。

本系列文章目录

[一] 基础篇

[二] 进阶篇——写入CSV

[三] 进阶篇——读取CSV

处理读取得到的数据

在基础篇中,仅仅是将数据读取出来然后输出,并未将其转换为相应的数据类型。对于整数,我们可以使用 atoi()atol()atoll() 函数分别将字符串转换为 intlonglong long类型;对于浮点数,我们可以使用 atof() 函数将字符串转换为 double 类型;而对于字符串,我们只需要使用 strdup() 进行复制一下即可。

利用结构体来保存数据

在同一个 CSV 中的数据是具有相关性的,因此最好的方式是将构建一个结构体,利用结构体的成员来记录CSV文件不同列的数据。例如 CSV 文件内容如下:

  1. ID,Name,Points
  2. 1,qwe,1.1
  3. 2,asd,2.200000

可以用如下的结构体进行记录:

  1. struct student {
  2. int id;
  3. char *name;
  4. double point;
  5. };

结合上一小节处理读取得到的数据,那么最后的代码如下:

  1. // 3-1.c
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. char* get_field(char *line, int num);
  6. char* remove_quoted(char *str);
  7. struct student {
  8. int id;
  9. char *name;
  10. double point;
  11. };
  12. void print_student_info(struct student *stu);
  13. int main()
  14. {
  15. FILE *fp = fopen("tmp.csv", "r");
  16. if (fp == NULL) {
  17. fprintf(stderr, "fopen() failed.\n");
  18. exit(EXIT_FAILURE);
  19. }
  20. char row[80];
  21. char *token;
  22. fgets(row, 80, fp);
  23. char *header_1 = get_field(strdup(row), 1);
  24. char *header_2 = get_field(strdup(row), 2);
  25. char *header_3 = get_field(strdup(row), 3);
  26. printf("%s\t%s\t%s", header_1, header_2, header_3);
  27. char *tmp;
  28. struct student stu;
  29. while (fgets(row, 80, fp) != NULL) {
  30. tmp = get_field(strdup(row), 1);
  31. stu.id = atoi(tmp);
  32. tmp = get_field(strdup(row), 2);
  33. stu.name = strdup(tmp);
  34. tmp = get_field(strdup(row), 3);
  35. stu.point = atof(tmp);
  36. print_student_info(&stu);
  37. }
  38. fclose(fp);
  39. return 0;
  40. }
  41. char* get_field(char *line, int num)
  42. {
  43. char *tok;
  44. tok = strtok(line, ",");
  45. for (int i = 1; i != num; i++) {
  46. tok = strtok(NULL, ",");
  47. }
  48. char *result = remove_quoted(tok);
  49. return result;
  50. }
  51. char* remove_quoted(char *str)
  52. {
  53. int length = strlen(str);
  54. char *result = malloc(length + 1);
  55. int index = 0;
  56. for (int i = 0; i < length; i++) {
  57. if (str[i] != '\"') {
  58. result[index] = str[i];
  59. index++;
  60. }
  61. }
  62. result[index] = '\0';
  63. return result;
  64. }
  65. void print_student_info(struct student *stu)
  66. {
  67. printf("%d\t%s\t%f\n", stu->id, stu->name, stu->point);
  68. }

运行上述代码得到的结果如下:

  1. $ clang 3-1.c -o 3-1
  2. $ ./3-1
  3. ID Name Points
  4. 1 qwe 1.100000
  5. 2 asd 2.200000

识别被包裹的字段

[二] 进阶篇——写入CSV中提到过包裹的概念,包裹的主要作用是为了能够让字段中包含一些特殊字符(如逗号、双引号等)。下面用包裹的字段中含有分隔符即逗号为例,来讲解如何识别被包裹的字段。

因为被包裹的字段中存在逗号,若再用 strtok() 函数来进行解析,则会将包裹的字段截断。因此处理方式应该为逐个去遍历字符串,当出现双引号(")时,作一个标记,直到再遇到下一个双引号时取消标记。编写了一个名为 char** get_field_arr(char *line) 的解析函数,返回的是一个字符串数组。在只给定某行CSV的字符串时,无法确定其存在的字段数量,进而无法分配合适的空间供保存结果,因此还需要另一个 int count_field(char *line) 函数来计算的字段数量。

处理字段开头和结尾处的空格和制表符

在本文中,我们采用 RFC 4180 标准中的规定,需要保留字段开头和结尾处的空格和制表符,具体实现上比不保留这些字符容易很多,只需要把空格和制表符视为普通的字符一样,进行保存即可。最后的代码如下:

  1. // 3-2.c
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. int count_field(const char *line);
  6. char** get_field_arr(const char *line);
  7. struct student {
  8. int id;
  9. char *name;
  10. double point;
  11. };
  12. void print_student_info(struct student *stu);
  13. int main()
  14. {
  15. const char *line = " \"4\",def,\"4.4\" \0";
  16. int count = count_field(line);
  17. char **result = get_field_arr(line);
  18. printf("--- Parse line result ---\n");
  19. for (int i = 0; i < count; i++) {
  20. printf("result[%d] = %s\n", i, result[i]);
  21. }
  22. struct student stu;
  23. stu.id = atoi(result[0]);
  24. stu.name = strdup(result[1]);
  25. stu.point = atof(result[2]);
  26. print_student_info(&stu);
  27. return 0;
  28. }
  29. int count_field(const char *line) {
  30. const char *p_line = line;
  31. int count = 1, is_quoted = 0;
  32. for (; *p_line != '\0'; p_line++) {
  33. if (is_quoted) {
  34. if (*p_line == '\"') {
  35. if (p_line[1] == '\"') {
  36. p_line++;
  37. continue;
  38. }
  39. is_quoted = 0;
  40. }
  41. continue;
  42. }
  43. switch(*p_line) {
  44. case '\"':
  45. is_quoted = 1;
  46. continue;
  47. case ',':
  48. count++;
  49. continue;
  50. default:
  51. continue;
  52. }
  53. }
  54. if (is_quoted) {
  55. return -1;
  56. }
  57. return count;
  58. }
  59. char** get_field_arr(const char *line) {
  60. int count = count_field(line);
  61. if (count == -1) {
  62. return NULL;
  63. }
  64. char **buf = malloc(sizeof(char*) * (count+1));
  65. if (buf == NULL) {
  66. return NULL;
  67. }
  68. char **pbuf = buf;
  69. char *tmp = malloc(strlen(line)+1);
  70. if (tmp == NULL) {
  71. free(buf);
  72. return NULL;
  73. }
  74. *tmp = '\0';
  75. char *ptmp = tmp;
  76. const char *p_line = line;
  77. int is_quoted = 0, is_end = 0;
  78. for (; ; p_line++) {
  79. if (is_quoted) {
  80. if (*p_line == '\0') {
  81. break;
  82. }
  83. if (*p_line == '\"') {
  84. if (p_line[1] == '\"') {
  85. *ptmp++ = '\"';
  86. p_line++;
  87. continue;
  88. }
  89. is_quoted = 0;
  90. }
  91. else {
  92. *ptmp++ = *p_line;
  93. }
  94. continue;
  95. }
  96. switch(*p_line) {
  97. case '\"':
  98. is_quoted = 1;
  99. continue;
  100. case '\0':
  101. is_end = 1;
  102. case ',':
  103. *ptmp = '\0';
  104. *pbuf = strdup(tmp);
  105. if (*pbuf == NULL) {
  106. for (pbuf--; pbuf >= buf; pbuf--) {
  107. free(*pbuf);
  108. }
  109. free(buf);
  110. free(tmp);
  111. return NULL;
  112. }
  113. pbuf++;
  114. ptmp = tmp;
  115. if (is_end) {
  116. break;
  117. } else {
  118. continue;
  119. }
  120. default:
  121. *ptmp++ = *p_line;
  122. continue;
  123. }
  124. if (is_end) {
  125. break;
  126. }
  127. }
  128. *pbuf = NULL;
  129. free(tmp);
  130. return buf;
  131. }
  132. void print_student_info(struct student *stu)
  133. {
  134. printf("--- Student info ---\n");
  135. printf("%d\t%s\t%f\n", stu->id, stu->name, stu->point);
  136. }

代码的运行结果如下所示:

  1. $ clang 3-2.c -o 3-2
  2. $ ./3-2
  3. --- Parse line result ---
  4. result[0] = 4
  5. result[1] = def
  6. result[2] = 4.4
  7. --- Student info ---
  8. 4 def 4.400000

其他分隔符

[二] 进阶篇——写入CSV中的最后,也提到在某些国家的CSV文件中,可能会使用分号(;)来作为分隔符,那么我们在解析CSV时只需要把原本判断逗号(,)的语句改变为分号(;)

使用库

最后,解析CSV文件更好地策略是使用别人已经写好的库,不要重复发明轮子!例如libcsv,其就是使用纯 ANSI C 写成的库,具体的安装方式可参考其主页,使用方式可以通过阅读其手册来进行了解,此处不再赘述。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/黑客灵魂/article/detail/959221
推荐阅读
相关标签
  

闽ICP备14008679号