所以我有一个代码,它可以输入任意数量的任意长度的行。每一行通常包含一个或多个用空格分隔的单词。我必须输出那些字符串。
所以这是我的代码。我是C的新手,对我的哑码;)
#include <stdio.h>
#include <stdlib.h>
int main () {
int i = 1;
char **list = malloc(1000 * sizeof(char*));
while(!feof(stdin)){
list[i] = malloc(1000 * sizeof(char));
printf("Enter string with num %d: ", i);
scanf("%99[^n]", list[i]);
i++;
}
printf("n");
for (int j = 1; j < i; j++)
printf("Your strings by num %d: %sn", j, list[j]);
return 0;
}
但我对scanf有问题,我不能在空白后继续输入。我想我的问题是
scanf("%99[^n]", list[i]);
除此之外,我不能使用gets()、getchar()族,也不能使用规范%c和%m。
使用%99[^n]
进行扫描,直到遇到n
。所以第一行读起来很顺利。但由于n
尚未被扫描,它仍保留在stdin
的未读部分。下次扫描时,您会再次遇到"\n",并且实际上扫描了一个空字符串。所以使用%99[^n]%*c
——%*c
部分意味着忽略一个字符。
您已经完成了保证会导致逻辑出现问题的首要任务之一。您将i
混合为基于1的(用于计数),但对于索引应该是基于0的。在C中,所有阵列和存储都是基于零的。第一个元素是元素0
。当你开始混合基于1的计数器和基于0的索引时,你会使你的逻辑复杂化,并且必须考虑到变量用作计数器或索引的每个实例中的偏移——尽一切代价避免这种情况。
不是将int i = 1;
初始化为int i = 0;
,而是将其用作索引。当您需要作为计数器输出时,只需使用i + 1
。在您的情况下,这意味着您正在分配1000
指针,但从未使用第一个指针。(您可以这样做,但这只是草率的)始终从0
进行索引,并根据需要通过添加到索引来调整输出编号。(也不只是使用i
来跟踪分配的指针数量,而只是一个更具描述性的变量名称,例如n_pointers
或nptrs
)
正如评论中所提到的,由于您是从键盘上获取用户输入,因此在开始时预先分配1000个指针没有任何好处——也没有提高程序效率。在用户键入"cat"
的时间内,您可以在不引入任何延迟的情况下重新分配新指针并为存储分配1000次。
这里的一个更好的方法是简单地声明一个固定缓冲区(字符数组)用于所有输入,然后在验证接收到良好的输入后,只需realloc()
一个新指针并为输入分配存储。这样就不会过度分配指针。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXC 1000 /* if you need a constant, #define one (or more) */
int main () {
size_t nptrs = 0; /* counter for no. pointers allocated */
char **list = NULL, /* list and fixed buffer for read */
buf[MAXC];
...
正如评论中所提到的,由于你显然被scanf()
卡住了,所以花一个小时在man页面上——这将为你节省100个小时的悲伤。对于新的C程序员来说,scanf()
充满了陷阱,比如知道哪些转换说明符丢弃前导空格,哪些不丢弃前导空格。。。还有50个类似的陷阱。
在这里,您希望使用scanf()
读取空白分隔的单词,因此您将使用%[...]
转换说明符来读取不包括'n'
字符的所有字符。(第一课,%[...]
、%c
和%n
是不丢弃前导空格的转换)。因此,在您的情况下,要丢弃前导空格,您需要在" %[^n]"
之前包含一个空格。
您还需要知道消耗的字符数,以便根据999
字段宽度限制进行检查,以了解您刚刚尝试读取的行是否太长而无法容纳。您可以使用伪转换%n
来获取消耗的字符数,直到它出现在格式字符串中为止。
由于您的读取在末尾不包括'n'
字符,如果缓冲区已填充999
字符(加上nul终止字符),则该行中的其他字符将保持未读状态,您需要读取并丢弃这些字符,以便您的下一次读取将从下一行开始。
(您通常会使用getchar()
进行循环,直到找到'n'
,但由于您不能使用它,您将再次使用scanf()
,但不丢弃前导空格)
每当你循环收集用户输入时(尤其是当需要像整数值这样的特定输入时),你会在输入例程周围使用一个连续的循环,要求用户提供所需类型的输入以及特定值范围内的任何输入。当您收到并验证所需的输入时,只需break
您的读取循环。否则,您只需再次循环,直到得到所需内容。验证任何用户输入的关键是检查返回(如果需要,还检查限制范围内的值)。
将读取方法放入固定缓冲区,验证并在行太长时丢弃字符,您可以这样做:
...
puts ("inputn");
while (1) { /* loop continually until manual EOF */
int nchar, rtn; /* no. of chars consumed, scanf return */
printf ("Enter string with num %zu: ", nptrs + 1); /* prompt */
rtn = scanf (" %999[^n]%n", buf, &nchar); /* save return */
if (rtn == EOF) { /* check EOF */
puts ("EOFnnoutputn");
break;
}
if (nchar == 999) { /* check line-too-long */
fputs ("errro: line exceeds MAXC chars.n", stderr);
while (nchar == 999) { /* loop discarding remainder of line */
if (scanf ("%999[^n]%n", buf, &nchar) != 1) {
goto getnextline; /* skip over allocation/storage */
}
}
}
...
现在,为每个输入重新分配一个额外的指针和存储,并从固定缓冲区复制到分配的存储,您可以执行以下操作:
...
/* always realloc using temp pointer or risk memory leak */
void *tmp = realloc (list, (nptrs + 1) * sizeof *list);
if (!tmp) {
perror ("realloc-list");
break;
}
list = tmp; /* assign realloc'ed block of ptrs to list */
list[nptrs] = malloc (nchar + 1); /* allocate for string */
memcpy (list[nptrs], buf, nchar + 1); /* don's scan for n */
nptrs += 1; /* increment pointer count */
getnextline:;
}
...
剩下的就是输出行(并将索引移动到计数器以匹配输入提示)。既然你要结束你的程序,不要忘记free()
每一行的存储,然后在最后释放指针。是的,当程序退出时会发生这种情况,但您并不总是在main()
中进行分配,并且未能释放您在函数中分配的内存将在程序中造成内存泄漏。所以尽早养成好习惯。始终跟踪您的内存分配,并在不再需要内存时free()
,例如
...
for (size_t j = 0; j < nptrs; j++) {
printf("Your strings by num %zu: %sn", j + 1, list[j]);
free (list[j]); /* free storage for string */
}
free (list); /* don't forget to free pointers */
}
就是这样。这将占用所需的所有输入,消除对可以输入的行数的任何限制(直到虚拟内存的限制),输出所有存储的行,并在退出前释放所有分配的内存。
示例使用/输出*
$ ./bin/scanflines
input
Enter string with num 1: string with 1
Enter string with num 2: string with 2
Enter string with num 3: string with 3
Enter string with num 4: string with 4
Enter string with num 5: string with 5
Enter string with num 6: string with 6
Enter string with num 7: string with 7
Enter string with num 8: string with 8
Enter string with num 9: EOF
output
Your strings by num 1: string with 1
Your strings by num 2: string with 2
Your strings by num 3: string with 3
Your strings by num 4: string with 4
Your strings by num 5: string with 5
Your strings by num 6: string with 6
Your strings by num 7: string with 7
Your strings by num 8: string with 8
内存使用/错误检查
在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有2个责任:(1)始终为内存块保留一个指向起始地址的指针,因此,(2)当不再需要时,它可以被释放。
您必须使用内存错误检查程序来确保您不会试图访问内存或在分配的块的边界之外写入,尝试读取或基于未初始化的值进行条件跳转,最后确认您释放了所有分配的内存。
对于Linux,valgrind
是正常的选择。每个平台都有类似的内存检查器。它们都很容易使用,只需通过它运行您的程序即可
$ valgrind ./bin/scanflines
==7141== Memcheck, a memory error detector
==7141== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==7141== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==7141== Command: ./bin/scanflines
==7141==
input
Enter string with num 1: string with 1
Enter string with num 2: string with 2
Enter string with num 3: string with 3
Enter string with num 4: string with 4
Enter string with num 5: string with 5
Enter string with num 6: string with 6
Enter string with num 7: string with 7
Enter string with num 8: string with 8
Enter string with num 9: EOF
output
Your strings by num 1: string with 1
Your strings by num 2: string with 2
Your strings by num 3: string with 3
Your strings by num 4: string with 4
Your strings by num 5: string with 5
Your strings by num 6: string with 6
Your strings by num 7: string with 7
Your strings by num 8: string with 8
==7141==
==7141== HEAP SUMMARY:
==7141== in use at exit: 0 bytes in 0 blocks
==7141== total heap usage: 18 allocs, 18 frees, 2,492 bytes allocated
==7141==
==7141== All heap blocks were freed -- no leaks are possible
==7141==
==7141== For counts of detected and suppressed errors, rerun with: -v
==7141== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
请始终确认您已经释放了分配的所有内存,并且没有内存错误。
这里有很多东西需要消化,所以慢慢来,确保你理解为什么每一行都是这样写的,以及它在做什么。如果你有问题,请在下面留言。
最后,要知道,在这种情况下,将fgets()
转换为buffer
将是一种比使用scanf()
更好的推荐方法。但由于这似乎是禁止的,可以用scanf()
来完成,这是一个很好的学习练习——但如果没有学习练习,fgets()
是更好的选择。
完整程序
为方便起见:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXC 1000 /* if you need a constant, #define one (or more) */
int main () {
size_t nptrs = 0; /* counter for no. pointers allocated */
char **list = NULL, /* list and fixed buffer for read */
buf[MAXC];
puts ("inputn");
while (1) { /* loop continually until manual EOF */
int nchar, rtn; /* no. of chars consumed, scanf return */
printf ("Enter string with num %zu: ", nptrs + 1); /* prompt */
rtn = scanf (" %999[^n]%n", buf, &nchar); /* save return */
if (rtn == EOF) { /* check EOF */
puts ("EOFnnoutputn");
break;
}
if (nchar == 999) { /* check line-too-long */
fputs ("errro: line exceeds MAXC chars.n", stderr);
while (nchar == 999) { /* loop discarding remainder of line */
if (scanf ("%999[^n]%n", buf, &nchar) != 1) {
goto getnextline; /* skip over allocation/storage */
}
}
}
/* always realloc using temp pointer or risk memory leak */
void *tmp = realloc (list, (nptrs + 1) * sizeof *list);
if (!tmp) {
perror ("realloc-list");
break;
}
list = tmp; /* assign realloc'ed block of ptrs to list */
list[nptrs] = malloc (nchar + 1); /* allocate for string */
memcpy (list[nptrs], buf, nchar + 1); /* don's scan for n */
nptrs += 1; /* increment pointer count */
getnextline:;
}
for (size_t j = 0; j < nptrs; j++) {
printf("Your strings by num %zu: %sn", j + 1, list[j]);
free (list[j]); /* free storage for string */
}
free (list); /* don't forget to free pointers */
}