Каталог статей

Главная » Статьи » C/С++

Структуры с меняющимися размерами данных
В разных форумах по программированию на C++ регулярно задаются вопросы: как записать структуру с текстовыми полями в бинарный файл, а затем прочитать её оттуда? Типичной ошибкой для начинающих программистов является попытка записать напрямую в файл структуру, содержащую указатели на текстовые строки. В итоге в файл записываются лишь значения указателей, но не сами строки.

Возьмём в качестве примера небольшую структуру, в которой используются классы CString для текстовых данных:
 
typedef struct _CUSTOM_DATA
{
  CString strName; // Имя
  CString strFamily; // Фамилия
  CString strAddress; // Адрес
  CString strCompany; // Компания
  CString strMailBox; // Почта
} CUSTOM_DATA, *LPCUSTOM_DATA;

В таком виде структура удобна для обмена данными внутри приложения, например: ввод информации через графический интерфейс, запись в таблицу СУБД, вывод в отчётный документ или экспорт в текстовый файл, и т.д. Но для записи в бинарный файл такая структура неприемлема, поскольку класс CString содержит лишь указатель на символьный массив, а сам текст находится вне класса. Тогда каким же образом текстовые данные можно записать из данной структуры в бинарный файл, а затем прочитать их из файла обратно в структуру? Для этого используются так называемые структуры с меняющимся размером данных, у которых фиксированный размер имеет лишь начало структуры (заголовок), а за ним располагаются сами данные в определённой последовательности.

Выглядит структура так:
typedef struct _CUSTOM_DATA_PERSIST
{
  DWORD dwDataSize;
  WORD wOffsetName;
  WORD wOffsetFamily;
  WORD wOffsetAddress;
  WORD wOffsetCompany;
  WORD wOffsetMailBox;
  TCHAR tcStrData[1];
} CUSTOM_DATA_PERSIST, *LPCUSTOM_DATA_PERSIST;

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

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

Вот как это выглядит в коде на C++:
#include "stdafx.h"
#include <stdio.h>
   
typedef struct _CUSTOM_DATA
{
  CString strName; // Имя
  CString strFamily; // Фамилия
  CString strAddress; // Адрес
  CString strCompany; // Компания
  CString strMailBox; // Почта
} CUSTOM_DATA, *LPCUSTOM_DATA;
   
typedef struct _CUSTOM_DATA_PERSIST
{
  DWORD dwDataSize;
  WORD wOffsetName;
  WORD wOffsetFamily;
  WORD wOffsetAddress;
  WORD wOffsetCompany;
  WORD wOffsetMailBox;
  TCHAR tcStrData[1];
} CUSTOM_DATA_PERSIST, *LPCUSTOM_DATA_PERSIST;
   
int SaveDataToFile(LPCUSTOM_DATA data, LPCTSTR szFile)
{
  ATLASSERT (data != NULL);
  CUSTOM_DATA_PERSIST pers;
  pers.wOffsetName = 0;
  pers.wOffsetFamily = pers.wOffsetName + ::lstrlen(data->strName) + 1;
  pers.wOffsetAddress = pers.wOffsetFamily + ::lstrlen(data->strFamily) + 1;
  pers.wOffsetCompany = pers.wOffsetAddress + ::lstrlen(data->strAddress) + 1;
  pers.wOffsetMailBox = pers.wOffsetCompany + ::lstrlen(data->strCompany) + 1;
  DWORD dwStrLen = pers.wOffsetMailBox + ::lstrlen(data->strMailBox);
  pers.dwDataSize = dwStrLen * sizeof(TCHAR) + sizeof(pers);
  // Создание массива и заполнение его данными...
  LPCUSTOM_DATA_PERSIST lpDataPers = 
  (LPCUSTOM_DATA_PERSIST)(new BYTE[pers.dwDataSize]);
  *lpDataPers = pers;
  ::lstrcpy(lpDataPers->tcStrData + pers.wOffsetName, data->strName);
  ::lstrcpy(lpDataPers->tcStrData + pers.wOffsetFamily, data->strFamily);
  ::lstrcpy(lpDataPers->tcStrData + pers.wOffsetAddress, data->strAddress);
  ::lstrcpy(lpDataPers->tcStrData + pers.wOffsetCompany, data->strCompany);
  ::lstrcpy(lpDataPers->tcStrData + pers.wOffsetMailBox, data->strMailBox);
  // Запись массива в файл...
  int nResult(0);
  FILE *file(NULL);
  _tfopen_s(&file, szFile, _T("wb"));
  if (file != NULL)
  {
  fwrite(lpDataPers, 1, pers.dwDataSize, file);
  fclose(file);
  } else
  {
  nResult = -1;
  }
  delete lpDataPers;
  //
  return nResult;
}
   
int LoadDataFromFile(LPCUSTOM_DATA data, LPCTSTR szFile)
{
  ATLASSERT (data != NULL);
  CUSTOM_DATA_PERSIST pers;
  //
  int nResult(0);
  FILE *file(NULL);
  _tfopen_s(&file, szFile, _T("rb"));
  if (file != NULL)
  {
  // Считываем заголовок структуры и создаём массив...
  fread(&pers, sizeof(pers), 1, file);
  LPCUSTOM_DATA_PERSIST lpDataPers = 
  (LPCUSTOM_DATA_PERSIST)(new BYTE[pers.dwDataSize]);
  *lpDataPers = pers;
  // Потом дочитываем остальные данные...
  fread((LPBYTE)lpDataPers + sizeof(pers), 1,
  pers.dwDataSize - sizeof(pers), file);
  fclose(file);
  // Передача данных в другую структуру...
  data->strName = lpDataPers->tcStrData + lpDataPers->wOffsetName;
  data->strFamily = lpDataPers->tcStrData + lpDataPers->wOffsetFamily;
  data->strAddress = lpDataPers->tcStrData + lpDataPers->wOffsetAddress;
  data->strCompany = lpDataPers->tcStrData + lpDataPers->wOffsetCompany;
  data->strMailBox = lpDataPers->tcStrData + lpDataPers->wOffsetMailBox;
  //
  delete lpDataPers;
  } else
  {
  nResult = -1;
  }
  return nResult;
}
   
// Для вывода текста на консоль...
BOOL PrintText(LPCTSTR szText);
   
// Применение функций записи в файл и чтения из файла,
// отображение полученных данных...
int _tmain(int argc, _TCHAR* argv[])
{  
  // Инициализация структуры...
  CUSTOM_DATA data = 
  {
  _T("Виталий Сергеевич"), 
  _T("Рычков"), 
  _T("129329, г. Москва, ул Кольская, д. 11"), 
  _T("WinMain &Co Ltd"), 
  _T("rychkov@inbox.ru")
  };
   
  LPCTSTR szFile = _T("WinMain.bin");
   
  // Запись данных в файл...
  SaveDataToFile(&data, szFile);
   
  // Чтение данных из файла...
  CUSTOM_DATA dataNew;
  LoadDataFromFile(&dataNew, szFile);
   
  // Вывод данных на консоль...
  PrintText(dataNew.strFamily); 
  _puttc('\n', stdout);
  PrintText(dataNew.strName); 
  _puttc('\n', stdout);
  PrintText(dataNew.strAddress); 
  _puttc('\n', stdout);
  PrintText(dataNew.strCompany);
  _puttc('\n', stdout);
  PrintText(dataNew.strMailBox); 
  _puttc('\n', stdout);
   
  // Ожидание...
  _gettc(stdin);
   
  return 0;
}
   
BOOL PrintText(LPCTSTR szText)
{
  static HANDLE hConsole = ::GetStdHandle(STD_OUTPUT_HANDLE);
  // Вывод текста на консоль...
  DWORD dw(0);
  return ::WriteConsole(hConsole, szText, ::lstrlen(szText), &dw, NULL);
}

Представленный пример выполнен в среде Visual C++ 2005. Для его повторения нужно с помощью <визарда> создать проект приложения Win32 Console, включив опцию поддержки ATL.

Категория: C/С++ | Добавил: virusik (03.07.2009)
Просмотров: 1342 | Комментарии: 2 | Рейтинг: 0.0/0 |
Всего комментариев: 0
Имя *:
Email *:
Код *:
вход на сайт
Категории раздела
C/С++ [24]
статьи о языке C/С++
Visual FoxPro [1]
информация о языке Visual FoxPro
.NET [24]
статьи о языке программирования .NET
ваши статьи [4]
здесь вы можете размещать сваи статьи
модинг [4]
статьи о модинге ПК
Поиск по сайту
наши опросы
каким браузерам вы пользуетесь
Всего ответов: 21
Мини-чат
помощь проекту
помоги проекту
ЯндексЯндекс. ДеньгиХочу такую же кнопку
Друзья сайта
  • Официальный блог
  • Сообщество uCoz
  • FAQ по системе
  • Инструкции для uCoz
  • Каталог http://www.internetmir.ru Лучшие сайты интернета в каталоге Intenetmir
  • Delphi.int.ru - Сообщество программистов: общение, помощь, обмен опытом.
    реклама 88х31
    Каталог сайтов. Раскрутка. Хостинг. Каталог ссылок. Информационный портал - Старого.NET Шпоры, курсовые, пособия, рефераты, ВУЗы.
    Статистика
    webgari.com Рейтинг сайтов
    Раскрутка сайтов
    Яндекс цитирования
    Онлайн всего: 1
    Гостей: 1
    Пользователей: 0