正如@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()
或 POSIXgetline()
对于所有用户输入。主要好处是面向行的输入函数每次都会消耗一整行用户输入——因此可能会留下未读的字符stdin
只需等待下一个输入就可以消除(假设为输入提供了合理大小的数组)fgets()
)
Using fgets()
对于用户输入
无论您使用哪种类型的输入函数,都不能使用任何正确的输入函数,除非您检查退货。除非您检查返回结果,否则您无法确定用户输入是否成功或失败。如果您在验证输入成功之前盲目地使用您认为保存输入的变量 - 您正在要求未定义的行为。规则:验证每个输入和每个转换...
那么如何使用fgets()
?这真的很容易。只需提供一个足够大的缓冲区(字符数组)来容纳最长的预期用户输入,然后乘以 4(或合理的值)。指针是不要吝惜缓冲区大小。宁愿太长 10,000 个字符,也不愿太短 1 个字符。对于一般用户来说,1K 缓冲区就足够了(1024 字节)。如果猫踩到键盘,它甚至可以保护您。
如果您在内存有限的微控制器上进行编程,请将缓冲区大小减少到最大预期输入的 2 倍。 (让猫远离)
当需要将填充的字符数组转换为数字时,最简单的方法是调用sscanf()
使用缓冲区作为输入(类似于您使用的方式scanf()
)。但这样做的好处是,如果转换失败也没关系,不会留下任何未读的内容stdin
(读取已经发生,因此转换不可能影响输入流的状态)。如果允许同时输入多个整数,则strtol()
可以处理从开始到结束的缓冲区转换值的工作。
使用面向行的函数读取字符串输入时唯一需要注意的是该函数将读取并包含'\n'
作为它填充的缓冲区的一部分。如果您将该值存储为字符串,则需要删除它。你可以这样做strcspn()
它返回拒绝列表中包含的任何字符之前的字符数。所以你只需使用"\n"
作为您的拒绝列表,它会告诉您直到换行符的字符数。然后你只需使用该数字来覆盖'\n'
with '\0'
修剪'\n'
从最后。例如,如果您正在读入名为的缓冲区line
,那么您只需执行以下操作即可修剪'\n'
从输入的末尾开始:
char line[1024];
if (fgets (line, sizeof line, stdin))
line[strcspn (line, "\n")] = 0;
(note:在您的情况下,您只是简单地转换其中包含的内容line
到一个数字——无需修剪'\n'
反正,sscanf()
将忽略空格,并且'\n'
是空白)
那么在您的情况下,读取您的输入需要什么?只需声明一个缓冲区(字符数组)来保存用户输入并使用fgets()
处理来自用户的所有用户输入。您可以为所有输入重复使用相同的缓冲区。您可以通过创建一个简短的函数来让自己的生活更轻松,该函数将要填充的数组和提示作为参数。然后如果没有提示NULL
显示提示并读取用户输入——检查返回。
If fgets()
returns NULL
it means EOF
was reached before any input was received (and it is perfectly valid for the user to cancel input by generating a manual EOF
with Ctrl + d, or Ctrl + z on windows). So check the return, if the user canceled input, just handle that gracefully. You could write a getstr()
function to help as:
#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'
(或在空行上按回车键)您所需要的只是:
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 */
}
(note:价值如何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
and b
只是指向数组中元素的指针。 (qsort()
uses a void*
类型,以便它可以将任何类型对象传递给比较函数)编写比较函数的工作只是将它们转换回正确的类型,然后编写上面的逻辑。在你的情况下a
是一个指向int
(e.g. 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
*/
}
(note:只是返回ia - ib
可以工作,但是如果ia
是一个很大的负值并且ib
一个大的正值,反之亦然。因此,您可以使用两个比较运算的差异来消除这种机会 - 尝试一下,选择两个数字ia
and ib
看看效果如何...)
现在如果我的数组是number
我有n
其中的元素,我命名了我的比较函数cmpint
使用起来有多困难qsort()
对中的值进行排序number
array??
qsort (number, n, sizeof *number, cmpint); /* sort numbers */
(done!)
总而言之*
如果你把它全部包装到你的程序中,你最终会得到:
#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
作为有效输入或,仅输出用户输入的第一个数字并跳过排序等......由您决定。
和往常一样,这件事的结束比我预期的要长得多。但是看看你的程序被困在哪里——没有捷径可以帮助你了解你需要做什么以及为什么不需要几个额外的(几十个)段落。所以这里有很多。放慢速度,消化它,与鸭子交谈(参见如何调试小程序 https://ericlippert.com/2014/03/05/how-to-debug-small-programs/——别笑,这有效)
然后,如果您还有其他问题,请在下面发表评论,我很乐意为您提供进一步帮助。