The gets()
function is unsafe and extremely susceptible to buffer overflows. Never use this function. A better alternative is fgets()
, though the non-standard (but widely available) POSIX getline()
function is also a good option.
Both fgets()
and gets()
fetch a line of input, but gets()
discards the newline character, while fgets()
keeps it and stores it in the input buffer (if there is room). This means that you may need to remove the newline character after fetching a line of input with fgets()
. It also means that when the user simply presses ENTER, the input buffer contains only a \n
character, followed by a \0
null terminator; fgets()
always null-terminates the input buffer.
To read multiple lines of user input, stopping only when the user presses ENTER, fgets()
should be called in a loop. One way of doing this is to use a for(;;) {}
loop construction, which never terminates (equivalent to while(1) {}
). If the first character in a line of input is a \n
character, then a break;
statement exits the loop.
Notes on the Posted Code
The posted code is comparing input characters with character constants to determine whether the input is numeric, alphabetic, or otherwise. This is better, and more portably, accomplished by using Standard Library functions from ctype.h
. Using library functions here means that the code does not need to explicitly consider the current locale or character encoding (which may not be ASCII or UTF-8).
The posted code contains the line:
for (counter = 0; str[counter] != NULL; counter++) {}
Note that NULL
is the null pointer constant, equivalent to (void *) 0
, but not equivalent to 0
. The goal of this loop appears to be to iterate over a string, terminating when the null terminator (\0
) is reached. So, the controlling expression should be changed:
for (counter = 0; str[counter] != '\0'; counter++) {}
Also, the purpose of this loop in the posted code is unclear:
while (str[counter] != '\n')
{
if (str[counter] = '\n')
{
linecount ++;
}
}
If str[counter]
is a newline character, then the loop is not entered; otherwise the if
statement in the loop body assigns '\n'
to str[counter]
, evaluating to true and incrementing linecount
. On the next iteration str[counter] == '\n'
, so the loop is terminated. After the previous loop (with NULL
changed to '\0'
in the controlling expression), counter
is the index of the \0
character in str
, so this loop replaces the null terminator with a newline character, making str
a string no longer. This will lead to undefined behavior if the code later attempts to treat str
as a string.
If the line if (str[counter] = '\n')
is a typo, meant to be if (str[counter] == '\n')
, then this is an infinite loop once entered.
An Example Program
Here is a heavily modified of the posted code that uses fgets()
to get user input, and Standard Library functions to classify input characters.
The fgets()
function returns a null pointer in the event of an error, so this is checked for and handled in about the simplest way possible. After input has been stored in the str[]
buffer array, the first character is checked; if it is \n
, then the user entered an empty line (probably: see the next paragraph), and the loop is terminated. Otherwise, the next step is to see if the input line contains a newline character at all. The strchr()
(from string.h
) function is used here for this. If the \n
is not found, then a null pointer is returned, otherwise a pointer to the \n
character is returned. This is used to write over the \n
with \0
, effectively removing the newline character. Then linecount
is incremented. Thus, the line counter is incremented only when a newline character is encountered in the input.
Note that when input is too large for the input buffer, at least the newline character will remain in the input stream waiting for the next I/O function call. It is possible that only the newline character remains in the input stream, so on the next loop iteration the first character in the buffer is \n
, interpreted by this program as an empty line. If there is a possibility that input will be larger than the buffer allocation, more subtlety will be required to handle this situation. One solution is to use a flag to indicate whether the start of a line is being read. Here, line_start
is initialized to 1
, set to 1
whenever linecount
is incremented, and set to 0
whenever a newline character is not found in the input buffer. In order for a newline to indicate an empty line of input, line_start
must be set to 1
, and the first character in the input buffer must be a \n
character. With this modification, the program will reliably read lines of input even longer than the allocated 1000 characters. You can test this out by making the allocation for str[]
smaller; try char str[2];
.
Here is the complete program:
#include <stdio.h>
#include <ctype.h> // for isdigit(), isalpha()
#include <string.h> // for strchr()
int main(void)
{
char str[1000];
int Digits = 0;
int Char = 0;
int SpecialChar = 0;
int linecount = 0;
int counter;
int total;
int average;
puts("Please type in your words here:");
int line_start = 1;
for (;;) {
if (fgets(str, sizeof str, stdin) == NULL) {
/* Handle error */
fprintf(stdin, "I/O error\n");
return 1;
}
/* Terminate loop on empty line */
if (line_start && str[0] == '\n') {
break;
}
/* If newline present, remove and increment linecount */
char *cptr = strchr(str, '\n');
if (cptr != NULL) {
*cptr = '\0';
++linecount;
line_start = 1;
} else {
line_start = 0; // complete line not read
}
/* update character counters */
for (counter = 0; str[counter] != '\0'; counter++) {
unsigned char uc = str[counter];
if (isdigit(uc)) {
Digits++;
} else if (isalpha(uc)) {
Char++;
} else {
SpecialChar++;
}
}
}
total = Digits + Char + SpecialChar;
average = total / linecount; // integer division
printf("Digits: %d \nCharacters: %d \nSpecial Characters: %d \nLine: %d\n",
Digits, Char, SpecialChar, linecount);
printf("Total no. of characters = %d\n", total);
printf("Average no. of characters = %d\n", average);
return 0;
}