我有一个包含空格分隔值的输入文件
AA BB 4
A B
AB BA
AA CC
CC BB
A B 3
A C
B C
c B
我希望第一行的内容分别移到
s1
、s2
和n
接下来的n行包含2个空格分隔的字符串,这些字符串将移动到
分别为
production_left
和production_right
。布局也将在后续的行块中重复。样本数据有两个输入块。
我的代码如下
int main()
{
char *s1[20], *t1[20];
char *production_left[20], *production_right[20];
int n;
FILE *fp;
fp = fopen("suffix.txt","r");
int iss=0;
do{
fscanf(fp,"%s %s %d",s1[iss],t1[iss],&n);
int i=0;
while(i<n)
{
fscanf(fp,"%s %s",production_left[i],production_right[i]);
i++;
}
}while(!eof(fp));
}
每次它都会给分割带来错误。
最佳答案
很多C语言,只是在你的头脑中保持清晰,你需要什么样的数据在你的代码的什么范围(块)中可用。在您的例子中,您正在使用的数据是数据文件每个部分的第一行(或标题)。为此,您有s1
和t1
,但您没有什么可以保留的n
,因此它可以与您的数据一起重用。由于n
包含每个标题下预期的production_left
和production_right
索引数,只需创建一个索引数组,例如int idx[MAXC] = {0};
来存储与每个n
和s1
相关联的每个t1
。这样您就可以保留该值,以便以后在迭代中使用。(MAXC
只是为20
定义的常量,以防止在代码中使用幻数)
接下来,您需要了解指针声明及其使用。char *s1[MAXC], *t1[MAXC], *production_left[MAXC], *production_right[MAXC];
为20
、s1
、t1
和production_left
声明4个指针数组(production_right
每个指针)。指针未初始化和未分配。虽然每一个都可以初始化为指针值,但是没有任何存储(分配的内存)与允许复制数据的指针值相关联。不能简单地使用fscanf
并为每个值分配相同的指针值(它们都指向最后一个值——如果它仍在作用域中的话)
因此,您有两个选择:(1)使用2D数组,或(2)为每个字符串分配存储空间,并将字符串复制到新的内存块,并将指向该块开头的指针分配给(例如s1[x]
)。strdup
函数在单个函数调用中提供分配和复制。如果没有strdup
,则使用strlen
、malloc
和memcopy
编写是一个简单的函数(如果字符串有重叠的可能,则使用memmove
)。
一旦确定了需要保留哪些值以供以后在代码中使用,就可以确保声明的变量的作用域正确,并且确保每个变量都已正确初始化并分配了存储空间,剩下的就是编写逻辑以使事情按您的意愿工作。
在转向示例之前,值得注意的是,您对数据的面向行的输入感兴趣。scanf
系列提供格式化输入,但通常最好对实际输入使用面向行的函数(例如fgets
),然后使用单独的解析,例如sscanf
。在这种情况下,这很大程度上是一种清洗,因为第一个值是字符串值,%s
格式说明符将跳过中间的空白,但通常情况并非如此。例如,你正在有效地阅读:
char tmp1[MAXC] = "", tmp2[MAXC] = "";
...
if (fscanf (fp, "%s %s %d", tmp1, tmp2, &n) != 3)
break;
它可以很容易地替换为更强大的一点:
char buf[MAXC] = "", tmp1[MAXC] = "", tmp2[MAXC] = "";
...
if (!fgets (buf, sizeof buf, fp) ||
sscanf (buf, "%s %s %d", tmp1, tmp2, &n) != 3)
break;
(这将分别验证读取和转换)
上面还注意到临时缓冲区
tmp1
和tmp2
(和buf
用于fgets
)的使用它通常有利于将输入读入临时值,这些值可以在最终存储之前验证,以便以后使用。剩下的就是简单地按照正确的顺序把各个部分组合起来,以实现你的目标。下面是一个例子,它读取从第一个参数给出的文件名中的数据(或者如果没有给出文件名,则从
stdin
),然后输出数据并释放在退出之前分配的所有内存。#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXC 20
int main (int argc, char **argv) {
char *s1[MAXC], *t1[MAXC],
*production_left[MAXC], *production_right[MAXC];
int idx[MAXC] = {0}, /* storage for 'n' values */
iss = 0, ipp = 0, pidx = 0;
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
return 1;
}
/* loop until no header row read, protecting array bounds */
for (; ipp < MAXC && iss < MAXC; iss++) {
char buf[MAXC] = "", tmp1[MAXC] = "", tmp2[MAXC] = "";
int n = 0;
if (!fgets (buf, sizeof buf, fp) ||
sscanf (buf, "%s %s %d", tmp1, tmp2, &n) != 3)
break;
idx[iss] = n;
s1[iss] = strdup (tmp1); /* strdup - allocate & copy */
t1[iss] = strdup (tmp2);
if (!s1[iss] || !t1[iss]) { /* if either is NULL, handle error */
fprintf (stderr, "error: s1 or s1 empty/NULL, iss: %d\n", iss);
return 1;
}
/* read 'n' data lines from file, protecting array bounds */
for (int i = 0; i < n && ipp < MAXC; i++, ipp++) {
char ptmp1[MAXC] = "", ptmp2[MAXC] = "";
if (!fgets (buf, sizeof buf, fp) ||
sscanf (buf, "%s %s", ptmp1, ptmp2) != 2) {
fprintf (stderr, "error: read failure, ipp: %d\n", iss);
return 1;
}
production_left[ipp] = strdup (ptmp1);
production_right[ipp] = strdup (ptmp2);
if (!production_left[ipp] || !production_right[ipp]) {
fprintf (stderr, "error: production_left or "
"production_right empty/NULL, iss: %d\n", iss);
return 1;
}
}
}
if (fp != stdin) fclose (fp); /* close file if not stdin */
for (int i = 0; i < iss; i++) {
printf ("%-8s %-8s %2d\n", s1[i], t1[i], idx[i]);
free (s1[i]); /* free s & t allocations */
free (t1[i]);
for (int j = pidx; j < pidx + idx[i]; j++) {
printf (" %-8s %-8s\n", production_left[j], production_right[j]);
free (production_left[j]); /* free production allocations */
free (production_right[j]);
}
pidx += idx[i]; /* increment previous index value */
}
return 0;
}
示例输入文件
$ cat dat/production.txt
AA BB 4
A B
AB BA
AA CC
CC BB
A B 3
A C
B C
c B
示例使用/输出
$ ./bin/production <dat/production.txt
AA BB 4
A B
AB BA
AA CC
CC BB
A B 3
A C
B C
c B
内存使用/错误检查
在动态分配内存的任何代码中,对于任何分配的内存块,您都有两个职责:(1)始终保留指向内存块起始地址的指针,以便(2)在不再需要时可以释放它。
必须使用内存错误检查程序,以确保您不会尝试写入超出或超出已分配内存块的界限,尝试读取未初始化值或将条件跳转基于未初始化值,最后确认您释放了已分配的所有内存。
对于Linux
valgrind
是正常的选择。每个平台都有类似的内存检查程序。它们都很容易使用,只要运行你的程序就可以了。$ valgrind ./bin/production <dat/production.txt
==3946== Memcheck, a memory error detector
==3946== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==3946== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==3946== Command: ./bin/production
==3946==
AA BB 4
A B
AB BA
AA CC
CC BB
A B 3
A C
B C
c B
==3946==
==3946== HEAP SUMMARY:
==3946== in use at exit: 0 bytes in 0 blocks
==3946== total heap usage: 18 allocs, 18 frees, 44 bytes allocated
==3946==
==3946== All heap blocks were freed -- no leaks are possible
==3946==
==3946== For counts of detected and suppressed errors, rerun with: -v
==3946== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
始终确认已释放所有已分配的内存,并且没有内存错误。
再看一遍,如果你还有问题,请告诉我。