So, I am new to this site and I am new to programming. I am at my first book and my dad gave a "homework", he doesn't know the codes to do what this, but I want to do it. So here it is what I want to do:
I want to create a program like a calculator where you input a value A and a value B and then the program shows the result of A + B. I have done that, the problem is that if I input a character the program keeps reading the other codes. Here is what i have written, maybe you will understand better.
#include <stdio.h>
#include <conio.h>
#include <ctype.h>
void main(void)
{
int val_a;
int val_b;
int result;
printf("Write a number A=");
scanf("%d", &val_a);
if(isdigit(val_a))
{
printf("\nWrite a number B=");
scanf("%d", &val_b);
}
else
printf("\nI said number.\n");
result = val_a + val_b;
printf("%d + %d = %d", val_a, val_b, result);
getch();
printf("\033[2J"); \\ to clear the screen
}
So what I want is that the program will come back to where I have to input the number A.
I found on google about labels, but I didn't understand anything :D. Maybe someone can post an example of what I want so I can study it.
Should i write my name? Maybe next time :P
PS: I hope someone will find a NOOB mistake in what I wrote and tell me about it. :pray:
Critique
#include <stdio.h>
#include <conio.h>
So you are working on Windows.
#include <ctype.h>
void main(void)
The return type of main()
should be int
, even though you'll find lots of examples to the contrary.
{
int val_a;
int val_b;
int result;
printf("Write a number A=");
scanf("%d", &val_a);
You should be testing the return value from scanf()
to see whether it was able to read a number or not.
if (isdigit(val_a))
This test 'works', but it checks whether the number entered was a value in the range 48..57 (which are the ASCII, CP1252, Unicode, ISO 8859-1, etc codes for the digits '0'
to '9'
). This probably wasn't what you had in mind.
{
printf("\nWrite a number B=");
scanf("%d", &val_b);
The user entered a newline, so the newline in front ot the printf()
format string is not necessary, though it is also not harmful. The same comment about checking scanf()
applies here, too.
}
else
printf("\nI said number.\n");
It is good to do error checking. However, you continue onwards even if you detect the error. You really need to stop.
result = val_a + val_b;
printf("%d + %d = %d", val_a, val_b, result);
On the whole, it is best to end lines with a newline. It flushes the data to the file or screen.
getch();
printf("\033[2J"); \\ to clear the screen
The printf()
format string is not portable. Given that you included <conio.h>
, you should probably use clrscr()
(which probably does send that escape sequence, or an equivalent, to the terminal). I'm not convinced that it is necessary to clear the screen at the end of the program, but I work mainly on Unix systems, not with Windows.
}
Given that MSVC is still a C89 compiler, you should include return 0;
at the end of the program to return a success status.
Rewritten
Adding all the changes, you'd end up with:
#include <stdio.h>
#include <conio.h>
int main(void)
{
int val_a;
int val_b;
int result;
printf("Write a number A=");
if (scanf("%d", &val_a) != 1)
{
printf("I said number.\n");
getch();
clrscr();
return(1);
}
printf("\nWrite a number B=");
if (scanf("%d", &val_b) != 1)
{
printf("I said number.\n");
getch();
clrscr();
return(1);
}
result = val_a + val_b;
printf("%d + %d = %d\n", val_a, val_b, result);
getch();
clrscr();
return(0);
}
When you've learned about writing functions, you might compress that to:
#include <stdio.h>
#include <conio.h>
static int get_a_number(const char *prompt)
{
int value;
printf(prompt);
if (scanf("%d", &value) != 1)
{
printf("I said number.\n");
getch();
clrscr();
exit(1);
}
return value;
}
int main(void)
{
int val_a = get_a_number("Write a number A=");
int val_b = get_a_number("Write a number B=");
int result = val_a + val_b;
printf("%d + %d = %d\n", val_a, val_b, result);
getch();
clrscr();
return(0);
}
The get_a_number()
function is not a general purpose function, but it is useful in this context since it encapsulates a common piece of code.
- A subroutine call permits us to summarize the irregularities in the argument list.
- The subroutine itself summarizes the regularities of the code.
That's a quote from Kernighan & Plauger, "The Elements of Programming Style".
Using atexit()
The code above was updated to include getch()
and clrscr()
on all exit paths (and to remove #include <ctype.h>
since none of the functions from it were used any more). It's a nuisance to write out the two function calls repeatedly. Another way to avoid that problem is to write a function that is called when the program exits; you register it with the atexit()
function, which is declared in <stdlib.h>
:
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
static void wait_at_exit(void)
{
getch();
clrscr();
}
static int get_a_number(const char *prompt)
{
int value;
printf(prompt);
if (scanf("%d", &value) != 1)
{
printf("I said number.\n");
exit(1);
}
return value;
}
int main(void)
{
atexit(wait_at_exit);
int val_a = get_a_number("Write a number A=");
int val_b = get_a_number("Write a number B=");
int result = val_a + val_b;
printf("%d + %d = %d\n", val_a, val_b, result);
return(0);
}
That code in main()
assumes you can mix statements (such as atexit()
) and declarations (such as int val_a
) arbitrarily. That is permitted by both the current (2011) and old (1999) versions of Standard C, but was not permitted by the original (1989) version of Standard C. I believe MSVC adheres to the original standard. If so, you probably need to write that main()
as:
int main(void)
{
atexit(wait_at_exit);
{
int val_a = get_a_number("Write a number A=");
int val_b = get_a_number("Write a number B=");
int result = val_a + val_b;
printf("%d + %d = %d\n", val_a, val_b, result);
}
return(0);
}
It is a fine discussion point whether the code inside the inner block (the inner braces) should be indented an extra level or not. You're always allowed to declare variables at the start of a block of code enclosed in braces, so that allows me write the call to atexit()
and then define and use those variables.
If you read about Agile programming, you'll find that one of the mantras is:
- DRY — Don't Repeat Yourself
The rewrites above to avoid the repeated getscr()
calls could be considered an application of that mantra. Similarly with the get_a_number()
function; it to applies the DRY mantra.
Trying again
If you want the program to go back and prompt again if the user makes a mistake in their typing, then you need to modify the get_a_number()
function. And, because the function grows considerably more complex, you definitely need to be using a function for the job. You also run into a problem with scanf()
; it doesn't allow you to enforce one number per line. If you typed 1 2
in response to the first prompt, the first call to get_a_number()
would read the one and the blank, stop parsing (and put the blank back for reuse), and return with the value 1. The second call to get_a_number()
would output the prompt but return with the value 2 without the user typing anything more. If that's not the behaviour you want, then you have to arrange to read the whole first line and scan for the number, and then discard the rest. There's another reason for worrying too. If the user types a
instead of 1
, the scanf()
will never read anything more; it will go into a loop, finding that the a
is not valid as a digit, returning 0 conversions complete, and your code will probably end up in an infinite loop, prompting for input and reading the a
. This sort of problem is why many people (particularly me) avoid using scanf()
.
The simplest way to avoid most of the problems above is with fgets()
and sscanf()
:
enum { MAX_LINELENGTH = 4096 };
enum { MAX_ATTEMPTS = 10 };
static int get_a_number(const char *prompt)
{
int value;
char line[MAX_LINELENGTH];
int count = 0;
while (fputs(prompt, stdout) != EOF &&
fgets(line, sizeof(line), stdin) != 0)
{
if (sscanf(line, "%d", &value) == 1)
return value;
if (count++ > MAX_ATTEMPTS)
{
printf("I give in; I don't understand what you're typing\n");
exit(1);
}
printf("I said please enter a number.\n");
}
printf("Oops: I got EOF or an error; goodbye!\n");
exit(1);
}
That covers quite a lot of issues. Often, it is a good idea to echo back the erroneous data. You could do that with:
printf("I didn't understand what you meant when you typed:\n%s\n"
"I said please enter a number.\n", line);
This uses 'adjacent string concatenation', and shows the user what the program received, which can often help the user understand what's wrong.
I used fputs(prompt, stdout)
instead of printf(prompt)
to output the prompt. Either could be used (printf(prompt) != 0
would be the test), but there is a slight advantage to fputs()
because if someone managed to subvert your program and get a %
into the prompt, the printf()
would try to process an argument it was not passed, whereas fputs()
does not interpret its argument string at all. Here, the difference is negligible; it can reasonably be argued that I'm over-complicating things. But the security of programs is also something you end up learning, and techniques such as "do not use printf(variable)
" can help make your programs more resilient. (Actually, the complication is the explanation; the code is quite simple.)
Another detailed discussion point is 'should there be a fflush()
after fputs()
and before fgets()
?' It would be possible to add the extra 'condition' fflush(stdout) == 0 &&
to the loop control, but in practice it is usually unnecessary. It would only make a difference if standard input was coming from something other than the terminal and standard output was going to a file. So, I left it out.
The counter in the loop prevents things going badly awry — the program exits rather than hang around forever, frustrating the user who can't get the data entered correctly. Have you ever had a program pop up a dialog box at which you can only hit the 'OK' button (even though its not OK) and then come back with the same box again, over and over and over again? I hope not for your sake. I have; it frustrates me when it happens.
And you can see why it is advantageous to write the code in get_a_number()
just once, rather than twice; that would be a lot of repetition.
You need to check if scanf()
successfully read the input it should read. In general, you should always check after reading that the read was successful. scanf()
returns the number of successfully read inputs which essentially map to the number of format specifiers it could successfully read. For example, you should check that
if (scanf("%d, &val_a) == 1)) {
...
}
The value read may or not be be a "digit". In general, it is probably not a digit according to isdigit()
because this function only returns true for integer values of the characters '0'
, '1'
, ..., '9'
. Testing if scanf()
could read all its format specifiers should avoid this problem.
If scanf()
fails you can read one character (using e.g. fgetc()
) and retry again or read an entire line (not using gets()
but using fgets()
). You might also want to print a message the input wasn't what was expected.