是的,你是对的,你错误地使用了strtok。
我要做的第一件事是读取每一行并使用解析该行
strtok,像这样:
char line[1024];
const char *delim=",\n";
while(fgets(line, sizeof line, fp))
{
char *token = strtok(line, delim);
do {
printf("token: %s\n", token);
} while(token = strtok(NULL, delim));
}
strtok 要求strtok 的所有后续调用必须使用
NULL。当找不到更多令牌时,strtok 将返回 NULL,通常是
已到达行尾。请注意,我在
分隔符参数。当目标缓冲区足够大时fgets 写入
换行符也是如此。将换行符放在分隔符列表中是个好技巧
因为strtok 会为你去掉换行符。
上面的代码为您提供了一种将 csv 的每个单元格作为字符串获取的方法。你
必须自己转换值。这是棘手的一点,如果 csv
包含空格、引号等,您需要不同的策略来解析
单元格的正确值。您可以使用像strtol 和朋友这样的功能
允许您从错误中恢复,但它们不是防弹的,会有
失败的情况也是如此。
一个简单的例子是:
char line[1024];
const char *delim=",\n";
while(fgets(line, sizeof line, fp))
{
char *token = strtok(line, delim);
do {
int val;
if(sscanf(token, "%d", &n) != 1)
fprintf(stderr, "'%s' is not a number!\n", token);
else
printf("number found: %d\n", val);
} while(token = strtok(NULL, delim));
}
请注意,这并不涵盖所有情况,例如引号中的单元格。
最后要做的就是存储这些值。一种方法是
为指向 int 数组的指针分配内存并为
每个细胞。这里的问题再次出在 csv 文件中,有时他们有
格式错误,有些行会是空的,或者有些行会有更多或更少
列而不是其他行,这可能很棘手。在这一点上,这将是一个很好的
使用库来解析 csv 的想法。
以下代码将假定 csv 格式正确,并且
列在所有行中始终相同,并且没有行长于 1023
长字符。当*cols 为0时,我计算列数基于
第一行。如果其他行的列数较少,则所有剩余值为 0
(因为 calloc 将新分配的内存设置为 0)。如果还有更多
colmuns 比第一行,此列将被忽略:
int **parse_csv(const char *filename, size_t *rows, size_t *cols)
{
if(filename == NULL || rows == NULL || cols == NULL)
return NULL;
FILE *fp = fopen(filename, "r");
if(fp == NULL)
return NULL;
int **csv = NULL, **tmp;
*rows = 0;
*cols = 0;
char line[1024];
char *token;
char *delim = ",\n";
while(fgets(line, sizeof line, fp))
{
tmp = realloc(csv, (*rows + 1) * sizeof *csv);
if(tmp == NULL)
return csv; // return all parsed rows so far
csv = tmp;
if(*cols == 0)
{
// calculating number of rows
char copy[1024];
strcpy(copy, line);
token = strtok(copy, delim);
do {
(*cols)++;
} while((token = strtok(NULL, delim)));
}
int *row = calloc(*cols, sizeof *row);
if(row == NULL)
{
if(*rows == 0)
{
free(csv);
return NULL;
}
return csv; // return all parsed rows so far
}
// increment rows count
(*rows)++;
size_t idx = 0;
token = strtok(line, delim);
do {
if(sscanf(token, "%d", row + idx) != 1)
row[idx] = 0; // in case the conversion fails,
// just to make sure to have a defined value
// in the cell
idx++;
} while((token = strtok(NULL, delim)) && idx < *cols);
csv[*rows - 1] = row;
}
fclose(fp);
return csv;
}
void free_csv(int **csv, size_t rows)
{
if(csv == NULL)
return;
for(size_t i = 0; i < rows; ++i)
free(csv[i]);
free(csv);
}
现在你可以像这样解析它:
size_t cols, rows;
int **csv = parse_csv("file.csv", &rows, &cols);
if(csv == NULL)
{
// error handling...
// do not continue
}
...
free_csv(csv, rows);
现在csv[3][4] 将为您提供第 3 行第 4 列的单元格(从 0 开始)。
编辑
我从你的代码中注意到的事情:
void main() 是错误的。 main 应该只有以下原型之一:
int main(void);
int main(int argc, char **argv);
int main(int argc, char *argv[]);
另一个:
int main(void)
{
char *strcat(char *dest, const char *src);
char *strtok(char *str, const char *delim);
...
}
不要把那个放在main函数里面,放在外面,也有标准的
为此的头文件。在这种情况下包括string.h
#include <string.h>
int main(void)
{
...
}
另一个
const char *delim = (const char *)',';
这是错误的,这就像试图出售一个苹果并称其为橙子。 ','
是char 类型的单个字符。它的值为 44。它与
正在做:
const char *delim = (const char*) 44;
您正在设置delim 应指向的地址 44。
你必须使用双引号:
const char *delim = ",";
请注意,'x' 和 "x" 并不相同。 'x' 是 120(参见 ASCII),它是
一个字符。 "x" 是一个字符串文字,它返回一个指向开始的指针
以'\0'-终止字节结尾的字符序列,又名
细绳。这些在 C 中是完全不同的东西。