Как мне прочитать строку, введенную пользователем в C?
Я хочу прочитать имя, введенное моим пользователем с помощью программ на Си.
Для этого я написал:
char name[20];
printf("Enter name: ");
gets(name);
Но используя gets
не хорошо, так что лучше?
8 ответов
Вы никогда не должны использовать gets
(или же scanf
с неограниченным размером строки), так как это открывает вас для переполнения буфера. Использовать fgets
с stdin
обрабатывать, так как это позволяет вам ограничить данные, которые будут помещены в ваш буфер.
Вот небольшой фрагмент, который я использую для ввода строки от пользователя:
#include <stdio.h>
#include <string.h>
#define OK 0
#define NO_INPUT 1
#define TOO_LONG 2
static int getLine (char *prmpt, char *buff, size_t sz) {
int ch, extra;
// Get line with buffer overrun protection.
if (prmpt != NULL) {
printf ("%s", prmpt);
fflush (stdout);
}
if (fgets (buff, sz, stdin) == NULL)
return NO_INPUT;
// If it was too long, there'll be no newline. In that case, we flush
// to end of line so that excess doesn't affect the next call.
if (buff[strlen(buff)-1] != '\n') {
extra = 0;
while (((ch = getchar()) != '\n') && (ch != EOF))
extra = 1;
return (extra == 1) ? TOO_LONG : OK;
}
// Otherwise remove newline and give string back to caller.
buff[strlen(buff)-1] = '\0';
return OK;
}
Это позволяет мне установить максимальный размер, обнаружит, что в строку введено слишком много данных, а также очистит остальную часть строки, чтобы это не повлияло на следующую операцию ввода.
Вы можете проверить это с чем-то вроде:
// Test program for getLine().
int main (void) {
int rc;
char buff[10];
rc = getLine ("Enter string> ", buff, sizeof(buff));
if (rc == NO_INPUT) {
// Extra NL since my system doesn't output that on EOF.
printf ("\nNo input\n");
return 1;
}
if (rc == TOO_LONG) {
printf ("Input too long [%s]\n", buff);
return 1;
}
printf ("OK [%s]\n", buff);
return 0;
}
Я думаю, что лучший и самый безопасный способ чтения строк, введенных пользователем, использует getline()
Вот пример, как это сделать:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
char *buffer = NULL;
int read;
unsigned int len;
read = getline(&buffer, &len, stdin);
if (-1 != read)
puts(buffer);
else
printf("No line read...\n");
printf("Size read: %d\n Len: %d\n", read, len);
free(buffer);
return 0;
}
В системе POSIX вы, вероятно, должны использовать getline
если это доступно.
Вы также можете использовать общественное достояние Чака Фальконера ggets
функция, которая обеспечивает синтаксис ближе к gets
но без проблем. (Веб-сайт Чака Фальконера больше недоступен, хотя у archive.org есть копия, и я создал свою собственную страницу для ggets.)
Я нашел простое и приятное решение:
char*string_acquire(char*s,int size,FILE*stream){
int i;
fgets(s,size,stream);
i=strlen(s)-1;
if(s[i]!='\n') while(getchar()!='\n');
if(s[i]=='\n') s[i]='\0';
return s;
}
он основан на fgets, но свободен от '\n' и дополнительных символов stdin (вместо fflush(stdin), который не работает на всех ОС, полезно, если вам нужно получить строки после этого).
С помощью scanf
удаление любых пробелов перед вводом строки и ограничение количества считываемых символов:
#define SIZE 100
....
char str[SIZE];
scanf(" %99[^\n]", str);
/* Or even you can do it like this */
scanf(" %99[a-zA-Z0-9 ]", str);
Если вы не ограничиваете количество символов для чтения с помощью scanf
это может быть так же опасно, как gets
На системах BSD и Android вы также можете использовать fgetln
:
#include <stdio.h>
char *
fgetln(FILE *stream, size_t *len);
Вот так:
size_t line_len;
const char *line = fgetln(stdin, &line_len);
line
не имеет нулевого завершения и содержит \n
(или то, что ваша платформа использует) в конце. Он становится недействительным после следующей операции ввода-вывода в потоке. Вы можете изменить возвращенный line
буфер.
Вы можете использовать функцию scanf для чтения строки
scanf("%[^\n]",name);
я не знаю других лучших вариантов получения строки,
ANSI C неизвестное решение максимальной длины
Йоханнеса Шауба. Просто скопируйте из https://stackoverflow.com/a/314422/895245
Не забудьтеfree
возвращаемый указатель, как только вы закончите с ним.
char * getline(void) {
char * line = malloc(100), * linep = line;
size_t lenmax = 100, len = lenmax;
int c;
if(line == NULL)
return NULL;
for(;;) {
c = fgetc(stdin);
if(c == EOF)
break;
if(--len == 0) {
len = lenmax;
char * linen = realloc(linep, lenmax *= 2);
if(linen == NULL) {
free(linep);
return NULL;
}
line = linen + (line - linep);
linep = linen;
}
if((*line++ = c) == '\n')
break;
}
*line = '\0';
return linep;
}
Этот код используетmalloc
выделить 100 символов. Затем он извлекает символ за символом от пользователя. Если пользователь достигает 101 символа, он удваивает буфер до 200. Когда достигается 201, он снова удваивается до 400 и так далее, пока память не переполнится.
Причина, по которой мы удваиваем, скажем, просто добавляем 100 дополнительных каждый раз, заключается в том, что увеличение размера буфера может привести к копии старого буфера, что является потенциально дорогостоящей операцией.
Массивы должны быть непрерывными в памяти, потому что мы хотим иметь возможность эффективного произвольного доступа к ним по адресу памяти. Следовательно, если бы у нас было в оперативной памяти:
content buffer[0] | buffer[1] | ... | buffer[99] | empty | empty | int i
RAM address 1000 | 1001 | | 1100 | 1101 | 1102 | 1103
мы не сможем просто увеличить размерbuffer
, так как это перезапишет нашint i
. Такrealloc
потребуется найти другое место в памяти, в котором есть 200 свободных байтов, а затем скопировать туда старые 100 байт и освободить 100 старых байтов.
Удваивая, а не добавляя, мы быстро достигаем порядка величины текущего размера строки, поскольку экспоненциальные числа растут очень быстро, поэтому выполняется только разумное количество копий.