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;
|