ввод и вывод строки в C - PullRequest
       1

ввод и вывод строки в C

7 голосов
/ 02 февраля 2011

У меня есть этот фрагмент кода:

char* receiveInput(){
    char *s;
    scanf("%s",s);

    return s;
}

int main()
{
    char *str = receiveInput();
    int length = strlen(str);

    printf("Your string is %s, length is %d\n", str, length);

    return 0;
}

Я получаю этот вывод:

Your string is hellàÿ", length is 11

мой ввод был:

helloworld!

Может кто-нибудь объяснитьпочему и почему этот стиль кодирования плох, заранее спасибо

Ответы [ 3 ]

20 голосов
/ 02 февраля 2011

Несколько вопросов касались того, что вы сделали неправильно и как это исправить, но вы также сказали (выделение мое):

может кто-нибудь объяснить, почему, и , почему этот стиль кодирования плохой

Я думаю, scanf - ужасный способ чтения ввода. Он несовместим с printf, позволяет легко забыть проверять ошибки, затрудняет восстановление после ошибок и несовместим с обычными (и их проще делать правильно) операциями чтения (такими как fgets и компания).

Во-первых, обратите внимание, что формат "%s" будет считываться только до тех пор, пока не увидит пробел. Почему пробел? Почему "%s" распечатывает всю строку, но читает в строках с такой ограниченной емкостью?

Если вы хотите читать всю строку, как это часто бывает, scanf предоставляет ... "%[^\n]". Какие? Что это такое? Когда это стало Perl?

Но настоящая проблема в том, что ни один из них не является безопасным. Они оба свободно переполняются без проверки границ. Хотите проверить границы? Хорошо, вы поняли: "%10s""%10[^\n]" начинает выглядеть еще хуже). Это будет читать только 9 символов и автоматически добавлять завершающий nul-символ. Так что это хорошо ... когда размер нашего массива никогда не нужно менять .

Что если мы хотим передать размер нашего массива в качестве аргумента scanf? printf может сделать это:

char string[] = "Hello, world!";
printf("%.*s\n", sizeof string, string); // prints whole message;
printf("%.*s\n", 6, string); // prints just "Hello,"

Хотите сделать то же самое с scanf? Вот как это сделать:

static char tmp[/*bit twiddling to get the log10 of SIZE_MAX plus a few*/];
// if we did the math right we shouldn't need to use snprintf
snprintf(tmp, sizeof tmp, "%%%us", bufsize);
scanf(tmp, buffer);

Это верно - scanf не поддерживает "%.*s" переменную точность printf, поэтому для динамической проверки границ с помощью scanf мы должны построить нашу собственную строку формата в временный буфер. Это все виды плохих, и хотя здесь на самом деле безопасно, это будет выглядеть очень плохой идеей для любого, кто только что зашел.

А пока давайте посмотрим на другой мир. Давайте посмотрим на мир fgets. Вот как мы читаем строку данных с fgets:

fgets(buffer, bufsize, stdin);

Бесконечно меньше головной боли, не тратится впустую процессорное время, преобразующее целочисленную точность в строку, которая будет перечитываться только библиотекой обратно в целое число, и все соответствующие элементы находятся там на одной строке для нас чтобы увидеть, как они работают вместе.

Конечно, это может не прочитать всю строку. Он будет читать всю строку, только если она короче bufsize - 1 символов. Вот как мы можем прочитать всю строку:

char *readline(FILE *file)
{
    size_t size  = 80; // start off small
    size_t curr  = 0;
    char *buffer = malloc(size);
    while(fgets(buffer + curr, size - curr, file))
      {
        if(strchr(buffer + curr, '\n')) return buffer; // success
        curr = size - 1;
        size *= 2;
        char *tmp = realloc(buffer, size);
        if(tmp == NULL) /* handle error */;
        buffer = tmp;
      }
    /* handle error */;
}

Переменная curr - это оптимизация, предотвращающая перепроверку уже прочитанных нами данных, и она не нужна (хотя и полезна, когда мы читаем больше данных). Мы могли бы даже использовать возвращаемое значение strchr, чтобы удалить конечный символ "\n", если хотите.

Также обратите внимание, что size_t size = 80; в качестве отправной точки совершенно произвольно. Мы могли бы использовать 81, или 79, или 100, или добавить его в качестве предоставленного пользователем аргумента функции. Мы могли бы даже добавить аргумент int (*inc)(int) и изменить size *= 2; на size = inc(size);, позволяя пользователю контролировать скорость роста массива. Это может быть полезно для повышения эффективности, когда перераспределение становится дорогостоящим, и необходимо прочитать и обработать множество строк данных.

Мы могли бы написать то же самое с scanf, но подумайте, сколько раз нам пришлось бы переписывать строку формата. Мы могли бы ограничить его постоянным приращением вместо удвоения (легко), реализованного выше, и никогда не пришлось бы корректировать строку формата; мы могли бы дать и просто сохранить число, выполнить математику, как указано выше, и использовать snprintf для преобразования его в строку формата каждый раз, когда мы перераспределяем , чтобы scanf мог преобразовать его обратно в такое же количество; мы могли бы ограничить наш рост и начальную позицию таким образом, чтобы мы могли вручную настроить строку формата (скажем, просто увеличить цифры), но через некоторое время это может стать проблематичным и может потребовать рекурсии (!) для чистой работы.

Кроме того, трудно совмещать чтение с scanf с чтением с другими функциями. Зачем? Скажем, вы хотите прочитать целое число из строки, а затем прочитать строку из следующей строки. Вы попробуйте это:

int i;
char buf[BUSIZE];
scanf("%i", &i);
fgets(buf, BUFSIZE, stdin);

Это будет читать "2", но тогда fgets будет читать пустую строку, потому что scanf не читал новую строку!Хорошо, возьмем два:

...
scanf("%i\n", &i);
...

Вы думаете, что это съедает новую строку, и это делает - но это также пожирает ведущие пробелы на следующей строке, потому что scanf не может определить разницу между новыми строкамии другие формы пробелов.(Кроме того, оказывается, что вы пишете парсер Python, и начальные пробелы в строках важны.) Чтобы это работало, вам нужно вызвать getchar или что-то для чтения в новой строке и выбросить его:

...
scanf("%i", &i);
getchar();
...

Разве это не глупо?Что происходит, если вы используете scanf в функции, но не вызываете getchar, потому что вы не знаете, будет ли следующее чтение scanf или что-то более разумное (или даже следующий символбудет перевод строки)?Внезапно лучший способ справиться с ситуацией, кажется, выбрать один или другой: используем ли мы исключительно scanf и никогда не имеем доступа к входу полного управления в стиле fgets, или мы используем исключительно fgets и делаемсложнее выполнить сложный разбор?

На самом деле, ответ: мы не .Мы используем fgets (или не scanf функции) исключительно, и когда нам нужна scanf -подобная функциональность, мы просто вызываем sscanf для строк! Нам не нужно иметьscanf испортить наши файловые потоки без необходимости!Мы можем иметь точный контроль над нашим вводом, который мы хотим, и все еще получить всю функциональность scanf форматирования.И даже если бы мы не могли, многие опции формата scanf имеют почти прямые соответствующие функции в стандартной библиотеке, такие как бесконечно более гибкие функции strtol и strtod (и друзья).Кроме того, i = strtoumax(str, NULL) для целочисленных типов размера C99 выглядит намного чище, чем scanf("%" SCNuMAX, &i);, и намного безопаснее (мы можем использовать эту строку strtoumax неизменной для меньших типов и позволить неявному преобразованию обрабатывать дополнительные биты, но с scanf мы должны сделать временное uintmax_t для чтения).

Мораль этой истории: избегать scanf.Если вам нужно предоставляемое форматирование, и вы не хотите (или не можете) сделать это (более эффективно) самостоятельно, используйте fgets / sscanf.

11 голосов
/ 02 февраля 2011

scanf не выделяет память для вас.

Вам необходимо выделить память для переменной, переданной scanf.

Вы можете сделать так:

char* receiveInput(){
    char *s = (char*) malloc( 100 );
    scanf("%s",s);
    return s;
}

Но предупреждение:

  1. функция, которая вызывает receiveInput, получит в собственность возвращенную память: вам придется free(str) после ее печати в main.(Передача прав собственности таким способом обычно не считается хорошей практикой).

    Простое решение - получить выделенную память в качестве параметра.

  2. , если вводстрока длиннее 99 (в моем случае) ваша программа будет страдать от переполнения буфера (что и происходит).

    Простое решение - передать scanf длину буфера:

    scanf("%99s",s);
    

Фиксированный код может быть таким:

// s must be of at least 100 chars!!!
char* receiveInput( char *s ){
    scanf("%99s",s);
    return s;
}
int main()
{
    char str[100];
    receiveInput( str );
    int length = strlen(str);

    printf("Your string is %s, length is %d\n", str, length);

    return 0;
}
2 голосов
/ 02 февраля 2011

Вы должны сначала выделить память для вашего объекта в вашем методе receiveInput (). Такие как:

s = (char *)calloc(50, sizeof(char));
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...