I have a simple program, intended simply to excercise my freshly gained knowledge of C.
Please keep in mind I have attempted to solve the problem of the newline leftovers remaining in the stdin buffer using the knowledge I already have. From the problems I was having before I figured out the newline issue, I have made considerable progress in getting this program to act the way I imagine it should. This is all I have left to debug and I am stymied. While my fluency in C is marginal, at best, I believe this program can easily work without a whole lot of modification.
The program asks the user to select a tool, of 2 simple tools I have constructed. The tools themselves work fine, but the cheesy 'pick-a-letter' UI that I've built has a bug I just can't figure out. It runs through the program as expected the first time, but the second time, the variable used for tool selection gets switched to a newline character, and the user input assignment for that variable gets overwritten. Therefore the program enters the switch construct used for the tool selection variable and activates:
case '\n':
which prints an error message indicating a newline got in the way, and breaks out to return to the beginning of the menu loop. On this third time around, the program will again work as expected. It will continue to alternate like this until you exit the program via the exit provided at the end of a normal program run.
This is a direct cut and paste of pertainant code, not included are the D_Time(); and cal(); functions. Feel free to compile and run with substitue functions to see the problem firsthand if need be. Both are void functions, one is a simple 2 argument calculator and the other displays date and time via stdout. And any other suggestions you have that could improve my knowledge of C or this program would be happily accepted and are sought. I am still learning, and although pointers and calculus escape me, i will get the hang of this.
void clrnl(void)
{
while (getc(stdin) != '\n');
} /* This is the fix that kind-of but not-really fixes the '\n' problem. Without it, the error message would pop up every time i used the menu, not including the first run. */
void menu(void)
{
char tool = 1;
int r = 0;
while (r != 1)
{
printf("\n ---Select a tool.---\n\n ------------------\n\n C = Calculator\n\n D = Date/Time\n\n ");
tool = getc(stdin);
clrnl(); /* This seems to be the only line where clrnl(); has a positive effect. */
switch (tool)
{
case 'D':
printf("\n You selected Date/Time ");
D_Time();
r = 1;
break;
case 'C':
printf("\n You selected Calculator ");
cal();
r = 1;
break;
case 'd':
printf("\n You selected Date/Time ");
D_Time();
r = 1;
break;
case 'c':
printf("\n You selected Calculator ");
cal();
r = 1;
break;
case '\n':
printf("\n Error!! -- Newline character detected!! Try again.\n\n");
r = 0;
break;
default:
printf("\n Error!! -- Unknown. Try again.\n\n");
r = 0;
break;
}
}
}
int main()
{
char lx;
do
{
menu();
printf("\n Enter 'x' to exit.\n\n ");
scanf(" %c", &lx);
if (lx != 'x')
{
continue;
}
}
while (lx != 'x');
return 0;
}
Usually a context menu is not waiting for the user to press enter before getting the option while your loop does it.
In order to select the option when pressing the relevant key instead waiting for the "enter" you can call OS specific functions. Under linux reconfigure your terminal using system ("/bin/stty raw") just before entering in your while loop and than leave things as they are. Under windows consider _getch() instead of getc(stdin).
Remember that getchar() and getc() return an integer and not a char.
Doing this you have a more reactive menu and you don't need to handle the extra chars generated.
The problem is when you input your selection, for example D, and you press enter key, various things can happen. If you're on windows, your application will recieve characters: 'D', '\r', '\n'. On linux, it will be 'D','\n'. To eat up any extra input read by scanf use this simple trick:
scanf(" %c*", &lx);
The * in format string will skip your carriage return and newline characters.
This is changed code for tool selection:
printf("\n ---Select a tool.---\n\n ------------------\n\n C = Calculator\n\n D = Date/Time\n\n ");
scanf(" %c*", &tool);
This is the part for the exit input:
printf("\n Enter 'x' to exit.\n\n ");
scanf(" %c*", &lx);
You can further put more switch cases together like this to reduce code duplication:
case 'D':
case 'd':
printf("\n You selected Date/Time ");
D_Time();
r = 1;
break;
This will work, because there's no break statement after case 'D', so if this case happens, the code continues pass the case 'd': line into the corresponding code.
Related
I need switch case in while. Every case in the switch is part of the menu which does something. Also I need to handle all the inputs from switch, so default isn't good choice for me. I need to leave while completely when user press CTRL + Z in CMD, where program is happening in order to free memory and quit program. This code is only simplified version of mine. Could you please help me? It's the last thing I need to do to have completed task. Thank you.
while (1) {
scanf("%c", &choose);
switch (choose) {
case 1:
//something
break;
case 2:
// something
break;
// no default!
}
}
If the user presses Ctrl+Z on Windows, your program will see that as an end of input file (stdin). When scanf tries to read a byte from stdin, it will fail and return -1 (EOF), while the usual return value is 1 (i.e. number of items read).
So you can use the following code:
while (scanf("%c", &choose) == 1)
{
switch (choose) {
case 1:
//something
break;
case 2:
// something
break;
// no default!
}
}
I put the return value of scanf into the while's condition. When scanf cannot read a byte from stdin anymore, the loop will terminate.
Your problem in handling control-Z is not leaving the switch statement, it is leaving the while statement.
On a Windows system, when the user presses control-Z at the start of an input line, the software will act similarly to an end-of-file. Notably, when scanf("%c", &choose); executes, there will be no input for it to read, so nothing will be stored in choose. Instead, scanf will return EOF to indicate no match to "%c"could be made (because no input was available). However, your code does not test the return value of scanf, so it will not see this. One way to fix this is:
while (1)
{
int n = scanf("%c", choose);
if (n != 1) // Did scanf match %c and store something in choose?
break; // No, so break out of the while loop.
switch (choose)
…
}
Then there is the matter of what should be in the switch statement. You say you “need to handle all the inputs from switch.” A char typically has 256 possible values. So, unless you have 256 case labels in the switch, you are not handling all the inputs in the switch statement. You should have a default label.
If any of the cases inside the switch needs to cause execution to leave the while statement, you can arrange this in various ways. One way is to use a variable to indicate that:
while (1)
{
int n = scanf("%c", choose);
if (n != 1) // Did scanf match %c and store something in choose?
break; // No, so break out of the while loop.
int LeaveWhile = 0;
switch (choose)
{
case 'A':
// Code to handle A input.
break;
case 'B':
…
case something: // Some case where we want to end the loop.
LeaveWhile = 1; // Indicate end of loop.
break;
…
default:
// Code to handle remaining values.
break;
}
// If it is time to leave the while loop, break out of it.
if (LeaveWhile)
break;
}
Before describing how I would handle the problem of the "break" keyword being overused in C, there is another issue you may hit that should be mentioned.
The behavior of control-C and control-Z vary with the OS and the TTY (console) settings.
Linux, with default TTY settings, will cause an interrupt with control-C, which if not handled will end the program. control-Z will suspend the program. By default, in neither case is the character delivered to the program, and so it can not be read by scanf, and it will not be dispatched by the switch statement.
Windows will also interrupt with control-C by default.
I mention this because you depend on reading control characters in your input parsing. If you aren't having the behavior you want, consider looking at TTY settings.
As for exiting the "while(1)" from inside the switch, the standard way is to change the loop from "while(1){}" to "int looping =1; while (looping){}", and set looping to 0 inside the body of the while loop.
In some cases, I will do this with (cover the eyes of the children watching) a goto to a label after the while body. I prominently display goto label, and reverse indent it to outside the level of the while loop body.
Sometimes the break condition is more natural, and sometimes the goto is clearer. If you don't have an aesthetic preference for one over the other, you might default to the break condition rather than the goto.
Add a flag at the top of the loop that gets set when you want to break out, then do so after the switch:
while (1) {
int breakout = 0;
scanf("%c", &choose);
switch (choose) {
case 1:
//something
break;
case 2:
// something else
// exit the loop in this case
breakout = 1;
break;
}
if (breakout) break;
}
you have also *do - while* loop.
bool continueLoop = true;
do
{
switch(something)
{
case smth1:
/* ... */
break
case smth2:
/* ... */
continueLoop = false; //whis will exit the loop
break;
default
/* ... */
break;
}
}while(continueLoop)
I am a beginner in C programming and I'm trying to do basic stuff. I'm trying to create a simple menu using a switch statement like this:
int disp(){
int check;
while(check!=1){
int choose;
printf("1 \n");
printf("5quit\n");
scanf("%d", &choose);
switch(choose)
{
case 1:func1();
break;
case 5: check = 1;
break;
default:
printf("wrong input");
}
return 0;
}
}
However, when either default or case 1 happen, I want to make it still loop the menu until a good input is made. For example, if I enter random stuff like "asdf", the program should say "wrong input" and then display the menu again and wait for user input. And if user enters "1", the program will run func1 and then go back to the menu and wait for the user to input a choice.
Currently, when a wrong input happens it just shuts down the program instead of displaying the menu again, and I don't know how to solve that.
Your return 0 statement is inside of your while loop. Move it down one line, outside of the loop.
This is one reason why proper indentation is important.
At this point, entering in non-numeric input results in an infinite loop because scanf isn't reading in anything. To remedy this, you call getchar in a loop after calling scanf to flush the buffer:
int c;
scanf(" %d", &choose);
while ((c=getchar()) != EOF && c != '\n');
This is correct and close to what you want
// Use a proper formatting standard to improve readability
int disp() {
int check = 0; // Initialize the loop variable
while (check != 1) {
int choose;
printf("1 \n");
printf("5quit\n");
scanf("%d\n", &choose); // Ask the scanf to swallow the newline
switch(choose) {
case 1:
func1();
break;
case 5:
check = 1;
break;
default:
printf("wrong input");
}
}
return 0; // Pull the statement out of the loop
}
EDIT: Since you are providing invalid input intentionally, it's better to check the return value of scanf to avoid errors.
I've been introduced to C this year as part of my degree, during which I have to write simple programs and test them to be idiot-proof by running them over and over again, putting nonsense variables in, etc. and I had an idea to write a program with the ability to restart itself without having to run the program again.
I've tried writing a program to perform this function (which turned out to be harder than I first thought) and I now have it working, albeit using a goto function that are frowned upon. Now the only problem I have is a while loop to check for nonsense input, that seems determined to run at least once ignoring a prompt for a valid input.
Please could someone give me an idea why this is happening? (My compiler is Dev-C++ 4.9.9.2)
int main (void)
{
mainprogram:
printf("\nPROGRAM START\n");
//code copied from an exam, to check that the program performs a function
//when ran through again
int i,j,k;
printf("Please enter 7:");
scanf("%d",&i);
printf("Please enter 4:");
scanf("%d",&j);
printf("Please enter 0:");
scanf("%d",&k);
//this is to check that the program will take input when it is restarted
do {
switch (i%j) {
case 3:
i--;
k*=i;
break;
case 2:
i--;
k+=i;
default:
i--;
k++;
break;
}
printf("i is %d k is %d\n",i,k);
} while (i>0);
//end of copied code
char prompt='y';
printf("\nRestart program?");
scanf("%c",&prompt);
while (prompt != 'y' && prompt != 'Y' && prompt != 'n' && prompt != 'N')
{
//this is the problem section, when entering nonsense input, the error messages
//display twice before pausing for input, and when restarted, the program does
//run again but displays the error messages once before pausing for input
printf("\nERROR: INVALID INPUT");
printf("\n\nRestart program?");
prompt='y';
scanf("%c",&prompt);
}
if (prompt == 'y' || prompt == 'Y')
{
goto mainprogram;
}
//
return 0;
}
while(1){ //parent
printf("\n\nRun program?");
scanf("%c",&prompt);
if (prompt == 'n' || prompt == `N`)
{
printf("\nEXITINT")
return 0;
}
int i,j,k;
printf("Please enter 7:");
scanf("%d",&i);
printf("Please enter 4:");
scanf("%d",&j);
printf("Please enter 0:");
scanf("%d",&k);
switch (i%j)
{
case 3:
i--;
k*=i;
break;
case 2:
i--;
k+=i;
break;
default:
i--;
k++;
break;
}
printf("i is %d k is %d\n",i,k);
} //end while parent
//end of copied code
There are a couple of ways one could restart a program, or more generally loop over some code. Of course, all of that can be done with gotos, but that makes the code hard to understand, so C has equivalent structures for dealing with the most common patterns:
execute code if and as long as condition holds
while (condition)
{
/* code */
}
This means before executing the code, the condition is checked. If the condition holds (its value is non-zero), the code is executed and then looped back to the top. This is equivalent to:
top_of_while:
if (!condition)
goto done;
/* code */
goto top_of_while:
done:
execute code and redo while condition holds
do
{
/* code */
} while (condition)
This means execute code first and then check for a condition. If the condition holds, executed the code again. This is equivalent to:
top_of_do_while:
/* code */
if (condition)
goto top_of_do_while;
iteration
for (initialization; condition; iteration)
{
/* code */
}
This is a kind of while loop that happens a lot, in which there is an initialization, followed by a while loop, which on the bottom changes a variable to form some sort of iteration. This is equivalent to:
initialization;
while (condition)
{
/* code */
iteration;
}
To restart a program, most likely you want the do-while loop, since for sure you know that the program has to execute once. However, by properly initialization the condition variable of a while loop, you can also ensure that the loop is always entered the first time. It's a matter of style and your liking.
Where to actually use goto
Many people would tell you to never use goto. This roots from the fact that overuse of goto had led to a great number of overly complicated programs, the so-called spaghetti code. The reason is that it's hard to build a mental model of a program where the execution can jump around to any other part.
However, gotos are actually very useful in C, without which error-handling becomes a huge pain in the ... neck. This useful usage of goto is for handling errors and cleanup, which are always in the bottom of the function. An example would be:
int *read_nums(int n)
{
int *array, i;
array = malloc(n * sizeof *array);
if (array == NULL)
goto exit_no_mem;
for (i = 0; i < n; ++i)
if (scanf("%d", &array[i]) != 1)
goto exit_bad_input;
return array;
exit_bad_input:
free(array);
exit_no_mem:
return NULL;
}
This way, the code is not cluttered (much) with error handling and cleanup is done very nicely based on how far the function has executed.
I just reformatted your code and indeed # herohuyongtao is right, the break; for case 2 has moved at the end of default which is not useful there.
But there's something really shocking in your code, is that you use a goto. Just remember that rule: WHENEVER YOU USE GOTO, THERE'S A BETTER WAY TO DO IT!
#include <stdio.h>
short int read_input (void) {
printf("\nPROGRAM START\n");
//code copied from an exam, to check that the program performs a function
//when ran through again
int i,j,k;
printf("Please enter 7:");
scanf("%d",&i);
printf("Please enter 4:");
scanf("%d",&j);
printf("Please enter 0:");
scanf("%d",&k);
//this is to check that the program will take input when it is restarted
do {
switch (i%j) {
case 3:
i--;
k*=i;
break;
case 2:
i--;
k+=i;
break; // break at the right spot
default:
i--;
k++;
}
printf("i is %d k is %d\n",i,k);
} while (i>0);
// getchar(); // could be inserted here (discards one char)
// fflush(stdin); // could also do the job (discards all remaining chars in buffer)
char prompt='y';
// here the design choice is to let the user input whatever
// and not updated the output until the right character is given
// which will hide how many wrong chars has been previously in the buffer.
printf("\nRestart program? ");
do {
prompt = getchar();
} while (prompt != 'y' && prompt != 'Y' && prompt != 'n' && prompt != 'N');
if (prompt == 'y' || prompt == 'Y')
return 1;
return 0;
}
int main() {
while (read_input() == 1);
return 0;
}
Now that the code is clean, for your exact problem, you will get into a carriage return problem. Basically, what you do is get input until \n is hit, but when you hit carriage return, you actually send CR + LF. So there's one character that never gets read in the input buffer, that you need to discard.
So you should first read that SO question that sums up very well your problem, and you can either:
add a lone getchar(); just after your //end of copied code comment,
or a fflush(stdin) can do the job (cf the SO Question to learn more about it) but has been designed about flushing output buffers, not input ones,
add a fseek(stdin,0,SEEK_END); which is a bit dirty (and non-portable) but works,
or change your conditions and your use of scanf to take into account that you're actually having more chars in the buffer.
In the code I gave you, I chose the most simplistic solution, which is to discard any wrong input without printing anything.
When searching for everything about getchar() function in this really great site, I found this post: Why doesn't getchar() wait for me to press enter after scanf()?
#include <stdio.h>
int main()
{
int value;
printf("1. option 1.\n2. option 2.\n3. option 3.\n4. Exit\n\nMake an option: ");
scanf("%d", &value);
switch (value)
{
case 1:
printf("you selected the option 1.");
break;
case 2:
printf("you selected the option 2.");
break;
case 3:
printf("you selected the option 3.");
break;
case 4:
printf("goodbye");
break;
default:
printf("thats not an option");
break;
}
getchar();//here is the question,why it's useful ?
return 0;
}
I understand the whole program, and I understand that each time it is called, getchar reads the next input character from a text stream and returns that as its value. That is, after
c = getchar();
the variable c contains the next character of input. The characters normally come from the
keyboard.
But here is the question: why did the programmer call getchar() at the end of the program?
this practice is used especially for console applications, that way you force the program to not stop until you press a key so you can read the output. Usually the console closes when the program execution ends
The last getchar() function is there in order for the application to wait for a keypress before exit.
Generally in console apps, when you work in an IDE and run the code, a terminal window pops up runs the code, and as soon as the main function ends the terminal window also vanishes, and the users cannot see the last output. We need to make some way that the screen waits. One way is to make the program from being terminated by waiting for an input. Some people uses unbuffered input function at the end, so whenever you press a key the function reads it and returns immediately. In this case you use getchar which is a buffered input function, in this case, when you have finished inspecting the screen, press any character and press enter which will return from the function and after that terminate the program. You could have used scanf or other ways to hold the screen too.
It is good for debugging, please remove it from final release.
I'm just starting to study C. I have a program that prints a menu and let users choose what to do step by step. Now I would like to return to the main menu whenever the user enters an empty line, but how can I do that?
I think I can make a function that return the program to the main menu, but when to call that function? I know it's not good to put an if-else whenever I scanf something...
I am used to the OO world, so this is a bit unfamiliar to me, please help :)
I'm guessing you're using a switch statement to filter your input, so just make an option for '\n', or the newline character, to output the print the menu.
If you're doing something along the lines of:
printf("0) do something\n");
printf("1) do something else\n");
printf("enter) main menu\n");
...then scanf isn't really your friend.
You could do something like this:
char buf[80];
int choice;
printf(menu_text);
fgets(buf, 80, stdin);
if(strlen(buf))
{
sscanf(buf, "%d", &choice);
switch(choice)
{
case 0:
/* etc */
break;
case 1:
/* etc */
break;
}
}
else
{
go_back_to_main_menu();
}
It depends on if the user is communicating by sending a string or a single character/keypress.
If communicating by string, try starting with:
char buffer[MAX_BUF_LEN];
char* pBuffer = buffer;
scanf("%s%*c",pBuffer);
if (strlen(pBuffer) == 0)
goto_main_menu();
else
process_user_input(pBuffer);
If communicating by character/keystroke, try starting with:
int inkey = getchar();
if (inkey == '\n')
goto_main_menu();
else
process_user_input(inkey);
Using an "if/else" after you scanf something is perfectly valid. Anything sent from the user should be checked and validated before it is used anyway.
C language is not an OO world, so I'd say stick with if-else. Anyway, when creating text menus you usually end up with switch(user_choice) of if-else.
Do you need a help with scanf()?