Nickolay.info. Алгоритмы. Работаем с составными типами данных C++

Напишем небольшое учебное приложение, которое будет использовать основные составные типы данных, которые есть в C++ - структуру, в том числе, с битовыми полями, объединение union, перечисление enum.

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

Для социального статуса естественно использовать перечисление - не определён, иждивенец, учащийся, работающий, пенсионер:

typedef enum status { undefined, dependent, pupil, working, pensioner };

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

char *get_status (status s) {
 char *status_strings[] = { "undefined", "dependent", "pupil", "working", "pensioner" };
 return status_strings[s];
}

Паспорт опишем отдельной структурой, для 4-значной серии хватит обычного типа int, а вот 6-значный номер потребует уже long int:

typedef struct passport {
 unsigned int series;
 unsigned long int number;
};

Чтобы проиллюстрировать работу с битовыми полями, будем хранить дату упакованной по битам в отдельной структуре. Для дня месяца (возможные значения 1-31) отведём старшие 5 бит, следующие 4 бита будут хранить номер месяца от 1 до 12, младшие 7 бит останутся для года - структура с битовыми полями должна занимать целое число байт, мы ограничимся двумя. В 7 бит "влезет" всего 128 значений лет (28 = 128), поэтому поступим как разработчики Unix - "началом отсчёта" сделаем 1970-й год, который примем за ноль. Тогда нам хватит значений дат до 2098 года.

typedef struct date {
 unsigned int day :5;
 unsigned int month :4;
 unsigned int year :7; //Год 1970=0...до 2098
};

Теперь пригодится и union - возможно, в разных контекстах нам придётся работать со структурой date и как с обычным числом (например, при записи в базу данных или чтении из неё) и как с отдельными значениями "день-месяц-год". Сделать этот процесс удобным позволит вот такое маленькое объединение:

union udate {
 date d;
 unsigned int u;
};

Теперь всё готово для того, чтоб описать основную структуру. Она будет состоять сплошь из подчинённых структур, если не считать указателя на char для хранения фамилии:

typedef struct person {
 char *name; //если сразу name[30] - не пришлось бы выделять память под name
 status status;
 passport passport;
 date date;
};

Служебная функция error будет печатать нужное сообщение msg и ждать нажатия Enter, чтоб выйти из программы:

void error (char *msg) {
 puts ("\n"); puts (msg); puts ("\nEnter to EXIT");
 fflush (stdin); getchar(); exit(0);
}

А весь остальной текст программы расположим прямо в функции main, вот она, с комментариями всех действий:

void main () {
 const int maxrecords=5; //Максимальное число записей
 person p[maxrecords]; //Массив структур - наши записи о людях
 char buf[128],name[40]; //Буфер для чтения строки базы и отдельно - имени, т.к. память будет выделяться
 FILE *f=fopen ("data.txt","r+t"); //"Базой" будет текстовый файл data.txt из текущей папки
 if (f==NULL) error("Can't open data.txt"); //Если не удалось открыть - выходим
 int count=0; //Счётчик записей
 while (1) { //Так лучше при чтении тектового файла под Windows без Ctrl+Z в конце
  fgets (buf,128,f); //Читаем сразу строку
  sscanf (buf,"%s %d %d %ld %u",name,&p[count].status,&p[count].passport.series,&p[count].passport.number,
   &p[count].date); //И разбираем её по полям структуры
  p[count].name = new char[strlen(name)+1]; //Выделяем память под поле "имя"
  if (p[count].name==NULL) { //Если не удалось выделить память - выходим
   sprintf (buf,"No memory for rec. no. %d",count+1); error (buf); 
  }
  strcpy(p[count].name,name); //Переписываем в поле "имя" из буфера
  count++;
  if (feof(f) || count>maxrecords-1) break; //Если кончились записи или достигнут предел - прервать
 }
 //Допишем в файл 1 запись, если нет последней записи с именем Newname
 if (count && strcmp(p[count-1].name,"Newname")) {
  udate d = { 22, 1, 2013-1970 };
  fseek (f, 0, SEEK_END);
  fprintf (f, "\n%s %d %d %ld %u","Newname",1,5004,456789,d.u);
 }
 fclose (f);
 //Напечатать прочитанный из файла массив записей p
 for (int i=0; i<count; i++)
  printf ("\n%d. %s, %s, %04u %06lu, %02d.%02d.%d",
   i+1, p[i].name, get_status(p[i].status),p[i].passport.series,p[i].passport.number,p[i].date.day, 
   p[i].date.month,p[i].date.year+1970);
 error ("");
}

Файл data.txt может быть, например, таким:

Ivanov 0 5014 123456 21745
Petrov 1 5102 432567 23111

Я лично вижу в этой программе один недостаток - мы ввели собственный формат (пресловутую структуру с битовыми полями), но не предусмотрели функций для конвертирования формата во что-нибудь более удобоваримое :) Так что рискуем получить, к примеру, 21-е число 14-го месяца, если передадим в базу какое-нибудь неподходящее для разбора на битовые поля целое число.

Поэтому напишем небольшую утилиту, которая решит эту проблему с помощью методов date_to_int и int_to_date. Для проверки используем 1 января 2013 года, она же число 2219. Вывод текста в консоль сделаем, для разнообразия, через библиотеку iostream.

#include <iostream.h>

typedef struct date {
 unsigned int day :5;
 unsigned int month :4;
 unsigned int year :7;
};

unsigned int date_to_int (date d) {
 return (d.day<<11)|(d.month<<7)|(d.year & 0x007F);
}

date int_to_date (unsigned int n) {
 date d;
 d.day=(n&0xF800)>>11;
 d.month=(n&0x0780)>>7;
 d.year=n&0x007F;
 return d;
}

void main () {
 date d = { 1, 1, 2013-1970 };
 cout << "\n" << date_to_int(d);
 d=int_to_date (2219);
 cout << "\n" << d.day << "." << d.month << "." << d.year+1970;
 cin.get();
}

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

 Скачать архив ZIP с файлами из этой статьи (2 Кб)

Рейтинг@Mail.ru

вверх гостевая; E-mail