C Цикл For пропускает первую итерацию и фиктивный номер из сканирования цикла.
Я создаю генератор почтовых этикеток для школы, и у меня возникла проблема с несколькими проблемами. Моя программа состоит в том, чтобы брать полное имя, адрес, город, штат и почтовый индекс для лиц от 0 до 10. При запуске моей программы у меня возникают две основные проблемы. Цикл for пропускает полное имя "safergets()" и переходит к адресу safergets. Я перешел к проверке, работает ли все остальное, но моя проверка почтового индекса работала некорректно. Я добавил printf, чтобы увидеть, был ли введен тот же номер, и обнаружил, что это подделка. Кроме того, я получаю код ошибки для моей строки, пытающейся использовать вывод состояния с большой буквы. Я уверен, что неправильно использую toupper. Ниже приведен мой код, код ошибки и результат.
#include <stdio.h>
#include <ctype.h>
/* Define structure */
struct information
{
char full_name[35], address[50], city[25], state[3];
long int zip_code;
};
/* Function safer_gets */
/* ------------------- */
void safer_gets (char array[], int max_chars)
{
/* Declare variables. */
/* ------------------ */
int i;
/* Read info from input buffer, character by character, */
/* up until the maximum number of possible characters. */
/* ------------------------------------------------------ */
for (i = 0; i < max_chars; i++)
{
array[i] = getchar();
/* If "this" character is the carriage return, exit loop */
/* ----------------------------------------------------- */
if (array[i] == '\n')
break;
} /* end for */
/* If we have pulled out the most we can based on the size of array, */
/* and, if there are more chars in the input buffer, */
/* clear out the remaining chars in the buffer. */
/* ---------------------------------------------------------------- */
if (i == max_chars )
if (array[i] != '\n')
while (getchar() != '\n');
/* At this point, i is pointing to the element after the last character */
/* in the string. Terminate the string with the null terminator. */
/* -------------------------------------------------------------------- */
array[i] = '\0';
} /* end safer_gets */
/* Begin main */
int main()
{
/* Declare variables */
struct information person[10];
int x, i;
/* Issue greeting */
printf("Welcome to the mailing label generator program.\n\n");
/* Prompt user for number of individuals between 0 - 10. If invalid, re-prompt */
do
{
printf("How many people do you want to generate labels for (0-10)? ");
scanf("%i", &x);
if(x<0 || x>10)
printf("Invalid number. Please re-enter number. Must be from 0 to 10.\n");
}while(x<0 || x>10);
/* Begin loop for individual information */
for(i = 0; i < x; i++)
{
printf("\n\nEnter name: ");
safer_gets(person[i].full_name, 35); /* This is the step being skipped */
printf("\nEnter street address: ");
safer_gets(person[i].address, 50);
printf("\nEnter city: ");
safer_gets(person[i].city, 25);
printf("\nEnter state: ");
gets(person[i].state);
/* Begin loop to verify correct zipcode */
do
{
printf("\nEnter zipcode: ");
scanf("%ld", person[i].zip_code); /* I get a bogus number here */
if(person[i].zip_code<00001 || person[i].zip_code>99999)
{
printf("\nInvalid zipcode. Must be from 00001 to 99999.");
}
}while(person[i].zip_code<00001 || person[i].zip_code>99999);
/* end loop */
}/* end of loop */
/* Output individual information in mailing format, condition for 0 individuals */
if(x>0 && x<10)
{
printf("\n\nBelow are your mailing labels:\n\n");
}
/* Begin loop for outputting individual(s) mailing labels */
for(i = 0; i < x; i++)
{
printf("%s\n",person[i].full_name);
printf("%s\n",person[i].address);
printf("%s\n",person[i].city);
/* Output state in all uppercase */
printf("%s\n", toupper(person[i].state)); /* This is where the error code is occurring */
printf("%.5ld\n\n", person[i].zip_code);
} /* end of loop */
printf("Thank you for using the program.\n");
}/*end of main */
Код ошибки:142: предупреждение: передача аргумента 1 из `toupper'делает целое число из указателя без приведения.
Выход:
Welcome to the mailing label generator program.
How many people do you want to generate labels for (0-10)? 1
Enter name:
Enter street address: 100 Needhelp Ave.
Enter city: Gardner
Enter state: NY
Enter zipcode: 01420
Invalid zipcode. Must be from 00001 to 99999.
Enter zipcode:
Я рассмотрел несколько вопросов здесь, чтобы попытаться понять, в чем я ошибаюсь, но если чувствую, это может быть пара проблем, влияющих на мою программу. Кроме того, наш профессор дал моему классу функцию safergets, чтобы гарантировать, что пользователь не вводит больше символов, чем может вместить массив. Спасибо за вашу помощь и терпение, которые помогли мне понять свои ошибки!
1 ответ
Давайте рассмотрим проблему по порядку:
новая строка остается в стандартном вводе после числа или прочитанных лиц
printf("\n\nEnter name: "); safer_gets(person[i].full_name, 35); /* This is the step being skipped */
Он пропущен, потому что ваш safer_gets()
читает только до первого '\n'
(символновой строки - не возврат каретки, то есть'\r'
). Однако первый персонажsaver_gets()
видит во входном потоке '\n'
характер, который остается в stdin
непрочитанный после вашего звонка scanf
в:
printf("How many people do you want to generate labels for (0-10)? "); scanf("%i", &x);
Все scanf
спецификаторы формата для числового преобразования читаются только через последнюю цифру (или десятичную точку), которая составляет число, выходящее из'\n'
генерируется пользователем, нажимающим Enter непрочитанное во входном потоке (stdin
Вот). Это одна из основных причин, по которой начинающим программистам на C рекомендуется читать вводимые пользователем данные с помощью строчно-ориентированной функции ввода, такой какfgets()
(или POSIX getline()
), а затем используйте sscanf
для анализа значений из заполненного буфера.
Почему функции линейно-ориентированного ввода предпочтительнее для пользовательского ввода
При использовании строчно-ориентированной функции ввода с достаточным буфером потребляется вся строка пользовательского ввода (включая'\n'
от нажатия пользователем Enter). Это гарантирует, чтоstdin
готов для следующего ввода и не имеет непрочитанных символов, оставшихся от предыдущего ввода, которые ждут, чтобы вас укусить.
Правильное использование всех функций ввода
Если вы не берете ничего другого из этого ответа, изучите это - вы не сможете правильно использовать любую функцию ввода, если не проверите возврат. Это особенно актуально дляscanf
семейство функций. Зачем? Если вы пытаетесь прочитать целое число с помощьюscanf
и пользователь входит "four"
вместо этого происходит сбой сопоставления, и извлечение символов из входного потока прекращается, и первый недопустимый символ оставляет все недопустимые символы во входном потоке непрочитанными. (просто жду, чтобы снова тебя укусить).
Правильное использование scanf
scanf
может быть использован при правильном использовании. Это означает, что вы несете ответственность за проверку возвращения изscanf
каждый раз. Вы должны выполнить три условия
(return == EOF)
пользователь отменил ввод, создав руководствоEOF
нажатием Ctrl+d(или на окнах Ctrl+z);(return < expected No. of conversions)
соответствия или вход произошел сбой. Для сбоя сопоставления вы должны учитывать каждый символ, оставшийся во входном буфере. (сканирование вперед во входном буфере чтения и отбрасывания символов до'\n'
илиEOF
находится); и наконец(return == expected No. of conversions)
указывает на успешное чтение - затем вы должны проверить, соответствует ли ввод каким-либо дополнительным критериям (например, положительное целое число, положительное число с плавающей запятой, в пределах необходимого диапазона и т. д.).
Вы также должны учитывать, что остается во входном потоке после успешного чтения с помощью scanf
. Как обсуждалось выше,scanf
оставит '\n'
во входном потоке непрочитано для ВСЕХ спецификаторов преобразования, если вы специально не учитываете это в своей строке формата (что, если учитывать, обычно приводит к хрупкой строке входного формата, которую легко сорвать дополнительными посторонними символами после желаемого ввода, но до'\n'
) Когда используешь scanf
для ввода вы должны надеть шляпу бухгалтера и учесть каждый символ, который остается во входном потоке, и при необходимости очистить входной поток от любых недопустимых символов.
Вы можете написать простой empty_stdin()
функция для обработки удаления всех посторонних символов, которые остаются после ввода пользователя, путем простого сканирования вперед, отбрасывая все символы, которые остаются до '\n'
найден или EOF
столкнулся. Вы делаете это в той или иной степени в своейsafer_gets()
функция. Вы можете написать простую функцию как:
void empty_stdin(void)
{
int c = getchar(); /* read character */
while (c != '\n' && c != EOF) /* if not '\n' and not EOF */
c = getchar(); /* repeat */
}
Вы можете сделать то же самое с простым for
встроенный цикл, например
for (int c = getchar(); c != '\n' && c != EOF; c = getchar()) {}
Следующая проблема - попытка записи на неверный адрес
Когда используешь scanf
, scanf
ожидает, что параметр для соответствующего преобразования будет указателем на соответствующий тип. В:
printf("\nEnter zipcode: "); scanf("%ld", person[i].zip_code); /* I get a bogus number here */
Вы не можете указать указатель, предоставляя long int
значение вместо этого. посколькуperson[i].zip_code
это тип long int
чтобы предоставить указатель дляscanf
для заполнения вы должны использовать адрес оператора, например&person[i].zip_code
сказать scanf
какой адрес следует заполнить значением, для которого он обеспечивает преобразование.
Подождите? Почему мне не нужно делать это с массивом? При доступе массив преобразуется в указатель на первый элемент. Таким образом, для строкового ввода, если для хранения строки используется массив, он автоматически преобразуется в указатель C11 Standard - 6.3.2.1 Другие операнды - L-значения, массивы и указатели функций (p3).
toupper работает с символами, а не со строками
printf("%s\n", toupper(person[i].state)); /* This is where the error code is occurring */
Как обсуждалось в моем комментарии, toupper
берет тип int
как параметр, а не тип char*
. Чтобы преобразовать строку в верхний / нижний регистр, вам нужно перебрать каждый символ, преобразовывая каждый символ индивидуально. Однако в вашем случае с.state
член вашей структуры, есть только 2 символа, о которых нужно беспокоиться, поэтому просто преобразуйте их оба, когда они будут прочитаны, например
/* just 2-chars to convert to upper - do it here */
person[i].state[0] = toupper (person[i].state[0]);
person[i].state[1] = toupper (person[i].state[1]);
Фундаментальные проблемы в safer_gets()
Это решит большинство очевидных проблем, но safer_gets()
Сама функция имеет несколько фундаментальных проблем. В частности, он не справляетсяEOF
когда возвращается getchar()
и он не может предоставить пользователю какое-либо указание на то, был ли запрошенный пользовательский ввод успешным или неудачным из-за отсутствия ничего с типом void
. В любой функции, которую вы пишете, где есть какая-либо возможность отказа внутри функции, вы ДОЛЖНЫ предоставить значимый возврат вызывающей функции, чтобы указать, была ли запрошенная операция функции успешной или неудачной.
Что ты можешь сделать с safer_gets()
? Почему бы не вернуть простойint
значение, обеспечивающее количество символов, прочитанных в случае успеха, или -1
(нормальное значение для EOF
) при неудаче. Вы получаете двойной бонус в виде возможности проверить успешность ввода - и вы также получаете количество символов в строке (ограничено2147483647
символы). Теперь у вас также есть возможность обрабатывать отмену ввода пользователем путем создания вручную EOF с помощью Ctrl+d Linux или Ctrl+z(Windows).
Вы также должны пустить stdin
ввода всех символов во ВСЕХ случаях, кроме EOF
. Это гарантирует отсутствие непрочитанных символов после вашего обращения кsafer_gets()
которые могут вас укусить, если вы позже вызовете другую функцию ввода. Внеся эти изменения, вы можете написать свойsafer_gets()
в качестве:
/* always provide a meaninful return to indicate success/failure */
int safer_gets (char *array, int max_chars)
{
int c = 0, nchar = 0;
/* loop while room in array and char read isn't '\n' or EOF */
while (nchar + 1 < max_chars && (c = getchar()) != '\n' && c != EOF)
array[nchar++] = c; /* assing to array, increment index */
array[nchar] = 0; /* nul-terminate array on loop exit */
while (c != EOF && c != '\n') /* read/discard until newline or EOF */
c = getchar();
/* if c == EOF and no chars read, return -1, otherwise no. of chars */
return c == EOF && !nchar ? -1 : nchar;
}
(примечание: над тестом наnchar + 1 < max_chars
гарантирует, что символ остается для символа, завершающего нулевой символ, и является просто более безопасной перестановкойnchar < max_chars - 1
)
Общий подход к проверке входных данных
Теперь у вас есть функция ввода, которую вы можете использовать, которая указывает на успех / неудачу ввода, позволяя вам проверить ввод обратно в вызывающей функции (main()
Вот). Взять, к примеру, чтение.full_name
член, использующий safer_gets()
. Вы не можете просто слепо позвонитьsafer_gets()
и не знаю, был ли ввод отменен или преждевременный EOF
встретился и использовать, а затем перейти к использованию строки, которую он заполнил с любой уверенностью в вашем коде. * Подтвердите, подтвердите, подтвердите каждое выражение. Назад вmain()
, вы можете сделать это, позвонив safer_gets()
как следует читать .full_name
(и любая другая строковая переменная):
#define NAMELEN 35 /* if you need a constant, #define one (or more) */
#define ADDRLEN 50 /* (don't skimp on buffer size) */
...
for (;;) { /* loop continually until valid name input */
fputs ("\nEnter name : ", stdout); /* prompt */
int rtn = safer_gets(person[i].full_name, NAMELEN); /* read name */
if (rtn == -1) { /* user canceled input */
puts ("(user canceled input)");
return 1; /* handle with graceful exit */
}
else if (rtn == 0) { /* if name empty - handle error */
fputs (" error: full_name empty.\n", stderr);
continue; /* try again */
}
else /* good input */
break;
}
(примечание: возвращениеsafer_gets()
фиксируется в переменной rtn
а затем оценивается для -1
(EOF
), 0
пустая строка или больше 0
, хороший ввод)
Вы можете сделать это для каждой строковой переменной, которую вам нужно использовать, а затем использовать те же принципы, которые обсуждались выше, для чтения и проверки. .zip_code
. Вкратце, вы могли бы сделать:
#include <stdio.h>
#include <ctype.h>
#define NAMELEN 35 /* if you need a constant, #define one (or more) */
#define ADDRLEN 50 /* (don't skimp on buffer size) */
#define CITYLEN 25
#define STATELEN 3
#define PERSONS 10
struct information {
char full_name[NAMELEN],
address[ADDRLEN],
city[CITYLEN],
state[STATELEN];
long int zip_code;
};
/* always provide a meaninful return to indicate success/failure */
int safer_gets (char *array, int max_chars)
{
int c = 0, nchar = 0;
/* loop while room in array and char read isn't '\n' or EOF */
while (nchar + 1 < max_chars && (c = getchar()) != '\n' && c != EOF)
array[nchar++] = c; /* assing to array, increment index */
array[nchar] = 0; /* nul-terminate array on loop exit */
while (c != EOF && c != '\n') /* read/discard until newline or EOF */
c = getchar();
/* if c == EOF and no chars read, return -1, otherwise no. of chars */
return c == EOF && !nchar ? -1 : nchar;
}
int main (void) {
/* declare varaibles, initialize to all zero */
struct information person[PERSONS] = {{ .full_name = "" }};
int i = 0, x = 0;
puts ("\nWelcome to the mailing label generator program.\n"); /* greeting */
for (;;) { /* loop continually until a valid no. of people entered */
int rtn = 0; /* variable to hold RETURN from scanf */
fputs ("Number of people to generate labels for? (0-10): ", stdout);
rtn = scanf ("%d", &x);
if (rtn == EOF) { /* user generated manual EOF (ctrl+d [ctrl+z windows]) */
puts ("(user canceled input)");
return 0;
}
else { /* either good input or (matching failure or out-of-range) */
/* all required clearing though newline - do that here */
for (int c = getchar(); c != '\n' && c != EOF; c = getchar()) {}
if (rtn == 1) { /* return equals requested conversions - good input */
if (0 <= x && x <= PERSONS) /* validate input in range */
break; /* all checks passed, break read loop */
else /* otherwise, input out of range */
fprintf (stderr, " error: %d, not in range 0 - %d.\n",
x, PERSONS);
}
else /* matching failure */
fputs (" error: invalid integer input.\n", stderr);
}
}
if (!x) { /* since zero is a valid input, check here, exit if zero requested */
fputs ("\nzero persons requested - nothing further to do.\n", stdout);
return 0;
}
/* Begin loop for individual information */
for (i = 0; i < x; i++) { /* loop until all person filled */
/* read name, address, city, state */
for (;;) { /* loop continually until valid name input */
fputs ("\nEnter name : ", stdout); /* prompt */
int rtn = safer_gets(person[i].full_name, NAMELEN); /* read name */
if (rtn == -1) { /* user canceled input */
puts ("(user canceled input)");
return 1; /* handle with graceful exit */
}
else if (rtn == 0) { /* if name empty - handle error */
fputs (" error: full_name empty.\n", stderr);
continue; /* try again */
}
else /* good input */
break;
}
for (;;) { /* loop continually until valid street input */
fputs ("Enter street address : ", stdout); /* prompt */
int rtn = safer_gets(person[i].address, ADDRLEN); /* read address */
if (rtn == -1) { /* user canceled input */
puts ("(user canceled input)");
return 1; /* handle with graceful exit */
}
else if (rtn == 0) { /* if address empty - handle error */
fputs ("error: street address empty.\n", stderr);
continue; /* try again */
}
else /* good input */
break;
}
for (;;) { /* loop continually until valid city input */
fputs ("Enter city : ", stdout); /* prompt */
int rtn = safer_gets(person[i].city, CITYLEN); /* read city */
if (rtn == -1) { /* user canceled input */
puts ("(user canceled input)");
return 1; /* handle with graceful exit */
}
else if (rtn == 0) { /* if city empty - handle error */
fputs ("error: city empty.\n", stderr);
continue; /* try again */
}
else /* good input */
break;
}
for (;;) { /* loop continually until valid state input */
fputs ("Enter state : ", stdout); /* prompt */
int rtn = safer_gets(person[i].state, STATELEN); /* read state */
if (rtn == -1) { /* user canceled input */
puts ("(user canceled input)");
return 1; /* handle with graceful exit */
}
else if (rtn == 0) { /* if state empty - handle error */
fputs ("error: state empty.\n", stderr);
continue; /* try again */
}
else { /* good input */
/* just 2-chars to convert to upper - do it here */
person[i].state[0] = toupper (person[i].state[0]);
person[i].state[1] = toupper (person[i].state[1]);
break;
}
}
/* read/validate zipcode */
for (;;) { /* loop continually until valid zipcode input */
fputs ("Enter zipcode : ", stdout); /* prompt */
int rtn = scanf ("%ld", &person[i].zip_code); /* read zip */
if (rtn == EOF) { /* user pressed ctrl+d [ctrl+z windows] */
puts ("(user canceled input)");
return 1;
}
else { /* handle all other cases */
/* remove all chars through newline or EOF */
for (int c = getchar(); c != '\n' && c != EOF; c = getchar()) {}
if (rtn == 1) { /* long int read */
/* validate in range */
if (1 <= person[i].zip_code && person[i].zip_code <= 99999)
break;
else
fprintf (stderr, " error: %ld not in range of 1 - 99999.\n",
person[i].zip_code);
}
else /* matching failure */
fputs (" error: invalid long integer input.\n", stderr);
}
}
}
/* Output individual information in mailing format, condition for 0 individuals */
for(i = 0; i < x; i++)
/* you only need a single printf */
printf ("\n%s\n%s\n%s, %s %ld\n", person[i].full_name, person[i].address,
person[i].city, person[i].state, person[i].zip_code);
fputs ("\nThank you for using the program.\n", stdout);
}
(примечание: используя#define
для создания необходимых констант, если вам нужно отрегулировать число, у вас есть одно место для внесения изменений, и вам не остается выбирать, хотя каждое объявление переменной и ограничение цикла, чтобы попытаться внести изменение)
Пример использования / вывода
Когда вы закончите писать любую процедуру ввода - попробуйте ее сломать! Найдите угловые случаи, которые терпят неудачу, и исправьте их. Продолжайте пытаться сломать его, намеренно вводя неверный / недопустимый ввод, пока он больше не будет исключать ничего, кроме того, что пользователь должен ввести. Выполните свои процедуры ввода, например
$ ./bin/nameaddrstruct
Welcome to the mailing label generator program.
Number of people to generate labels for? (0-10): 3
Enter name : Mickey Mouse
Enter street address : 111 Disney Ln.
Enter city : Orlando
Enter state : fL
Enter zipcode : 44441
Enter name : Minnie Mouse
Enter street address : 112 Disney Ln.
Enter city : Orlando
Enter state : Fl
Enter zipcode : 44441
Enter name : Pluto (the dog)
Enter street address : 111-b.yard Disney Ln.
Enter city : Orlando
Enter state : fl
Enter zipcode : 44441
Mickey Mouse
111 Disney Ln.
Orlando, FL 44441
Minnie Mouse
112 Disney Ln.
Orlando, FL 44441
Pluto (the dog)
111-b.yard Disney Ln.
Orlando, FL 44441
Thank you for using the program.
Уважая пользователей, желающих отменить ввод в любой момент, когда они создают ручной EOF с помощью Ctrl+d Linux или Ctrl+z(Windows), вы должны иметь возможность обрабатывать это из любой точки вашего кода.
При первом запросе:
$ ./bin/nameaddrstruct
Welcome to the mailing label generator program.
Number of people to generate labels for? (0-10): (user canceled input)
Или в любой другой запрос после этого:
$ ./bin/nameaddrstruct
Welcome to the mailing label generator program.
Number of people to generate labels for? (0-10): 3
Enter name : Mickey Mouse
Enter street address : 111 Disney Ln.
Enter city : (user canceled input)
Обработать запрос для нулевого человека:
$ ./bin/nameaddrstruct
Welcome to the mailing label generator program.
Number of people to generate labels for? (0-10): 0
zero persons requested - nothing further to do.
(** лично я бы просто изменил тест ввода и попросил их ввести значение из 1-10
вместо)
Неправильный ввод:
$ ./bin/nameaddrstruct
Welcome to the mailing label generator program.
Number of people to generate labels for? (0-10): -1
error: -1, not in range 0 - 10.
Number of people to generate labels for? (0-10): 11
error: 11, not in range 0 - 10.
Number of people to generate labels for? (0-10): banannas
error: invalid integer input.
Number of people to generate labels for? (0-10): 10
Enter name : (user canceled input)
Вы уловили суть... Итог, вы должны проверять каждый ввод пользователя и знать, что он действителен, прежде чем использовать ввод в своей программе. Вы не можете проверить ввод какой-либо функции, если не проверите возврат. Если вы ничего не уберете, кроме этого, вы научитесь этому.
Посмотрите все и дайте мне знать, если у вас возникнут дополнительные вопросы. (и спросите своего проф., какsafer_gets()
ручки EOF
и как вы должны проверять, удалось ли выполнить функцию или нет)