嘿伙计们,我正在阅读一个文件,并希望将数据解析为一个struct数组。该文件看起来像这样:
Country,City,Area Code,Population
China,Beijing,,21256972
France,Paris,334,3568253
Italy,Rome,,1235682
我想解析数据并将成员分配给文件中的每个相应区域。我在解析第1行和第3行的数据时没有问题。但是如果没有区号并且彼此相邻有两个逗号,则令牌变为空并且我收到错误。我一直在寻找,似乎无法找到解决方案。这是我的代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
static struct Locations {
char country[20];
char city[20];
char areaCode[5];
char population[100];
} line[2000000];
// open file
FILE *Lfile;
Lfile = fopen("locations.txt", "r");
if (!Lfile) {
perror("File Error");
}
char buf[100];
const char delim[2] = ",";
char *token;
int i = 0;
while (fgets(buf, 100, Lfile) != NULL) {
token = strtok(buf, delim);
while (token != NULL) {
strcpy(line[i].country, token);
token = strtok(NULL, delim);
strcpy(line[i].city, token);
token = strtok(NULL, delim);
strcpy(line[i].areaCode, token); //Error here
token = strtok(NULL, delim);
strcpy(line[i].population, token);
token = strtok(NULL, delim);
}
printf("%s %s %s %s\n", line[i].country,
line[i].city, line[i].areaCode, line[i].population);
i++;
}
return 0;
}
答案 0 :(得分:1)
您无法使用strtok()
拆分CSV文件,因为strtok
会将分隔符字符串中的字符序列视为单个分隔符。它仅用于分隔空格分隔的标记。
您无法使用sscanf("%[^,]", ...)
,因为sscanf
期望解析至少一个与,
不同的字符。
您可以使用strchr
,或者您可以使用<string.h>
中的其他功能来实现您的目的:strcspn()
:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* copy at most len bytes from src to an array size char and
null terminate it. Return the length of the resulting C string
possibly smaller than len if the destination is too small.
*/
size_t strcpymem(char *dest, size_t size, const char *src, size_t len) {
if (len >= size)
len = size - 1;
memcpy(dest, src, len);
dest[len] = '\0';
return len;
}
#define RECORD_NUMBER 2000000
int main(void) {
static struct Locations {
char country[20];
char city[20];
char areaCode[5];
char population[100];
} line[RECORD_NUMBER];
// open file
FILE *Lfile;
Lfile = fopen("locations.txt", "r");
if (!Lfile) {
perror("File Error");
}
char buf[100];
int i = 0;
while (i < RECORD_NUMBER && fgets(buf, 100, Lfile) != NULL) {
char *p = buf;
int len = strcspn(p, ",\n");
strcpymem(line[i].country, sizeof line[i].country, p, len);
p += len;
if (*p == ',') p++;
len = strcspn(p, ",\n");
strcpymem(line[i].city, sizeof line[i].city, p, len);
p += len;
if (*p == ',') p++;
len = strcspn(p, ",\n");
strcpymem(line[i].areacode, sizeof line[i].areacode, p, len);
p += len;
if (*p == ',') p++;
len = strcspn(p, ",\n");
strcpymem(line[i].population, sizeof line[i].population, p, len);
printf("%s %s %s %s\n", line[i].country,
line[i].city, line[i].areaCode, line[i].population);
i++;
}
return 0;
}
答案 1 :(得分:0)
sscanf()
用于扫描零长度字段很麻烦
strtok()
结合了相邻的令牌
有时最好的答案是编写一些代码。
形成一个辅助函数来解析子字符串。一些未经测试的代码如下。关键是将代码的功能划分为更容易维护的功能
// Starting a `p`, copy until delimiter found or size exhausted
// Return NULL on failure
const char *foo(const char *p, char *dest, size_t size, int end) {
if (p) {
while (size-- > 0 && *p) {
*dest = *p++;
if (*dest == end) {
*dest = '\0';
return p;
}
dest++;
}
}
return NULL;
}
重写主循环
// Make buf big enough
char buf[sizeof line[0] * 2];
// Use size_t rather than int
size_t i = 0;
// Limit iteration count
while (i < RECORD_NUMBER && fgets(buf, sizeof buf, Lfile) != NULL) {
const char *p = buf;
p = foo(p, line[i].country, sizeof line[i].country, ',');
p = foo(p, line[i].city, sizeof line[i].city, ',');
p = foo(p, line[i].areaCode, sizeof line[i].areaCode, ',');
p = foo(p, line[i].population, sizeof line[i].population, '\n');
if (p == NULL || *p != '\0') {
printf("Bad line '%s'\n", buf);
exit (-1);
}
i++;
}
答案 2 :(得分:-1)
我喜欢使用sscanf
进行这种解析。
int i = 0;
while (fgets(buf, 100, Lfile) != NULL) {
char *pbuf = buf;
int offset;
pbuf += sscanf( pbuf, "%[^,],%n", line[i].country , &offset) ? offset : 1;
pbuf += sscanf( pbuf, "%[^,],%n", line[i].city , &offset) ? offset : 1;
pbuf += sscanf( pbuf, "%[^,],%n", line[i].areaCode, &offset) ? offset : 1;
pbuf += sscanf( pbuf, " %s", line[i].population );
printf("%s %s %s %s\n", line[i].country,
line[i].city, line[i].areaCode, line[i].population);
i++;
}
也许这是较慢的代码,也许它会因格式错误而崩溃。也许它有其他缺陷,但这就是我喜欢做这种解析的方式。
注意:如果解析空白字段,则char指针不会设置为NULL
。因此,初始化结构很重要。
编辑:修复了处理空白字段时的错误。