Вот основной скелет моего кода:
int getSentence(SearchResults* input){
printf("Enter sentence:");
fgets(input->sentence, 100, stdin);
printf("Enter message ID:");
fgets(input->messageID, 20, stdin);
return 1;
}
Как есть, у этого есть пара проблем. Во-первых, когда я печатаю строку позже, в конце у них есть символ новой строки, чего я не хочу. Во-вторых, если я введу более 100 или 20 символов, эти символы будут переполнены при следующем вызове fgets.
Я нашел «решения» в Интернете, но все они либо создают новые проблемы, либо не рекомендуются к использованию. Большая часть того, что я нашел, суммирована в этом сообщении SO: Как правильно очистить стандартный ввод в цикле fgets.
*(strstr(input->sentence, "\n")) = '\0';
для первого выпуска, но это не помогает со вторымwhile ((getchar()) != '\n')
(и варианты) — это обычно рекомендуемое решение для обеих проблем, но оно вводит новую проблему: для моего следующего ввода требуется два ввода, прежде чем программа продолжит работу. Я попытался добавить fputc('\n', stdin);
после цикла while, и это решает все три проблемы, но только когда ввод превышает ограничение на количество символов. Если я пишу предложение длиной менее 100 символов, мне нужно дважды нажать Enter, чтобы программа продолжила работу.setbuf(stdin, NULL)
- одно из приведенных решений, но в одном из комментариев говорится, что «возиться с setbuf еще хуже [чем с fflush]»В общем, вы не хотите, чтобы fgets переполнялся. Буфер, который вы ему даете, должен быть удобно больше, чем самый большой ввод, который вы ожидаете. Переполнение должно быть действительно исключительным событием. Но вы должны решить, как вы хотите с этим справиться: (а) проигнорировать возможность, (б) отбросить слишком длинный ввод, (в) обработать слишком длинный ввод. fgets предполагает, что вы, возможно, захотите сделать (c), поэтому он оставляет слишком длинный ввод в буфере.
Поэтому, если вы хотите сделать (b), вам нужен цикл очистки ввода, но только если fgets не читал всю строку, то есть только если в возвращаемом буфере не было новой строки.
Если fgets не имеет \n в конце, значит, вы не получили всю строку. Если у него есть \n в конце, значит, вы это сделали, и вы можете удалить его. fflush(stdin) это неопределенное поведение geeksforgeeks.org/use-fflushstdin-c
Здесь, в SO, есть несколько вопросов, подробно описывающих различные приемы для эффективного решения этой ситуации во всех ее вариантах. Но если все это слишком хлопотно, вы можете вместо этого использовать getline.
И это объясняет, почему fgets() автоматически не удаляет новую строку. Его присутствие передает важную информацию: что входная строка использована до конца.
Спасибо за информацию @SteveSummit. Мне нужно использовать c99 для моего задания, поэтому getline, к сожалению, не сработает, есть ли что-нибудь подобное, что я мог бы использовать? Если нет, вот решение, которое я придумал на основе предоставленной вами информации, есть ли более элегантный способ сделать это? fgets(input->sentence, 100, stdin); int temp = strcspn(target, "\n"); if (temp == chars - 1) while ((getchar()) != '\n'); else target[temp] = '\0';
@Frostbiyt: Строка while ((getchar()) != '\n'); не идеальна, потому что она может создать бесконечный цикл, если getchar() по какой-то причине вернет EOF. Вот почему я также проверяю EOF в своем ответе на ваш вопрос. Функция getchar может возвращать EOF, если во входном потоке возникает ошибка или достигается конец файла. Конец файла может быть достигнут, например, если ввод передается из файла или если пользователь нажимает специальную комбинацию клавиш (которая зависит от используемой операционной системы), чтобы сигнализировать о конце файла.
@Frostbiyt: В коде, который вы разместили в своем предыдущем комментарии, вы говорите fgets написать input->sentence, но затем вы используете strcspn на target. Это не имеет смысла, если только выражения input->sentence и target не указывают на одну и ту же строку.
Я переместил код в новую функцию в своей программе, но хотел, чтобы мой комментарий соответствовал моему вопросу, но я пропустил изменение второго target на input->sentence, я отредактирую. Или нет, есть ограничение по времени на редактирование комментария?
@Frostbiyt: Комментарии можно редактировать только через 5 минут после публикации. Это ограничение по времени распространяется только на комментарии.
Frostbiyt, данный ввод может быть больше, чем хотелось бы, что вы хотите сделать с этим дополнительным вводом, и хотите ли вы, чтобы ваша функция ввода предупреждала вас о том, что дополнительный ввод существует и выбрасывается?
Я рекомендую вам создать собственную функцию, которая вызывает fgets, проверяет, была ли прочитана вся строка, и удаляет символ новой строки. Если функция не работает из-за слишком длинного ввода, вы можете распечатать сообщение об ошибке, отбросить оставшуюся часть строки и повторно запросить пользователя. Вот функция, которую я написал для этой цели некоторое время назад:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//This function will read exactly one line of input from the
//user. It will remove the newline character, if it exists. If
//the line is too long to fit in the buffer, then the function
//will automatically reprompt the user for input. On failure,
//the function will never return, but will print an error
//message and call "exit" instead.
void get_line_from_user( const char prompt[], char buffer[], int buffer_size )
{
for (;;) //infinite loop, equivalent to while(1)
{
char *p;
//prompt user for input
fputs( prompt, stdout );
//attempt to read one line of input
if ( fgets( buffer, buffer_size, stdin ) == NULL )
{
printf( "Error reading from input!\n" );
exit( EXIT_FAILURE );
}
//attempt to find newline character
p = strchr( buffer, '\n' );
//make sure that entire line was read in (i.e. that
//the buffer was not too small to store the entire line)
if ( p == NULL )
{
int c;
//a missing newline character is ok if the next
//character is a newline character or if we have
//reached end-of-file (for example if the input is
//being piped from a file or if the user enters
//end-of-file in the terminal itself)
if ( !feof(stdin) && (c=getchar()) != '\n' )
{
printf( "Input was too long to fit in buffer!\n" );
//discard remainder of line
do
{
if ( c == EOF )
{
printf( "Error reading from input!\n" );
exit( EXIT_FAILURE );
}
c = getchar();
} while ( c != '\n' );
//reprompt user for input by restarting loop
continue;
}
}
else
{
//remove newline character by overwriting it with
//null character
*p = '\0';
}
//input was ok, so break out of loop
break;
}
}
Вот демонстрационная программа, в которой я применяю свою функцию к вашему коду:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//forward declaration
void get_line_from_user( const char prompt[], char buffer[], int buffer_size );
int main( void )
{
char sentence[100];
char messageID[20];
get_line_from_user( "Enter sentence: ", sentence, sizeof sentence );
get_line_from_user( "Enter message ID: ", messageID, sizeof messageID );
printf(
"\n"
"The following input has been successfully read:\n"
"sentence: %s\n"
"message ID: %s\n",
sentence, messageID
);
}
//This function will read exactly one line of input from the
//user. It will remove the newline character, if it exists. If
//the line is too long to fit in the buffer, then the function
//will automatically reprompt the user for input. On failure,
//the function will never return, but will print an error
//message and call "exit" instead.
void get_line_from_user( const char prompt[], char buffer[], int buffer_size )
{
for (;;) //infinite loop, equivalent to while(1)
{
char *p;
//prompt user for input
fputs( prompt, stdout );
//attempt to read one line of input
if ( fgets( buffer, buffer_size, stdin ) == NULL )
{
printf( "Error reading from input!\n" );
exit( EXIT_FAILURE );
}
//attempt to find newline character
p = strchr( buffer, '\n' );
//make sure that entire line was read in (i.e. that
//the buffer was not too small to store the entire line)
if ( p == NULL )
{
int c;
//a missing newline character is ok if the next
//character is a newline character or if we have
//reached end-of-file (for example if the input is
//being piped from a file or if the user enters
//end-of-file in the terminal itself)
if ( !feof(stdin) && (c=getchar()) != '\n' )
{
printf( "Input was too long to fit in buffer!\n" );
//discard remainder of line
do
{
if ( c == EOF )
{
printf( "Error reading from input!\n" );
exit( EXIT_FAILURE );
}
c = getchar();
} while ( c != '\n' );
//reprompt user for input by restarting loop
continue;
}
}
else
{
//remove newline character by overwriting it with
//null character
*p = '\0';
}
//input was ok, so break out of loop
break;
}
}
Эта программа имеет следующее поведение:
Enter sentence: This is a test sentence.
Enter message ID: This is another test sentence that is longer than 20 characters and therefore too long.
Input was too long to fit in buffer!
Enter message ID: This is shorter.
The following input has been successfully read:
sentence: This is a test sentence.
message ID: This is shorter.
Ваша get_line_from_user() определенно одна из лучших my_getline() процедур. Не поклонник int size против size_t и всей печати и exit() в коде, но все же в целом хороший поток функций. УФ
@chux: Причина, по которой моя функция использует int вместо size_t, заключается в том, что это также то, что использует fgets. Если бы вместо этого я использовал size_t, то не смог бы просто перенаправить аргумент функции на fgets.
@chux: Что не так со «всей печатью» и моим призывом к exit? Считаете ли вы, что было бы лучше, если бы я использовал возвращаемое значение функции для обозначения ошибки? Или вы имеете в виду что-то другое?
Да, лучше указать ошибку. Вспомогательные функции не должны заканчиваться кодом.
Многие fgets() используют константу, переданную в размере, и многие из них size_t соответствуют диапазону int. fgets() использование размера int, если это недостаток дизайна - нам не нужно его продолжать. Но в любом случае это не главная проблема. Ваша обработка входных данных здесь - хорошая часть.
Вот что я использую для решения своей проблемы, спасибо Steve Summit и Andreas Wenzel за их комментарии.
int getSentence(SearchResults* input){
printf("Enter sentence:");
fgets(input->sentence, 100, stdin);
int temp = strcspn(input->sentence, "\n");
if (temp < 100 - 1) input->sentence[temp] = '\0';
else while ((temp = getchar()) != '\n' && temp != EOF);
printf("Enter message ID:");
fgets(input->messageID, 20, stdin);
temp = strcspn(input->messageID, "\n");
if (temp < 20 - 1) input->messageID[temp] = '\0';
else while ((temp = getchar()) != '\n' && temp != EOF);
return 1;
}
Обновлено: Для всех, у кого есть такая же проблема, как у меня, см. Комментарии ниже для дополнительных проблем, которые вам могут понадобиться, если вы используете это решение, например, fgets возвращает NULL.
Не видя определения .sentence и .messageID, это решение выглядит сомнительным.
@chux моя фактическая реализация немного отличается. Я определил длину предложения и идентификатора в заголовочном файле и использую его вместо 20 и 100 как в этой функции, так и в определении структуры, поэтому размер всегда будет совпадать, если это вас беспокоит.
Как правило, рекомендуется проверить возвращаемое значение fgets, прежде чем использовать результат fgets. Например, если пользователь сообщает о конце файла в пустой строке, нажимая CTRL-D в Linux или CTRL-Z в Windows, то fgets вернет NULL и не будет записывать в буфер памяти строку с завершающим нулем. Однако ваш вызов функции strcspn требует, чтобы этот буфер памяти заканчивался нулем. Это может привести к сбою вашей программы. Но эта возможная проблема, вероятно, не имеет отношения к вашему варианту использования.
@Frostbiyt У этого подхода есть недостатки. Лучше в качестве вспомогательной функции, предложенной Андреасом Венцелем.
Удаление новой строки немного неприятно. См. Удаление завершающего символа новой строки из ввода fgets() для канонических методов.