БлогNot. Про fgets и fflush(stdin) в Studio 2015

Про fgets и fflush(stdin) в Studio 2015

Обычные проблемы с "пропущенными" элементами ввода при последовательном использовании нескольких fgets (gets) или scanf (scanf_s) с шаблонами %s и %c для ввода с консоли нескольких строк подряд:

const int SIZE = 30; //размер текстовых полей
struct person { //описание структуры
 char f[SIZE]; //фамилия
 char d[SIZE]; //должность
 float zp, pr; //зарплата, премия
 char prof; //профсоюз
 person *next; //указатель на следующего в списке
};
//...
person vvesti() { //ввод элемента с клавиатуры
 person p;
 printf("\nFIO?"); 
 fgets(p.f, SIZE-1, stdin);
 p.f[strlen(p.f) - 1] = '\0';
 printf("\nDolgnost?"); fgets(p.d, SIZE-1, stdin);
 p.d[strlen(p.d) - 1] = '\0';
 printf("\nZP?"); scanf_s("%f", &p.zp);
 printf("\nPremia?"); scanf_s("%f", &p.pr);
 printf("\nProf (+/-)?"); scanf_s("%c", &p.prof);
 return p;
}

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

 
 1. Show all
 2. Add in head
 3. Add in tail
 4. Delete by number
 5. Sort by name
 6. Edit by number
 7. Search by name part
 0. Exit 2

FIO?
Dolgnost?_

Классические fflush (stdin);, поставленные перед fgets, могут помочь в Студии-2010 но не 2015. Опытным путём нетрудно выяснить, что в 2015 рулит только

scanf_s("%*c");

перед fgets или scanf_s с шаблонами %s, %c.

Но общий рецепт остаётся прежним - предпочитать в консольных программах на C++ cin/cout и getline, то есть, средства C++, а не C-совместимые.

Проблема состоит в том, что scanf читает ввод только до переноса строки не включительно, а перенос строки остаётся в буфере. Именно его может прочитать следующая за scanf функция fgets (мы пользовались scanf_s, когда вводили номер пункта меню в программе).

Функция fgets читает буфер до переноса строки включительно (и этот перенос остаётся в прочитанной строке, кстати, его нужно оттуда удалять).

Получается, что в буфере у находится \n, и fgets только его и считывает, ситуация типовая.

Приводимая программка показывает, как можно "поставить костыли" при работе из Studio 2015 со scanf_s и fgets (gets не используйте никогда, даже если среда разрешает, так как gets не получает явно максимальный размер буфера). Листинг также подойдёт как образец выполнения задания по теме "Динамические структуры". Само задание может быть составлено, например, так:

Запись о заработной плате имеет вид: фамилия сотрудника, должность, оклад, премия, членство в профсоюзе.
Описать структуру в соответствии с вариантом задания.
Сформировать динамический односвязный список структур (или другой динамический контейнер, если вариант задания предполагает это).
Обязательные элементы приложения:

  • Реализован просмотр всего списка;
  • Реализовано добавление элементов в начало или в конец списка или по возрастанию некоторого поля. Обосновать выбор именно такого добавления элементов;
  • Реализовано удаление элементов, выбранных по первичному ключу или номеру записи;
  • Реализована правка элемента списка, выбранного по первичному ключу или номеру записи.

#include <conio.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

const int SIZE = 30; //размер текстовых полей
struct person { //описание структуры
 char f[SIZE]; //фамилия
 char d[SIZE]; //должность
 float zp, pr; //зарплата, премия
 char prof; //профсоюз
 person *next; //указатель на следующего в списке
};

int show(person *head) { //показ всего списка с определением количества элементов
 int count = 0;
 if (head) while (1) {
  printf("\n%s %s %.2f+%.2f %c", head->f, head->d, head->zp, head->pr, head->prof);
  count++;
  if (head->next == nullptr) break;
  head = head->next;
 }
 printf("\nAll=%d", count);
 return count;
}

person *search(person *head, char *st) { //поиск в списке строки st
 if (head == nullptr) return nullptr;
 person *next = head;
 do {
  char *find = strstr(next->f, st);
  if (find != nullptr) return next;
  if (next->next == nullptr) break;
  next = next->next;
 } while (1);
 return nullptr;
}

void copy1(person *to, person *from) { //копирование одной записи
 strcpy_s(to->f, from->f);
 strcpy_s(to->d, from->d);
 to->zp = from->zp; to->pr = from->pr; to->prof = from->prof;
}

person *add1(person *head, person *st) { //добавление элемента st в начало списка
                                         //*st уже заполнен данными
 person *current = new person;
 copy1(current, st); //копирует 1 запись
 if (head == nullptr) current->next = nullptr;
 else {
  current->next = head;
  head = current;
 }
 return current;
}

person *add2(person *head, person *st) { //добавление элемента st в конец списка
 person *last = nullptr;
 if (head != nullptr) {
  last = head;
  while (last->next != nullptr) last = last->next;
 }
 person *current = new person;
 copy1(current, st);
 current->next = nullptr;
 if (last) last->next = current;
 return current;
}

person *delete1(person *head0, int n) { 
 //Удалит элемент номер n и вернет новый указатель на начало списка
 person *head = head0;
 if (head == nullptr) return nullptr;
 if (n == 1) { //удаляем первый
  person *ptr = head->next;
  delete head;
  return ptr;
 }
 person *prev = nullptr, *start = head; int i = 1;
 while (i<n) { //Ищем n-ый элемент
  prev = head; head = head->next;
  if (head == nullptr) return start;
  i++;
 }
 person *ptr = head->next;
 delete head;
 prev->next = ptr;
 return start;
}

person *sort(person *ph) { //сортировка списка методом вставок
 person *q, *p, *pr, *out = nullptr;
 while (ph != nullptr) {
  q = ph; ph = ph->next; //исключить эл-т
                         //ищем, куда его включить:
  for (p = out, pr = nullptr; p != nullptr &&
   strcmp(q->f, p->f)>0; pr = p, p = p->next);
   //или ваш критерий, когда переставлять!
   if (pr == nullptr) { q->next = out; out = q; } //в нач.
   else { q->next = p; pr->next = q; } //после пред.
 }
 return out;
}

person vvesti() { //ввод элемента с клавиатуры
 person p;
 printf("\nFIO?"); 
 scanf_s("%*c"); /* Под Studio 2015 только это очищает буфер
  клавиатуры, в отличие от стандартного fflush(stdin)
  Применять перед fgets или scanf_s с шаблонами %s, %c 
  Но лучше использовать cin/cout и getline() */
 fgets(p.f, SIZE-1, stdin);
 p.f[strlen(p.f) - 1] = '\0';
 printf("\nDolgnost?"); scanf_s("%*c"); fgets(p.d, SIZE-1, stdin);
 p.d[strlen(p.d) - 1] = '\0';
 printf("\nZP?"); scanf_s("%f", &p.zp);
 printf("\nPremia?"); scanf_s("%f", &p.pr);
 printf("\nProf (+/-)?"); scanf_s("%*c"); scanf_s("%c", &p.prof);
 return p;
}

int edit(person *head, int n) {
 if (head == nullptr) return 1;
 int i = 1;
 while (i < n) { head = head->next; i++; }
 person temp = vvesti(); copy1(head, &temp); return 0;
}

int main() { //меню и главная программа
 person *head = nullptr; person p; person *cur;
 int c; int n, all=0;
 while (1) { 
  printf("\n 1. Show all \n 2. Add in head \
           \n 3. Add in tail \n 4. Delete by number \
	       \n 5. Sort by name \n 6. Edit by number \
        \n 7. Search by name part \n 0. Exit ");
  scanf_s("%d", &c);
  switch (c) {
  case 1: show(head);  break;
  case 2: p = vvesti(); head = add1(head, &p);  break;
  case 3: p = vvesti(); cur = add2(head, &p);
   if (head == nullptr) head = cur;  break;
  case 4: 
   all = show(head);
   if (all) {
    while (1) {
     printf("\nVvedi nomer (1-%d): ", all);
     scanf_s("%d", &n); if (n >= 1 && n <= all) break;
    }
    head = delete1(head, n);
   }
   else printf("\nNo items!");
   break;
  case 5: head = sort(head);  break;
  case 6: 
   all = show(head);
   if (all) {
    while (1) {
     printf("\nVvedi nomer (1-%d): ", all);
     scanf_s("%d", &n); if (n >= 1 && n <= all) break;
    }
    edit(head, n);
   }
   else printf("\nNo items!");
  break;
  case 7: 
   char str[SIZE];
   printf("\nFIO or part for search?"); 
   scanf_s("%*c"); fgets(str, SIZE-1, stdin);
   if (strlen(str) > 1) str[strlen(str) - 1] = '\0';
   cur = head; n = 0;
   do { //возможность неоднократного поиска
    cur = search(cur, str);
    if (cur) {
     printf("\n%s", cur->f); n++;
     cur = cur->next; //следующий поиск - со след.эл.-та
    }
   } while (cur);
   if (!n) printf("\nNot found!"); else printf("\n%d item(s)",n);
  break;
  case 0: exit(0); break;
  } 
 } 
 return 0; 
}

Примеры функций для ввода с проверкой ограничений числа и строки в современных версиях Studio лучше найти здесь.

10.02.2017, 16:02 [4767 просмотров]


теги: c++ учебное ошибка studio

К этой статье пока нет комментариев, Ваш будет первым