Для просмотра сайта
рекомендуется :
-Разрешение : 800*600
-Броузер : Internet Explorer
-Для более эффективного
просмотра нажмите F11.
FAQ: от Turbo/Borland Pascal к Delphi
Этот список FAQ вопросов написан в помощь тем, кто недавно
перешел на Delphi с таких продуктов, как Borland и Turbo Pascal. Здесь
рассмотрены основные вопросы, возникающие у людей, которые по тем или
иным причинам не смогли (или не захотели) прочитать серьезную литературу,
посвященную работе в Delphi. Так же этот список FAQ вопросов рекомендуется
тем, кто использует среду визуального программирования Delphi, руководствуясь
интуитивным представлением о компонентах и какой-нибудь мало вразумительной
книжкой (к таковым автор относит, в частности, большинство книг В.В.Фаронова).
Этот список нетрадиционен по своей нацеленности на аудиторию,
и как следствие нетрадиционен по своему составу. Здесь содержатся ответы
не на вопрос _Как_ поступать в той или иной ситуации, а _Почему_ в той
или иной ситуации следует поступать так-то и так-то. В результате, автор
надеется, этот документ будет интерпретироваться не просто, как список
решения проблем, но и как некоторый лекционный материал, который полезно
прочитать от и до, вне зависимости от того, знаете ли вы ответ на поставленные
вопросы, или нет.
Кроме того, данный FAQ список нетрадиционен в плане соотношения
вопросов и ответов. Как Вы увидите, здесь приводятся достаточно развернутые
ответы, каждый из которых охватывает сразу несколько потенциальных FAQ
вопросов.
Итак...
(1) Динамическое создание, отображение и уничтожение объектов
Что такое класс? (Новое название для объекта?).
Почему не работает старая схема динамического создания объекта?
Что такое _утилита класса_?
Почему везде рекомендуют использовать не деструктор а процедуру Free?
Почему динамически созданные визуальные компоненты VCL не появляются
на форме?
Какая разница между Owner и Parent?
Прежде всего разберемся с определениями. К сожалению до определенного
времени в Паскале существовала путаница в понимании слова "объект". Итак,
отныне и навсегда (по крайней мере, покуда речь идет об Object Pascal):
описание структуры объекта, его полей и методов, мы будем называть _классом_.
Конкретный экземпляр (конкретный представитель) класса мы будем называть
объектом. Таким образом, класс -- описание, объект -- конкретные значения,
память машины, отведенная под хранение данных.
TMyObject = class(TObject)
MyValue:byte
end;
var
MyObject :TMyObject;
В указанном выше примере, TMyObject является классом, порожденным
от TObject (любой класс является потомком этого базового класса), а MyObject
-- объект. Причем объект, который (как и любой другой объект) будет создан
динамически, то есть в процессе выполнения программы. Hо заметим, что
объект-объектом, однако, MyObject всего лишь... ссылка. Строка "var MyObject"
лишь определяет переменную, которая в дальнейшем будет интерпретироваться,
как ссылка на структуру, в которой хранятся все данные об объекте (в первую
очередь значения его полей). При этом место в памяти под эту структуру
не выделяется,-- ее выделение и заполнение произойдет, как уже говорилось,
в процессе выполнения программы, точнее, только после вызова конструктора
нашего объекта.
А теперь самое главное, как мы можем вызвать конструктор объекта,
если самого этого объекта еще нет, а есть только ссылка, непонятно на
что указывающая? Ощутили проблему? Еще раз: объявив MyObject мы выделили
4 (?) байта для хранения ссылки. Ничего более мы не сделали. Если в старом
Паскале динамическое создание объекта подразумевало создание ссылки на
описанную структуру (точнее, выделение памяти под эту структур) при помощи
оператора new, то в Object Pascal'е, динамическое создание происходит
путем вызова конструктора. Но как вызвать конструктор, если объект еще
не создан? Получаем замкнутый круг. А потому, не удивительно, что при
запуске нашей программы команда:
MyObject.Create
приведет к появлению красивого окошка с сообщением, о том,
что наша программа попыталась обратится в недоступную ей область памяти.
Такую табличку принято называть AV (Access Violation). Так что же делать?
Решение этой проблемы тесно связано с понятием _утилиты класса_. Быть
может, листая встроенное в Delphi справочное руководство или исходники
VCL, Вы уже натыкались на "странное" использование слова class при описании
методов какого-нибудь класса?
classfunction ClassName: ShortString;
Вот это и есть пресловутая _утилита класса_. Это метод, который
может прекрасно работать при отсутствии самого объекта. Так, вышеописанная
функция выдает имя класса, то есть для нашего самого первого примера,
это будет строка 'TMyObject'. Согласитесь, что для работы этой функции
сам экземпляр класса, то есть сам объект (конкретные значения его полей)
совсем не нужен. Раз такие методы не нуждаются в объекте, то и вызывать
их разумно не для объекта, а для класса:
MyShortString := TMyObject.ClassName;
Утилиту класса иногда называют "статическим методом". Это
не совсем правильно, но зато точно отражает физическую сущность утилит
класса: так же, как и в случае статического метода, адрес утилиты класса
в явном виде подставляется внутрь машинного кода программы, в то место,
где производится вызов утилиты класса (это осуществляется компоновщиком
при создании *.exe файла).
Конструктор описывается _без_ зарезервированного слова "class":
constructor Create;
однако, Вас это не должно смущать. По своей сути, конструктор
это именно _утилита класса_. Более того, конструктор выдает результат
своей "деятельности", а именно, он выдает ссылку на ту структуру, которую
он создал в памяти. То есть, конструктор выделяет необходимую область
памяти, заносит в нее требуемую информацию, а ссылку на эту структуру
выдает, как результат своей работы.
Суммируя все вышесказанное, можно понять, что правильный вызов
конструктора для нашего объекта MyObject должен иметь вид:
MyObject := TMyObject.Create;
Теперь хочется отметить, что _после_ создания объекта вызов
его конструктора ("MyObject.Create") допустим. При этом конструктор будет
работать как обычный метод, то есть будут выполнены только те действия,
которые описаны в конструкторе, например, инициализация каких-либо полей.
Научившись создавать объект, неплохо бы научится его уничтожать.
По аналогии, логично предположить, что вместо использования деструктора
в совокупности с оператором dispose (как было принято в Паскале), достаточно
будет вызвать просто деструктор: MyObject.Destroy. Более того, попытка
поступить именно так приносит ожидаемые плоды: объект благополучно уничтожается.
Однако файл помощи рекомендует вместо привычного деструктора использовать
для уничтожения всех объектов процедуру Free.
Ответ прост: разработчики позаботились о вас, описав у TObject
процедуру, которая перед уничтожением объекта производит предварительную
проверку его "существования". Другими словами, написав MyObject.Free;
MyObject:=nil; вы с гарантией не получите AV при повторном вызове MyObject.Free.
А вот вызов двух деструкторов Destroy подряд приведет к ошибке (к тому
самому AV).
Напоследок, опишем разницу между свойством Parent наследников
TWinControl и уже упомянутом свойстве Owner наследников TComponent. К
сожалению, нередко можно встретить недопонимание, между различиями этих
двух свойств, хотя разница проста и легко определима (правда, определение
получается громоздким и, подчас, неудобно читаемым).
Свойство Owner указывает на компонент, который является _владельцем_
данного компонента. Если компонент является владельцем других компонентов,
то при своем уничтожении он уничтожит все те компоненты, владельцами которых
он является. У каждого компонента есть свойство Components -- массив,
в котором хранятся ссылки на все компоненты владельцем которого является
данный компонент. С другой стороны, компонент может быть уничтожен сам
по себе. Тогда, было бы неплохо, если бы компонент сообщил об этом своему
владельцу. Вот для установления этой, обратной, связи и заводится свойство
Owner.
Свойство Parent указывает на контрол (наследник TWinControl
-- оконный элемент управления; фактически, обыкновенное окно, в понимании
Windows, то есть нечто, имеющее дескриптор hWnd), который является _родителем_