Delphi. Урок 24. Приведение типов

Уроки для начинающих   30 Октябрь 2013  Автор статьи: admin 

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

Явное приведение

Мы уже не раз сталкивались с ситуациями, когда необходимо, например, получить строковое представление числа, или число из его строкового представления. Далеко за примерами таких ситуаций идти не придется, т.к. нам потребуется реализовать соответствующие преобразования даже при попытке отобразить число на экране, например в виде сообщения. Кроме того, ранее мы уже упоминали о существовании таких полезных функций, как StrToInt, IntToStr и т.п. Еще раз вспомним, как они работают:

1
2
3
4
5
6
7
8
9
10
11
12
var
  a: integer;
  b: string;
begin
  a := 123;
  b := IntToStr(a);

  ...

  b := '234';
  a := StrToInt(b);
end;

Также существуют функции FloatToStr, StrToFloat, которые выполняют аналогичные действия, но только между строковыми и вещественными типами данных.
Т.е. большинство явных приведений в Delphi выполняется при помощи специальных функций. В число таких функций войдут и DateToStr, DateTimeToStr и многие другие.
Некоторые явные преобразования можно реализовывать и следующим образом:

1
2
3
4
5
6
7
var
  c: char;
  b: byte;
begin
  b := 69;
  c := Char(b); // В результате переменная "c" будет содержать букву "E"
end;

В таком случае произойдет преобразование числа в символ по таблице ASCII. Тоже самое можно проделать и наоборот:

1
2
3
4
5
6
7
var
  c: char;
  b: byte;
begin
  c := 'E';
  b := Byte(c); // b = 69
end;
Неявное приведение

Неявными считаются те преобразования типов переменных, которые происходят без нашего непосредственного контроля. Обычно они происходят посредством операций присвоения или сравнения. Например:

1
2
3
4
5
6
7
8
var
  d: double;
  a: integer;
begin
  a := 100;
  d := a;
  if a = d then ShowMessage('a = d');
end;

Но важно учитывать те случаи, когда присваиваемое значение может принципиально не соответствовать переменной, в которую происходит присвоение. Примером таких случаев является попытка присвоить целочисленной переменной вещественное значение (или математическое выражение, которое сводится к вещественному значению), или попытка выйти за границы допустимых значений у какого-либо типа. Последняя ошибка в некоторых случаях может отлавливаться компилятором еще до компиляции («Constant expression violates subrange bounds»), но чаще всего такую ошибку допускают именно в run-time, т.е. во время работы программы, поэтому стоит с осторожностью применять неявные приведения.

Приведения объектов

Рассмотрим два класса, один из которых будет являться родительским для другого:

1
2
3
4
5
6
7
8
9
10
11
type

TParent = class
public
  procedure ParentMethod;
end;

TChild = class(TParent)
public
  procedure ChildMethod;
end;

В случае с экземплярами классов, возможны привидения объекта дочернего класса к родительскому и наоборот. Рассмотрим эти приведения подробнее:

Восходящие приведения

Такие приведения позволяют представить объект дочернего класса в виде родительского. На самом деле никакого преобразования в данном случае производить не требуется, т.к. возможность обращаться к экземпляру дочернего класса, как к экземпляру родительского класса, уже автоматически предусмотрена в Delphi. Не нужно ничего делать — мы всегда можем обратиться к методам, полям (и т.п.) родительского класса.

1
2
3
4
5
6
7
8
9
p := TParent.Create;
c := TChild.Create;
(c as TParent).ParentMethod;
// Приведения экземпляров класса к другому классу
// (к родительскому, а далее и к дочернему) реализуются
// таким образом при помощи оператора "as"
// НО! Как мы уже сказали, в таком ручном приведении
// нет никакого смысла, и мы можем сделать это так:
c.ParentMethod;
Нисходящие приведения

Нисходящие приведения наоборот позволяют преобразовать объект, изначально представленный в виде экземпляра родительского класса, к виду дочернего класса.
[tip]Кстати говоря, абсолютно все классы в Delphi являются в конечном счете дочерними для класса TObject. Это позволяет представить или описывать любой объект как экземпляр класса TObject. Но с этим следует быть осторожным, чтобы не допустить неправильных приведений.[/tip]
В случае с нашим примером, можно реализовать следующее:

1
2
3
4
5
6
7
8
var
  o : TParent;
begin
  o := TChild.Create;
  (o as TChild).ChildMethod;
// Имея объект, изначально представленный экземпляром родительского класса,
// при помощи оператора "as", мы обращаемся к методу дочернего класса.
end;

[warning]Если бы объект «o» был изначально создан как TParent, т.е. так:

1
2
3
4
5
6
var
  o : TParent;
begin
  o := TParent.Create;
  (o as TChild).ChildMethod; // Ошибка "Invalid class typecast"
end;

то во время работы программы (в run-time) мы бы получили ошибку «Invalid class typecast», т.к. на самом деле не каждый экземпляр класса TParent может являться еще и экземпляром класса TChild!
[/warning]
Ну и как уже было упомянуто, можно использовать и самый базовый класс TObject, чтобы описывать при помощи него (потенциально) экземпляр любого другого класса, т.к. любой другой класс является дочерним для TObject. Главное не совершить ошибку из предыдущего примера! Тоже приведем пример:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var
  a: array [1..3] of TObject;
begin
  a[1] := TParent.Create;
  // К объекту a[1] можно будет обратиться как к объекту класса
  // TParent при помощи нисходящего приведения типов
  a[2] := TChild.Create;
  // К объекту a[2] можно будет обратиться как к объекту класса
  // TChild при помощи нисходящего приведения типов
  a[3] := TButton.Create(Form1);
  // Можно даже создать кнопку =)
  (a[3] as TButton).Parent := Form1;
  // И обращаться к ее методам, полям и свойствам
  (a[3] as TButton).Left := 100;
  (a[3] as TButton).Top := 100;
  (a[3] as TButton).Caption := 'My button';
end;

На самом деле код с присвоением свойств для кнопки можно было оптимизировать так:

1
2
3
4
5
6
7
with a[3] as TButton do
begin
  Parent := Form1;
  Left := 100;
  Top := 100;
  Caption := 'My button';
end;

Ключевое слово with позволяет временно установить приоритет для полей, методов или свойств указанного после объекта.
Ну и напоследок хотелось бы еще сказать пару слов об операторе is, который позволяет сравнить класс объекта с каким-либо другим классом. Вот еще небольшое дополнение для последнего примера, которое как раз и демонстрирует использование is:

1
2
3
4
5
6
7
8
if a[3] is TButton then
begin
  ShowMessage('a[3] - это кнопка. Ее можно переименовать!');
  (a[3] as TButton).Caption := 'New name for button';
end;
if a[2] is TParent then
  ShowMessage('a[2] является TParent');
// Условие выполнится, т.к. всякий TChild может быть преобразован к TParent.

Таким образом, конструкция

1
(Object is TClass)

возвращает значение Boolean. True — если объект является экземпляром класса TClass, False — в противном случае.

  • Ирина

    Большое спасибо за уроки, это самое толковое изложение основ ООП из встретившихся.

  • vlad6k

    классные уроки… полезные.. простые.. доступно и просто все изложил.. так держать..

Научиться программировать

  • на Delphi

  • на Java

  • на C++