I was compiling my C programs in online gdb. There I faced a peculiar problem. I was trying to store 10 sentences in a 2D array, for which I wanted to take 10 inputs of string with spaces terminated by new line. I tried all possible syntax like scanf("%[^\n]%*c")
or scanf("%[^\n]s")
and so on; none of which worked. There after I tried to manually create a function to take input with spaces; even that didn't work. I need help as to why things aren't working. I have attached my code below and the manual way in which I attempted to take input with spaces.
int main()
{
int c;
printf("Enter the number of sentences\n");
scanf("%d",&c);
char s[c][100];
for(int i=0; i<c; i )
{
printf("Enter your sentence ");
int k = 0;
scanf("%c", &s[i][k]);
while (s[i][k]!='\n')
{
k ;
scanf("%c", &s[i][k]);
}
s[i][k]='\0';
}
}
CodePudding user response:
If you flush the \n
that scanf()
leaves behind then you can use fgets() to read each sentence:
#include <stdio.h>
#define SENTENCE_LEN 100
int main() {
printf("Enter the number of sentences\n");
int c;
if(scanf("%d",&c) != 1) {
// handle error
return 1;
}
for(;;) {
int ch = getchar();
if(ch == EOF) return 1;
if(ch == '\n') break;
}
char s[c][SENTENCE_LEN];
for(size_t i = 0; i < c; i ) {
printf("Enter your sentence ");
if(!fgets(s[i], SENTENCE_LEN, stdin)) {
// handle error
return 1;
}
// strip trailing newline
s[i][strcspn(s[i], "\n")] = '\0';
}
for(size_t i = 0; i < c; i ) {
printf("%s\n", s[i]);
}
}
and example session:
Enter the number of sentences
2
Enter your sentence hello world
Enter your sentence hello friend
hello world
hello friend
CodePudding user response:
The line
scanf("%[^\n]s", ... )
will not work, as %[...]
and %s
are two distinct conversion format specifiers. You appear to be attempting to use a hybrid of both, which will probably not work. The s
in the format string will be interpreted as a literal s
, so that scanf
will expect to see a literal s
in the input stream and consume it. This is not what you want.
Conversely, the line
scanf("%[^\n]%*c", ... )
should work (unless the input is an empty line), despite your claim that it doesn't work. Since you have not provided a minimal reproducible example of that code, I cannot tell you why it doesn't work with you.
Regarding your posted code, the problem is described in the following question:
scanf() leaves the newline character in the buffer
The problem is that the line
scanf("%d",&c);
will not consume an entire line. It will at least leave the newline character of the line on the input stream. Therefore, the first call to
scanf("%c", &s[i][k]);
will probably read the leftover newline character, instead of the first character of the sentence.
One simple solution is to call the function getchar
beforehand, so that the newline character is consumed from the input stream. However, this will only work if the newline character is the only leftover character on the input stream. If it is possible that there are more leftover characters on the input stream, then there are several ways of consuming all of them:
Using scanf
:
scanf( "%*[^\n]" );
getchar();
Using getchar
in a do
...while
loop:
int c;
do
{
c = getchar();
} while ( c != EOF && c != '\n' );
Using getchar
in a compact for
loop:
for ( int c; ( c = getchar() ) != EOF && c != '\n'; )
;
After fixing all of the issues mentioned above and adding code to output the results, your program should look like this:
#include <stdio.h>
int main( void )
{
int c;
//get the number of sentences from the user
printf( "Enter the number of sentences: " );
scanf( "%d", &c );
//discard all leftover characters
scanf( "%*[^\n]" );
getchar();
//declare variable-length array based on user input
char s[c][100];
//get the input sentences
for ( int i=0; i<c; i )
{
printf( "Enter your sentence: " );
int k = 0;
scanf( "%c", &s[i][k] );
while ( s[i][k] != '\n' )
{
k ;
scanf( "%c", &s[i][k] );
}
s[i][k] = '\0';
}
//print the sentences
printf( "\nThe results are:\n\n" );
for ( int i=0; i<c; i )
{
printf( "%s\n", s[i] );
}
}
This program has the following behavior:
Enter the number of sentences: 5
Enter your sentence: test1
Enter your sentence: test2
Enter your sentence: test3
Enter your sentence: test4
Enter your sentence: test5
The results are:
test1
test2
test3
test4
test5
Note that this program is only guaranteed to work with lines up to 99
bytes, otherwise you will have a buffer overflow, which means that your program may crash. Also, if the number the user enters at the start of the program is invalid (for example a negative number or not a number at all), then the program may also crash or misbehave in some other way.
When dealing with line-based user input, using scanf
to partially read a line is generally not recommended. It is generally better to always read an entire line of input at once, for example using fgets
. I have therefore rewritten your entire program to not use scanf
at all, but to use fgets
instead:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <errno.h>
//forward declarations
int get_int_from_user( const char prompt[] );
void get_line_from_user( const char prompt[], char buffer[], int buffer_size );
int main( void )
{
int c;
//get the number of sentences from the user
for (;;)
{
c = get_int_from_user( "Enter the number of sentences: " );
//break out of loop if input is valid
if ( c > 1 )
break;
printf( "Input must be a positive integer, please try again!\n" );
}
//declare variable-length array based on user input
char s[c][100];
//get the input sentences
for ( int i=0; i<c; i )
{
get_line_from_user( "Enter your sentence: ", s[i], sizeof s[i] );
}
//print the sentences
printf( "\nThe results are:\n\n" );
for ( int i=0; i<c; i )
{
printf( "%s\n", s[i] );
}
}
//This function will attempt to read one integer from the user. If
//the input is invalid, it will automatically reprompt the user,
//until the input is valid.
int get_int_from_user( const char prompt[] )
{
//loop forever until user enters a valid number
for (;;)
{
char buffer[1024], *p;
long l;
//prompt user for input
fputs( prompt, stdout );
//get one line of input from input stream
if ( fgets( buffer, sizeof buffer, stdin ) == NULL )
{
fprintf( stderr, "Unrecoverable input error!\n" );
exit( EXIT_FAILURE );
}
//make sure that entire line was read in (i.e. that
//the buffer was not too small)
if ( strchr( buffer, '\n' ) == NULL && !feof( stdin ) )
{
int c;
printf( "Line input was too long!\n" );
//discard remainder of line
do
{
c = getchar();
if ( c == EOF )
{
fprintf( stderr, "Unrecoverable error reading from input!\n" );
exit( EXIT_FAILURE );
}
} while ( c != '\n' );
continue;
}
//attempt to convert string to number
errno = 0;
l = strtol( buffer, &p, 10 );
if ( p == buffer )
{
printf( "Error converting string to number!\n" );
continue;
}
//make sure that number is representable as an "int"
if ( errno == ERANGE || l < INT_MIN || l > INT_MAX )
{
printf( "Number out of range error!\n" );
continue;
}
//make sure that remainder of line contains only whitespace,
//so that input such as "6sdfj23jlj" gets rejected
for ( ; *p != '\0'; p )
{
if ( !isspace( (unsigned char)*p ) )
{
printf( "Unexpected input encountered!\n" );
//cannot use `continue` here, because that would go to
//the next iteration of the innermost loop, but we
//want to go to the next iteration of the outer loop
goto continue_outer_loop;
}
}
return l;
continue_outer_loop:
continue;
}
}
//This function will read exactly one line of input from the
//user. If the line is too long to fit in the buffer, then the
//function will automatically reprompt the user for input. On
//failure, the function will never return, but will print an
//error message and call "exit" instead.
void get_line_from_user( const char prompt[], char buffer[], int buffer_size )
{
for (;;)
{
char *p;
//prompt user for input
fputs( prompt, stdout );
//attempt to read one line of input
if ( fgets( buffer, buffer_size, stdin ) == NULL )
{
printf( "Error reading from input!\n" );
exit( EXIT_FAILURE );
}
//attempt to find newline character
p = strchr( buffer, '\n' );
//make sure that entire line was read in (i.e. that
//the buffer was not too small to store the entire line)
if ( p == NULL )
{
int c;
//a missing newline character is ok if the next
//character is a newline character or if we have
//reached end-of-file (for example if the input is
//being piped from a file or if the user enters
//end-of-file in the terminal itself)
if ( !feof(stdin) && (c=getchar()) != '\n' )
{
printf( "Input was too long to fit in buffer!\n" );
//discard remainder of line
do
{
if ( c == EOF )
{
printf( "Error reading from input!\n" );
exit( EXIT_FAILURE );
}
c = getchar();
} while ( c != '\n' );
continue;
}
}
else
{
//remove newline character by overwriting it with
//null character
*p = '\0';
}
//input was ok, so break out of loop
break;
}
}
I have taken the function get_int_from_user
from this answer of mine to another question. Please see that answer for more information on how that function works. For example, I have designed the function in such a way that it performs full input validation and automatically reprompts the user if the input is invalid.
This second program has the same behavior as the first program, except that the input validation is now robust and the program won't crash if the user enters invalid input:
Enter the number of sentences: -5
Input must be a positive integer, please try again!
Enter the number of sentences: 0
Input must be a positive integer, please try again!
Enter the number of sentences: test
Error converting string to number!
Enter the number of sentences: 6abc
Unexpected input encountered!
Enter the number of sentences: 6
Enter your sentence: test1
Enter your sentence: test2
Enter your sentence: test3
Enter your sentence: test4
Enter your sentence: test5
Enter your sentence: test6
The results are:
test1
test2
test3
test4
test5
test6