Учитывая этот фрагмент из опубликованного кода ОП:
char cAFirst[25]; // <- Uninitialized, the contents are indeterminated
// ...
fgets(cAFirst,25,stdin); // <- We don't know if it succeeded or not
// ...
for (size_t i = 0; i < 25; i++) {
// ... ^^^^^^
}
Копирует до 24 символов из stdin
в массив (включая символ новой строки, если присутствует), также добавляя нулевой терминатор (если не произошла ошибка потока), но когда цикл выполняется, он перебирает все 25 символов массива, даже если введенная строка короче, с учетом символов с неопределенным значением.
Чтобы избежать этого, нам просто нужно протестировать только те символы, которые действительно были прочитаны из входного потока, путем их подсчета или использования нулевого терминатора в качестве часового.
Мы могли бы инициализировать массив (в ноль), а также избежать использования повторяющихся магических чисел, что подвержено ошибкам.
#include <stdio.h>
// I'd use a bigger value, like 256, but OP's use case is not specified.
enum {
BUF_SIZE = 25
};
int main(void)
{
// All the elements of the array are set to zero, not only the first.
char buf[BUF_SIZE] = {'\0'};
// ...
Теперь, даже если все символы в массиве проверены в предыдущем цикле, ложные значения не будут и статистика будет согласованной. Однако это не является реальным решением, и хотя обычно предлагается инициализировать все переменные перед их использованием, в этом случае в этом нет необходимости.
Как отмечает chux , необходимо проверить возвращаемое значение функций, таких как fgets
, чтобы убедиться, что операция прошла успешно и наши переменные находятся в допустимом состоянии.
if ( fgets(buf, BUF_SIZE, stdin) == NULL ) {
if (feof(stdin)) {
fprintf(stderr, "Abnormal end of input.\n");
// The contents of the array are not altered
}
if (ferror(stdin)) {
fprintf(stderr, "A stream error occurred.\n");
// The contents of the array are indeterminated, maybe not even null-terminated
}
exit(EXIT_FAILURE);
}
Ключевым моментом является то, чтобы убедиться, что после этого вызова массив завершается нулем. Даже чека, подобного следующему, может быть достаточно:
if ( fgets(buf, BUF_SIZE, stdin) == NULL ) {
fprintf(stderr, "An error occurred while reading from stdin.\n");
buf[0] = '\0';
// Continues the program despite the error, but with a valid (empty) string
}
Стоит помнить, что любые другие символы, кроме прочитанных, остаются во входном потоке.
Теперь, когда у нас есть действительный массив с нулевым символом в конце, мы можем пройти по нему:
int uc = 0, lc = 0;
for (size_t i = 0; buf[i] != '\0'; i++) {
// which can also be written as 'for (size_t i = 0; buf[i]; i++) {'
// It's also more readable without magic numbers:
if ( 'A' <= buf[i] && buf[i] <= 'Z' ) {
uc++;
}
else if ( 'a' <= buf[i] && buf[i] <= 'z' ) {
lc++;
}
}
printf("Uppercase Letters = %i\nLowercase Letters = %i\n", uc, lc);
return EXIT_SUCCESS;
}
Предыдущий фрагмент может быть обобщен из кодов ASCII с использованием таких функций, как isupper
и islower
, определенных в заголовке <ctype.h>
. Кроме того, фактическая длина строки может использоваться для ограничения цикла for:
// Length of the maximum initial segment of the array, that consists of
// only the characters not equal to a newline or null character
size_t length = strcspn(buf, "\n");
int uc = 0, lc = 0;
for (size_t i = 0; i < length; i++) {
unsigned char k = buf[i];
if ( isupper(k) ) {
uc++;
}
else if ( islower(k) ) {
lc++;
}
}
// ...