如何正确获取一行并用 C 解析它
How to properly get a line and parse it with C
我正在编写一个 C 程序,它将打开一个文件,写入文件,然后读取写入的内容。我可以打开、写入和关闭文件,但无法读取行并正确解析它们。
我已经阅读了许多其他博客和网站,但是 none 完全解决了我正在尝试做的事情。我试过调整他们的一般解决方案,但我从来没有得到我想要的行为。我有 运行 这段代码,其中包含 fgets()、gets()、strtok()、scanf() 和 fscanf()。我使用了 strtok_r() ,因为它被推荐为最佳实践。我使用 gets() 和 scanf() 作为实验来查看它们的输出,而不是 fgets() 和 fscanf()。
我想做的事情:
- 获取第一行 // 第一行是 space 分隔的字符串“1 2 3 4 5”
- 解析这一行,将每个字符数转换成一个整数
- 将其存储到一个数组中。
- 获取下一行并重复直到 EOF
有人可以告诉我我缺少什么以及哪些功能被认为是最佳实践吗?
谢谢
我的代码:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(){
FILE * file;
// read data from customer.txt
char lines[30];
file = fopen("data.txt", "r");
// data.txt currently holds five lines
// 1 1 1 1 1
// 2 2 2 2 2
// 3 3 3 3 3
// 4 4 4 4 4
// 5 5 5 5 5
char *number;
char *next = lines;
int s = 0;
int t = 0;
int num;
int prams[30][30];
while(fgets(lines, 30, file)){
char *from = next;
while((number = strtok_r(from, " ", &next)) != NULL){
int i = atoi(number);
prams[t][s] = i;
printf("this is prams[%d][%d]: %d\n", t, s, prams[t][s]);
s++;
from = NULL;
}
t++;
}
fclose(file);
}// main
预期输出:
this is prams[0][0]: 1
...
this is prams[4][4]: 5
实际输出:
this is prams[0][0]: 1
this is prams[0][1]: 1
this is prams[0][2]: 1
this is prams[0][3]: 1
this is prams[0][4]: 1
program ends
直接的主要问题是您一直告诉 strtok_r()
从字符串的开头开始,所以它一直返回相同的值。您需要将第一个参数 strtok_r()
设置为 NULL,以便它从中断处继续:
char *from = next;
while ((number = strtok_r(from, " ", &next)) != NULL)
{
int i = atoi(number);
prams[t][s] = i;
printf("this is prams[%d][%d]: %d\n", t, s, prams[t][s]);
s++;
from = NULL;
}
有些人会赞成 strtol()
而不是 atoi()
;他们有一些正义,但可能还不够重要。
另请参阅 How to use sscanf()
in loops? 了解如何使用 sscanf() 解析行。
使用:
while (fgets(lines, 30, file))
用于外环控制; don't use feof()
除了(可能)在循环终止后区分 EOF 和 I/O 错误。 (几年前,我检查了我的数百个 C 源文件,发现 eof()
的使用不到六次,全部用于错误检查代码和循环控制中的 none。你真的赢了根本不需要经常使用它。)
主要问题是:
- 你永远不会将 s 重置为 0,所以列总是增加而不是从 0 到 4(如果每行 5 个数字),所以你不要写在第二行数组中的预期条目,并且您有可能以未定义的行为(如分段错误)从数组中写出
- 检查你没有读取太多的列和行(在你的代码中是 30 个),否则你可以用未定义的行为(如分段错误)写出数组
你用错了strtok_r
,第一个参数只能在你第一次解析一行时(在你编辑之前)不为空
- doing
number = strtok_r(from, " ", &next)
next 被 strtok_r
修改,用于初始化 from 下一个行,所以第二行不会被正确读取,你的执行只是 :
this is prams[0][0]: 11
this is prams[0][1]: 12
this is prams[0][2]: 13
this is prams[0][3]: 14
this is prams[0][4]: 15
this is prams[3][5]: 0
与 data.txt 包含:
11 12 13 14 15
21 22 23 24 25
31 32 33 34 35
41 42 43 44 45
51 52 53 54 55
(还要查看索引 [3][5]
因为你错过了重置 s )
补充说明:
- 检查打开成功
- 初始化 prams 或记住第一行有多少列,并检查下一行的列数是否始终相同,当然还要记住多少行,否则你以后不知道数组中读取的数字在哪里
- atoi不表示是否读到数字
考虑到这些评论的建议是(我用 0 初始化数组而不假设每行的数字数):
#include <stdio.h>
#include <string.h>
#define LINELENGTH 30
#define SIZE 30
int main(){
// read data from customer.txt
char lines[LINELENGTH];
FILE * file = fopen("data.txt", "r");
if (file == NULL) {
fprintf(stderr, "cannot read data.txt");
return -1;
}
// data.txt currently holds five lines
// 1 1 1 1 1
// 2 2 2 2 2
// 3 3 3 3 3
// 4 4 4 4 4
// 5 5 5 5 5
int t = 0;
int prams[SIZE][SIZE] = { 0 };
while (fgets(lines, LINELENGTH, file)) {
char * number;
char * str = lines;
int s = 0;
while ((number = strtok(str, " \n")) != NULL) {
char c;
int i;
if (sscanf(number, "%d%c", &i, &c) != 1) {
fprintf(stderr, "invalid number '%s'\n", number);
return -1;
}
prams[t][s] = i;
printf("this is prams[%d][%d]: %d\n", t, s, prams[t][s]);
str = NULL;
if (++s == SIZE)
break;
}
if (++t == SIZE)
break;
}
fclose(file);
}// main
我使用 sscanf(number, "%d%c", &i, &c) != 1
来轻松检测是否读取了一个数字并且只读取了一个数字,注意我添加了 \n
是 strtok 的分隔符
编译与执行:
pi@raspberrypi:/tmp $ !g
gcc -pedantic -Wall -Wextra l.c
pi@raspberrypi:/tmp $ cat data.txt
11 12 13 14 15
21 22 23 24 25
31 32 33 34 35
41 42 43 44 45
51 52 53 54 55
pi@raspberrypi:/tmp $ ./a.out
this is prams[0][0]: 11
this is prams[0][1]: 12
this is prams[0][2]: 13
this is prams[0][3]: 14
this is prams[0][4]: 15
this is prams[1][0]: 21
this is prams[1][1]: 22
this is prams[1][2]: 23
this is prams[1][3]: 24
this is prams[1][4]: 25
this is prams[2][0]: 31
this is prams[2][1]: 32
this is prams[2][2]: 33
this is prams[2][3]: 34
this is prams[2][4]: 35
this is prams[3][0]: 41
this is prams[3][1]: 42
this is prams[3][2]: 43
this is prams[3][3]: 44
this is prams[3][4]: 45
this is prams[4][0]: 51
this is prams[4][1]: 52
this is prams[4][2]: 53
this is prams[4][3]: 54
this is prams[4][4]: 55
如果您想解析以空格分隔的文本,那么 scanf 和 friends 是您最好的选择。但是,如果您想特殊处理换行符而不是空格,那么您需要 fgets+sscanf 循环:
#define ROWS 30
#define COLS 30
#define MAXLINE 512
int prams[ROWS][COLS];
int row, col, len;
char buffer[MAXLINE], *p;
row = 0;
while (row < ROWS && fgets(buffer, MAXLINE, stdin)) {
col = 0;
p = buffer;
while (col < COLS && sscanf(p, "%d %n", &prams[row][col], &len) > 0) {
p += len;
++col; }
if (*p) {
/* extra stuff on the end of the line -- error? */ }
++row; }
注意还要检查边界以确保不超过固定大小的数组边界。
我正在编写一个 C 程序,它将打开一个文件,写入文件,然后读取写入的内容。我可以打开、写入和关闭文件,但无法读取行并正确解析它们。
我已经阅读了许多其他博客和网站,但是 none 完全解决了我正在尝试做的事情。我试过调整他们的一般解决方案,但我从来没有得到我想要的行为。我有 运行 这段代码,其中包含 fgets()、gets()、strtok()、scanf() 和 fscanf()。我使用了 strtok_r() ,因为它被推荐为最佳实践。我使用 gets() 和 scanf() 作为实验来查看它们的输出,而不是 fgets() 和 fscanf()。
我想做的事情:
- 获取第一行 // 第一行是 space 分隔的字符串“1 2 3 4 5”
- 解析这一行,将每个字符数转换成一个整数
- 将其存储到一个数组中。
- 获取下一行并重复直到 EOF
有人可以告诉我我缺少什么以及哪些功能被认为是最佳实践吗?
谢谢
我的代码:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(){
FILE * file;
// read data from customer.txt
char lines[30];
file = fopen("data.txt", "r");
// data.txt currently holds five lines
// 1 1 1 1 1
// 2 2 2 2 2
// 3 3 3 3 3
// 4 4 4 4 4
// 5 5 5 5 5
char *number;
char *next = lines;
int s = 0;
int t = 0;
int num;
int prams[30][30];
while(fgets(lines, 30, file)){
char *from = next;
while((number = strtok_r(from, " ", &next)) != NULL){
int i = atoi(number);
prams[t][s] = i;
printf("this is prams[%d][%d]: %d\n", t, s, prams[t][s]);
s++;
from = NULL;
}
t++;
}
fclose(file);
}// main
预期输出:
this is prams[0][0]: 1
...
this is prams[4][4]: 5
实际输出:
this is prams[0][0]: 1
this is prams[0][1]: 1
this is prams[0][2]: 1
this is prams[0][3]: 1
this is prams[0][4]: 1
program ends
直接的主要问题是您一直告诉 strtok_r()
从字符串的开头开始,所以它一直返回相同的值。您需要将第一个参数 strtok_r()
设置为 NULL,以便它从中断处继续:
char *from = next;
while ((number = strtok_r(from, " ", &next)) != NULL)
{
int i = atoi(number);
prams[t][s] = i;
printf("this is prams[%d][%d]: %d\n", t, s, prams[t][s]);
s++;
from = NULL;
}
有些人会赞成 strtol()
而不是 atoi()
;他们有一些正义,但可能还不够重要。
另请参阅 How to use sscanf()
in loops? 了解如何使用 sscanf() 解析行。
使用:
while (fgets(lines, 30, file))
用于外环控制; don't use feof()
除了(可能)在循环终止后区分 EOF 和 I/O 错误。 (几年前,我检查了我的数百个 C 源文件,发现 eof()
的使用不到六次,全部用于错误检查代码和循环控制中的 none。你真的赢了根本不需要经常使用它。)
主要问题是:
- 你永远不会将 s 重置为 0,所以列总是增加而不是从 0 到 4(如果每行 5 个数字),所以你不要写在第二行数组中的预期条目,并且您有可能以未定义的行为(如分段错误)从数组中写出
- 检查你没有读取太多的列和行(在你的代码中是 30 个),否则你可以用未定义的行为(如分段错误)写出数组
你用错了strtok_r
,第一个参数只能在你第一次解析一行时(在你编辑之前)不为空- doing
number = strtok_r(from, " ", &next)
next 被strtok_r
修改,用于初始化 from 下一个行,所以第二行不会被正确读取,你的执行只是 :
this is prams[0][0]: 11
this is prams[0][1]: 12
this is prams[0][2]: 13
this is prams[0][3]: 14
this is prams[0][4]: 15
this is prams[3][5]: 0
与 data.txt 包含:
11 12 13 14 15
21 22 23 24 25
31 32 33 34 35
41 42 43 44 45
51 52 53 54 55
(还要查看索引 [3][5]
因为你错过了重置 s )
补充说明:
- 检查打开成功
- 初始化 prams 或记住第一行有多少列,并检查下一行的列数是否始终相同,当然还要记住多少行,否则你以后不知道数组中读取的数字在哪里
- atoi不表示是否读到数字
考虑到这些评论的建议是(我用 0 初始化数组而不假设每行的数字数):
#include <stdio.h>
#include <string.h>
#define LINELENGTH 30
#define SIZE 30
int main(){
// read data from customer.txt
char lines[LINELENGTH];
FILE * file = fopen("data.txt", "r");
if (file == NULL) {
fprintf(stderr, "cannot read data.txt");
return -1;
}
// data.txt currently holds five lines
// 1 1 1 1 1
// 2 2 2 2 2
// 3 3 3 3 3
// 4 4 4 4 4
// 5 5 5 5 5
int t = 0;
int prams[SIZE][SIZE] = { 0 };
while (fgets(lines, LINELENGTH, file)) {
char * number;
char * str = lines;
int s = 0;
while ((number = strtok(str, " \n")) != NULL) {
char c;
int i;
if (sscanf(number, "%d%c", &i, &c) != 1) {
fprintf(stderr, "invalid number '%s'\n", number);
return -1;
}
prams[t][s] = i;
printf("this is prams[%d][%d]: %d\n", t, s, prams[t][s]);
str = NULL;
if (++s == SIZE)
break;
}
if (++t == SIZE)
break;
}
fclose(file);
}// main
我使用 sscanf(number, "%d%c", &i, &c) != 1
来轻松检测是否读取了一个数字并且只读取了一个数字,注意我添加了 \n
是 strtok 的分隔符
编译与执行:
pi@raspberrypi:/tmp $ !g
gcc -pedantic -Wall -Wextra l.c
pi@raspberrypi:/tmp $ cat data.txt
11 12 13 14 15
21 22 23 24 25
31 32 33 34 35
41 42 43 44 45
51 52 53 54 55
pi@raspberrypi:/tmp $ ./a.out
this is prams[0][0]: 11
this is prams[0][1]: 12
this is prams[0][2]: 13
this is prams[0][3]: 14
this is prams[0][4]: 15
this is prams[1][0]: 21
this is prams[1][1]: 22
this is prams[1][2]: 23
this is prams[1][3]: 24
this is prams[1][4]: 25
this is prams[2][0]: 31
this is prams[2][1]: 32
this is prams[2][2]: 33
this is prams[2][3]: 34
this is prams[2][4]: 35
this is prams[3][0]: 41
this is prams[3][1]: 42
this is prams[3][2]: 43
this is prams[3][3]: 44
this is prams[3][4]: 45
this is prams[4][0]: 51
this is prams[4][1]: 52
this is prams[4][2]: 53
this is prams[4][3]: 54
this is prams[4][4]: 55
如果您想解析以空格分隔的文本,那么 scanf 和 friends 是您最好的选择。但是,如果您想特殊处理换行符而不是空格,那么您需要 fgets+sscanf 循环:
#define ROWS 30
#define COLS 30
#define MAXLINE 512
int prams[ROWS][COLS];
int row, col, len;
char buffer[MAXLINE], *p;
row = 0;
while (row < ROWS && fgets(buffer, MAXLINE, stdin)) {
col = 0;
p = buffer;
while (col < COLS && sscanf(p, "%d %n", &prams[row][col], &len) > 0) {
p += len;
++col; }
if (*p) {
/* extra stuff on the end of the line -- error? */ }
++row; }
注意还要检查边界以确保不超过固定大小的数组边界。