как использовать fgets и sscanf для целых чисел в цикле

Новичок с C здесь. Я пытаюсь запустить цикл, в котором строки и ints вводятся в различные поля struct. При запросе «фамилии» пользователь может нажать клавишу ввода без ввода других данных, и цикл должен закончиться.

Проблема в том, что с этим кодом цикл не заканчивается (запросы на ввод фамилии и имени выполняются вместе в одной строке), и значение зарплаты всегда получается неправильным (0 или какое-то большое число)

while (employee_num <= 2)
{
    printf("Enter last name ");
    fgets(employee[employee_num].last_name, sizeof(employee[employee_num].last_name), stdin);                   

    if(strlen(employee[employee_num].last_name) == 0)
        break;

    printf("Enter first name ");
    fgets(employee[employee_num].first_name, sizeof(employee[employee_num].first_name), stdin);

    printf("Enter title ");
    fgets(employee[employee_num].title, sizeof(employee[employee_num].title), stdin);

    printf("Enter salary ");
    fgets(strng_buffer, 1, stdin);
    sscanf(strng_buffer, "%d", &employee[employee_num].salary);     
    ++employee_num;
    getchar();
}

Если вместо этого я попробую этот код, я смогу правильно выйти из цикла после первого его запуска, но не смогу выйти после этого (нажав клавишу ввода в части фамилии - возможно, \n я не могу очистить?):

char strng_buffer[16];
while (employee_num <= 5)
{
    printf("Enter last name ");
    fgets(strng_buffer, sizeof(strng_buffer), stdin);                   
    sscanf(strng_buffer, "%s", employee[employee_num].last_name);       

    if(strlen(employee[employee_num].last_name) == 0)
        break;

    printf("Enter first name ");
    fgets(strng_buffer, sizeof(strng_buffer), stdin);
    sscanf(strng_buffer, "%s", employee[employee_num].first_name);


    printf("Enter title ");
    fgets(strng_buffer, sizeof(strng_buffer), stdin);
    sscanf(strng_buffer, "%s", employee[employee_num].title);

    printf("Enter salary ");
    scanf("%d", &employee[employee_num].salary);        
    ++employee_num;
    getchar();
}

Мне любопытно, как заставить это работать так, как задумано, и что лучше всего подходит для таких записей (например, использование sscanf, fgets и т. д.)

Заранее спасибо!


person Gitarooman    schedule 23.01.2013    source источник


Ответы (3)


Предполагая исправление, упомянутое Абхиджитом, зачем превращать первое во второе? Знаете ли вы, что второй ведет себя иначе, чем первый, из-за добавления sscanf? Если вы намеревались укоротить первое, то второе кажется довольно громоздким. Вместо того, чтобы добавлять sscanf к ситуации, почему бы не сократить первую, объявив struct employee *e = employee + employee_num; и многократно используя ее вместо employee[employee_num]?

Одна из «лучших практик» в отношении fgets — проверить возвращаемое значение. Как вы думаете, что может вернуть fgets, если встретится EOF? Как вы думаете, что вернет fgets в случае успеха?

Одна из «лучших практик» в отношении scanf — проверить возвращаемое значение. Что касается возвращаемого значения scanf, я предлагаю прочитать это scanf руководство. внимательно и ответив на следующие вопросы:

  1. int x = scanf("%d", &employee[employee_num].salary); Как вы думаете, каким будет x, если я введу "fubar\n" в качестве входных данных?
  2. Как вы думаете, куда пойдет 'f' из "fubar\n"?
  3. Если ungetc снова изменится на stdin, какой будет фамилия вашего следующего сотрудника?
  4. int x = scanf("%d", &employee[employee_num].salary); Как вы думаете, каким будет x, если я запущу этот код в Windows и нажму CTRL+Z, чтобы отправить EOF на stdin?
  5. int x = scanf("%d %d", &y, &z); Что вы ожидаете от x, предполагая, что scanf успешно помещает значения в две переменные y и z?

P.S. EOF можно отправить через stdin в Windows с помощью CTRL+Z, а в Linux и других — с помощью CTRL+D, в дополнение к использованию конвейеров и перенаправлению для перенаправления ввода из других программ и файлов.

person autistic    schedule 23.01.2013
comment
Спасибо! Работа над этими вопросами дала мне гораздо лучшее понимание того, что происходит при использовании scanf(). Поправьте меня, если я ошибаюсь, но тогда я мог бы записать стандартный ввод в массив и прочитать значения int обратно в переменную, используя sscanf()? - person Gitarooman; 23.01.2013
comment
Да, fgets и sscanf будут работать таким образом. Имейте в виду, что спецификатор формата %s указывает sscanf скопировать слово (например, ноль или более непробельных символов до первого пробела). Что происходит с остальной частью вашей линии fgets? Это один из способов, которым ваш второй пост отличается от вашего первого. - person autistic; 24.01.2013

Цикл прерывается преждевременно, когда он сталкивается с оператором break.

if(strlen(strng_buffer) == 0)
        break;

Буфер неинициализированных символов strng_buffer по совпадению имеет нуль в качестве первого символа, из-за чего strlen возвращает 0

Я полагаю, вы, возможно, намеревались

if(strlen(employee[employee_num].last_name) == 0)
            break;

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

person Abhijit    schedule 23.01.2013
comment
Вы правы (я немного устал). Я сделал редактирование, показывающее больше кода из обеих моих попыток. Я до сих пор не могу заставить ни один из них работать должным образом. Кто-то упомянул об использовании fgets(), а затем sscanf() для считывания зарплаты в части int, но мне не удалось найти какой-либо четкий способ сделать это в моих поисках - действительно ли это жизнеспособный вариант? - person Gitarooman; 23.01.2013

Проблема в том, что fgets возвращает строку с включенным разрывом строки (\n). Таким образом, даже если пользователь нажмет клавишу возврата без ввода информации, строка не будет пустой. Кроме того, размер вашего буфера для salary слишком мал.

Итак, либо вы убираете \n из каждой fgets, либо меняете свой чек на:

if(strlen(employee[employee_num].last_name) == 1) break;

Кроме того, когда вы получаете буфер, измените 1 на что-то большее, например

fgets(strng_buffer, 10, stdin);

Однако, если вы хотите удалить \n из каждого файла fget, вы можете сделать что-то вроде:

employee[employee_num].last_name[strlen(employee[employee_num].last_name)-1] = 0;

Вы можете сделать это для каждой строки или, что еще лучше, создать функцию, которая это сделает.

РЕДАКТИРОВАТЬ: если вы можете гарантировать, что пользователь будет нажимать ввод после каждого ввода, вы можете смело предположить это. Однако, если это не всегда так, возможно, что последний символ не \n, и простое удаление таким образом может вызвать проблемы.

person pldoverflow    schedule 23.01.2013
comment
fgets не всегда возвращает строку с разрывом строки. Я предлагаю e-›last_name[strcspn(e-›last_name, \n)] = '\0'; для обработки случаев, когда '\n' отсутствует, поскольку ваше предложение отбрасывает полезный символ, создавая ошибку. - person autistic; 23.01.2013
comment
Да, но я сосредоточился на его ситуации, в которой ожидается, что пользователь нажмет Enter (и предположим, что не будет EOF). Просто вычитание 1 опасно, так как strlen может вернуть 0. Я обновлю комментарий, чтобы отметить все это. - person pldoverflow; 23.01.2013
comment
char strng_buffer[10]; fgets(strng_buffer, 10, стандартный ввод); А как насчет того, когда fgets считывает 9 байтов, прежде чем достигает '\n'? - person autistic; 23.01.2013