БлогNot. С++: пишем пример на структуру, битовые поля, перечисление и объединение

Помощь дата->рейтинг Поиск Почта RSS канал Статистика nickolay.info Домой

С++: пишем пример на структуру, битовые поля, перечисление и объединение

Теорию о составных типах данных в C/C++ можно почитать здесь, а заметка преследует скромную цель разработки законченного приложения-примера, в котором "органично" использовались бы все основные структурные типы - собственно структура с демонстрацией вложения других структурных типов, структура с битовыми полями, перечисление, объединение.

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

Начнём определять типы для нашей будущей структуры. "Социальный статус" здесь прямо-таки напрашивается стать перечислением:

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

Логично, что "неопределённый" статус будет соответствовать нулевому значению, а остальные статусы упорядочены по возрасту.

К сожалению, enum всегда интерпретируется как int и "строковое" значение именованной константы вывести просто так нельзя. Чтобы это всё-таки сделать, нам придётся написать дополнительную функцию, в которой те же самые социальные статусы будут перечислены в строковом массиве:

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

Заметим, что при нарушении "естественного" порядка нумерации 0,1,2,... в перечислении придётся изменить и логику этой функции.

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

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

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

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

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

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

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

Наконец, структурный тип "персона" будет объединять введённые ранее типы данных:

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

Все наши ключевые слова "typedef", вообще говоря, на C++ можно и не писать, определение структурного типа происходит автоматически. Но мы всё равно напишем для сохранения совместимости с классическим Си.

Код потребует подключения следующих стандартных библиотек:

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

Напишем функцию-обработчик ошибок, принимающую параметр "сообщение об ошибке" (msg):

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 = { 2013-1970, 12, 31 };
  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 1 5004 096345 10000
Petrov 2 5001 233245 20000
Sidorov 0 5111 233456 30000

Нашему решению не хватает возможности преобразования "сжатых" дат в числа и обратно, напишем соответствующее решение отдельной демо-программой, функции date_to_int и int_to_date дают нужные преобразования формата:

#include <iostream>
using namespace std;

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

unsigned short int date_to_int (date d) {
 return ((d.year<<9)|(d.month<<5)|(d.day&0x001F));
}

date int_to_date (unsigned short int n) {
 date d;
 d.year=(n&0xFE00)>>9;
 d.month=(n&0x01E0)>>5;
 d.day=n&0x001F;
 return d;
}

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

 Скачать файлы примера в арихве .zip (2 Кб)


теги: программирование форматы c++

10.11.2013, 16:23; рейтинг: 11013

  свежие записипоиск по блогукомментариистатистика

Наверх Яндекс.Метрика
© PerS
вход