Я пишу программу, которая принимает пользовательский ввод данных о человеке, который состоит из имени, идентификатора и номера телефона. Я ограничиваю свой идентификатор и номер телефона максимум 14 и 13 символами соответственно (исключая символ новой строки) - например. 010203-1234567
для ID и 010-1111-2222
для номера телефона. По праву, если я правильно понял, fgets
должен автоматически добавлять байт новой строки после того, как он читает строку из пользовательского ввода, а fputs
должен записывать новую строку в конце строки, хранящейся в id
и phoneNum
, но при попытке ввести больше символов, чем разрешено, напр. 010203-12345678
для идентификатора следующий fputs
не выводит новую строку, как следует. Как я могу убедиться, что даже если пользователь вводит больше символов, чем разрешено, новая строка записывается fputs
?
Ниже приведен мой код:
#include <stdio.h>
void FlushInputBuf(void)
{
while (getchar() != '\n');
}
int main(void)
{
FILE *fp = fopen("details.txt", "wt");
if (fp == NULL)
{
puts("File open failed.\n");
return -1;
}
char name[20];
char id[15];
char phoneNum[14];
printf("Enter name: ");
fgets(name, sizeof(name), stdin);
printf("Enter ID: ");
fgets(id, sizeof(id), stdin);
FlushInputBuf();
printf("Enter phone number: ");
fgets(phoneNum, sizeof(phoneNum), stdin);
fputs("#Name: ", fp);
fputs(name, fp);
fputs("#SSID: ", fp);
fputs(id, fp);
fputs("#Phone number: ", fp);
fputs(phoneNum, fp);
fclose(fp);
return 0;
}
Я предполагаю, что ввод для имени не будет длиннее 20.
Когда я ввожу идентификатор как 010203-1234567
и номер телефона как 010-1111-2222
, я получаю следующий результат в файле details.txt:
#Name: John
#SSID: 010203-1234567#Phone number: 010-1111-2222
Как мне получить код для записи в details.txt следующим образом:
#Name: John
#SSID: 010203-1234567
#Phone number: 010-1111-2222
char id[15];
может содержать 14 символов и 0 терминатора. Там нет места для новой строки. Обычно вы все равно удаляете новую строку из любой строки, прочитанной fgets
, а затем форматируете вывод, предполагая, что ее там нет.
fgets()
не «автоматически добавляет символ новой строки» — если он читает новую строку до того, как закончится место для хранения данных, он будет включать новую строку, которую он прочитал, но если места недостаточно, он прекратит чтение, когда места не осталось — возможно, во вводе осталась новая строка (возможно, с другими непрочитанными символами перед концом строки).
Что произойдет, когда getchar()
вернется EOF
?
По праву, если я правильно понял, fgets должен автоматически добавлять символ новой строки после того, как он читает строку из пользовательского ввода.
Нет. fgets()
будет копировать символы в буфер до тех пор, пока не закончится место (при этом резервируя место для разделителя строки) или пока не будет скопирована новая строка. Он не добавляет никакой новой строки, которой еще не было во входных данных, и (поэтому) он не может гарантировать наличие новой строки в конце строки. Часто он есть, но если ваш буфер слишком мал для всей строки, то его не будет. Это основной способ узнать, есть ли у вас полная линия.
fputs должен записывать символ новой строки в конце строки, хранящейся в id и phoneNum.
Да, fputs
запишет все новые строки, появляющиеся в указанной строке. То, что ни один из них не печатается, является убедительным доказательством того, что ни один из них не считывается в ваш буфер id
(и phoneNum
).
при попытке ввести больше символов, чем разрешено, например. «010203-12345678» для идентификатора, следующие fputs не записывают новую строку, как следует.
Он не печатает новую строку, как вы ожидали. Но он делает именно то, что должен.
Ваш массив id
имеет длину 15 char
, что достаточно для 14 символов идентификатора и одного нулевого терминатора. Соответственно, fgets
прекращает чтение после копирования 14-го символа, «7», и завершает строку последним байтом буфера. Затем ваша функция FlushInputBuf()
читает и отбрасывает конец строки, включая следующую новую строку. Новая строка не сохраняется. То же самое произойдет и с записью, состоящей ровно из 14 символов. И если введенный идентификатор короче этого, то вызов FlushInputBuf()
заставит вас ввести вторую новую строку, прежде чем он запросит номер телефона (и отбросит все, что вы наберете до этого).
Хорошим способом сделать это было бы
Обеспечьте буфер, по крайней мере, достаточно длинный, чтобы вместить ожидаемый ввод, а также новую строку и признак конца строки.
#define MAX_ID_LENGTH 14
// ...
char id[MAX_ID_LENGTH + 2];
Иногда полезно позволить себе немного больше, чем вы на самом деле ожидаете.
После вызова fgets()
проверьте наличие новой строки в буфере. Используйте хвост входной строки, только если он есть, то есть если новая строка еще не была прочитана.
size_t newline_pos = strcspn(id, "\n");
if (id[newline_pos] != '\0') {
assert(id[newline_pos] == '\n');
FlushInputBuf();
}
Подтвердите или, по крайней мере, исправьте ввод. В частности, удалите символ новой строки, если он есть, так как он фактически не является частью идентификатора.
id[newline_pos] = '\0';
Отклоняйте или усекайте слишком длинные идентификаторы.
// maybe:
id[MAX_ID_LENGTH] = '\0';
Выполните любые другие необходимые проверки.
Печатайте новые строки на выходе вручную, где вы хотите. В вашем конкретном случае я бы использовал один вызов fprintf()
вместо каждой пары вызовов fputs()
с правильным размещением новых строк в форматах.
fprintf(fp, "#SSID: %s\n", id);
Но если вы должны использовать только fputs()
, вы можете вместо этого добавить fputs("\n", fp);
.
Если бы вы не беспокоились о последствиях коротких записей (а вы должны были бы), то этого шага было бы достаточно.
В дополнение к отличному ответу @JohnBollinger, вот модифицированная версия вашего кода, использующая вспомогательную функцию для контролируемого пользовательского ввода:
#include <errno.h>
#include <stdio.h>
#include <string.h>
int input_string(const char *prompt, char *dest, int size) {
int c; // byte read from stdin
int i = 0; // index into dest
int n = 0; // number of bytes read excluding the newline
printf("%s", prompt);
/* ensure prompt is visible (necessary on some older systems) */
fflush(stdout);
/* read all bytes from the user input line */
while ((c = getchar()) != EOF && c != '\n') {
if (i + 1 < size) {
/* append the character to dest, space permitting */
dest[i++] = (char)c;
}
n++;
}
if (size > 0) {
// set the null terminator
dest[i] = '\0';
}
if (n >= size) {
printf("discarded %d extra characters\n", n - size - 1);
}
if (c == EOF) {
if (i == 0)
printf("premature end of file\n");
return EOF;
} else {
printf("missing newline at end of file\n");
}
}
/* return the number of characters stored into dest */
return i;
}
int main(void) {
FILE *fp = fopen("details.txt", "w");
if (fp == NULL) {
fprintf(stderr, "Failed to open %s: %s\n", "details.txt",
strerror(errno));
return 1;
}
char name[20];
char id[15];
char phoneNum[14];
if (input_string("Enter name: ", name, sizeof(name)) < 0
|| input_string("Enter ID: ", id, sizeof(id)) < 0
|| input_string("Enter phone number: ", phoneNum, sizeof(phoneNum)) < 0) {
fprintf(stderr, "Missing input\n");
fclose(fp);
return 1;
}
fprintf(fp, "#Name: %s\n", name);
fprintf(fp, "#SSID: %s\n", id);
fprintf(fp, "#Phone number: %s\n", phoneNum);
fclose(fp);
return 0;
}
Последним символом будет \0. И он перезапишет \n ввода. только что сделал идентификатор на 1 символ больше.