Помощь новичкам

Спасибо за ответы. То есть всегда нужно помнить, что динамический массив это последовательность и соответственно применять метод последовательности. Хорошо, тогда если a = b сравнивает адреса и @a = @b делает то же самое, если я не ошибаюсь, то зачем повторяться? Может быть лучше было бы если бы по a = b сравнивалось содержимое массивов? И, кстати, в таком случае, почему не работает такое a = @a?

Потому что PascalАВС.NЕТ - язык, базирующийся на ссылочной модели. Во всех подобных языках присваивание переменных с типом, не являющимся базовым, ведет к копированию ссылки.

Потому что а - ссылка, в то время как @a - просто адрес в памяти. Меньше увлекайтесь С/С++, если пишете на Паскале.

Иногда вот думаю: для кого я полтора года книгу писал? Там ведь все это есть…

1 лайк

Адрес в @b не может быть тем же самым что в b. @b это адрес адреса, а b просто адрес.

И поэтому сравнить array of T и ^array of T компилятор ни за что не позволит, потому что у них ничего общего чтоб сравнивать:

1 это ссылочный тип, адрес в котором хранится как кишки. И только внутренности реализации функционала ссылочных типов (как не перегруженный оператор =) имеют доступ к этому адресу.
А 2 это обычный указатель. То есть адрес памяти в чистом виде. Ну, разве что в данном случае он типизированный.

В языках со статичной типизацией надо сначала приводить к чему то общему, что можно сравнить.


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


Ну а для вашего случая, если хотите чтоб = работало по вашему - перегружайте его:

function operator=<T>(a,b: array of T): boolean; extensionmethod :=
(a.Length=b.Length) and a.SequenceEqual(b);

begin
  var a := Arr(1,2,3);
  var b := Arr(1,2,3);
  object.ReferenceEquals(a,b).Println; // обычное сравнение адресов
  (a=b).Println; // вызов перегруженного оператора
end.

Правда, сейчас почему то не работает… a=b в этом коде превращается в обычный IL оператор ceq, вместо вызова перегруженного оператора…

@Admin вроде что то про это было в issue, но найти не могу… Это новая issue или как?

1 лайк

Да, это Issue. Работает с чем угодно кроме как с обобщенными массивами. Мистика.

unit u1;

type
  
  t1 = abstract class
    // обязательно видимость private
    private function f1: byte; abstract;
  end;
  t2 = class(t1)
    private function f1: byte; override := 0;
  end;
  
  // обязательно ещё 1 промежуточный тип между t2 и t4
  t3 = class(t2) end;
  
end.

Здесь явно нарушение инкапсуляции - Вы используете детали реализации класса t1 в t2. Минимальный уровень доступа здесь должен быть protected, если требуется переопределение метода. То, что язык позволяет - вовсе не значит, что так делать правильно. Глобальные переменные язык тоже позволяет использовать, но это устаревшая и неверная практика на сегодня.

В паскале единица инкапсуляции это файл а не класс. C# разрешает создавать вложенные классы как раз чтоб не выносить доступ к приватным членам из класса.

Данная конструкция позволяет сделать f1 невидимой из t4. В C# так же красиво не сделать, даже с вложенными классами.

То что вы не умеете правильно использовать фичу языка, вместо этого применяя правила другого языка и игнорируя случаи, которые не сходятся - это не проблема компилятора.

Я применяю принципы ООП, а не другого языка. В любом случае, это обращение к деталям реализации. Это - плохой тон. Зависеть надо от интерфейса типа, а не от того, как и что изнутри в нём реализовано. При нужде переопределения членов типов прекрасно подходит protected и override. В данном же случае при изменении реализации изнутри в чужом классе (здесь - t1) у Вас (здесь - t2) в коде могут начаться сбои. Здесь правильно будет (и возможно) использовать модификатор protected. Я проверил в FPC 3.0.0 (TutorialsPoint - здесь смотрел):

//fpc 3.0.0

program HelloWorld;

{$MODE OBJFPC}
{$M+}

type
  T1 = class
  private
    function f(): byte; virtual; abstract;
  end;
  
  T2 = class(T1)
  private
    function f(): byte; override;
  end;

function T2.f(): byte;
begin
end;  
  
begin
end.

, компилятор проглатывает данный код, но, снова повторюсь, модификатор доступа неверный.

В C#, подобная проблема всплывает при написании вложенных типов. Это - проблема, это - нарушение инкапсуляции, но по-другому здесь не сделать. Пример - написание структур-перечислителей в коллекциях.

Да, это временами раздражает, но это почти так. Я бы сказал не файл, а модуль (файл не может содержать более одного модуля, поскольку имя модуля должно совпадать с именем файла). В пределах модуля все, что описано “выше” и не является телом процедуры или функции, видно “ниже” - это причина, по которой нельзя во вложенном блоке описать переменную, которая описана в объемлющем блоке. После старых языков типа Algol или PL/1 меня это раздражает временами, но так уж задумано было. Попытаться иначе сделать - потеряем совместимость с “древними” паскалями, да и я как-то уже спрашивал разработчиков на эту тему, получил ответ, что проблемы где-то в IL возникают. Т.е. нельзя описать во вложенном блоке переменную, ранее описанную во внешнем блоке, потому что память под все эти var распределяется один раз. В том же Алгол-60 (и более поздних языках) конструкция вида

for i := 1 step 1 to 1000 do
begin
    integer k;
    k := 0;
    for x := -2.5 step 0.5 to 3.2 do
    begin
        real y;
        y := sqrt(abs(sin(x + 0.18))-1);
        k := k + if y > 0 then 1 else -1
    end
end;

приведет к тому, что память под k будет выделяться и освобождаться 1000 раз, а под у - 29000 раз. Т.е. когда человек писал в этих языках программу, он должен был очень хорошо думать (каждая ячейка памяти в тем времена на вес золота была, и память тех лет все никак не дает авторам нынешних задач для школ и вузов избавиться от понятия “задача, эффективная по памяти”). На самом деле, диспетчеры/супервизоры памяти и сборщики мусора тогда еще не были в моде, и “освобождение памяти” было всего лишь подсказкой компилятору, что освободившуюся память теперь можно отвести под другую переменную. А ведь в блоке можно было и функцию описать, и процедуру. В современных системах 29 тысяч раз вызвать GC - это будет катастрофа.

P.S. Посмотрите, что будет, если в приведенном примере integer сократить до int, а real заменить на double. Поняли теперь, откуда у С “ноги растут”? ))

1 лайк

Цитата:

Кроме того, скрывать реализацию интерфейсов невозможно, потому что у вас запрещены все модификаторы доступа кроме public для элементов интерфейса.

Не вижу трагедии. Явная реализация позволяет скрыть реализуемые члены интерфейса. Точнее, требуется приведение к типу интерфейса для получения доступа к явно реализованным его членам.

Разве? У меня не позволяет:

type
  i1 = interface
    procedure p1;
  end;
  
  //Ошибка: Функция класса t1, реализующая метод p1 интерфейса i1, должна быть нестатической с уровнем доступа public 
  t1 = class(i1)
    
    private procedure i1.p1;
    begin
      
    end;
    
  end;
  
begin end.

Ваш код неверен. Тем более, я про модификаторы доступа ничего не говорил, заметьте. В том же C# модификатор доступа у членов, явно реализующих интерфейс, указывать нельзя. А здесь - public.

type
  I = interface
    procedure P();
  end;
  
  T = class(I)
  public
    procedure I.P();
    begin
    end;
  end;
  
begin
  var instance := new T();
  I(instance).P(); // <--
end.

Тогда что значит

То и значит. Код - выше.

А, понятно, но по моему это баг:

type
  I = interface
    procedure P();
  end;
  
  T = class(I)
  public
    procedure I.P();
    begin
    end;
  end;
  
begin
  var instance := new T();
  instance.P(); //Ошибка: Неизвестное имя 'P'
end.

@Admin так задумано?

Это - не баг. Это - явная реализация интерфейса. Она так работает и надо это принять. В C# также (за исключением того, что public и любой другой модификатор ставить нельзя рядом с таким членами).

Но, если бы это действительно было ошибкой, то в чём бы тогда состояло отличие неявной реализации интерфейса от явной? В чём смысл последней тогда был бы?

Что действительно мелкая недоработка - так то, что Intellisence даже без I(...) будет по нажатию точки показывать P.

1 лайк

В первую очередь чтоб реализовывать одноимённые методы из 2 интерфейсов.

Но 2 одноимённых вызываемых метода не может быть, так что в остальном всё логично.

И в последнюю (если без всего остального). Вторая задача явной реализации интерфейсов состоит в скрытии и разграничении реализуемых членов интерфейсов. Без неё - они не столь полезны.

Нелогично откидывать часть функционала явных реализаций интерфейсов. Как Вы, например, реализуете два идентичных по сигнатуре метода с разных интерфейсов? Никак, если убрать такую составляющую явной реализации как скрытие. А что, если они выполняют разные задачи? И оба нужны. Тогда мы потеряем возможность их реализовать. Сами же такие реализации позволяют нам определить “дополнительные” члены типов (доступные только через явное приведение к типу интерфейса), имитируя множественное наследование без некоторых его проблем, какие имеются, например, в C++.

Ну конечно, ради этого в C# вводилась явная реализация интерфейсов.

Почему при:

"C:\Program Files (x86)\PascalABC.NET\pabcnetc.exe" "C:\Users\Windows\Documents\SharpDevelop Projects\NETMouse - .NET release\NETMouse - .NET release\Examples\PascalABC.NET\Arrays_1.pas"

в .bat файле выбрасывается ошибка (запускаю через Visual Studio автоматически после компиляции проекта):

Необработанное исключение: System.IO.IOException: Неверный дескриптор.
1>  
1>     в System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
1>     в System.Console.GetBufferInfo(Boolean throwOnNoConsole, Boolean& succeeded)
1>     в PascalABCCompiler.ConsoleCompiler.Main(String[] initialArgs)

?

Подобная проблема присутствует и здесь.