Программирование на языке Delphi. Глава 3. Объектно-ориентированное программирование (ООП). Часть 3
Автор Administrator   
12.03.2009 г.

Оглавление

Классы в программных модулях

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

  • все классы, предназначенные для использования за пределами модуля, следует определять в секции interface;
  • описание классов, предназначенных для употребления внутри модуля, следует располагать в секции implementation;
  • если модуль B использует модуль A, то в модуле B можно определять классы, порожденные от классов модуля A.

Соберем рассмотренные ранее классы TTextReader, TDelimitedReader и TFixedReader в отдельный модуль ReadersUnit:

unit ReadersUnit;
 
interface
 
type
 TTextReader = class
 private
 // Поля
 FFile: TextFile;
 FItems: array of string;
 FActive: Boolean;
 // Методы
 procedure PutItem(Index: Integer; const Item: string);
 // Методы чтения и записи свойств
 procedure SetActive(const AActive: Boolean);
 function GetItemCount: Integer;
 function GetEndOfFile: Boolean;
 protected
 // Методы чтения и записи свойств
 function GetItem(Index: Integer): string;
 // Абстрактные методы
 function ParseLine(const Line: string): Integer; virtual; abstract;
 public
 // Конструкторы и деструкторы
 constructor Create(const FileName: string);
 destructor Destroy; override;
 // Методы
 function NextLine: Boolean;
 // Свойства
 property Active: Boolean read FActive write SetActive;
 property Items[Index: Integer]: string read GetItem; default;
 property ItemCount: Integer read GetItemCount;
 property EndOfFile: Boolean read GetEndOfFile;
 end;
 
 TDelimitedReader = class(TTextReader)
 private
 // Поля
 FDelimiter: Char;
 protected
 // Методы
 function ParseLine(const Line: string): Integer; override;
 public
 // Конструкторы и деструкторы
 constructor Create(const FileName: string; const ADelimiter: Char = ';');
 // Свойства
 property Delimiter: Char read FDelimiter;
 end;
 
 TFixedReader = class(TTextReader)
 private
 // Поля
 FItemWidths: array of Integer;
 protected
 // Методы
 function ParseLine(const Line: string): Integer; override;
 public
 // Конструкторы и деструкторы
 constructor Create(const FileName: string;
 const AItemWidths: array of Integer);
 end;
 
 TMyReader = class(TDelimitedReader)
 property FirstName: string index 0 read GetItem;
 property LastName: string index 1 read GetItem;
 property Phone: string index 2 read GetItem;
 end;
 
implementation
 
{ TTextReader }
 
constructor TTextReader.Create(const FileName: string);
begin
 inherited Create;
 AssignFile(FFile, FileName);
 FActive := False;
end;
 
destructor TTextReader.Destroy;
begin
 Active := False;
 inherited;
end;
 
function TTextReader.GetEndOfFile: Boolean;
begin
 Result := Eof(FFile);
end;
 
function TTextReader.GetItem(Index: Integer): string;
begin
 Result := FItems[Index];
end;
 
function TTextReader.GetItemCount: Integer;
begin
 Result := Length(FItems);
end;
 
function TTextReader.NextLine: Boolean;
var
 S: string;
 N: Integer;
begin
 Result := not EndOfFile;
 if Result then // Если не достигнут конец файла
 begin
 Readln(FFile, S); // Чтение очередной строки из файла
 N := ParseLine(S); // Разбор считанной строки
 if N <> ItemCount then
 SetLength(FItems, N); // Отсечение массива (если необходимо)
 end;
end;
 
procedure TTextReader.PutItem(Index: Integer; const Item: string);
begin
 if Index > High(FItems) then // Если индекс выходит за границы массива,
 SetLength(FItems, Index + 1); // то увеличение размера массива
 FItems[Index] := Item; // Установка соответствующего элемента
end;
 
procedure TTextReader.SetActive(const AActive: Boolean);
begin
 if Active <> AActive then // Если состояние изменяется
 begin
 if AActive then
 Reset(FFile) // Открытие файла
 else
 CloseFile(FFile); // Закрытие файла
 FActive := AActive; // Сохранение состояния в поле
 end;
end;
 
{ TDelimitedReader }
 
constructor TDelimitedReader.Create(const FileName: string;
 const ADelimiter: Char = ';');
begin
 inherited Create(FileName);
 FDelimiter := ADelimiter;
end;
 
function TDelimitedReader.ParseLine(const Line: string): Integer;
var
 S: string;
 P: Integer;
begin
 S := Line;
 Result := 0;
 repeat
 P := Pos(Delimiter, S); // Поиск разделителя
 if P = 0 then // Если разделитель не найден, то считается, что
 P := Length(S) + 1; // разделитель находится за последним символом
 PutItem(Result, Copy(S, 1, P - 1)); // Установка элемента
 Delete(S, 1, P); // Удаление элемента из строки
 Result := Result + 1; // Переход к следующему элементу
 until S = ''; // Пока в строке есть символы
end;
 
{ TFixedReader }
 
constructor TFixedReader.Create(const FileName: string;
 const AItemWidths: array of Integer);
var
 I: Integer;
begin
 inherited Create(FileName);
 // Копирование AItemWidths в FItemWidths
 SetLength(FItemWidths, Length(AItemWidths));
 for I := 0 to High(AItemWidths) do
 FItemWidths[I] := AItemWidths[I];
end;
 
function TFixedReader.ParseLine(const Line: string): Integer;
var
 I, P: Integer;
begin
 P := 1;
 for I := 0 to High(FItemWidths) do
 begin
 PutItem(I, Copy(Line, P, FItemWidths[I])); // Установка элемента
 P := P + FItemWidths[I]; // Переход к следующему элементу
 end;
 Result := Length(FItemWidths); // Количество элементов постоянно
end;
 
end.
 

Как можно заметить, в описании классов присутствуют новые ключевые слова private, protected и public. С их помощью регулируется видимость частей класса для других модулей и основной программы. Назначение каждого ключевого слова поясняется ниже.

Разграничение доступа к атрибутам объектов

Программист может разграничить доступ к атрибутам своих объектов для других программистов (и себя самого) с помощью специальных ключевых слов: private, protected, public, published (последнее не используется в модуле ReadersUnit).

  • Private. Все, что объявлено в секции private недоступно за пределами модуля. Секция private позволяет скрыть те поля и методы, которые относятся к так называемым особеностям реализации. Например, в этой секции класса TTextReader объявлены поля FFile, FActive и FItems, а также методы PutItem, SetActive, GetItemCount и GetEndOfFile.
  • Public. Поля, методы и свойства, объявленные в секции public не имеют никаких ограничений на использование, т.е. всегда видны за пределами модуля. Все, что помещается в секцию public, служит для манипуляций с объектами и составляет программный интерфейс класса. Например, в классе TTextReader в эту секцию помещены конструктор Create, метод NextLine, свойства Active, Items, ItemCount.
  • Protected. Поля, методы и свойства, объявленные в секции protected, видны за пределами модуля только потомкам данного класса; остальным частям программы они не видны. Так же как и private, директива protected позволяет скрыть особенности реализации класса, но в отличие от нее разрешает другим программистам порождать новые классы и обращаться к полям, методам и свойствам, которые составляют так называемый интерфейс разработчика. В эту секцию обычно помещаются виртуальные методы. Примером такого метода является ParseLine.
  • Published. Устанавливает правила видимости те же, что и директива public. Особенность состоит в том, что для элементов, помещенных в секцию published, компилятор генерирует информацию о типах этих элементов. Эта информация доступна во время выполнения программы, что позволяет превращать объекты в компоненты визуальной среды разработки. Секцию published разрешено использовать только тогда, когда для самого класса или его предка включена директива компилятора $TYPEINFO.

Перечисленные секции могут чередоваться в объявлении класса в произвольном порядке, однако в пределах секции сначала следует описание полей, а потом методов и свойств. Если в определении класса нет ключевых слов private, protected, public и published, то для обычных классов всем полям, методам и свойствам приписывается атрибут видимости public, а для тех классов, которые порождены от классов библиотеки VCL, - атрибут видимости published.

Внутри модуля никакие ограничения на доступ к атрибутам классов, реализованных в этом же модуле, не действуют. Кстати, это отличается от соглашений, принятых в некоторых других языках программирования, в частности в языке C++.

Указатели на методы объектов

В языке Delphi существуют процедурные типы данных для методов объектов. Внешне объявление процедурного типа для метода отличается от обычного словосочетанием of object, записанным после прототипа процедуры или функции:

type
 TReadLineEvent = procedure (Reader: TTextReader; const Line: string) of object;
 

Переменная такого типа называется указателем на метод (method pointer). Она занимает в памяти 8 байт и хранит одновременно ссылку на объект и адрес его метода.

type
 TTextReader = class
 private
 FOnReadLine: TReadLineEvent;
 ...
 public
 property OnReadLine: TReadLineEvent read FOnReadLine write FOnReadLine;
 end;

Методы объектов, объявленные по приведенному выше шаблону, становятся совместимы по типу со свойством OnReadLine.

type
 TForm1 = class(TForm)
 procedure HandleLine(Reader: TTextReader; const Line: string);
 end;
 
var
 Form1: TForm1;
 Reader: TTextReader;

Если установить значение свойства OnReadLine:

Reader.OnReadLine := Form1.HandleLine;

и переписать метод NextLine,

function TTextReader.NextLine: Boolean;
var
 S: string;
 N: Integer;
begin
 Result := not EndOfFile;
 if Result then // Если строки для считывания еще есть, то
 begin
 Readln(FFile, S); // Считывание очередной строки
 N := ParseLine(S); // Выделение элементов строки (разбор строки)
 if N <> ItemCount then
 SetLength(FItems, N);
 if Assigned(FOnReadLine) then
 FOnReadLine(Self, S); // уведомление о чтении очередной строки
 end;
end;

то объект Form1 через метод HandleLine получит уведомление об очередной считанной строке. Обратите внимание, что вызов метода через указатель происходит лишь в том случае, если указатель не равен nil. Эта проверка выполняется с помощью стандартной функции Assigned, которая возвращает True, если ее аргумент является связанным указателем.

Описанный выше механизм называется делегированием, поскольку он позволяет передать часть работы другому объекту, например, сосредоточить в одном объекте обработку событий, возникающих в других объектах. Это избавляет программиста от необходимости порождать многочисленные классы-наследники и перекрывать в них виртуальные методы. Делегирование широко применяется в среде Delphi. Например, все компоненты делегируют обработку своих событий той форме, в которую они помещены.

Метаклассы

Ссылки на классы

Язык Delphi позволяет рассматривать классы объектов как своего рода объекты, которыми можно манипулировать в программе. Такая возможность рождает новое понятие - класс класса; его принято обозначать термином метакласс.

Для поддержки метаклассов введен специальный тип данных - ссылка на класс (class reference). Он описывается с помощью словосочетания class of, например:

type
 TTextReaderClass = class of TTextReader;

Переменная типа TTextReaderClass объявляется в программе обычным образом:

var
 ClassRef: TTextReaderClass;

Значениями переменной ClassRef могут быть класс TTextReader и все порожденные от него классы. Допустимы следующие операторы:

ClassRef := TTextReader;
ClassRef := TDelimitedReader;
ClassRef := TFixedReader;

По аналогии с тем, как для всех классов существует общий предок TObject, у ссылок на классы существует базовый тип TClass, определенный, как:

type
 TClass = class of TObject;

Переменная типа TClass может ссылаться на любой класс.

Практическая ценность ссылок на классы состоит в возможности создавать программные модули, работающие с любыми классами объектов, даже теми, которые еще не разработаны.

Физический смысл и взаимосвязь таких понятий, как переменная-объект, экземпляр объекта в памяти, переменная-класс и экземпляр класса в памяти поясняет рисунок 4.

Рисунок 4. Переменная-объект, экземпляр объекта в памяти, переменная-класс и экземпляр класса в памяти

Рисунок 4. Переменная-объект, экземпляр объекта в памяти, переменная-класс и экземпляр класса в памяти

Методы классов

Метаклассы привели к возникновению нового типа методов - методов класса. Метод класса оперирует не экземпляром объекта, а непосредственно классом. Он объявляется как обычный метод, но перед словом procedure или function записывается зарезервированное слово class, например:

type
 TTextReader = class
 ...
 class function GetClassName: string;
 end;

Передаваемый в метод класса неявный параметр Self содержит не ссылку на объект, а ссылку на класс, поэтому в теле метода нельзя обращаться к полям, методам и свойствам объекта. Зато можно вызывать другие методы класса, например:

class function TTextReader.GetClassName: string;
begin
 Result := ClassName;
end;

Метод ClassName объявлен в классе TObject и возвращает имя класса, к которому применяется. Очевидно, что надуманный метод GetClassName просто дублирует эту функциональность для класса TTextReader и всех его наследников.

Методы класса применимы и к классам, и к объектам. В обоих случаях в параметре Self передается ссылка на класс объекта. Пример:

var
 Reader: TTextReader;
 S: string;
begin
 // Вызов метода с помощью ссылки на класс
 S := TTextReader.GetClassName; // S получит значение 'TTextReader'
 
 // Создание объекта класса TDelimitedReader
 Reader := TDelimitedReader.Create('MyData.del');
 
 // Вызов метода с помощью ссылки на объект
 S := Reader.GetClassName; // S получит значение 'TDelimitedReader'
end.

Методы классов могут быть виртуальными. Например, в классе TObject определен виртуальный метод класса NewInstance. Он служит для распределения памяти под объект и автоматически вызывается конструктором. Его можно перекрыть в своем классе, чтобы обеспечить нестандартный способ выделения памяти для экземпляров. Метод NewInstance должен перекрываться вместе с другим методом FreeInstance, который автоматически вызывается из деструктора и служит для освобождения памяти. Добавим, что размер памяти, требуемый для экземпляра, можно узнать вызовом предопределенного метода класса InstanceSize.

Виртуальные конструкторы

Особая прелесть ссылок на классы проявляется в сочетании с виртуальными конструкторами. Виртуальный конструктор объявляется с ключевым словом virtual. Вызов виртуального конструктора происходит по фактическому значению ссылки на класс, а не по ее формальному типу. Это позволяет создавать объекты, классы которых неизвестны на этапе компиляции. Механизм виртуальных конструкторов применяется в среде Delphi при восстановлении компонентов формы из файла. Восстановление компонента происходит следующим образом. Из файла считывается имя класса. По этому имени отыскивается ссылка на класс (метакласс). У метакласса вызывается виртуальный конструктор, который создает объект нужного класса.

var
 P: TComponent;
 T: TComponentClass; // TComponentClass = class of TComponent;
...
 T := FindClass(ReadStr);
 P := T.Create(nil);
...

На этом закончим изучение теории объектно-ориентированного программирования и в качестве практики рассмотрим несколько широко используемых инструментальных классов среды Delphi. Разберитесь с их назначением и работой. Это поможет глубже понять ООП и пригодится на будущее.

Классы общего назначения

Как показывает практика, в большинстве задач приходится использовать однотипные структуры данных: списки, массивы, множества и т.д. От задачи к задаче изменяются только их элементы, а методы работы сохраняются. Например, для любого списка нужны процедуры вставки и удаления элементов. В связи с этим возникает естественное желание решить задачу "в общем виде", т.е. создать универсальные средства для управления основными структурами данных. Эта идея не нова. Она давно пришла в голову разработчикам инструментальных пакетов, которые быстро наплодили множество вспомогательных библиотек. Эти библиотеки содержали классы объектов для работы со списками, коллекциями (динамические массивы с переменным количеством элементов), словарями (коллекции, индексированные строками) и другими "абстрактными" структурами. Для среды Delphi тоже разработаны аналогичные классы объектов. Их большая часть сосредоточена в модуле Classes. Наиболее нужными для вас являются списки строк (TStrings, TStringList) и потоки (TSream, THandleSream, TFileStream, TMemoryStream и TBlobStream). Рассмотрим кратко их назначение и применение.

Классы для представления списка строк

Для работы со списками строк служат классы TStrings и TStringList. Они используются в библиотеке VCL повсеместно и имеют гораздо большую универсальность, чем та, что можно почерпнуть из их названия. Классы TStrings и TStringList служат для представления не просто списка строк, а списка элементов, каждый из которых представляет собой пару строка-объект. Если со строками не ассоциированы объекты, получается обычный список строк.

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

Свойства класса TStrings описаны ниже.

  • Count: Integer - число элементов в списке.
  • Strings[Index: Integer]: string - обеспечивает доступ к массиву строк по индексу. Первая строка имеет индекс, равный 0. Свойство Strings является основным свойством объекта.
  • Objects[Index: Integer]: TObject - обеспечивает доступ к массиву объектов. Свойства Strings и Objects позволяют использовать объект TStrings как хранилище строк и ассоциированных с ними объектов произвольных классов.
  • Text: string - позволяет интерпретировать список строк, как одну большую строку, в которой элементы разделены символами #13#10 (возврат каретки и перевод строки).

Наследники класса TStrings иногда используются для хранения строк вида Имя=Значение, в частности, строк INI-файлов (см. гл. 6). Для удобной работы с такими строками в классе TStrings дополнительно имеются следующие свойства.

  • Names[Index: Integer]: string - обеспечивает доступ к той части строки, в которой содержится имя.
  • Values[const Name: string]: string - обеспечивает доступ к той части строки, в которой содержится значение. Указывая вместо Name ту часть строки, которая находится слева от знака равенства, вы получаете ту часть, что находится справа.

Управление элементами списка осуществляется с помощью следующих методов:

  • Add(const S: string): Integer - добавляет новую строку S в список и возвращает ее позицию. Новая строка добавляется в конец списка.
  • AddObject(const S: string; AObject: TObject): Integer - добавляет в список строку S и ассоциированный с ней объект AObject. Возвращает индекс пары строка-объект.
  • AddStrings(Strings: TStrings) - добавляет группу строк в существующий список.
  • Append(const S: string) - делает то же, что и Add, но не возвращает значения.
  • Clear - удаляет из списка все элементы.
  • Delete(Index: Integer) - удаляет строку и ассоциированный с ней объект. Метод Delete, также как метод Clear не разрушают объектов, т.е. не вызывают у них деструктор. Об этом вы должны позаботиться сами.
  • Equals(Strings: TStrings): Boolean - Возвращает True, если список строк в точности равен тому, что передан в параметре Strings.
  • Exchange(Index1, Index2: Integer) - меняет два элемента местами.
  • GetText: PChar - возвращает все строки списка в виде одной большой нуль-терминированной строки.
  • IndexOf(const S: string): Integer - возвращает позицию строки S в списке. Если заданная строка в списке отсутствует, функция возвращает значение -1.
  • IndexOfName(const Name: string): Integer - возвращает позицию строки, которая имеет вид Имя=Значение и содержит в себе Имя, равное Name.
  • IndexOfObject(AObject: TObject): Integer - возвращает позицию объекта AObject в массиве Objects. Если заданный объект в списке отсутствует, функция возвращает значение -1.
  • Insert(Index: Integer; const S: string) - вставляет в список строку S в позицию Index.
  • InsertObject(Index: Integer; const S: string; AObject: TObject) - вставляет в список строку S и ассоциированный с ней объект AObject в позицию Index.
  • LoadFromFile(const FileName: string) - загружает строки списка из текстового файла.
  • LoadFromStream(Stream: TStream) - загружает строки списка из потока данных (см. ниже).
  • Move(CurIndex, NewIndex: Integer) - изменяет позицию элемента (пары строка-объект) в списке.
  • SaveToFile(const FileName: string) - сохраняет строки списка в текстовом файле.
  • SaveToStream(Stream: TStream) - сохраняет строки списка в потоке данных.
  • SetText(Text: PChar) - загружает строки списка из одной большой нуль-терминированной строки.

Класс TStringList добавляет к TStrings несколько дополнительных свойств и методов, а также два свойства-события для уведомления об изменениях в списке. Они описаны ниже.

Свойства:

  • Duplicates: TDuplicates - определяет, разрешено ли использовать дублированные строки в списке. Свойство может принимать следующие значения: dupIgnore (дубликаты игнорируются), dupAccept (дубликаты разрешены), dupError (дубликаты запрещены, попытка добавить в список дубликат вызывает ошибку).
  • Sorted: Boolean - если имеет значение True, то строки автоматически сортируются в алфавитном порядке.

Методы:

  • Find(const S: string; var Index: Integer): Boolean - выполняет поиск строки S в списке строк. Если строка найдена, Find помещает ее позицию в переменную, переданную в параметре Index, и возвращает True.
  • Sort - сортирует строки в алфавитном порядке.

События:

  • OnChange: TNotifyEvent - указывает на обработчик события, который выполнится при изменении содержимого списка. Событие OnChange генерируется после того, как были сделаны изменения.
  • OnChanging: TNotifyEvent - указывает на обработчик события, который выполнится при изменении содержимого списка. Событие OnChanging генерируется перед тем, как будут сделаны изменения.

Ниже приводится фрагмент программы, демонстрирующий создание списка строк и манипулирование его элементами:

var
 Items: TStrings;
 I: Integer;
begin
 // Создание списка
 Items := TStringList.Create;
 Items.Add('Туризм');
 Items.Add('Наука');
 Items.Insert(1, 'Бизнес');
 ...
 // Работа со списком
 for I := 0 to Items.Count - 1 do
 Items[I] := UpperCase(Items[I]);
 ...
 // Удаление списка
 Items.Free;
end;

Классы для представления потока данных

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

Таблица 1. Классы потоков

Класс

Описание

TStream Абстрактный поток, от которого наследуются все остальные. Свойства и методы класса TStream образуют базовый интерфейс потоковых объектов.
THandleStream Поток, который хранит свои данные в файле. Для чтения-записи файла используется дескриптор (handle), поэтому поток называется дескрипторным. Дескриптор - это номер открытого файла в операционной системе. Его возвращают низкоуровневые функции создания и открытия файла.
TFileStream Поток, который хранит свои данные в файле. Отличается от ThandleStream тем, что сам открывает (создает) файл по имени, переданному в конструктор.
TMemoryStream Поток, который хранит свои данные в оперативной памяти. Моделирует работу с файлом. Используется для хранения промежуточных результатов, когда файловый поток не подходит из-за низкой скорости передачи данных.
TResourceStream Поток, обеспечивающий доступ к ресурсам в Windows-приложении.
TBlobStream Обеспечивает последовательный доступ к большим полям таблиц в базах данных.

Потоки широко применяются в библиотеке VCL и наверняка вам понадобятся. Поэтому ниже кратко перечислены их основные общие свойства и методы.

Общие свойства:

  • Position: Longint - текущая позиция чтения-записи.
  • Size: Longint - текущий размер потока в байтах.

Общие методы:

  • CopyFrom(Source: TStream; Count: Longint): Longint - копирует Count байт из потока Source в свой поток.
  • Read(var Buffer; Count: Longint): Longint - читает Count байт из потока в буфер Buffer, продвигает текущую позицию на Count байт вперед и возвращает число прочитанных байт. Если значение функции меньше значения Count, то в результате чтения был достигнут конец потока.
  • ReadBuffer(var Buffer; Count: Longint) - читает из потока Count байт в буфер Buffer и продвигает текущую позицию на Count байт вперед. Если выполняется попытка чтения за концом потока, то генерируется ошибка.
  • Seek(Offset: Longint; Origin: Word): Longint - продвигает текущую позицию в потоке на Offset байт относительно позиции, заданной параметром Origin. Параметр Origin может иметь одно из следующих значений: 0 - смещение задается относительно начала потока; 1 - смещение задается относительно текущей позиции в потоке; 2 - смещение задается относительно конца потока.
  • Write(const Buffer; Count: Longint): Longint - записывает в поток Count байт из буфера Buffer, продвигает текущую позицию на Count байт вперед и возвращает реально записанное количество байт. Если значение функции отличается от значения Count, то при записи была ошибка.
  • WriteBuffer(const Buffer; Count: Longint) - записывает в поток Count байт из буфера Buffer и продвигает текущую позицию на Count байт вперед. Если по какой-либо причине невозможно записать все байты буфера, то генерируется ошибка.

Ниже приводится фрагмент программы, демонстрирующий создание файлового потока и запись в него строки:

var
 Stream: TStream;
 S: AnsiString;
 StrLen: Integer;
 
begin
 // Создание файлового потока
 Stream := TFileStream.Create('Sample.Dat', fmCreate);
 ...
 // Запись в поток некоторой строки
 StrLen := Length(S) * SizeOf(Char);
 Stream.Write(StrLen, SizeOf(Integer)); // запись длины строки
 Stream.Write(S, StrLen); // запись символов строки
 ...
 // Закрытие потока
 Stream.Free;
end;

Итоги

Теперь для вас нет секретов в мире ООП. Вы на достаточно серьезном уровне познакомились с объектами и их свойствами; узнали, как объекты создаются, используются и уничтожаются. Если не все удалось запомнить сразу - не беда. Возвращайтесь к материалам главы по мере решения стоящих перед вами задач, и работа с объектами станет простой, естественной и даже приятной. Когда вы достигните понимания того, как работает один объект, то автоматически поймете, как работают все остальные. Теперь мы рассмотрим то, с чем вы встретитесь очень скоро - ошибки программирования.