В программировании часто возникает необходимость создавать типы, которые представляют разные сущности, но основаны на одном базовом типе данных. Например, метры и километры — оба являются числами с плавающей точкой, но логически представляют разные физические величины. В этой статье мы рассмотрим, как в Delphi и Pascal создать типы, которые будут несовместимы между собой, даже если они основаны на одном базовом типе.
В этом случае компилятор считает TMetres и TKilometres полностью совместимыми типами, что может привести к ошибкам:
var
m: TMetres;
km: TKilometres;
begin
m := km; // Компилятор разрешит это присваивание, хотя логически это ошибка
end;
Решения проблемы
1. Использование ключевого слова type
Первый подход — использование ключевого слова type при объявлении:
type
TMetres = type Double;
TKilometres = type Double;
Однако, как отмечалось в обсуждении, это не делает типы полностью несовместимыми. Они по-прежнему могут быть присвоены друг другу.
2. Использование записей (records)
Более надежное решение — использование записей с одним полем:
type
TMetres = record
Value: Double;
end;
TKilometres = record
Value: Double;
end;
Теперь присвоение между типами будет запрещено:
var
m: TMetres;
km: TKilometres;
begin
m := km; // Ошибка компиляции: Incompatible types
end;
Недостаток этого подхода — необходимость явного обращения к полю Value:
m.Value := 1500.0;
3. Расширенные записи с операторами
В современных версиях Delphi и Free Pascal можно использовать расширенные записи с перегруженными операторами:
{$ModeSwitch AdvancedRecords}
type
TMetres = record
private
FValue: Double;
public
class operator Implicit(const AValue: Double): TMetres;
class operator Implicit(const AMetres: TMetres): Double;
property Value: Double read FValue write FValue;
end;
TKilometres = record
private
FValue: Double;
public
class operator Implicit(const AValue: Double): TKilometres;
class operator Implicit(const AKilometres: TKilometres): Double;
property Value: Double read FValue write FValue;
end;
class operator TMetres.Implicit(const AValue: Double): TMetres;
begin
Result.FValue := AValue;
end;
class operator TMetres.Implicit(const AMetres: TMetres): Double;
begin
Result := AMetres.FValue;
end;
// Аналогично для TKilometres
Теперь можно работать с типами более естественно:
var
m: TMetres;
km: TKilometres;
d: Double;
begin
m := 1500.0; // Неявное преобразование
km := 20.0; // Неявное преобразование
d := m; // Неявное преобразование в Double
// m := km; // Ошибка компиляции!
end;
4. Генерация типов с использованием дженериков (только FPC)
В Free Pascal (начиная с определенных версий) можно использовать константные параметры в дженериках:
type
generic TQuantity<T, const Symbol: string> = record
private
FValue: T;
public
class operator Implicit(const AValue: T): TQuantity;
class operator Implicit(const AQuantity: TQuantity): T;
property Value: T read FValue write FValue;
end;
type
TMetres = specialize TQuantity<Double, 'm'>;
TKilometres = specialize TQuantity<Double, 'km'>;
Это создаст действительно несовместимые типы, так как TMetres и TKilometres будут разными специализациями.
Практический пример
Рассмотрим полный пример с использованием расширенных записей:
program IncompatibleTypesDemo;
{$mode objfpc}{$H+}
{$ModeSwitch AdvancedRecords}
type
TMetres = record
private
FValue: Double;
public
class operator Implicit(const AValue: Double): TMetres;
class operator Implicit(const AMetres: TMetres): Double;
class operator Add(const A, B: TMetres): TMetres;
property Value: Double read FValue write FValue;
end;
TKilometres = record
private
FValue: Double;
public
class operator Implicit(const AValue: Double): TKilometres;
class operator Implicit(const AKilometres: TKilometres): Double;
class operator Add(const A, B: TKilometres): TKilometres;
property Value: Double read FValue write FValue;
end;
{ TMetres }
class operator TMetres.Implicit(const AValue: Double): TMetres;
begin
Result.FValue := AValue;
end;
class operator TMetres.Implicit(const AMetres: TMetres): Double;
begin
Result := AMetres.FValue;
end;
class operator TMetres.Add(const A, B: TMetres): TMetres;
begin
Result.FValue := A.FValue + B.FValue;
end;
{ TKilometres }
class operator TKilometres.Implicit(const AValue: Double): TKilometres;
begin
Result.FValue := AValue;
end;
class operator TKilometres.Implicit(const AKilometres: TKilometres): Double;
begin
Result := AKilometres.FValue;
end;
class operator TKilometres.Add(const A, B: TKilometres): TKilometres;
begin
Result.FValue := A.FValue + B.FValue;
end;
var
m1, m2: TMetres;
km1, km2: TKilometres;
d: Double;
begin
m1 := 1000.0;
m2 := 500.0;
m1 := m1 + m2; // Корректно
km1 := 5.0;
km2 := 10.0;
km1 := km1 + km2; // Корректно
// m1 := km1; // Ошибка компиляции: Несовместимые типы
// km1 := m1; // Ошибка компиляции: Несовместимые типы
d := m1; // Корректно (неявное преобразование в Double)
d := km1; // Корректно (неявное преобразование в Double)
// m1 := d; // Ошибка компиляции, если не определен соответствующий оператор
end.
Заключение
Создание действительно несовместимых типов на основе одного базового типа в Pascal требует использования дополнительных механизмов языка. Наиболее надежные решения:
Для простых случаев — использование записей с одним полем
Для более удобного использования — расширенные записи с перегруженными операторами
В Free Pascal — специализированные дженерики с константными параметрами
Выбор метода зависит от конкретных требований проекта и используемой версии компилятора. Представленные подходы помогают избежать ошибок, связанных с неявными преобразованиями между логически разными типами данных.
Статья описывает методы создания несовместимых типов в Delphi и Pascal для предотвращения логических ошибок при работе с разными сущностями одного базового типа.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.