Спасибо за ответы. То есть всегда нужно помнить, что динамический массив это последовательность и соответственно применять метод последовательности. Хорошо, тогда если a = b
сравнивает адреса и @a = @b
делает то же самое, если я не ошибаюсь, то зачем повторяться? Может быть лучше было бы если бы по a = b
сравнивалось содержимое массивов? И, кстати, в таком случае, почему не работает такое a = @a
?
Потому что PascalАВС.NЕТ - язык, базирующийся на ссылочной модели. Во всех подобных языках присваивание переменных с типом, не являющимся базовым, ведет к копированию ссылки.
Потому что а - ссылка, в то время как @a - просто адрес в памяти. Меньше увлекайтесь С/С++, если пишете на Паскале.
Иногда вот думаю: для кого я полтора года книгу писал? Там ведь все это есть…
Адрес в @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 или как?
Да, это 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
. Поняли теперь, откуда у С “ноги растут”? ))
Кроме того, скрывать реализацию интерфейсов невозможно, потому что у вас запрещены все модификаторы доступа кроме 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
.
В первую очередь чтоб реализовывать одноимённые методы из 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)
?
Подобная проблема присутствует и здесь.