正如@MikeCAT 解释的那样,您的直接问题是:
do
{
printf("Please enter numbers you wanna input: ");
scanf("%d", &n);
for(int i=0; i<n; ++i) {
scanf("%d",&number[i]);
}
option = getchar (); /* reads '\n' left in stdin by scanf */
while(getchar() != '\n'); /* stdin empty -- getchar() blocks waiting for next input */
} while (option != 'q');
它揭示的真正问题是 scanf() 充满了陷阱,应该避免用于用户输入,直到您充分理解它以知道为什么不应该将其用于用户输入。这意味着了解每个转换说明符以及它是否会消耗前导空格,并进一步了解匹配失败会发生什么以及您必须做什么来丢弃有问题的输入以进行更正问题。这甚至没有达到验证每个输入和转换的失败。
就scanf() 的陷阱而言,这些只是冰山一角——这就是为什么鼓励所有新的C 程序员使用面向行的 输入函数,例如fgets() 或POSIX getline() 用于所有用户输入。主要的好处是面向行的输入函数每次都会消耗一整行用户输入——因此在stdin中留下未读字符的可能性是等待下一个输入咬你消除(假设为fgets() 的输入提供了一个合理大小的数组)
使用fgets() 进行用户输入
不管你使用什么类型的输入函数,你不能使用任何正确的,除非你检查返回。除非您检查返回,否则您无法确定用户输入是成功还是失败。如果您在验证输入成功之前盲目地使用您认为保存输入的变量——您要求的是未定义的行为。规则:验证每个输入和每个转换...
那么fgets()怎么用呢?这真的很容易。只需提供一个足够大的缓冲区(字符数组)以容纳最长的预期用户输入,然后乘以 4(或一些合理的值)。指针不要跳过缓冲区大小。您宁愿 10,000 个字符太长也不愿 1 个字符太短。对于一般用户来说,1K 的缓冲区就可以了(1024 字节)。如果猫踩到键盘,它甚至可以保护你。
如果您在内存有限的微控制器上编程,则将缓冲区大小减少到最大预期输入的 2 倍。 (让猫远离)
当您需要从填充的字符数组中转换一个数字时,最简单的方法是使用缓冲区作为输入调用sscanf()(类似于您使用scanf() 的方式)。但是这里的好处是,如果转换失败并不重要,它不会在stdin 中留下任何未读的内容(读取已经发生,因此转换不会影响输入流的状态) .如果您允许同时输入多个整数,那么strtol() 可以处理从缓冲区开始到结束的工作,同时转换值。
使用面向行的函数读取字符串输入时唯一需要注意的是,该函数将读取并包含 '\n' 作为它填充的缓冲区的一部分。如果您将该值存储为字符串,则需要将其删除。您可以使用strcspn() 来执行此操作,它返回拒绝列表中包含的任何字符之前的字符数。因此,您只需使用"\n" 作为您的拒绝列表,它会告诉您到换行符的字符数。然后,您只需使用该数字覆盖'\n' 和'\0',从末尾修剪'\n'。例如,如果您正在读取名为 line 的缓冲区,那么您只需从输入末尾修剪 '\n':
char line[1024];
if (fgets (line, sizeof line, stdin))
line[strcspn (line, "\n")] = 0;
(注意: 在您只是将 line 中包含的内容转换为数字的情况下 - 无论如何都不需要修剪 '\n',sscanf() 将忽略whtiespace,'\n' 是空格)
那么在您的情况下,阅读您的输入需要什么?只需声明一个缓冲区(字符数组)来保存用户输入并使用fgets() 处理来自用户的所有用户输入。您可以为所有输入重复使用相同的缓冲区。您可以通过创建一个以数组填充和提示作为参数的简短函数来使自己的生活更轻松。然后,如果提示不是NULL,则显示提示并读取用户输入——检查返回。
如果fgets() 返回NULL,则意味着在接收到任何输入之前已达到EOF(用户可以通过使用Ctrl + dEOF 来取消输入是完全有效的/kbd> 或 Ctrl + z(在 Windows 上)。所以检查返回,如果用户取消输入,就优雅地处理它。您可以编写一个getstr() 函数来帮助:
#include <stdio.h>
#include <stdlib.h> /* for qsort */
#define MAXC 1024 /* if you need a constant, #define one (or more) */
#define MAXI 200
#define PROMPT "No. of intgers to read (max 200): "
...
/* fill array s with string, display prompt if not NULL
* returns pointer to string on success, NULL otherwise
*/
char *getstr (char *s, const char *prompt)
{
if (prompt) /* if prompt not NULL, display */
fputs (prompt, stdout);
if (!fgets (s, MAXC, stdin)) { /* read entire line of input */
puts ("(user canceled input)"); /* handlie EOF case */
return NULL;
}
return s; /* convenience return of s for immediate use if needed */
}
int main (void) {
char buf[MAXC]; /* array to hold ALL input */
int number[MAXI] = {0}, /* initialize all arrays */
i = 0, n = 0;
while (i == 0 || i != n) { /* loop until numbers filled */
if (!getstr (buf, PROMPT)) /* validate EVERY user-input */
return 0;
...
允许用户使用'q' 退出(或在空行上按 [Enter])
当使用fgets() 填充数组时,可以非常非常轻松地响应任何字符并执行特殊操作。您刚刚填充了一个字符数组。如果你想检查一个特殊字符——只需检查数组中的第一个字符(元素)!这意味着您需要检查的只是 buf[0] 中的字符(或等效地只是 *buf - 它是指针表示法中 *(buf + 0) 的缩写)
因此,如果用户输入 'q'(或在 emply 行上按回车键)允许用户退出,您只需要:
if (buf[0] == 'q' || *buf == '\n') /* exit on 'q' or empty-line */
return 0;
您可以在main() 中的任何时候使用它。如果您正在签入一个函数,您只需选择一个表示用户退出的返回值(如返回类型为int,只需选择一个整数,例如-1 表示用户退出,保存0对于其他一些失败和1 表示成功)。但是由于您的逻辑在main() 中,因此只需从main() 返回就可以了。由于退出不是错误,return 0;(相当于exit (EXIT_SUCCESS);)。
从数组中转换整数值
如上所述,在使用fgets() 阅读后,如果用户没有退出,那么你的下一个工作就是将缓冲区中的数字转换为整数值。使用 sscanf() 类似于您尝试使用 scanf() 的方式很好。与sscanf() 的唯一区别是它将保存数字的缓冲区作为其第一个参数,例如
if (sscanf (buf, "%d", &n) != 1) { /* validate EVERY conversion */
fputs (" error: invalid integer input.\n", stderr);
continue;
}
如果转换失败,您将处理错误并将continue 返回循环顶部,允许用户“重试”。如果转换成功,您的验证还没有结束。输入必须是正值,大于0 且小于或等于200 - 否则0 没有可输入的内容,小于或大于200 的任何内容都将调用 试图在数组边界之前或之外写入的未定义行为。只需添加该验证:
if (n <= 0 || 200 < n) { /* validate input in range */
fprintf (stderr, " error: out of range, (0 < n <= %d)\n", MAXI);
continue;
}
与上次转换相同,如果用户弄错了,处理错误并continue; 允许用户“重试”。一旦您获得了将保存在数组中的整数个数的有效数字,读取各个值与读取要输入的整数个数完全相同。在这里,您只需循环,直到用户正确输入所有值:
for (i = 0; i < n;) { /* loop reading n integers */
printf ("number[%2d]: ", i+1); /* prompt */
if (!getstr(buf, NULL) || /* read/validate input */
*buf == 'q' || *buf == '\n') /* quit on 'q' or empty line */
return 0;
if (sscanf (buf, "%d", &number[i]) != 1) { /* validate conversion */
fputs (" error: invalid integer input.\n", stderr);
continue;
}
i += 1; /* only increment on good input */
}
(注意:i 的值如何仅在用户提供有效输入时才增加)
C 中的排序(qsort)
您正在学习 C。无论您需要对什么进行排序,C 都提供 qsort() 作为标准库的一部分,可以对您需要的任何数组进行排序(并且比尝试“自己凭空整理出一个排序......“。qsort() 对任何类型对象的数组进行排序。唯一让新 C 程序员回头看的事情就是需要编写一个 compare 函数,它告诉qsort() 如何对数组进行排序。(当你的眼睛向后滚动时,它真的很简单)
每个比较函数都有相同的声明:
int compare (const void *a, const void *b)
{
/* cast a & b to proper type,
* return: -1 - if a sorts before b
* 0 - if a & b are equal
* 1 - if b sorts before a
*/
}
const void *what??放松。 a 和 b 只是指向数组中元素的指针。 (qsort() 使用void* 类型,因此它可以将任何类型对象传递给比较函数)编写比较函数的工作只是将它们转换回正确的类型,然后编写上面的逻辑。在您的情况下,a 是指向int 的指针(例如int*),因此您需要做的就是转换为(int*),然后取消引用指针以获取整数值,例如
/* qsort compare function, sort integers ascending
* using result of (a > b) - (a < b) prevents overflow
* use (a < b) - (a > b) for descending.
*/
int cmpint (const void *a, const void *b)
{
int ia = *(int*)a, /* a & b are pointers to elements of the array to sort */
ib = *(int*)b; /* cast to correct type and dereference to obtain value */
return (ia > ib) - (ia < ib); /* return difference: -1 - a sort before b
* 0 - a and b are equal
* 1 - b sorts before a
*/
}
(注意: 简单地返回ia - ib 会起作用,但如果ia 是一个大的负值而ib 是一个大的正值,则会发生整数溢出,反之亦然. 所以你使用两个比较操作的差异来消除这个机会——试试看,为ia和ib选择两个数字,看看结果如何......)
现在,如果我的数组是 number 并且其中有 n 元素,并且我将比较函数命名为 cmpint 使用 qsort() 对 number 数组中的值进行排序有多难?
qsort (number, n, sizeof *number, cmpint); /* sort numbers */
(完成!)
总而言之*
如果你把它全部打包到你的程序中,你最终会得到:
#include <stdio.h>
#include <stdlib.h> /* for qsort */
#define MAXC 1024 /* if you need a constant, #define one (or more) */
#define MAXI 200
#define PROMPT "No. of intgers to read (max 200): "
/* qsort compare function, sort integers ascending
* using result of (a > b) - (a < b) prevents overflow
* use (a < b) - (a > b) for descending.
*/
int cmpint (const void *a, const void *b)
{
int ia = *(int*)a, /* a & b are pointers to elements of the array to sort */
ib = *(int*)b; /* cast to correct type and dereference to obtain value */
return (ia > ib) - (ia < ib); /* return difference: -1 - a sort before b
* 0 - a and b are equal
* 1 - b sorts before a
*/
}
/* fill array s with string, display prompt if not NULL
* returns pointer to string on success, NULL otherwise
*/
char *getstr (char *s, const char *prompt)
{
if (prompt) /* if prompt not NULL, display */
fputs (prompt, stdout);
if (!fgets (s, MAXC, stdin)) { /* read entire line of input */
puts ("(user canceled input)"); /* handlie EOF case */
return NULL;
}
return s; /* convenience return of s for immediate use if needed */
}
int main (void) {
char buf[MAXC]; /* array to hold ALL input */
int number[MAXI] = {0}, /* initialize all arrays */
i = 0, n = 0;
while (i == 0 || i != n) { /* loop until numbers filled */
if (!getstr (buf, PROMPT)) /* validate EVERY user-input */
return 0;
if (buf[0] == 'q' || *buf == '\n') /* exit on 'q' or empty-line */
return 0;
if (sscanf (buf, "%d", &n) != 1) { /* validate EVERY conversion */
fputs (" error: invalid integer input.\n", stderr);
continue;
}
if (n <= 0 || 200 < n) { /* validate input in range */
fprintf (stderr, " error: out of range, (0 < n <= %d)\n", MAXI);
continue;
}
for (i = 0; i < n;) { /* loop reading n integers */
printf ("number[%2d]: ", i+1); /* prompt */
if (!getstr(buf, NULL) || /* read/validate input */
*buf == 'q' || *buf == '\n') /* quit on 'q' or empty line */
return 0;
if (sscanf (buf, "%d", &number[i]) != 1) { /* validate conversion */
fputs (" error: invalid integer input.\n", stderr);
continue;
}
i += 1; /* only increment on good input */
}
}
qsort (number, n, sizeof *number, cmpint); /* sort numbers */
puts ("\nsorted values:"); /* output results */
for (i = 0; i < n; i++)
printf (i ? " %d" : "%d", number[i]); /* ternary to control space */
putchar ('\n'); /* tidy up with newline */
}
使用/输出示例
按预期使用:
$ ./bin/fgets_n_integers+sort
No. of intgers to read (max 200): 10
number[ 1]: 321
number[ 2]: 8
number[ 3]: -1
number[ 4]: 4
number[ 5]: -2
number[ 6]: 0
number[ 7]: -123
number[ 8]: 123
number[ 9]: 6
number[10]: 2
sorted values:
-123 -2 -1 0 2 4 6 8 123 321
滥用输入错误(故意)并在中间使用'q' 退出:
$ ./bin/fgets_n_integers+sort
No. of intgers to read (max 200): bananas
error: invalid integer input.
No. of intgers to read (max 200): 0
error: out of range, (0 < n <= 200)
No. of intgers to read (max 200): 201
error: out of range, (0 < n <= 200)
No. of intgers to read (max 200): 5
number[ 1]: bananas again!!!!!!!!!!!!!!!!!
error: invalid integer input.
number[ 1]: twenty-one
error: invalid integer input.
number[ 1]: 21
number[ 2]: 12
number[ 3]: done
error: invalid integer input.
number[ 3]: really
error: invalid integer input.
number[ 3]: q
当你编写任何输入例程时——去尝试打破它!如果失败,找出原因,修复它并重试。当你尝试了所有你能想到的糟糕的极端情况并且你的输入例程继续工作时——你会感觉相当不错——直到你给它的用户做了一些完全不合时宜的事情并找到一个新的极端情况(修复那也是)
留给你的角落案例
如果用户想在数组中输入1 数字会发生什么?有什么理由排序吗?返回并查看代码并找出您可以在哪里阻止用户输入1 作为有效输入,或者只输出用户输入的第一个数字并跳过排序等。 - 由您决定。
与往常一样,这比我预期的要长得多。但是看看你在你的程序中被困在哪里——没有一些额外的(几十个)段落,没有捷径可以帮助你了解你需要做什么以及为什么。所以这里有很多。慢下来,消化它,和鸭子谈谈(见How to debug small programs——别笑,它有效)
然后,如果您还有其他问题,请在下方发表评论,我很乐意为您提供进一步帮助。