Несмотря на то, что у вас уже есть правильный ответ на одну из ваших проблем, в вашем коде есть еще много проблемЯ приветствую ваши усилия по обучению, и проект меню всегда является хорошим опытом обучения, вы должны правильно обрабатывать ввод, чтобы избежать неопределенного поведения или вызова бесконечных циклов.
Почемуscanf
Дает новую проблему программистам на C
scanf
печально известна тем, что дает новым программистам соответствия, из-за тонких (и не очень тонких) проблем, возникающих из-за неправильного использования.В частности, scanf
(и семейство) не использует все символы в строке ввода, введенной пользователем.Кроме того, если существует несоответствие между тем, что вводит пользователь, и используемым спецификатором преобразования, происходит сбой сопоставления , извлечение символов прекращается, и символы, вызывающие сбой, остаются во входном потоке непрочитано в ожидании следующего кусочка при следующей попытке ввода.Сложность имеет значение, то, что остается во входном потоке на scanf
, зависит от используемого спецификатора преобразования .Если каждый из них не будет должным образом обработан программистом, в вашем коде произойдут плохие вещи.
Сравните проблему с использованием scanf
для ввода с простотой использования линейно-ориентированного вводафункция типа fgets
, которая при наличии достаточного размера буфера будет использовать всю строку ввода, предоставленную пользователем.Вы не можете быть укушены чем-то scanf
оставленным во входном потоке.Кроме того, после считывания всей строки ввода в буфер надлежащего размера можно использовать sscanf
для анализа необходимой информации из заполненного буфера без риска возникновения ошибки , влияющей на следующую попытку ввода пользователем.
При использовании scanf
вы не проверяете возврат каждый раз, например,
scanf("%f", &amount);
У вас нет возможности узнать, содержит ли amount
значение или нетпользователь скользнул, дотронувшись до клавиши 5
, и вместо этого нажал 'r'
.Это открытое приглашение на Неопределенное поведение .Далее, как упоминалось в моем комментарии, вы утвердительно вызываете Неопределенное поведение с:
scanf("%s", &newInvoice);
Вы не можете использовать %s
для сохранения в один символ.Как минимум, string (то, что обозначает спецификатор "%s"
, требуется 2 символа памяти (один для символа и один для нуль-заканчивающего *1052* символа)
Вместо этого используйте линейно-ориентированную функцию ввода (например, fgets
из POSIX getline
)
Вместо этого давайте рассмотрим, как использовать fgets
и обрабатывать любые ошибки вводаЭто может привести к тому, что, если вам нужна константа для объявления буфера, размер #define
или enum
для достижения того же результата. Избегайте использования magic-numbers . Например:
#define NCRS 3 /* if you need a constant, #define one (or more) */
#define MAXA 64 /* (do NOT skimp on buffer sizes) */
#define MAXC 1024
...
int main (void) {
char buf[MAXC] = ""; /* only variable needed outside loop */
do { /* loop continually until user chooses No more invoices */
int sid, ncrs, n = 0, crsno[NCRS] = {0};
double total = 0.0;
/* get student ID (sid) */
printf("\nPlease enter Student ID: ");
if (fgets (buf, MAXC, stdin)) { /* read entire line */
if (sscanf (buf, "%d", &sid) != 1) { /* parse int value */
fputs ("error: invalid integer input.\n", stderr);
continue; /* on invalid int - get another */
}
}
else { /* on manual EOF (ctrl+d on Linux, or ctrl+z on windows) */
fputs ("(user canceled input)\n", stderr);
return 1; /* gracefully exit */
}
...
Посмотрите, что происходит выше. Сначала максимальное количество символов для буфера (MAXC
) определяется как 1024
, которого должно быть более чем достаточно для обработки ожидаемого ввода (и обработки кошки, наступающей наклавиатура). buf[MAXC]
объявляется как буфер для хранения пользовательского ввода, и выполняется вызов fgets
для сохранения строки ввода, содержащей идентификатор студента (sid
), например,
printf("\nPlease enter Student ID: ");
if (fgets (buf, MAXC, stdin)) { /* read entire line */
...
}
else { /* on manual EOF (ctrl+d on Linux, or ctrl+z on windows) */
fputs ("(user canceled input)\n", stderr);
return 1; /* gracefully exit */
}
fgets
возвращает NULL
на EOF
, в остальномe возвращает указатель на buf
.Вы проверяете возврат, чтобы обработать случай, когда пользователь создает руководство EOF
( Ctrl + d в Linux или Ctrl + z в Windows).Если пользователь отменяет ввод, генерируя руководство EOF
, вы, как правило, захотите аккуратно выйти из своего кода (или хотя бы этого блока ввода).
После того, как у вас есть строка ввода, вы можете позвонить sscanf
чтобы проанализировать значение из buf
(как вы пытались вызвать scanf
в вашем коде).Однако обратите внимание, что с помощью fgets
вы уже подтвердили наличие строки ввода, и теперь вы можете отдельно и независимо проверять синтаксический анализ информации из этой строки в нужное вам значение, например,
...
if (sscanf (buf, "%d", &sid) != 1) { /* parse int value */
fputs ("error: invalid integer input.\n", stderr);
continue; /* on invalid int - get another */
}
Поскольку это первый ввод в цикле Invoice, если читается недопустимое целое число, вы можете просто continue
и снова запросить идентификатор студента.Это гарантирует, что у вас будет действительный идентификатор студента, введенный до того, как вы продолжите свою программу, иначе пользователь отменил ввод, и вы выходите.
Тот же подход будет применяться ко всем оставшимся входам в вашей программе.
Предоставление значимого типа возврата для courseInfo
решит вашу total
проблему
Следующая проблема, которую вы поднимаете, состоит в том, как сохранить промежуточный итог от ваших повторных вызовов наcourseInfo
.Это можно легко сделать, изменив тип return для courseInfo
, чтобы он (1) мог сообщать, была ли функция выполнена успешно или нет, и (2) возвращать эту информацию таким образом, чтобы она предоставляла необходимую информацию (например, стоимость номера курса, используемого в качестве входных данных для функции (или возвращает 0
, указывающий, что был предоставлен неверный номер курса). Как отмечено в комментариях, вы не должны использовать плавающую точку для валюты, поскольку произойдут ошибки округления (однакодля вашего упражнения здесь они подойдут, но для реальной обработки валюты используйте точный тип)
Так как обработать итоговую сумму? Так как в вашем коде пользователь просит ввести максимум3 номера курса, простой способ обработки ввода - это просто прочитать номера курса в массив. Затем, после подтверждения того, что они были действительными номерами курса, все, что вам нужно сделать, это зациклить массив, добавив возвращенную стоимость к вашему бегуНапример, обратите внимание на массив номеров курсов (crsno[NCRS]
), объявленный в верхней части цикла.После того как пользователь ввел номер курса, вы можете просто выполнить цикл заполнения массива, используя ту же технику ввода и проверки, которые мы представили выше, например,
/* loop until array filled with ncrs valid course numbers */
printf ("Enter the %d course number(s)\n", ncrs);
do {
printf (" enter course[%d]: ", n + 1); /* add 1 for display */
if (fgets (buf, MAXC, stdin)) { /* read line/validate */
if (sscanf (buf, "%d", &crsno[n]) == 1 &&
lookup (crsno[n])) { /* lookup valid no. ? */
n++; /* only increment array index if valid course */
}
else
fputs ("error: invalid course no.\n", stderr);
}
else {
fputs ("(user canceled input)\n", stderr);
return 1;
}
} while (n < ncrs);
В массиве crsno
теперь содержится номер действительного курсаномера, запрошенные пользователем.(функция lookup (crsno[n])
делает именно это, она просматривает таблицу поиска номеров курсов, чтобы убедиться, что введенный пользователем номер был действительным - как показано в примере ниже)
Заполнив свой массив, вытеперь готов вывести ваш счет. Примечание: вам нужен только один вызов printf
для вывода непрерывного блока текста (независимо от того, сколько строк).Поэтому выводите счет-фактуру, вы просто выводите информацию заголовка для счета-фактуры, а затем зацикливаете свой массив crsno
, передавая каждый номер курса в вашу функцию courseInfo
, суммируя итоги и выводя детали из courseInfo
и, наконец, выводяинформацию нижнего колонтитула с итогом и запрос о том, хочет ли пользователь напечатать другой счет, например,
/* you only need 1 printf to output all continous text */
printf ("\nVALENCE COMMUNITY COLLEGE\nORLANDO FL 10101\n"
"---------------------\n\n"
"Fee Invoice Prepared for Student V%d\n\n"
"1 Credit Hour = $120.25\n"
"CRN\tCR_PREFIX\tCR_HOURS\n", sid);
/* loop over array outputting course specifics, summing total */
for (int i = 0; i < ncrs; i++)
total += courseInfo(crsno[i]);
/* output total and prompt to print another */
printf ("\tHealth & id fees\t$ 35.00\n\n"
"--------------------------------------\n"
"\tTotal Payments\t $ %.2f\n\n"
"Would you like to print another invoice (Y/N): "
, total +35.00);
Внесение его в целом
Ввод краткого примера в целом, выможет сделать что-то похожее на следующее.Продумайте логический поток каждого из циклов.Если вы спросите, хочет ли пользователь напечатать другой счет, то весь ваш код, связанный с этим счетом, должен содержаться во внешнем цикле.Также обратите внимание, что scope переменные объявлены внутри.Единственная переменная, объявленная вне цикла счета-фактуры, - это buf
, и это только потому, что она повторно используется для всех входных данных, а затем сравнивается в условии while ()
цикла do { } while ();
, требуя, чтобы она была видна вне цикла.
Единственная объявленная глобальная переменная - это таблица поиска - и это, как правило, один из ограниченных случаев, когда необходимо использовать глобальные переменные.В противном случае все переменные, кроме buf
, объявляются в области видимости (теле) цикла счета, например,
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define NCRS 3 /* if you need a constant, #define one (or more) */
#define MAXA 64 /* (do NOT skimp on buffer sizes) */
#define MAXC 1024
int valid[] = { 4587, 4599, 8997, 9696, /* valid course lookup table */
4580, 4581, 4582, 4583,
3587, 4519, 6997 };
#define NVALID sizeof valid / sizeof *valid
/* simple lookup function, returns 1 if course valid, 0 otherwise */
int lookup (int course)
{
for (size_t i = 0; i < NVALID; i++)
if (course == valid[i])
return 1;
return 0;
}
/* function prototype for courseInfo - defintion moved to end */
double courseInfo (int myCourse);
int main (void) {
char buf[MAXC] = ""; /* only variable needed outside loop */
do { /* loop continually until user chooses No more invoices */
int sid, ncrs, n = 0, crsno[NCRS] = {0};
double total = 0.0;
/* get student ID (sid) */
printf("\nPlease enter Student ID: ");
if (fgets (buf, MAXC, stdin)) { /* read entire line */
if (sscanf (buf, "%d", &sid) != 1) { /* parse int value */
fputs ("error: invalid integer input.\n", stderr);
continue; /* on invalid int - get another */
}
}
else { /* on manual EOF (ctrl+d on Linux, or ctrl+z on windows) */
fputs ("(user canceled input)\n", stderr);
return 1; /* gracefully exit */
}
for (;;) { /* loop until valid number of courses (ncrs) entered */
printf ("Enter how may courses-up to 3: ");
if (fgets (buf, MAXC, stdin)) { /* read entire line */
if (sscanf (buf, "%d", &ncrs) != 1) { /* same validations */
fputs ("error: not an integer value\n", stderr);
continue;
}
}
else {
fputs ("(user canceled input)\n", stderr);
return 1;
}
if (0 <= ncrs && ncrs <= 3) /* additional range check */
break;
else
fputs ("error: valid no. of courses is 0-3.\n", stderr);
}
/* loop until array filled with ncrs valid course numbers */
printf ("Enter the %d course number(s)\n", ncrs);
do {
printf (" enter course[%d]: ", n + 1); /* add 1 for display */
if (fgets (buf, MAXC, stdin)) { /* read line/validate */
if (sscanf (buf, "%d", &crsno[n]) == 1 &&
lookup (crsno[n])) { /* lookup valid no. ? */
n++; /* only increment array index if valid course */
}
else
fputs ("error: invalid course no.\n", stderr);
}
else {
fputs ("(user canceled input)\n", stderr);
return 1;
}
} while (n < ncrs);
/* you only need 1 printf to output all continous text */
printf ("\nVALENCE COMMUNITY COLLEGE\nORLANDO FL 10101\n"
"---------------------\n\n"
"Fee Invoice Prepared for Student V%d\n\n"
"1 Credit Hour = $120.25\n"
"CRN\tCR_PREFIX\tCR_HOURS\n", sid);
/* loop over array outputting course specifics, summing total */
for (int i = 0; i < ncrs; i++)
total += courseInfo(crsno[i]);
/* output total and prompt to print another */
printf ("\tHealth & id fees\t$ 35.00\n\n"
"--------------------------------------\n"
"\tTotal Payments\t $ %.2f\n\n"
"Would you like to print another invoice (Y/N): "
, total +35.00);
if (!fgets (buf, MAXC, stdin))
break;
} while (tolower (*buf) == 'y');
return 0;
}
/* choose meaningful return type that can indicate success/failure
* and can also return needed information. returns cost of credits,
* or zero indicating failure.
*/
double courseInfo (int myCourse)
{
int credit1 = 0;
double cost = 0.0;
char a[MAXA]; /* don't use magic-number, use a constant */
switch(myCourse)
{
case 4587:
credit1 = 4;
strcpy(a, "MAT 236");
break;
case 4599:
credit1 = 3;
strcpy(a,"COP 220");
break;
case 8997:
credit1 = 1;
strcpy(a, "GOL 124");
break;
case 9696:
credit1 = 5;
strcpy(a, "COP 100");
break;
case 4580:
credit1 = 3;
strcpy(a, "MAT 230");
break;
case 4581:
credit1 = 4;
strcpy(a, "MAT 231");
break;
case 4582:
credit1 = 2;
strcpy(a, "MAT 232");
break;
case 4583:
credit1 = 2;
strcpy(a, "MAT 233");
break;
case 3587:
credit1 = 4;
strcpy(a, "MAT 256");
break;
case 4519:
credit1 = 3;
strcpy(a, "COP 420");
break;
case 6997:
credit1 = 1;
strcpy(a, "GOL 127");
break;
case 9494:
credit1 = 3;
strcpy(a, "COP 101");
break;
default:
printf("Sorry invalid entry!\n\n");
return 0;
}
cost = credit1 * 120.25;
printf ("%.2d\t%s\t\t%d\t\t$ %.2f\n\n",
myCourse, a, credit1 , credit1 * 120.25);
return cost;
}
( примечание: включение ctype.h
для преобразованияY/N
символ вводится в нижнем регистре, поэтому одно сравнение может обработать Yy
для определения, выбрал ли пользователь другой)
Пример использования / Вывод
Недопустимые числабыли намеренно введены для принудительной обработки ошибок в коде.
$ ./bin/courseinv
Please enter Student ID: 1234
Enter how may courses-up to 3: 8997
error: valid no. of courses is 0-3.
Enter how may courses-up to 3: 3
Enter the 3 course number(s)
enter course[1]: 8997
enter course[2]: 4583
enter course[3]: 4519
VALENCE COMMUNITY COLLEGE
ORLANDO FL 10101
---------------------
Fee Invoice Prepared for Student V1234
1 Credit Hour = $120.25
CRN CR_PREFIX CR_HOURS
8997 GOL 124 1 $ 120.25
4583 MAT 233 2 $ 240.50
4519 COP 420 3 $ 360.75
Health & id fees $ 35.00
--------------------------------------
Total Payments $ 756.50
Would you like to print another invoice (Y/N): y
Please enter Student ID: 1235
Enter how may courses-up to 3: 2
Enter the 2 course number(s)
enter course[1]: 10
error: invalid course no.
enter course[1]: 3587
enter course[2]: 6997
VALENCE COMMUNITY COLLEGE
ORLANDO FL 10101
---------------------
Fee Invoice Prepared for Student V1235
1 Credit Hour = $120.25
CRN CR_PREFIX CR_HOURS
3587 MAT 256 4 $ 481.00
6997 GOL 127 1 $ 120.25
Health & id fees $ 35.00
--------------------------------------
Total Payments $ 636.25
Would you like to print another invoice (Y/N): n
Наконец, с любой входной подпрограммой, которую вы пишете - попробуйте и сломайте ее.Если это сломалось, исправьте это и попробуйте снова.Это единственный способ, с помощью которого вы можете обнаружить угловые случаи, которые требуют дополнительной работы (и, может быть, многие из них выше, приведенный выше код не прошел тщательную проверку, но должен справиться с большинством предполагаемых злоупотреблений)
ключ проверить, подтвердить, подтвердить .Если пользователь может сделать что-то глупое с вашим кодом - он это сделает.Защищайте от как можно большего количества злоупотреблений.Дайте мне знать, если у вас есть дополнительные вопросы.