C语言读取列式文本文件
首先,如果这件事太琐碎,请原谅,我不是C开发人员,通常我用Fortran编程 我需要阅读一些列式文本文件。我遇到的问题是,有些列可能有空格(非填充值)或未完全归档字段 让我用一个简单的例子来说明这个问题。假设我有一个生成器程序,如:C语言读取列式文本文件,c,printf,scanf,C,Printf,Scanf,首先,如果这件事太琐碎,请原谅,我不是C开发人员,通常我用Fortran编程 我需要阅读一些列式文本文件。我遇到的问题是,有些列可能有空格(非填充值)或未完全归档字段 让我用一个简单的例子来说明这个问题。假设我有一个生成器程序,如: #include <stdio.h> #include <stdlib.h> int main(){ printf("xxxx%4d%4.2f\n",99,3.14); } 如果我将其放入文本文件并尝试使用(例如)sscanf和
#include <stdio.h>
#include <stdlib.h>
int main(){
printf("xxxx%4d%4.2f\n",99,3.14);
}
如果我将其放入文本文件并尝试使用(例如)sscanf和代码进行读取:
#include <stdio.h>
#include <stdlib.h>
int main() {
char *fmt = "%*4c%4d%4f";
char *line = "xxxx 993.14";
int ival;
float fval;
sscanf(line,fmt,&ival,&fval);
printf(">>>>%d|%f\n",ival,fval);
}
这里有什么问题?sscanf似乎认为所有空间都是毫无意义的,应该丢弃。因此“%4c”按其本意执行,它计数4个字符,但不丢弃任何空格,并丢弃因“”而产生的所有内容。接下来,%4d开始跳过所有空格,并在找到转换的第一个有效字符时开始计算字段的4个字符。所以这个值,意味着99变成了993,3.14变成了0.14
在Fortran中,读取代码为:
program t3
implicit none
integer :: ival
real :: fval
character(len=30) :: fmt="(4x,i4,f4.0)"
character(len=30) :: line="xxxx 993.14"
read(line,fmt) ival, fval
write(*,"('>>>>',i4,'|',f4.2)") ival,fval
end program t3
$ ./t3
>>>> 99|3.14
结果是:
program t3
implicit none
integer :: ival
real :: fval
character(len=30) :: fmt="(4x,i4,f4.0)"
character(len=30) :: line="xxxx 993.14"
read(line,fmt) ival, fval
write(*,"('>>>>',i4,'|',f4.2)") ival,fval
end program t3
$ ./t3
>>>> 99|3.14
也就是说,格式规范规定了字段宽度,并且在转换过程中不会丢弃任何内容,除非“nX”规范指示这样做
帮助助手的最后几句话:
如果每个元素的宽度固定,您实际上不需要
scanf()
,请尝试以下操作
char copy[5];
const char *line = "xxxx 993.14";
int ival;
float fval;
copy[0] = line[4];
copy[1] = line[5];
copy[2] = line[6];
copy[3] = line[7];
copy[4] = '\0'; // nul terminate for `atoi' to work
ival = atoi(copy);
fval = atof(&line[8]);
fprintf(stdout, "%d -- %f\n", ival, fval);
如果需要(可能应该),可以使用strtol()
而不是atoi()
和strtof()
而不是atof()
来检查格式错误的数据
这两个函数都使用一个参数来存储未转换/无效字符,您可以检查传递的指针以验证转换是否存在问题
或者,如果确实需要scanf()
执行相同的操作,请将整数+空格捕获到char
数组中,然后稍后将其转换为int
,如下所示
char integer[5];
const char *line = "xxxx 993.14";
int ival;
float fval;
if (sscanf(line, "%*4c%4[0-9 ]%f", integer, &fval) != 2)
return -1;
ival = atoi(integer);
fprintf(stdout, "%d -- %f\n", ival, fval);
格式“%*4c%4[0-9]%f”
将
float
值首先,我不知道。可能有一些方法可以让sscanf识别整数计数中的空格。但我只是不认为scanf是为这种格式而设计的。这个工具试图变得聪明而有用,它在咬你的屁股 但是如果它是列数据,并且您知道各个字段的位置,那么有一个非常简单的解决方法。只需提取所需的字段
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char** argv)
{
char line[] = "xxxx 893.14";
char tmp[100];
int thatDamnNumber;
float myfloatykins;
//Get that field
memcpy(tmp, line+4, 4);
sscanf(tmp, "%d", &thatDamnNumber);
//Kill that field so it doesn't goober-up the float
memset(line+4, ' ', 4);
sscanf(line, "%*4c%f", &myfloatykins);
printf("%d %f\n", thatDamnNumber, myfloatykins);
return 0;
}
#包括
#包括
#包括
int main(int argc,字符**argv)
{
字符行[]=“xxxx 893.14”;
char-tmp[100];
int thatDamnNumber;
浮动myfloatykins;
//去那块地
memcpy(tmp,线路+4,4);
sscanf(tmp、%d、&thatDamnNumber);
//杀了那块地,这样它就不会把浮子弄脏
memset(行+4',,4);
sscanf(行“%*4c%f”和myfloatykins);
printf(“%d%f\n”,thatDamnNumber,myfloatykins);
返回0;
}
如果有很多这样的函数,您可以生成一些通用函数:integerExtract(int-positionStart,int-sizeInCharacters),flootextract(),等等。当使用
sscanf
读取固定长度字段时,最好将值解析为字符串(您可以使用多种方法),然后对每个字段执行独立转换。这允许您在每个字段的基础上处理转换/错误检测。例如,可以使用以下格式字符串:
char *fmt = "%*4s%2[^0-9]%s";
它将读取/丢弃4个前导字符,然后读取2个字符作为整数,后跟行
的剩余部分(或直到下一个空格),作为包含浮点值的字符串
要将行
作为固定长度字段进行存储和解析,可以使用临时字符数组保存每个字符串,然后使用sscanf
填充它们,就像您尝试直接使用整数和浮点一样。e、 g:
char istr[8] = {0};
char fstr[16] = {0};
...
sscanf (line,fmt,istr,fstr);
(注意:您可以使用istr[3]
和fstr[7]
的最小存储空间。在这种情况下,根据需要调整存储长度,但为nul终止字符提供空间)
然后,您可以使用strtol
和strtof
对每个值进行错误检查。例如:
errno = 0;
if ((ival = (int)strtol (istr, NULL, 10)) == 0 && errno)
fprintf (stderr, "error: integer conversion failed.\n");
/* underflow/overflow checks omitted */
及
在您的示例中,将所有部分放在一起,您可以使用以下内容:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main() {
char *fmt = "%*4s%2[^0-9]%s";
char *line = "xxxx 993.14";
char istr[8] = {0};
char fstr[16] = {0};
int ival;
float fval;
sscanf (line,fmt,istr,fstr);
errno = 0;
if ((ival = (int)strtol (istr, NULL, 10)) == 0 && errno)
fprintf (stderr, "error: integer conversion failed.\n");
/* underflow/overflow checks omitted */
errno = 0;
if ((fval = strtof (fstr, NULL)) == 0 && errno)
fprintf (stderr, "error: integer conversion failed.\n");
/* nan and inf checks omitted */
printf(">>>>%d|%6.2f\n",ival,fval);
return 0;
}
我发布了我认为是从目前为止我得到的答案和其他来源得出的最终结论 在Fortran中非常简单的任务在其他语言中就不那么简单了。我猜 — 不确定 — 同样的任务可以像在其他语言中的Fortran一样简单。我认为穿孔卡时代的Cobol、Pascal、PL/I和其他语言可能是微不足道的 我认为现在的大多数语言更适合使用不同的数据结构,并从C继承了它的I/O结构。我认为Java、Python、Perl(?)和其他语言可以作为例子 从我在这个线程中看到的情况来看,使用C读取/转换固定列长度的文本数据存在两个主要问题 第一个问题是,正如Philip在回答中所说:“这个工具试图变得聪明而有用
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main() {
char *fmt = "%*4s%2[^0-9]%s";
char *line = "xxxx 993.14";
char istr[8] = {0};
char fstr[16] = {0};
int ival;
float fval;
sscanf (line,fmt,istr,fstr);
errno = 0;
if ((ival = (int)strtol (istr, NULL, 10)) == 0 && errno)
fprintf (stderr, "error: integer conversion failed.\n");
/* underflow/overflow checks omitted */
errno = 0;
if ((fval = strtof (fstr, NULL)) == 0 && errno)
fprintf (stderr, "error: integer conversion failed.\n");
/* nan and inf checks omitted */
printf(">>>>%d|%6.2f\n",ival,fval);
return 0;
}
$ >>>>0|993.14
char str_int[5];
char str_float[5];
int n = 0;
sscanf(buffer, "%*4c%4[^\n]%4[^\n]%n", str_int, str_float, &n);
if (n != 12 || buffer[n] != '\n') Fail();
// Now convert str_int, str_float as needed.
int ival;
float fval;
if (strlen(buffer) != 13) Fail();
if (sscanf(&buffer[8], "%f", &fval) != 1) Fail();
buffer[8] = '\0';
if (sscanf(&buffer[4], "%d", &ival) != 1) Fail();
printf("xxxx%4d%4.2f\n",ival, fval);
if (13 != printf("xxxx%4d%4.2f\n",ival, fval)) Fail();
printf("xxxx%4d%4.2f\n",123, 9.995000001f); // "xxxx 12310.00\n"