Object Pascal в сочетании с ассемблером в современной его форме Delphi 5/6/7
предоставляет неограниченные возможности для полета мысли программиста, и этой
статьей мы откроем серию, в которой последовательно будем это демонстрировать.
Начнем мы с простого, крайне полезного, но мало известного, по крайней мере,
для начинающих, стандартного класса Дельфи TStringList. Сейчас мы посмотрим, как
красиво решаются типичные задачи при помощи этого класса.
Для начала в двух словах, какие такие замечательные свойства есть у объектов
данного класса. TStringList - это класс, предназначенный для хранения списка
строк и списка объектов с текстовым представлением (прямо как в 1С - это
СписокЗначений). Кроме того, этот список может быть отсортирован по алфавиту или
при помощи сравнительной функции, написанной программистом. Кроме того, этот
список может быть интерпретирован как список значений (Name=string). Кроме того,
этот список может быть сохранен в файл или поток, преобразован в непрерывную
строку или строку, разделенную запятыми. Физически получаемая строка или поток
представляет собой обычный текст, в котором строки разделены символами CR/LF
(стандартный Windows Text File), или запятыми (CommaText, Excel). Ну и само
собой, есть возможность загружать список строк из файла, потока, строки и
строки, разделенной запятыми. Следует особо отметить замечательное свойство
упаковки в строку, разделенную запятыми: при обратной распаковке строки всегда
восстанавливаются в их исходном виде. Это означает, что допустима многократная
вложенность строк, разделенных запятыми, дающая огромный выигрыш при
упаковке/распаковке многомерных структурных данных в текстовый формат, что мы и
продемонстрируем во второй задаче.
Итак, для начала рассмотрим список строк как список объектов с текстовым
представлением, т. к. именно в данном ключе следует использовать список строк в
реальных приложениях. Что из себя представляет объект с текстовым
представлением? Это может быть, например, список товаров, имеющих помимо
наименования еще и дополнительные параметры типа единицы измерения, количества в
упаковке и цены. Итак, имеем предопределение типов:
type
TEdIzm=class
public
Name:string;
Weight:double;
end;
TTovar=class
public
Name:string; //наименование
EdIzm:TEdIzm; //ед. изм.
CountUp:integer; //кол-во в упаковке
PriceOut:currency; //цена продажная
end;
var
TovarList:TStringList;
Набор объектов TTovar - это классический справочник однородных товаров,
например, хлебобулочных изделий. Поле Weight в классе TEdIzm требуется для
перевода одних единиц в другие. Вернемся к нашим булкам. Допустим, поставщик
"Карякинский Хлебозавод" предоставил нам текстовый файл, в котором находится
информация о его новой продукции и отпускных ценах в формате текстового файла:
*Начало файла*
Товар1=[наименование товара]
ЕдИзм1=[наименование ед.изм.]
Вес1=[вес единицы измерения]
КолУп1=[количество в упаковке]
Цена1=[цена поставщика]
--
Товар2=[наименование товара]
ЕдИзм2=[наименование ед.изм.]
Вес2=[вес единицы измерения]
КолУп2=[количество в упаковке]
Цена2=[цена поставщика]
--
*Конец файла*
Всего в файле содержится, например, 2000 наименований. Наша задача -
загрузить данные этого файла в список строк ListBox1:TListBox (лежащий на форме)
в отсортированном виде так, чтобы была возможность по двойному щелчку
просмотреть параметры каждого товара (для этого к каждой строке будет прикреплен
объект типа TTovar).
Если решать эту задачу в лоб (как чтение построчно и разбор текстового
файла), а так же напрямую добавлять в ListBox, то это обернется неэффективной
работой компьютера, его подтормаживанием (на слабых машинах), и вообще, для
дальнейших внесений изменений в программный код это решение не является лучшим.
Гораздо эффективнее сделать "финт ушами", а именно, создать TStringList,
загрузить в него исходный файл, создать второй TStringList, загрузить в него
товары, отсортировать их, и в конце концов, присвоить свойству ListBox.Items:
function LoadTovary(filename:string):TStrings;
var str:TStringList;
tovar:TTovar;
edizm:TEdIzm;
I:integer;
begin
//загружаем файл с данными
str:=TStringList.Create;
str.LoadFromFile(filename);
//посчитаем, сколько будет товаров
result:=TStringList.Create; // TStrings являедся предком TStringList, поэтому данное присвоение корректно
result.Capacity:=str.count div 6;
for I:=0 to result.capacity do
begin
tovar:=TTovar.Create;
edizm:=TEdIzm.Create;
tovar.Name:=str.Values['Товар'+inttostr(i)];
edizm.Name:=str.Values['ЕдИзм'+inttostr(i)];
edizm.Weight:=strtofloat(str.Values['Вес'+inttostr(i)]);
tovar.CountUp:=strtoint(str.Values['КолУп'+inttostr(i)]);
tovar.PriceOut:=strtoint(str.Values['Цена'+inttostr(i)]);
tovar.EdIzm:=edizm;
result.AddObject(tovar.Name,tovar);
end;
result.Sort;
end;
...
ListBox1.Items:=LoadTovary('fromhlzd.txt');
...
Заметим, что если написать функцию типа TStringListSortCompare, то можно
будет сортировать не только по текстовому представлению, но и по любым другим
признакам, например, цене:
function StringListComparePrice(List: TStringList; Index1, Index2: Integer): Integer;
begin
Result := TTovar(List.Objects[Index1]).PriceOut-TTovar(List.Objects[Index2]).PriceOut;
end;
...
result.CustomSort(StringListComparePrice);
...
Ну и в конце концов, продемонстрируем реакцию на двойное нажатие на
получившемся списке товаров. По двойному щелчку мы покажем отпускную цену
товара:
procedure TForm1.ListBox1DblClick(Sender: TObject);
begin
showmessage('Цена поставщика '+
floattostr(TTovar(TListBox(sender).Items.Objects[TListBox(sender).ItemIndex]).PriceOut)
);
end;
Кстати, не забываем освобождать системные ресурсы объектов перед удалением
строк методами ListBox1.Items.Delete()/Clear() или str.Delete()/Clear()
(str:TStringList), если это требуется, а так же при закрытии приложения:
procedure TForm1.FormDestroy(Sender: TObject);
var i:integer;
begin
for i:=0 to ListBox1.Items.Count-1 do
with TTovar(ListBox1.Items.Objects[i]) do
begin
EdIzm.Free;
Free;
end;
end;
Теперь немного усложним задачу. Пусть у нас есть не один файл, а десять - от
десяти разных поставщиков. Нам надо в общей сложности загрузить 20000
наименований и затем отсортировать их. Если мы будем именно так и делать, то
сортировка такого большого списка объектов займет значительное время, это и
составляет задачу. Однако такая задача решается очень просто - достаточно после
создания объекта TStringList сразу присвоить его свойству Sorted значение true.
После этого вставка новых строк будет осуществляться с помощью быстрого
алгоритма, однако потеряется возможность сортировать по параметрам прикрепленных
объектов, т.к. в случае Sorted=true сортировка производится автоматически только
по текстовому представлению (см. исходники TStringList). При Sorted=true,
обработка дубликатов определяется свойством Duplicates. Можно пропускать,
разрешать и запрещать дубликаты объектов с одинаковым текстовым представлением.
При этом, если дубликаты пропускаются или запрещены, необходимо следить за
освобождением ресурсов объектов, не добавленных в список. По умолчанию, свойство
Duplicates равно dupIgnore, что означает, что дубликаты пропускаются, поэтому по
умолчанию надо следить за освобождением ресурсов.
Стоит отметить, что при сохранении или загрузке списка строк, прикрепленные к
строкам объекты не сохраняются и не восстанавливаются в стандартном TStringList,
однако можно очень красиво решить эту проблему - написать потомка TStringList с
переопределенными процедурами GetTextStr и SetTextStr, в которых придумать и
реализовать собственный формат хранения объектов и их текстового представления в
виде непрерывного текста.
Итак, мы увидели, что TStringList позволяет решать самые разнообразные задачи
от группировки объектов с текстовым представлением до простой обработки списка
значений переменных. Теперь посмотрим, как этот класс позволяет решать задачи
упаковки/распаковки сложно-структурированных данных в текстовый вид и обратно.
Такая задача очень часто возникает в коммуникационных задачах, например, при
передаче данных по сети или между программными модулями.
Предположим, что нам надо запустить некую программу obrab.exe с параметром
командной строки, в которой необходимо передать выбранный товар из
сформированного выше списка. Напишем упаковщик и распаковщик данных. С
TStringList эта задача программируется за две секунды:
Упаковщик
procedure runobrab;
var strtov,stred:TStringList;
begin
strtov:=TStringList.Create;
with TTovar(ListBox1.Items.Objects[ListBox1.ItemIndex]) do
begin
stred:=TStringList.Create;
stred.Values['ЕдИзм']:=EdIzm.Name;
stred.Values['Вес']:=floattostr(EdIzm.Weight);
strtov.Values['Товар']:=Name;
strtov.Values['ЕдИзм']:=stred.CommaText;
strtov.values['КолУп']:=inttostr(CountUp);
strtov.values['Цена']:=floattostr(PriceOut);
end;
winexec('obrab.exe '+strtov.CommaText,SW_SHOWNORMAL);
strtov.Free;
stred.Free;
end;
Распаковщик
function getparam:TTovar;
var strtov,stred:TStringList;
begin
strtov:=TStringList.Create;
stred:=TStringList.Create;
strtov.CommaText:=paramstr(1);
stred.CommaText:=strtov.Values['ЕдИзм'];
result:=TTovar.Create;
with result do
begin
EdIzm:=TEdIzm.Create;
EdIzm.Name:=stred.Values['ЕдИзм'];
EdIzm.Weight:=strtofloat(stred.Values['Вес']);
Name:=strtov.Values['Товар'];
CountUp:=strtoint(strtov.values['КолУп']);
PriceOut:=strtofloat(strtov.values['Цена']);
end;
stred.Free;
strtov.Free;
end;
Продолжение следует…
Автор: Цованян Роман
|