БлогNot. C++: Односвязный список в консольном приложении

C++: Односвязный список в консольном приложении

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

В начале работы программы список пуст, так что надо добавить записи с клавиатуры. Проверено в Visual C++ 2013, работает.

#define _CRT_SECURE_NO_WARNINGS
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <clocale>
#include <windows.h>
using namespace std;
#define MAX 30
static int max_id;

struct student {
 int id;
 unsigned char f[MAX];
 int ekz[4];
 student* next;
};

int show(student* head) {
 int count = 0;
 if (!head) return 0;
 while (1) {
  //вывести данные очередного узла
  printf("\n%d. %s", head->id, head->f);
  for (int i = 0; i < 4; i++)  printf(" %d", head->ekz[i]);
  count++;
  //проверка на конец списка
  if (head->next == NULL) break;
  //переход к следующему узлу
  head = head->next;
 }
 printf("\nAll=%d", count);
 return count;
}

student* search(student* head, char* st) {
 if (head == NULL) return NULL; //список пуст
 student* next = head;
 st = _strlwr((char*)st); //чтобы поиск не зависел от регистра символов
 do {
  char* find = strstr(_strlwr((char*)next->f), st);
  if (find != NULL) return next;
  if (next->next == NULL) break;
  next = next->next;
 } while (1);
 return NULL; //не найдено
}

void copy0(student* to, student* from) {
 //to->id = from->id;
 strcpy((char*)to->f, (char*)from->f);
 for (int i = 0; i < 4; i++) to->ekz[i] = from->ekz[i];
}

student* add1(student* head, student* st) {
 student* current = (student*)malloc(sizeof(student));
 current->id = ++max_id;
 copy0(current, st);
 if (head == NULL) current->next = NULL;
 else {
  current->next = head;
  head = current;
 }
 return current;
}

student* add2(student* head, student* st) {
 student* last = NULL;
 if (head) {
  last = head;
  while (last->next) last = last->next;
 }
 student* current = (student*)malloc(sizeof(student));
 current->id = ++max_id;
 copy0(current, st);
 current->next = NULL;
 if (last) last->next = current;
 return current;
}

int check(student* head, char* s) {
 while (head) {
  if (_stricmp((char*)head->f, s) == 0) return 1;
  head = head->next;
 }
 return 0;
}

student* sort(student* ph) {
 student* q, *out = NULL, *p, *pr; //out - выход - сначала пуст
 while (ph != NULL) { //пока не конец входного списка
  q = ph; ph = ph->next; //исключить очередной элемент
  for (p = out, pr = NULL;
   p != NULL && _stricmp((char*)q->f, (char*)p->f) > 0;
   pr = p, p = p->next);
  //ищем, куда включить очередной элемент - тут stricmp
  //задает критерий сравнения элементов, в вашей задаче м.б. другой
  if (pr == NULL) { q->next = out; out = q; } //включение в начало
  else { q->next = p; pr->next = q; } //или после предыдущего
 }
 return out;
}

student* exclude(student* head, char* f) {
 if (head == NULL) return NULL;
 student* current = head, //текущая запись
  *start = head, //начало списка
  *prev = NULL; //предыдущая запись
 while (current) {
  if (_stricmp((char*)current->f, f) == 0) {
   if (prev) prev->next = current->next;
   else start = current->next;
   free(current);
   break;
  }
  prev = current;
  current = current->next;
 }
 return start;
} //в коде выше заменил strlwr => _strlwr, strstr => _strstr

void enter(student *st) { //Ввод записи с клавиатуры в буферную запись
 printf("\nEnter the name and 4 balls:");
 scanf("%s %d %d %d %d", st->f, &st->ekz[0], &st->ekz[1],
  &st->ekz[2], &st->ekz[3]);
 //st->id = ++max_id;
}

void save(student* head) {
 if (!head) {
  printf("\nNotning to write"); return;
 }
 FILE* f = fopen("data.dat", "wb");
 if (!f) {
  printf("\nCan't open data.dat to write"); return;
 }
 do {
  fwrite(head, sizeof(student), 1, f);
  head = head->next;
  if (!head) break;
 } while (1);
 fclose(f);
}

int main() { //Меню и вызов функций
 setlocale(LC_ALL, ".1251"); SetConsoleCP(1251); SetConsoleOutputCP(1251);
 student* head = NULL; student* s; char buf[40]; FILE* f; 
 student st = {0, "Anonymous", {5,5,5,5}, nullptr }; //Буферная запись для ввода
 int k;
 do {
  printf("\n1. Add in head\n2. Add in tail\n3. Show all\n\
4. Sorting\n5. Delete\n6. Save to file\n7. Load from file\n8. Search\n0. Exit\n");
  fflush(stdin); int n; scanf("%d", &n);
  student* ptr; char searchbuf[MAX];
  switch (n) {
  case 1: enter(&st); head = add1(head, &st); break;
  case 2: enter(&st); if (head) add2(head, &st); else head = add1(head, &st);  break;
  case 3: show(head); break;
  case 4: head = sort(head); break;
  case 5:
   printf("\nName for deleting:");
   scanf("%s", buf);
   s = search(head, buf);
   if (!s) printf("\nNot found"); else head = exclude(head, buf);
   break;
  case 6: save(head); break;
  case 7:
   f = fopen("data.dat", "rb");
   if (!f) {
    printf("\nCan't open data.dat to read"); break;
   }
   if (head) do { //удалить имеющийся список
    head = exclude(head, (char*)head->f);
   } while (head);
   while (1) { //загрузить данные из файла
    k = fread(&st, sizeof(student), 1, f);
    if (k != 1) break;
    st.next = NULL;
    if (st.id > max_id) max_id = st.id;
    if (head) add2(head, &st);
    else head = add1(head, &st);
    if (feof(f)) break;
   }
   fclose(f);
   break;
  case 8:
   ptr = head;
   printf("\nEnter search string: ");
   fflush(stdin);
   scanf("%s", searchbuf);
   do { //возможность неоднократного поиска
    ptr = search(ptr, searchbuf);
    if (ptr) {
     printf("\n%s", ptr->f); //лучше - вызвать метод для вывода записи
     ptr = ptr->next; //следующий поиск - со след.эл.-та
    }
   } while (ptr);

   break;
  case 0: exit(0);
  }
 } while (1);
 return 0;
}

P.S. С выходом Visual Studio 2017 и выше приведённый код разумнее считать устаревшим. Сформулируем задачу более полно и решим её в современных версиях Visual Studio 2019 (2022).

Для заданной структуры сформировать динамический односвязный список. Обязательные элементы приложения:

  • В структуре есть поле первичного ключа.
  • Реализован просмотр всего списка.
  • Реализовано добавление элементов в начало или в конец списка или по возрастанию ключевого поля.
  • Реализовано удаление элементов, выбранных по первичному ключу.
  • При необходимости, реализована правка элемента списка, выбранного по первичному ключу.
  • Данные хранятся в текстовом или бинарном файле.
  • Реализован интерфейс приложения с выбором команд.

Для хранения строкового поля используем std::string, что позволит нам в естественном виде сравнивать и складывать строки, но потребует внимательности при записи в бинарный файл и чтении из него (просто записать составной объект std::string оператором write нельзя).

На основе этого шаблона легко реализовать свой вариант задания, изменив описание структурного типа и методы print, search, copy0, check, sort, enter, read, save и организацию меню в функции main.

/*
 Реализация лабораторной работы с помощью динамического односвязного списка
 структур фиксированного размера
*/
#define _CRT_SECURE_NO_WARNINGS /* _strlwr, strcpy в Studio */
#include <iostream>
#include <fstream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <cctype>
#include <clocale>
#include <climits>
#include <windows.h>
using namespace std;

static const int MAX = 30; //максимальная длина фамилии
static int max_id; //максимальный идентификатор в БД
#define SEARCH_ID 1 /* режимы поиска */
#define SEARCH_NAME 2
#define SEARCH_BALL 4

struct student {
 int id;
 string f;
 int ekz[4];
 student* next;
};

void print(student* cur) { //Вывод одной записи
 cout << endl << cur->id << ". " << cur->f;
 for (int i = 0; i < 4; i++)  cout << " " << cur->ekz[i];
}

int show(student* head) { //Вывод записей, начиная с head
 int count = 0;
 if (!head) return 0;
 while (1) {
  print(head); //вывести данные очередного узла
  count++;
  if (head->next == NULL) break; //проверка на конец списка
  head = head->next;   //переход к следующему узлу
 }
 cout << endl << "Всего: " << count;
 return count;
}

student* search(student* head, const unsigned char* st, int mode = SEARCH_NAME) {
 //Поиск в любом поле
 if (head == NULL) return NULL; //список пуст
 student* next = head;
 st = (unsigned char*)_strlwr((char*)st); //чтобы поиск не зависел от регистра символов
 string tmp;
 do {
  tmp.clear();
  if (mode & SEARCH_ID) tmp += to_string(next->id) + " ";
  if (mode & SEARCH_NAME) tmp += next->f + " ";
  if (mode & SEARCH_BALL)
   for (int i = 0; i < 4; i++) tmp += to_string(next->ekz[i]) + (i < 3 ? " " : "");
  std::transform(tmp.begin(), tmp.end(), tmp.begin(),
   [](unsigned char c) { return std::tolower(c); }); //std::string в нижний регистр
  std::size_t find = tmp.find((char*)st);
  if (find != std::string::npos) return next;
  if (next->next == NULL) break;
  next = next->next;
 } while (1);
 return NULL; //не найдено
}

void copy0(student* to, student* from) { //Копирование одной записи
 to->id = from->id;
 to->f = from->f;
 for (int i = 0; i < 4; i++) to->ekz[i] = from->ekz[i];
}

student* add1(student* head, student* st) { 
 //Добавление записи st в начало списка head
 student* current = new student;
 if (!current) {
  cout << endl << "Нет памяти!"; return NULL;
 }
 current->id = ++max_id;
 copy0(current, st);
 if (head == NULL) current->next = NULL;
 else {
  current->next = head;
  head = current;
 }
 return current;
}

student* add2(student* head, student* st) {
 //Добавление записи st в конец списка, начинающегося с head
 student* last = NULL;
 if (head) {
  last = head;
  while (last->next) last = last->next;
 }
 student* current = new student;
 if (!current) {
  cout << endl << "Нет памяти!"; return NULL;
 }
 current->id = ++max_id;
 copy0(current, st);
 current->next = NULL;
 if (last) last->next = current;
 return current;
}

int check(student* head, char* s) { 
 //1, если фамилия s есть в списке head
 while (head) {
  if (head->f == s) return 1;
  head = head->next;
 }
 return 0;
}

student* sort(student* ph) {
 //Сортировка методом вставок с перестановкой только указателей
 student* q, * out = NULL, * p, * pr; //out - выход - сначала пуст
 while (ph != NULL) { //пока не конец входного списка
  q = ph; ph = ph->next; //исключить очередной элемент
  for (p = out, pr = NULL;
   p != NULL && q->f > p->f; //сравнение!
   pr = p, p = p->next);
  //ищем, куда включить очередной элемент - тут сравнение строк
  //задает критерий сравнения элементов, в вашей задаче м.б. другой
  if (pr == NULL) { q->next = out; out = q; } //включение в начало
  else { q->next = p; pr->next = q; } //или после предыдущего
 }
 return out;
}

student* exclude(student* head, int id) {
 //Удаление записи по id
 if (head == NULL) return NULL;
 student *current = head, //текущая запись
  *start = head, //начало списка
  *prev = NULL; //предыдущая запись
 while (current) {
  if (current->id == id) {
   if (prev) prev->next = current->next;
   else start = current->next;
   delete current;
   break;
  }
  prev = current;
  current = current->next;
 }
 return start;
}

int enter_int(const unsigned char* msg, int min = INT_MIN, int max = INT_MAX, int num = 0) {
 //Ввод целого со значением от min до max и приглашением msg, номер num опционален
 int d;
 while (1) {
  cout << endl << msg;
  if (num) cout << " " << num;
  if (min != INT_MIN || max != INT_MAX) cout << " [" << min << "-" << max << "]";
  cout << ": ";
  if (cin >> d && d >= min && d <= max) {
   return d;
  }
  else {
   cin.clear();
  }
  cin.ignore(INT_MAX, '\n');
 }
}

string enter_string(const unsigned char* msg, int min = 1, int max = 79, int num = 0) {
 //Ввод строки с длиной от min до max символов и приглашением msg, номер num опционален
 string tmp;
 bool clear = true;
 while (1) {
  cout << endl << msg;
  if (num) cout << " " << num;
  cout << " [" << min << "-" << max << "]: ";
  if (clear) {
   cin.clear();
   cin.ignore(INT_MAX, '\n');
  }
  getline(cin, tmp);
  if (tmp.length() >= min && tmp.length() <= max) return tmp;
  else clear = false;
  tmp.clear();
 }
}

void enter(student* st) { 
 //Ввод одной записи с клавиатуры в буферную запись
 string tmp = enter_string((unsigned char*)"Enter the name", 2, MAX);
 st->f = tmp;
 for (int i = 0; i < 4; i++)
  st->ekz[i] = enter_int((unsigned char*)"Enter ball", 2, 5, i + 1);
 st->id = ++max_id;
}

student* read(student* head) {
 //Чтение списка из файла data.dat
 string tmp;
 student st = { 0, tmp, {5,5,5,5}, NULL }; //Буферная запись для ввода
 unsigned char buf[MAX + 1]; //Буфер для чтения строк
 student* start = head;
 ifstream f("data.dat");
 if (!f) {
  cout << endl << "Не могу открыть файл data.dat для чтения"; 
  return start;
 }
 if (head) do { //удалить имеющийся список
  head = exclude(head, head->id);
 } while (head);
 int cnt = 1;
 while (1) { //загрузить данные из файла
  f.read((char*)&st.id, sizeof(int));
  if (f.eof()) break;
  if (!f) { 
   cout << endl << "Не могу прочитать id #" << cnt << " из data.dat"; 
   return start; 
  }
  f.read((char*)&buf[0], MAX);
  if (!f) { 
   cout << endl << "Не могу прочитать имя #" << cnt << " из data.dat"; 
   return start; 
  }
  st.f = string((char*)buf);
  f.read((char*)&st.ekz[0], 4 * sizeof(int));
  if (!f) { 
   cout << endl << "Не могу прочитать баллы #" << cnt << " из data.dat"; 
   return start; 
  }
  st.next = NULL;
  if (st.id > max_id) max_id = st.id;
  if (head) add2(head, &st);
  else head = add1(head, &st);
  cnt++;
  if (f.eof()) break;
 }
 f.close();
 return head;
}

void save(student* head) {
 //Запись списка в файл data.dat
 unsigned char buf[MAX + 1]; //Буфер для записи строк
 if (!head) {
  cout << endl << "Нечего записывать"; return;
 }
 ofstream f("data.dat");
 if (!f) {
  cout << endl << "Не могу открыть data.dat для записи"; 
  return;
 }
 do {
  f.write((char*)&head->id, sizeof(int));
  strcpy((char*)buf, head->f.c_str());
  f.write((char*)&buf[0], MAX);
  f.write((char*)&head->ekz[0], 4 * sizeof(int));
  head = head->next;
  if (!head) break;
 } while (1);
 f.close();
}

int main() { //Меню и вызов функций
 string tmp;
 student st = { 0, tmp, {5,5,5,5}, NULL }; //Буферная запись для ввода
 student* head = NULL, * ptr;
 int n, id;
 setlocale(LC_ALL, ".1251"); SetConsoleCP(1251); SetConsoleOutputCP(1251);

 do {
  cout << endl << 
   "1. Добавить в начало\n" <<
   "2. Добавить в конец\n" <<
   "3. Показать все\n" <<
   "4. Сортировать по имени\n" <<
   "5. Удалить по id\n" <<
   "6. Сохранить в файл\n" <<
   "7. Загрузить из файла\n" <<
   "8. Поиск\n" <<
   "0. Выход";
  n = enter_int((const unsigned char*)"Выберите действие", 0, 8);
  switch (n) {
   case 1: /* добавить в начало */
    enter(&st);
    if (check(head, (char*)st.f.c_str())) //Фамилия уже есть
     cout << endl << "Имя уже существует, не добавлено";
    else
     head = add1(head, &st);
   break;
   case 2: /* добавить в конец */
    enter(&st);
    if (check(head, (char*)st.f.c_str())) //Фамилия уже есть
     cout << endl << "Имя уже существует, не добавлено";
    else {
     if (head) add2(head, &st);
     else head = add1(head, &st);
    }
   break;
   case 3: /* показать всё */
    if (!head) { cout << endl << "Список пуст"; continue; }
    show(head);
   break;
   case 4: /* сортировать */
    if (!head) { cout << endl << "Список пуст"; continue; }
    head = sort(head);
   break;
   case 5: /* удалить */
    if (!head) { cout << endl << "Список пуст"; continue; }
    id = enter_int((unsigned char*)"Id для удаления", 1, max(max_id, 1));
    tmp = std::to_string(id);
    ptr = search(head, (unsigned char*)tmp.c_str(), SEARCH_ID);
    if (!ptr) cout << endl << "Запись не найдена";
    else head = exclude(head, id);
   break;
   case 6: /* сохранить в файл */
    save(head);
   break;
   case 7: /* прочитать из файла */
    head = read(head);
   break;
   case 8: /* поиск */
    if (!head) { cout << endl << "Список пуст"; continue; }
    ptr = head; id = 0;
    tmp = enter_string((unsigned char*)"Введите строку для поиска", 1, MAX);
    do { //возможность неоднократного поиска
     ptr = search(ptr, (unsigned char*)tmp.c_str(), SEARCH_ID | SEARCH_NAME | SEARCH_BALL);
     if (ptr) {
      id++;
      print(ptr);
      ptr = ptr->next; //следующий поиск - со след.эл.-та
     }
    } while (ptr);
    if (!id) cout << endl << "Ничего не найдено";
   break;
   case 0: /* выход */
    exit(0);
   default: /* введено неверное n */
   break;
  }
 } while (1);
 return 0;
}

07.03.2015, 13:27 [19947 просмотров]


теги: c++ алгоритм studio

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