still making my way through C Programming Absolute Beginner's Guide.
I am at the example about Structures and I cannot figure out what is going wrong. When I compile and run the code, the first two questions run fine, but after it prompts for "How much did the book cost?", when I enter the input for this one, the next two questions get posted together. I have no idea why. I think I have the code written as shown in the book. I have read online that gets is not code but I am not sure why at this point. Any guidance would once again be greatly appreciated!
//This header file defines a structure for information about a book
struct bookInfo {
char title[40];
char author[25];
float price;
int pages;
};
/*This program gets the bookInfo structure by including structurePractice.h
and asks the user to fill in three structures and then prints them*/
//First, include the header file
#include "structurePractice.h"
#include <stdio.h>
int main()
{
int ctr;
struct bookInfo books[3]; //Array of 3 structure variables
//Get information about each book from the user
for (ctr = 0; ctr < 3; ctr++)
{
printf("What is the name of the book #%d?\n", (ctr+1));
gets(books[ctr].title);
puts("Who's is the author? ");
gets(books[ctr].author);
puts("How much did the book cost? ");
scanf(" $%f", &books[ctr].price);
puts("How many pages are in the book? ");
scanf(" %d", &books[ctr].pages);
getchar(); //Clears last newline for next loop
}
//Print a header line and then loop through and print the info
printf("\n\nHere is the collection of books: \n");
for (ctr = 0; ctr < 3; ctr++)
{
printf("#%d: %s by %s", (ctr+1), books[ctr].title, books[ctr].author);
printf("\nIt is %d pages and costs $%.2f", books[ctr].pages, books[ctr].price);
printf("\n\n");
}
return (0);
}
You have a typo in your scanf.
scanf(" $%f", &books[ctr].price);
^
|
here
That says you want a $ followed by a decimal number. If the user does not input a dollar sign, scanf will read nothing. The input will remain on the input buffer. It will be read by the next scanf.
This is one of the many problems with scanf. Because scanf mixes up reading input with parsing input, if the input does not match the expected format it will remain in the input buffer. Instead, read and parse separately with fgets (not gets) and sscanf. It's also important to check that the input was read else books[ctr].price will contain garbage.
// Declare a buffer once outside the loop. Reuse it for each fgets call.
// BUFSIZ is a constant for the natural I/O buffer size of your platform.
char buf[BUFSIZ];
puts("How much did the book cost? ");
// Read only as much as the buffer can handle. This is what makes fgets safe,
// and gets unsafe.
fgets(buf, sizeof(buf), stdin);
// sscanf returns the number of items matched. Check if they all matched.
if( sscanf(buf, " %f", &books[ctr].price) != 1) {
printf("Sorry, '%s' doesn't look like a price.", buf);
// Set it to something or else it will be garbage.
books[ctr].price = 0;
}
A real program would loop until it gets valid input. The important thing is to read the buffer, then parse it, and check if the parsing worked. Later on you might write a little function to package up this prompting code.
Related
I'm trying to store information of the name of a lesson and the day(s) I have that lesson using structures in C. Storing the name hasn't been an issue. The problem occurs when I try and store multiple days (weekdays as integers, ie. Monday = 1) in an array.
This is what I have:
#include<stdio.h>
#include<string.h>
struct lessons{
char name[20];
int day[3];
};
changelessons(){
int i, k;
struct lessons give[1], receive[1];
FILE *fptr;
fptr = fopen("lessons","wb");
fflush(stdin);
printf("\n\t ~ Change lessons ~");
printf("\n\nWhat's the lesson called?: ");
gets(give[0].name);
printf("\nHow many days do you have it?\n");
scanf("%d", &k);
for(i = 0; i < k; i++); { // Asks the weekday number for each day you have the lesson
printf("What day is lesson %d?: ", i);
scanf("%d", &give[0].day[i]);
}
fwrite(give, sizeof(give), 1, fptr);
fclose(fptr);
fptr = fopen("lessons", "rb");
fread(receive, sizeof(receive), 1, fptr);
printf("\n\t ~ Updated information: ~\n\nLesson name: %s\nDay: %d", receive[0].name, receive[0].day[1]);
for(i = 1; i < k; i++); { // Prints the extra weekdays if there are any
printf(", day: %d", receive[0].day[i]);
}
printf("\n\n");
fclose(fptr);
}
showlessons(){
struct lessons give[1], receive[1];
FILE *fptr;
fptr = fopen("lessons", "rb");
fread(receive, sizeof(receive), 1, fptr);
printf("\t ~ Current information: ~ \n\nLesson name: %s\nDay: %d\n\n", receive[0].name, receive[0].day[0]);
}
int main(){
showlessons();
changelessons();
return 0;
}
Also, in the first for loop it only ever loops once regardless of what k equals.
Thanks in advance for any help!
for(i = 0; i < k; i++);
should be
for(i = 0; i < k; i++)
The extra semi colon is a problem. Your loop code is not part of a loop. The semi colon terminates the loop.
nicomp pointed out the most obvious error.
But I would also like to point out a few issues I have with your code:
fflush(stdin); This is wrong, undefined behaviour. fflush is used to
flusg output streams, stdin is an input stream. See Using
fflush(stdin).
I know that in Windows fflush(stdin) clears the input buffer, but I strongly
recommend not to use it, because you lose portability when relaying on features
available only in one OS. If you don't care about portability, then use it. If
you use that because scanf left stuff in the input buffer, use this instead:
int c;
while((c = getchar()) != '\n' && c != EOF);
This is portable.
Never ever use gets in 2018. gets is an unsafe, dangerous functions that
has been deprecated in C99 and for good reasons: it doesn't take the size of the
buffer into consideration and if the text entered is longer than the buffer can
store, it will overflow the buffer. This is an accident waiting to happen. So,
never ever use gets again. Use fgets instead and if you don't want to have
a newline, you can remove it:
fgets(give[0].name, sizeof give[0].name, stdin);
give[0].name[sizeof(give[0].name) - 1] = 0; // remove possible newline
Why do you declare an array of struct lessons with dimension 1? What's the
point? You don't need an array for this. You could write it like this:
struct lessons give, receive;
...
printf("\n\nWhat's the lesson called?: ");
fgets(give.name, sizeof give.name, stdin);
give.name[strcspn(give.name, "\n")] = 0;
...
fwrite(&give, sizeof give, 1, fptr);
fclose(fptr);
...
fptr = fopen("lessons", "rb");
fread(&receive, sizeof receive, 1, fptr);
Also don't forget to check the return values of fwrite and fread.
Check the return value of fopen, if it returns NULL, you cannot use
fread or fwrite. Print an error value and return/exit.
stdout is buffered and when you write to it with printf, it won't
necessarily print the characters on screen right away. An exception is when
stdout is connected to a terminal and a newline is written with printf. For
user interaction this is great, because the user would see the output
immediately. That's why most people print a newline along the text.
If you don't do that but you still want that the user immediately sees the
output, then this time you should use fflush:
printf("What day is lesson %d?: ", i);
fflush(stdout);
scanf("%d", &give[0].day[i]);
I don't know if there's a guarantee that scanf flushes stdout when it's
connected to a terminal, regardless it is good practice to use fflush(stdout)
when you don't print a newline at the end.
It is a program to find out number of vowels, consonants, digits and whitespaces in a string to be input by the user. After compilation and during running fgets part is skipped. When I use scanf, the program works fine except that I can't input whitespace. Where does the problem lie? Please explain elaborately ( I am a newbie :-P) and possible remedies.
#include<stdio.h>
#include<string.h>
void main()
{
/*Getting the input*/
printf("How much long is your string?: ");
int n;
scanf("%d",&n);
int i,j,vowels=0,consonants=0,spaces=0,digits=0,actual_length;
char k, str[n+1];
printf("Please enter your string: ");
fgets(str,n+1,stdin);
actual_length=strlen(str);
/*Actual computation*/
for(i=0;i<actual_length;i++)
{
if(str[i]=='A'||str[i]=='E'||str[i]=='I'||str[i]=='O'||str[i]=='U')
vowels++;
if(str[i]=='a'||str[i]=='e'||str[i]=='i'||str[i]=='o'||str[i]=='u')
vowels++;
if(str[i]==32)
spaces++;
for(j='0';j<='9';j++)
{
if(str[i]==j)
digits++;
}
for(k='A';k<='Z';k++)
{
if(k!='A'&&k!='E'&&k!='I'&&k!='O'&&k!='U')
{
if(str[i]==k)
consonants++;
}
}
for(k='a';k<='z';k++)
{
if(k!='a'&&k!='e'&&k!='i'&&k!='o'&&k!='u')
{
if(str[i]==k)
consonants++;
}
}
}
printf("The number of vowels are %d, number of consonants are %d, number of digits are %d and number of white spaces are %d\n",vowels,consonants,digits,spaces);
}
Your problem is that when you say
scanf("%d",&n);
it reads the number but not the newline.
Then, when you call fgets, it reads the newline, and it thinks it's an empty line, and it never gets to the real string you wanted to read (that is, from the next line).
The general rule is, don't try to have a mixture of calls to scanf and fgets in one program like this -- you will always get tangled up in newlines, and it's a huge and pointless nuisance to try to untangle them.
One way of fixing the problem is to use fscanf to read the number, too:
printf("How much long is your string?: ");
char tmp_n_str[15];
fgets(tmp_n_str, sizeof(tmp_n_str), stdin);
int n = atoi(tmp_n_str);
The other way, which someone else will probably suggest, is to "flush the input" to get rid of the extra newline. You can do that, too, and it's certainly a popular technique, although personally I don't like it, for a number of reasons which I won't go in to here.
I don't know why, when I run this, it skips the "how many pages in the book" scanf and goes straight onto the second loop "who is the author".
I'm sure this is something to do with whitespace, but I thought I accounted for this with the getchar at the bottom of the for loop.
header:
struct bookInfo{
char title[40];
char author[25];
float price;
int pages;
};
.c file:
int main()
{
int ctr;
struct bookInfo books[3];
for (ctr = 0; ctr < 3; ctr++)
{
printf("what is the name of the book #%d?\n", (ctr+1));
gets(books[ctr].title);
puts("who is the author?");
gets(books[ctr].author);
puts("how much did the books cost");
scanf(" $%f", &books[ctr].price);
puts("how many pages in the book");
scanf(" %d", &books[ctr].pages);
getchar();
}
printf("here is the collection of books: \n");
for (ctr = 0; ctr <3; ctr++)
{
printf("book #%d: %s by %s", (ctr+1), books[ctr].title, books[ctr].author);
printf("\nit is %d pages and costs $%.2f", books[ctr].pages, books[ctr].price);
}
return 0;
}
Change this:
puts("how much did the books cost");
scanf(" $%f", &books[ctr].price);
to this:
printf("how much did the books cost: $");
fflush( stdout );
scanf("%f", &books[ctr].price);
Unless you intend for your user to type a $ before the price, which would be annoying. You don't need the leading blank in the format string, since %f tells scanf to skip over leading whitespace.
Secondly, NEVER NEVER NEVER NEVER NEVER NEVER NEVER NEVER use gets. Ever. In any way, shape, or form. It will (not might, will) introduce a point of failure / major security hole in your program. It was deprecated in the 1999 standard, and has been removed from the standard library as of the 2011 standard. It is the programming equivalent of splicing live wires while standing in a shower.
Use fgets (which, unlike gets, will attempt to store the newline character in the target buffer if there's room) or scanf (with an appropriate precision in the conversion specifier) instead.
That is because gets() reads the line of text present in the current buffer. and since the current buffer contains "What is name of author ?" it reads it.
If you display the contents of the struct members you can clearly observe this.
So I Suggest this
Use
char *temp;
fgets(STDIN,temp);
before Loop begins.
This helps you
I have a trouble with scanf and a manual function to get string in the input.
Here is my manual function to get a line of string in input (I also get the [nl] character):
void getln(char *a) {
int i,c;
i=0;
do {
c=getchar();
a[i]=(char)c;
i++;
} while(c!='\n');
}
Then, I using it like this (char hs.school[40]; char hs.pc[20]; int hs.age;):
printf("Import age: ");
scanf("%d",&hs.age);
printf("Import personal code: ");
getln(hs.pc);
printf("Import school: ");
getln(hs.school);
The output:
Import age: 18
Import personal code: Import school: Vo Thi Sau
Why the getln call right after scanf call is ignored? (But the next getln works well)
Can you explain me the details and suggest me how to fix this bug. Thanks!
Edited:
Here is my full code that take the user inputs and export that inputs back to the screen, which is run well after I did a little trick, but I decide to make a question, mainly for expanding my knowlegde ^_^ Thanks for your answers.
#include<stdio.h>
void getln(char *);
void putstr(char *);
int main(void) {
struct Student {
struct Fullname {
char first[10],middle[20],last[10];
}fu;
struct Native {
char social[30],district[30],province[30];
}na;
struct Score {
double maths,physics,chemistry;
}sc;
char pc[20],school[40];
int age;
}hs;
printf("Import stage:\n");
printf("- Import full name:\n");
printf("++ First name: ");
getln(hs.fu.first);
printf("++ Middle name: ");
getln(hs.fu.middle);
printf("++ Last name: ");
getln(hs.fu.last);
printf("- Import native living place:\n");
printf("++ Social: ");
getln(hs.na.social);
printf("++ District: ");
getln(hs.na.district);
printf("++ Province: ");
getln(hs.na.province);
printf("- Import school: ");
getln(hs.school);
printf("- Import personal code: "); // I have done a little trick
getln(hs.pc); // before I post the question,
printf("- Import age: "); // which swaped these two stage,
scanf("%d",&hs.age); // but it's works like a charm ^_^
printf("- Import scores:\n");
printf("++ Mathematics: ");
scanf("%lf",&hs.sc.maths);
printf("++ Physics: ");
scanf("%lf",&hs.sc.physics);
printf("++ Chemistry: ");
scanf("%lf",&hs.sc.chemistry);
printf("\nExport stage:\n");
printf("- Full name: ");
putstr(hs.fu.first);
printf(" ");
putstr(hs.fu.middle);
printf(" ");
putstr(hs.fu.last);
printf(".\n");
printf("- Native living place: ");
putstr(hs.na.social);
printf(", ");
putstr(hs.na.district);
printf(", ");
putstr(hs.na.province);
printf(".\n");
printf("- School: ");
putstr(hs.school);
printf(".\n");
printf("- Personal code: ");
putstr(hs.pc);
printf(".\n");
printf("- Age: %d.\n",hs.age);
printf("- Scores (Mathematics, Physics, Chemistry): %.2lf, %.2lf, %.2lf.\n",hs.sc.maths,hs.sc.physics,hs.sc.chemistry);
return 0;
}
void getln(char *a) {
int i,c;
i=0;
do {
c=getchar();
a[i]=(char)c;
i++;
} while(c!='\n');
}
void putstr(char *a) {
int i;
i=0;
while(a[i]!='\n') {
putchar(a[i]);
i++;
}
}
After taking input hs.age you pressed in Enter, which is a \n character. So your getln() is called but the loop is broken just after one iteration as c contains '\n'. if you print hs.pc, there will be a new line in your output screen.
You are not clearing the input buffer. So in this newline will be placed after the first input given to scanf. So getchar will get the new line as a input. So loop will quit.
Use this line after the scanf.
int c;
if ( scanf("%d",&hs.age) != 1 ) {
printf("Invalid Input\n");retrun 0; }
while((c=getchar()) != '\n' && c != EOF );
It will clear the input buffer. Then it will ask the second input from the user.
Your getln call isn't skipped, it is taking the newline character left in stdin (the input buffer) as its input and it reads '\n' as c, assigns it to a[i], checks whether c is a '\n' char and exits.
To solve the initial problem, you need to clear the input buffer before calling getln. You can either do that with the while loop as suggested in the other answer, or you can craft a proper format string for scanf that will consume the newline, emptying the buffer. (not foolproof), but an alternative scanf would be:
scanf(" %d%*c",&hs.age);
Which would skip all whitespace before the number (including any newlines), read the decimal value, and then read and discard the newline. Note: this only works for a number without trailing characters. Entering 13abc would leave bc\n in stdin. The while loop in this case is more flexible as it reads all characters until a newline is encountered and is probably the better choice:
scanf(" %d",&hs.age);
while ((c = getchar()) != '\n' && c != EOF);
As for your getln function, it only needs to read each character into a[i]. There isn't a real need for c. You will also want the same checks on your input so that no newline is left. You will also want to check i against the maximum lengths of a minus 1. I would suggest a #define MAXS 128 for the maximum string length for your input. That would allow something to test i against to prevent writing beyond the end of your string.
Here is an alternative to your getln. Note: it is type int allowing it to return the length of the line read so you can determine what to do if it has reached MAXS (as there will still be characters in stdin at that point). As a general rule, if you are doing something in a function when there is a potential for error, it is better to return a value indicating success/failure/problem:
#define MAXS 128
...
int getln (char *a)
{
size_t i = 0;
while ((a[i] = getchar()) != '\n' && a[i] != EOF)
{
i++;
if (i == MAXS - 1)
{
a[i] = 0;
break;
}
}
return i;
}
Can you explain me the details ...
I'll try not to use confusing terms such as buffer.
You probably already know that "%d" corresponds to a set of decimal digit characters which get transformed into an int. When you press 'Enter' as others have suggested, the '\n' character is transmitted via stdin. '\n' isn't a decimal digit character, so it gets placed back onto the stream for your getln function to discover later on...
In reality, your "getln call right after scanf call" probably isn't ignored; it's probably just reading the trailing '\n' and seeing an empty line.
That is assuming the other problem isn't coming into play. getln can't see how many bytes a points to, so it can't tell when it's about to overflow, and hence makes no attempt to prevent buffer overflows... You've basically rewritten gets. If your input is lengthy enough, then I suppose this could also cause your problem... A buffer overflow is undefined behaviour, and the consequences of using undefined behaviour are undefined.
On the topic of undefined behaviour, since getln isn't technically producing a string, I do hope you're not using it as input for a standard string function later on...
Also on the topic of undefined behaviour, what do you suppose might happen if the user enters something that isn't a set of decimal digits? scanf conveys input errors via the return value... so never ignore the return value. You can (and should, at some point) find more information about this in the scanf manual.
... and suggest me how to fix this bug.
It doesn't make a whole lot of sense to discard user input, but unfortunately you can't expect a solution that results in better user experience without blowing your code size (and this explanation) well out of proportion.
You can discard the remainder of the line (which is probably just a '\n') following the set of decimal digits using scanf like so: scanf("%*[^\n]"); getchar();... Following the "%d" scanf call, of course... You could even merge the two together, like so:
if (scanf("%d%*[^\n]", &hs.age) != 1) {
puts("ERROR: EOF or file access error.");
exit(0);
}
getchar();
Unfortunately, if your user uses the spacebar key rather than enter, he or she probably won't find out about the problems with this until it's slightly too late...
As for the buffer overflow problem, I recommend using fgets rather than gets. fgets also has failure modes, which are conveyed via the return value and the contents of the array. The return value is used to convey EOF and file access errors, and the presense (or rather lack of) of a '\n' in the return value is used to convey when the input line was too large to store in the array. We can notify the user of the overflow (which I'm sure they'll appreciate) and discard the excess using the same scanf trick used earlier...
if (fgets(hs.pc, sizeof hs.pc, stdin) == NULL) {
puts("ERROR: EOF or file access error.");
exit(0);
}
size_t size = strcspn(hs.pc, "\n");
if (hs.pc[size] != '\n') {
printf("WARNING: MAXIMUM SIZE OF %zu EXCEEDED! LINE TRUNCATED.\n", sizeof hs.pc - 1);
scanf("%*[^\n]");
getchar();
}
hs.pc[size] = '\0';
I suppose it would make sense to wrap these solutions into functions, except that the functions would then promote the discarding of user input. Nonetheless, the later one is lengthy enough that you'd most likely benefit from abstraction...
void getln(char *a, size_t a_size) {
if (fgets(a, a_size, stdin) == NULL) {
puts("ERROR: EOF or file access error.");
exit(0);
}
size_t size = strcspn(a, "\n");
if (a[size] != '\n') {
printf("WARNING: MAXIMUM SIZE OF %zu EXCEEDED! LINE TRUNCATED.\n", a_size - 1);
scanf("%*[^\n]");
getchar();
}
a[size] = '\0';
}
... and now you can use that like so: getln(hs.pc, sizeof hs.pc);
i create a really simple coding and it got no errors but when it run, i cant put input in the 'age' side.
#include <stdio.h>
#include <conio.h>
struct baby
{
char name[2][30];
char sex[2][7];
char birthday[2][12];
};
struct parents
{
char nama[2][30];
int age[2];
};
struct momdad
{
struct parents father;
struct parents mother;
};
struct momdad info;
struct baby newborn;
int main()
{
int i;
for(i=0;i<2;i++)
{
printf("\nEnter baby's name %d: ",i+1);
gets(newborn.name[i]);
printf("Enter baby's sex %d (Female/Male): ",i+1);
gets(newborn.sex[i]);
printf("Enter baby's birthday %d (dd/mm/yyyy): ",i+1);
gets(newborn.birthday[i]);
printf("Enter father's name %d: ",i+1);
gets(info.father.nama[i]);
printf("Enter father's age %d: ",i+1);
gets(info.father.age[i]);
printf("Enter mother's name %d: ",i+1);
gets(info.mother.nama[i]);
printf("Enter mother's age %d: ",i+1);
gets(info.mother.age[i]);
}
printf("\n\n\tNEW BORN BABY IN KUANTAN HOSPITAL");
printf("\n\n===============================================");
for(i=0;i<2;i++)
{
printf("\n\nBaby name: %s",newborn.name[i]);
printf("\nSex: %s",newborn.sex[i]);
printf("\nBirthday: %s",newborn.birthday[i]);
printf("\n\nFather name: %s",info.father.nama[i]);
printf("\nFather age: %s",info.father.age[i]);
printf("\n\nMother name: %s",info.mother.nama[i]);
printf("\nMother age: %s",info.mother.age[i]);
printf("\n\n----------------------------------------------");
}
getch();
}
this is my declaration that i think is wrong but i dont know how.
int age[2];
and the input will be putting in here
printf("Enter father's age %d: ",i+1);
gets(info.father.age[i]);
n in here
printf("Enter mother's age %d: ",i+1);
gets(info.mother.age[i]);
i'm still new in programming sorry for asking this simple question
Never use gets(). It cannot be used safely, and as of 2011 it's been removed from the language.
In a comment, you mention calling fflush(stdin);. The behavior of fflush is undefined for input streams. Some implementations define the behavior, but depending on that will make your program non-portable -- and you don't need it anyway.
The simplest way to read input data is to use scanf(), but that has some of its own problems. For example, if you use scanf("%d", &n); and type 123, it will consume the 123 and leave anything following it (such as a newline) waiting to be read.
A better way to read input is to use fgets to read a line of text, then sscanf to parse the data from the input line. It re
Here's an example:
#define MAX_LEN 200
char line[MAX_LEN];
int num;
printf("Enter an integer: ");
fflush(stdout);
if (fgets(line, MAX_LEN, stdin) == NULL) {
fprintf(stderr, "Error reading line\n");
exit(EXIT_FAILURE);
}
if (sscanf(line, "%d", &num) != 1) {
fprintf(stderr, "Error parsing integer from line\n");
exit(EXIT_FAILURE);
}
printf("The number is %d\n", num);
I call fflush(stdout) after the first printf to ensure that the prompt actually appears. stdout can be line-buffered, meaning that output won't appear until you've printed an entire line. The fflush isn't always necessary, but it's a good idea.
The fgets call reads a full line of input or MAX_LEN characters if the line is longer than that. (gets has no way to specify the maximum input size, so no matter how big your target array is, it can always read more and clobber random memory.) fgets returns a null pointer if there was a problem, and I check for that.
sscanf is similar to scanf, but it reads data from a string in memory, not from standard input. (There's also fscanf, which reads from a specified file.) It returns the number of items that it successfully scanned, so a value other than 1 would indicate an error.
I suggest reading the documentation for all these functions; I haven't covered everything they do. In fact, you should read the documentation for any standard library function you use.