gpt4 book ai didi

c - 如何不使用(fgets)继续扫描直到换行C

转载 作者:行者123 更新时间:2023-12-04 07:07:35 26 4
gpt4 key购买 nike

我正在尝试创建一个while循环来扫描一组单词,直到输入新行。我无法使用fgets,因为我需要每个单词的长度来计算单词的平均值,这是我的代码。任何帮助将不胜感激。

 while(1){
scanf("%s",&input);
if(input=="\n")
break;

最佳答案

为什么fgets()简化了事情(请参阅下面的非fgets()解决方案)

@user3386109所示,当您要读取一行输入时,则需要使用面向行的输入功能(例如fgets()或POSIX getline())来一次读取整行的单词。

然后,您可以使用fgets()strtok()填充的缓冲区拆分为令牌。在这里,您要基于分隔符space'\n'分割单词(可以添加tab','或其他所需的分隔符来分隔相邻单词)strtok将任意数量的顺序分隔符视为单个定界符,因此,例如,如果该行包含空字段,则不能对该行进行逗号分隔。 (例如,1,2,,4,...将被视为3个令牌,而不是4个带有一个空字段的令牌)

要理解的另一件事是strtok()修改其操作的字符串。因此,如果需要保留原件,请进行复印。如果需要保存令牌(例如在数组中),则需要将令牌复制到该存储中(如果不为每个令牌使用数组,则还需要分配存储)

如果您已将行读入缓冲区buf,则在第一次调用strtok()时,将buf作为参数以及定界符传递给第一个令牌(word),例如

char *p = strtok(buf, " \n");


在第一次调用 strtok()以获取指向所有剩余令牌的指针之后,您将 NULL作为参数以及定界符(您可以在两次调用之间更改定界符)作为参数传递。

p = strtok (NULL, " \n")


如果没有更多令牌,则 strtok()返回 NULL

将其放到一个简短的示例中,该示例使用每个标记来获取单词的长度以存储字符的 sum并保持标记的计数,以便可以计算每个标记(单词)的平均长度,可以做类似下面的事情。 (注意:由于没有存储任何令牌供以后使用,因此无需将令牌复制到数组等。)

#include <stdio.h>
#include <string.h>

#define MAXC 1024

int main (void) {

char buf[MAXC];

while (fgets (buf, MAXC, stdin)) {
int n = 0, sum = 0;
puts ("\ngroup:");
for (char *p = strtok(buf, " \n"); p; p = strtok (NULL, " \n")) {
puts (p);
sum += strlen (p);
n++;
}
printf ("average len: %.2f\n", (double)sum / n);
}
}


上面请注意,读取循环由读取函数本身( fgets())的返回控制。

为了进行测试,您可以创建一个简短的文件,每行包含多个单词,然后将其重定向为程序的输入,例如

输入文件示例

$ cat dat/groups.txt
My dog has fleas
My cat has none
Lucky feline


使用/输出示例

$ ./bin/strgroupavglen < dat/groups.txt

group:
My
dog
has
fleas
average len: 3.25

group:
My
cat
has
none
average len: 3.00

group:
Lucky
feline
average len: 5.50


您可以计算字符数,以确认计算出正确的平均长度。仔细检查一下,如果还有其他问题,请告诉我。

没有fgets()-使用状态循环

如果没有 fgets(),现在该回到旧的面向字符的良好输入和状态循环了。

什么是状态循环?

简单来说,这是对每个输入项的循环,您可以在其中跟踪需要跟踪的任何条件的状态,以从整个集合中分离/隔离所需的任何信息。在这种情况下,您希望将字符收集到单词中并将单词收集到行(组)中,以便可以对总字符进行求和并输出每行的平均值。

为了跟踪事物的状态,您将只使用状态变量,否则称为标志。 (只需将 int1设置为 0true的简单 false变量即可跟踪状态/条件)

您需要跟踪什么条件?您需要知道自己是否:


在单词中或单词之前,之间或之后的空格( int inword;
您需要知道自己是否在一行中,以便可以留置留置权( int in_line;-您不能使用 inline-关键字以及所有...)


使用这两种状态(或条件),您可以遍历输入的每个字符,并跟踪 length, sum, word_count等。使用一些计数器变量即可精确地完成所需的操作(旧方法-手动)通过包含 ctype.h标头并利用 isspace()宏来确定读取的字符是否为空格,可以使您的工作更轻松-否则,它是单词中的字符。

综上所述,您可以执行以下操作:

#include <stdio.h>
#include <ctype.h>

#define MAXC 128

int main (void) {

char word[MAXC]; /* array to hold word */
int c, /* char to read from stdin */
in_line = 0, /* state variable in/out line */
inword = 0, /* state varaible in/out word */
len = 0, /* word length */
n = 0, /* word count per-line */
sum = 0; /* sum of chars in words per-line */

while ((c = getchar()) != EOF) { /* read chars until EOF */
if (isspace (c)) { /* if space */
if (inword) { /* if inword */
word[len] = 0; /* nul-terminate word */
puts (word); /* output word */
n++; /* increment word-count */
sum += len; /* add length to sum */
if (c == '\n') { /* if \n, output average */
printf ("average len: %.2f\n", (double)sum / n);
in_line = 0; /* reset in_line flag */
n = 0; /* set word count 0 */
sum = 0; /* set sum zero */
}
}
len = 0; /* set length 0 if space of any kind */
}
else { /* if not space */
if (!in_line) { /* if not in_line */
puts ("\ngroup:"); /* output group heading */
in_line = 1; /* set in_line flag 1 */
}
word[len++] = c; /* add char to word, increment len */
inword = 1; /* set inword flag 1 */
}
}
if (n && inword) /* if word count and inword (non-POSIX EOF) */
printf ("average len: %.2f\n", (double)sum / n);
}


(注意:如果文件不包含最终的 if (n && inword),则最终的 '\n'输出最终的平均值)

(输出是相同的)

没有fgets()-使用scanf()

四舍五入您的选项,您也可以使用 scanf()。对于新的C程序员来说,这通常不是首选,因为 scanf()在使用格式字符串和考虑 stdin中剩余的字符(取决于所使用的转换说明符)方面都充满了陷阱。

为了使 scanf()在您的情况下起作用,您必须有一种方法来确定一行的结尾。否则,您将无法对每行字符求和并输出平均长度。您可以使用 "%s%c"形式来读取单词和其后的字符( "%s"在遇到第一个空格或 EOF时停止。)。 " %[..]"形式在这里没有提供任何好处,因为您只需要用 [^..]否定字符类,并将空格作为不可读内容包括在内即可。

除非您重做上面的State-Loop示例的某些逻辑以跟踪您在一行中的位置,否则使用 scanf()读取输入并产生相同输出的方法与控制每个组的标题并仅输出有关后面有单词时的标题。为了适应这种情况,您需要预读该行开头的字符,并确保它不是 '\n'EOF,然后将 ungetc()字符放回 stdin中。

将片段与 scanf()放在一起,您可以执行以下操作:

#include <stdio.h>
#include <string.h>

#define MAXC 128

int main (void) {

char c = 0; /* test character */

while ((c = getchar()) != EOF && c != '\n') {
char buf[MAXC]; /* declare buffer */
int n = 0, sum = 0; /* count and sum */

ungetc (c, stdin); /* put the char back */
puts ("\ngroup:"); /* output groups header */

while (1) { /* loop until no word or until \n */
int rtn = scanf (" %s%c", buf, &c); /* read word & char */
if (rtn < 1) /* if no word, break */
break;
puts (buf); /* output word */
sum += strlen (buf); /* add length to sum */
n++; /* increment word count */
if (c == '\n') /* if end of line, break */
break;
}
if (n) /* only average if words in group */
printf ("average len: %.2f\n", (double)sum / n);
}
}


(对于相同的输入,输出是相同的)

因此,您可以在没有 fgets()的情况下执行此操作-但我会让您自己确定,从而简化了所需的逻辑。如果您还有其他问题,请告诉我。



使用fgets()编辑请求提供的字符串组存储

当要存储已分配的组时,使用 fgets()/strtok()解析标记(单词)很简单,并且为每个单词提供存储很简单(如果使用 strdup,则只需使用 strlen() + 1,或者只是分配 char ***pointer;字节)。单个对象中的字符串-您必须为(1)每个组的指针(2)每个组中的每个字符串的指针和(3)每个单词的存储分配存储空间。

您很快发现您将不得不使用和分配3级指针间接寻址。通常的规则是,成为三星级程序员不是恭维,而且,如果您尝试使用 NULL,则通常应考虑重构或重新排列代码,以避免出现间接层之一。但是,对于像这样要处理未知数量的组,每个组包含未知数量的字符串以及每个字符串包含未知数量的字符的情况,您没有太多选择。

在研究如何处理分配之前,让我们看一下一个图表,该图概述了所需的不同分配,以及为组指针以及为每个组 used + 1 == allocated生成字符串指针的最后一个指针的方法(提供前哨NULL)。这使您不必为其他组的组和字符串数保留计数器,从而能够遍历集合并获取信息。

例如:

   Allocation 1               Allocation 2
group pointers string pointers

+------+ +------+------+------+------+------+
| g1 | ---> | s1 | s2 | s3 | s4 | NULL |
+------+ +------+------+------+------+------+
| g2 | ... | | | |
+------+ +---+ +---+ ... ....
| g3 | ... | M | | d |
+------+ +---+ +---+
| NULL | | y | | o | Allocations 3+
+------+ +---+ +---+ storage for each string
| \0| | g |
+---+ +---+
| \0|
+---+


如果这是您第一次接触动态分配,那么为三层间接分配存储的复杂性似乎令人生畏,但实际上为任何对象分配/重新分配都不再困难了–唯一的问题是您将事物嵌套3层深度。因此,让我们看一下单个级别的常规分配/重新分配。

如果您需要为字符串分配存储空间,而又不知道字符串多长时间,则只需分配一些初始字符数,并跟踪使用的字符数,以及 '\0',( +1为 realloc节省了空间,最后,您 getchar()有了更多存储空间并继续使用。使用 valgrind向字符串中添加未知数量的字符的简单示例可以是:

#include <stdio.h>
#include <stdlib.h>

int main (void) {

int c, allocated = 2, used = 0; /* char, bytes-allocated, bytes-used */
char *string = NULL; /* pointer to allocated storage */

string = malloc (allocated); /* allocate initial storage for 2-chars */
if (string == NULL) { /* validate EVERY allocation */
perror ("malloc-string");
return 1;
}

while ((c = getchar()) != '\n' && c != EOF)
{
/* check if reallocation needed */
if (used + 1 == allocated) { /* recall +1 is needed for \0 at end */
/* always realloc to a temporary pointer */
void *tmp = realloc (string, 2 * allocated);
if (tmp == NULL) { /* validate EVERY reallocation */
perror ("realloc-string");
break; /* storage pointed to by string still good, don't exit */
}
string = tmp; /* assign reallocated block to string */
allocated *= 2; /* update allocated with new size */
}
string[used++] = c; /* assign character to string */
}
string[used] = 0; /* nul-terminate string */

printf ("%s\nallocated: %d\nused : %d\n", string, allocated, used);
free (string); /* don't forget to free what you allocate */
}


使用/输出示例

$ echo "My dog has fleas and my cat has none" | ./bin/reallocgetchar
My dog has fleas and my cat has none
allocated: 64
used : 36


内存使用/错误检查

在您编写的任何动态分配内存的代码中,对于任何分配的内存块,您都有2个责任:(1)始终保留指向该内存块的起始地址的指针,因此,(2)在没有内存块时可以将其释放需要更长的时间。

必须使用内存错误检查程序来确保您不尝试访问内存或不在分配的块的边界之外/之外写,尝试读取或基于未初始化的值进行条件跳转,最后确认您可以释放已分配的所有内存。

对于Linux, (used == allocated)是正常选择。每个平台都有类似的内存检查器。它们都很容易使用,只需通过它运行程序即可。

$ echo "My dog has fleas and my cat has none" | valgrind ./bin/reallocgetchar
==4893== Memcheck, a memory error detector
==4893== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==4893== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==4893== Command: ./bin/reallocgetchar
==4893==
My dog has fleas and my cat has none
allocated: 64
used : 36
==4893==
==4893== HEAP SUMMARY:
==4893== in use at exit: 0 bytes in 0 blocks
==4893== total heap usage: 8 allocs, 8 frees, 5,246 bytes allocated
==4893==
==4893== All heap blocks were freed -- no leaks are possible
==4893==
==4893== For counts of detected and suppressed errors, rerun with: -v
==4893== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)


始终确认已释放已分配的所有内存,并且没有内存错误。

无论如何,方法都是相同的,添加间接级别时的唯一区别是,您正在为指针而不是字符(或整数,结构或其他基本元素)重新分配指针的存储。方法是:


分配一些值得存储的初始元素数量,并跟踪使用的元素数量和分配的数量。
realloc时检查是否需要重新分配
如果是这样,请始终将 realloc指向临时指针-当 NULL失败时,它将返回 pointer = realloc (pointer, ..);如果您盲目地执行 pointer,则您已经用 NULL覆盖了 char **所保存的地址,从而创建了内存泄漏。
验证每个分配/重新分配
成功重新分配后,将重新分配的内存块分配给指针,更新变量以跟踪分配的元素数,然后继续...。


以此为背景,我们可以应用3级深度的方法将所有字符串读入一个组(每个组具有 char***类型,这是一个分配的指针块,其中为每个字符串分配的存储空间分配给了每个指针)并绑定通过最后分配的指针块(类型为 grpsalloced)将每组字符串与每个分配的指针块的地址一起分配给分配给该最终内存块中的指针的每个组。

通过变量 grpsusedgrpsused跟踪分配和使用的组数以下,其中 groups[grpsused]成为我们集合中组的索引,例如 stralloced。对于每个组中的字符串,我们跟踪 strusedstrused分配和使用的指针,其中 group[grpsused][strused]变量成为该组中我们字符串的索引,例如 [..]'*'就像 strtok()一样用作指针的取消引用。

因此,要将 grpsused解析的每个单词作为字符串存储在组中,并将所有组一起收集到最终对象中,您可以将其作为参数传递或独立于读取循环进行循环,您可以执行以下操作:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NGROUPS 2 /* initial no. of groups to allocate */
#define MAXC 1024 /* max characters for read buffer */

int main (void) {

char buf[MAXC];
char ***groups = NULL; /* being a 3-star programmer isn't a compliment */
/* but it is necessary to access groups of strings */
int grpsalloced = NGROUPS, /* initial no. of groups (pointers) allocated */
grpsused = 0; /* counter to track groups (pointers) used */

groups = malloc (grpsalloced * sizeof *groups); /* allocate ngroups pointers */
if (groups == NULL) { /* validate EVERY allocation */
perror ("malloc-groups");
return 1;
}

/*
* add groups of strings, reallocating as needed, ensure last pointer to
* string in each group is NULL, and ensure last pointer to group is NULL
*/
while (fgets (buf, MAXC, stdin)) { /* read each line into buffer */
int stralloced = NGROUPS, /* reuse NGROUPS to set no. strings per-group */
strused = 0; /* strings used in group */
/* allocate ptrs for current group, assign to next available groups pointer */
groups[grpsused] = malloc (stralloced * sizeof *groups[grpsused]);
if (!groups[grpsused]) { /* validate group allocation */
perror ("malloc-groups[groupsused]");
break; /* again, break, don't exit, any prior groups data still good */
}

/* loop separating tokens (words) from buffer */
for (char *p = strtok(buf, " \n"); p; p = strtok (NULL, " \n")) {
size_t len = strlen (p);
/* allocate storage for each token (word), use strdup if available */
groups[grpsused][strused] = malloc (len + 1);
if (!groups[grpsused][strused]) {
perror ("malloc-groups[groupsused][n]");
break;
}
/* copy string to allocated storage */
memcpy (groups[grpsused][strused], p, len + 1);
strused++; /* increment string count */

/* check if more pointers for current group required */
if (strused == stralloced) {
void *tmp = realloc (groups[grpsused], /* realloc str ptrs */
2 * stralloced * sizeof *groups[grpsused]);
if (!tmp) { /* validate reallocation */
perror ("realloc-groups[groupsused]");
break;
}
groups[grpsused] = tmp; /* assign new block of pointers */
stralloced *= 2; /* increment allocated pointer count */
}
groups[grpsused][strused] = NULL; /* sentinel NULL at end str ptrs */
}
grpsused++; /* increment groups used counter */

if (grpsused == grpsalloced) { /* when groups reallocation needed */
/* always realloc to a temporary pointer, here doubling no. of pointers */
void *tmp = realloc (groups, 2 * grpsalloced * sizeof *groups);
if (!tmp) {
perror ("realloc-groups");
break; /* don't exit, original data for groups still valid */
}
groups = tmp; /* assign reallocated block to groups */
grpsalloced *= 2; /* update no. of group ptrs allocated */
}
}
groups[grpsused] = NULL; /* sentinel NULL at end of group pointers */

/*
* iterate over group pointers, iterate over each string pointer in group
*/
char ***g = groups; /* pointer to groups */
while (*g) {
char **s = *g; /* pointer to 1st group */
int n = 0, sum = 0; /* integers for string counter and sum */
puts ("\ngroup:"); /* output heading */
while (*s) { /* loop over each string pointer in group */
puts (*s); /* output string */
sum += strlen (*s); /* add length to sum */
free (*s); /* free storage for string (can be done later) */
s++; /* advance to next string */
n++; /* increment string counter */
}
printf ("average len: %.2f\n", (double)sum / n); /* group result */
free (*g); /* free current group (can be done later) */
g++; /* advance to next group pointer */
}
free (groups); /* free memory for group pointers */
}


您可以在分配之前,或者在 strused 值增加之后对重新分配进行重新排序(如上所述,以确保空指针可用于sentinel-NULL)。

这将在这里为您完成编辑-进一步的问题(上面所做的事情除外)需要一个新的问题。 (因为这可能应该是)

关于c - 如何不使用(fgets)继续扫描直到换行C,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60198370/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com