Reputation: 59
I have this following piece of function:
Customer_Account new_acc(){
Customer_Account new_account;
new_account.account_id = account_id;
account_id++;
printf("\nEnter your name: ");
scanf("%s", new_account.name);
printf("Enter your date of birth (DD.MM.YYYY): ");
scanf("%d.%d.%d", &new_account.birth_date.day, &new_account.birth_date.month, &new_account.birth_date.year);
printf("\nEnter your ID number: ");
scanf("%lf", &new_account.tc_ID_number);
printf("\nEnter your address: ");
scanf("%s", new_account.adress);
printf("\nEnter your phone number: ");
scanf("%lf", &new_account.phone);
printf("\nEnter the amount you would like to deposit: ");
scanf("%lf", &new_account.amount);
printf("\n\033[1;32m Account successfully created\033[0m\n\n");
sleep(1);
}
but when I run it and enter a name and a surname, it gives the following output:
Enter your name: name surname
Enter your date of birth (DD.MM.YYYY):
Enter your ID number:
Enter your address:
Enter your phone number:
If I'm not mistaken "\n" is left in the buffer so it skips the next scanf()'s. I looked up a few solutions but they didn't work either. I don't run into an issue when I enter only a single word. How do I read a full sentence without running into this issue?
Upvotes: 1
Views: 135
Reputation: 84579
Using scanf()
is full of pitfalls for the new C programmer. Specifically because different conversion specifiers handle leading whitespace differently. All but %c
, %[…]
(scan sets) and %n
will ignore leading whitespace. Further, any (meaning ANY) stray non-whitespace character left in the input buffer will cause a matching-failure, or if by chance it happens to match the next conversion specifier, it will be taken as your input instead of what you intend.
Further, you cannot reliably use any user-input function correctly unless you check the return to validate whether the input and conversion succeeded or failed.
To eliminate all pitfalls associated with using scanf()
for user input, it is recommended you use a line-oriented input function like fgets()
(or POSIX getline()
) to read an entire line of input at a time. All line-oriented input function read and include the trailing '\n'
in the buffer they fill. If you need to parse values from the line simply pass the buffer to sscanf()
and extract the values just as you would with scanf()
except you are reading from the buffer you filled instead of stdin
. That way regardless of whether the conversion succeeds or fails, it will not impact your next attempted input -- you consumed the entire line of input with fgets()
.
If you need to store what you have read into your buffer (character array), simply trim the trailing '\n'
using strcspn()
, e.g.
#define MAXC 1024
...
char buf[MAXC];
fputs ("enter some string: ", stdout);
if (fgets (buf, MAXC, stdin) == NULL) { /* validate EVERY input */
fputs ("(user canceled input with manual EOF)\n", stdout);
return 1;
}
buf[strcspn (buf, "\n")] = 0; /* trim \n by overwriting with \0 */
That's all there is to it and you will not have any problems with user input.
When taking input in a function you must provide a meaningful return type that can indicate whether the user input succeeded or failed. Return a struct does not provide any indication of whether the input was successful. Instead, make the return type anything that can distinguish between success/failure and pass a pointer to the struct you want to fill as a parameter. A return type of int
is fine, with a return of 0
indicating failure and 1
indicating success (or however you want to do it -- just be consistent).
User input may look boring and redundant, but you are not creating Art, you are creating an input routine that can handle any input provide by the user (or the cat stepping on the keyboard) and still produced proper (defined) results in your code.
With that in mind you can change your input routine to use fgets()
and so something similar to the following:
#define MAXC 1024 /* if you need a constant, #define one (or more) */
/* pass pointer to account to fill so return can be used
* to indicate success or failure of input routine,
* return 0 on error or manual EOF, 1 on successful input.
*/
int new_acc (Customer_Account *new_account)
{
char buf[MAXC]; /* array to hold all user input */
new_account->account_id = account_id;
account_id++;
/* name */
fputs ("\nEnter your name: "); /* no conversion, printf not required */
if (fgets (buf, MAXC, stdin) == NULL) { /* validate read with fgets */
fputs ("(user canceled input)\n", stderr);
return 0; /* return 0 on failure */
}
buf[strcspn (buf, "\n")] = 0; /* trim \n */
strcpy (new_account->name, buf); /* copy buf to new_account->name */
/* date of birth */
fputs ("Enter your date of birth (DD.MM.YYYY): ", stdout);
if (!fgets (buf, MAXC, stdin)) { /* same validation -- just shorthand */
fputs ("(user canceled input)\n", stderr);
return 0; /* return 0 on failure */
}
if (sscanf (buf, "%d.%d.%d",
&new_account->birth_date.day,
&new_account->birth_date.month,
&new_account->birth_date.year) != 3) {
fputs ("error: DOB - input or matching failure.\n", stderr);
return 0;
}
/* ID */
fputs ("\nEnter your ID number: ", stdout);
if (!fgets (buf, MAXC, stdin)) { /* ditto */
fputs ("(user canceled input)\n", stderr);
return 0; /* return 0 on failure */
}
if (sscanf (buf, "%lf", &new_account->tc_ID_number) != 1) {
fputs ("error: ID - input or matching failure.\n", stderr);
return 0;
}
/* Address */
fputs ("\nEnter your adress: ", stdout);
if (!fgets (buf, MAXC, stdin)) { /* ditto */
fputs ("(user canceled input)\n", stderr);
return 0; /* return 0 on failure */
}
buf[strcspn (buf, "\n")] = 0; /* trim \n */
strcpy (new_account->address, buf); /* copy buf to new_account->name */
/* Phone Number */
fputs ("\nEnter your phone number: ");
if (!fgets (buf, MAXC, stdin)) { /* ditto */
fputs ("(user canceled input)\n", stderr);
return 0; /* return 0 on failure */
}
if (sscanf (buf, "%lf", &new_account->phone) != 1) {
fputs ("error: Phone - input or matching failure.\n", stderr);
return 0;
}
/* Amount */
fputs ("\nEnter the amount you would like to deposit: ", stdout);
if (!fgets (buf, MAXC, stdin)) { /* ditto */
fputs ("(user canceled input)\n", stderr);
return 0; /* return 0 on failure */
}
if (sscanf (buf, "%lf", &new_account->amount) != 1) {
fputs ("error: Amount - input or matching failure.\n", stderr);
return 0;
}
return 1; /* return 1 indicating successful input */
}
(note: I would remove the '.'
between the "%d"
in date of birth because unless the user enters the '.'
a matching-failure will occur -- which is likely where your problem originated)
It basically does the same thing for each input -- that's fine. Redundant -- yes, solid -- also yes.
One other caveat with reading strings with scanf()
. If you don't provide a field-width modifier to ensure you do not exceed your arrays bound, scanf()
is no safer than gets()
, see Why gets() is so dangerous it should never be used!
You can use the input routine by declaring a temporary struct to fill in the calling function and pass a pointer to the function, e.g.
for (;;) {
Customer_Account tmp;
if (!new_acc (&tmp))
break;
/* assign tmp to final storage here */
sleep (1);
}
Which would be roughly equivalent to how you had your function working — except since we check the return of new_acc (&tmp)
— we know whether the input succeeded or failed.
Look things over and let me know if you have further questions.
Upvotes: 4