I know how to write a while loop that reads user input, stores into an array then terminates at the stopping value. However, that stopping value is also stored and I'm not sure how to have it not do that.
Here is what I have:
printf("Enter temperatures or -100.0 to stop: "); // user input prompt
int i = 0;
while (1) {
scanf("%f", &temp[i]); // user input filling array
if (temp[i] == -100.0) { // stop loop if input = -100.0
break;
}
i++;
}
Your immediate problem with your "stopvalue" being included in your temp[]
array is because you perform the conversion with scanf()
into &temp[i]
which puts the value in your array regardless whether it is the stopvalue or not. To solve this problem, just declare a temporary variable and use that to hold the converted value. You can then check the temporary value against your stopvalue before adding it to your array.
The manpage for scanf()
, man 3 scanf warns:
It is very difficult to use these functions correctly, and it is preferable to read entire lines with
fgets(3)
or getline(3) and parse them later withsscanf(3)
or more specialized functions such asstrtol(3)
.
That is why time and again on StackOverflow you will find answers to user input questions in C recommending that scanf()
isn't a wise choice for user input and you are better served by using fgets()
to read the entire line and the parse any needed value from the line of input read. The reasons for this are many but come down to:
stdin
ceases at that point and all characters causing the failure are left in stdin
unread, just waiting to bite you on your next attempted input.'\n'
character is left in stdin
just waiting to bite you again anyway on your next input that doesn't discard leading whitespace (scanf()
specifiers "%c"
, "%[..]"
, and "%n"
do not discard leading whitespace, and neither do fgets()
or getline()
- or any of the other syscalls that do input)scanf()
to determine if the input and conversion succeeded or failedstdin
) after your use of scanf()
to remove the characters that remain. (even if that is just the '\n'
left from the user pressing Enter)EOF
as well as checking for success to allow the user to cancel user input - which is also valid input.Reading an entire line of user-input with fgets()
avoids these problems, though you do still need to do proper validation by checking the fgets()
return.
The other problem you have is comparing floating point numbers for your stopvalue, e.g. if (temp[i] == -100.0)
. How close to -100.0
counts? Do you want to store -100.01
? What about -100.04
? What about -99.999
? All of these values can compare equal to -100.0
.1
Generally choosing a Magic-Number as your stopvalue has problems. fgets()
provides a simple solution to that issue as well. Rather than use a magic number, fgets()
can check for no-input (e.g. '\n'
only read) allowing the user to press Enter alone to end input. This allows all values to be considered valid temperatures without carving out a magic one.
Note also, regardless of what method you choose to use, you cannot simply loop while(1)
without any concern for your temp[]
array bounds. Instead, as either a check within the loop, or as the loop test itself, you need to only take input while you have space in your array to hold it. For example if you have a 100
element array, you would want to do something like while (i < 100) { ... }
to ensure you don't try and add more values than you have elements for. (of course, use a proper #define
to provide the constant)
Using scanf()
Even with the shortcomings, if you check each needed condition and take care of extracting extraneous characters left in stdin
, you can make scanf()
work for input. It's not recommended, but you can do something like:
#include <stdio.h>
#include <math.h>
#define NTEMPS 100 /* if you need a constant, #define one (or more) */
#define STOPV -100
#define ELIMIT 1.e-4
/* simple function to empty characters left in input stream */
void empty_stdin (void)
{
int c = getchar();
while (c != '\n' && c != EOF) {
c = getchar();
}
}
int main (void) {
float temp[NTEMPS] = { 0 }; /* array to hold temps */
int i = 0; /* counter variable */
/* loop only while space remains in array */
while (i < NTEMPS) {
float tmp = 0; /* temporary value for conversion */
printf ("\nEnter temperatures or %d to stop: ", STOPV);
fflush (stdout); /* avoid line-buffering issues */
if (scanf("%f", &tmp) != 1) { /* conversion to float failed */
if (feof (stdin)) { /* check for manual EOF */
puts (" user canceled input.");
return 0;
}
else { /* otherwise empty characters that remain in stdin unread */
fputs (" error: invalid floating-point input.\n", stderr);
empty_stdin();
}
continue; /* get next input */
}
/* check for magic-number exit condition */
if (fabs(tmp - STOPV) < ELIMIT) {
empty_stdin();
break;
}
temp[i] = tmp; /* store converted value in array */
i++; /* increment counter */
}
/* show output */
puts ("\nRecorded Temps:\n");
for (int j = 0; j < i; j++) {
printf ("temp[%2d] : %.2f\n", j, temp[j]);
}
}
note: the use of a range around -100
(within 1.e-4
) that is considered your magic stopvalue.
Using fgets()
Not only is using fgets()
recommended for user-input, it's actually easier to do that going through all the various checks and input buffer management you need to do with scanf()
. Instead of using scanf()
directly for input, all you are doing is declaring a character array to hold the user-input and then passing the array to sscanf()
instead of doing input directly with scanf()
. It makes things much easier (and shorter), e.g.
#include <stdio.h>
#include <string.h>
#define MAXC 1024 /* if you need a constant, #define one (or more) */
#define NTEMPS 100
int main (void) {
char buf[MAXC] = ""; /* array to hold line of user-input */
int i = 0; /* counter variable */
float temp[NTEMPS] = { 0 }; /* array to hold temps */
/* loop continually reading input */
while (i < NTEMPS) {
float tmp; /* temporary value for conversion */
/* prompt and flush output (no trailing '\n' in prompt) */
fputs ("\nEnter temperatures (empty-input to stop) : ", stdout);
fflush (stdout);
/* read input, check for manual EOF */
if (fgets (buf, MAXC, stdin) == NULL) {
puts (" user canceled input.");
return 0;
}
if (buf[0] == '\n') { /* if Enter pressed with no input */
break; /* done! */
}
/* validate conversion to float */
if (sscanf (buf, "%f", &tmp) == 1) {
temp[i] = tmp; /* add value to temperature array */
i++; /* increment counter */
}
else { /* otherwise conversion failed, show invalid input */
buf[strcspn (buf, "\n")] = 0; /* trim '\n' from string */
fprintf (stderr, " error: invalid floating-point input '%s'.\n",
buf);
}
}
/* show output */
puts ("\nRecorded Temps:\n");
for (int j = 0; j < i; j++) {
printf ("temp[%2d] : %.2f\n", j, temp[j]);
}
}
Example use/Output
$ ./read-float-arr-fgets
Enter temperatures (empty-input to stop) : 10.8
Enter temperatures (empty-input to stop) : -20.9
Enter temperatures (empty-input to stop) : 59.1
Enter temperatures (empty-input to stop) : 37.4
Enter temperatures (empty-input to stop) : bananas and barbeque sauce
error: invalid floating-point input 'bananas and barbeque sauce'.
Enter temperatures (empty-input to stop) : -100.2
Enter temperatures (empty-input to stop) : -99.99
Enter temperatures (empty-input to stop) :
Recorded Temps:
temp[ 0] : 10.80
temp[ 1] : -20.90
temp[ 2] : 59.10
temp[ 3] : 37.40
temp[ 4] : -100.20
temp[ 5] : -99.99
Try entering "bananas and barbeque sauce"
in your program and see what happens (hint: keep ctrl + c handy)
For the scanf()
implementation above, the only difference would be the stopvalue line as something like:
Enter temperatures or -100 to stop: -100.0001
Take the time to go through each example and lookup each function used and understand why it was necessary to do to create a reasonably robust input routine. If you have further questions, just drop a comment below and I'm happy to help further.
Footnotes: