Google

Новшества в языковых возможностях

Сделан новый оператор switch - выбор одной из многих веток исполнения:

switch (expr) case const1 [,const2 [,const3]] // операции case const11 // операции otherwise // операции end Для похожести на "do case","endcase" добавлены команды "do switch","endswitch"
Сделана новая операция :=@ - присвоение по ссылке: a:="a" b:=@a ? a,b // "a","a" b:="b" ? a,b // "b","b" b:=@NIL // отрывает ссылку.
Специально для удобной работы с MAP и объектами сделан вот такой цикл: (подробное описание о MAPах и объектах находится ниже) FOR value IN map_var ? value NEXT будет перебирать все элементы в map_var и класть их в value; или FOR key IN map_var KEYS ? key, map_var[key] NEXT будет перебирать все элементы в map_var и в key складывать значения ключей для доступа к текущему элементу MAP или объекта.
Макроподстановки компилируют и исполняют более сложные выражения и рекурсивные вызовы макроподстановок. Вернее обрабатываются практически все конструкции, кроме команд и операторов типа for, while и т.п.
Рациональные числа с неограниченной точностью!
Большие числа пока поддерживают только простые математические операции -+/*^%.
Чтобы задействовать данные возможности, надо просто сказать где-нибудь в программе
set(_SET_RATIONAL,.t.) или командой описанной в clip.ch set rational on|off Компилятор не поддерживает большие числа в качестве констант, т.е. при использовании выражений типа
x:=123456789012345678901234567890 компилятором будет сделана попытка преобразовать его в double, и чем это закончиться - одному сопроцессору известно.
Чтобы реально использовать большие константы, надо использовать функцию VAL()
x:=val("123456789012345678901234567890") Можно и так: x:=val("1234.567890") x:=val("1/3") Функция STR(val,len,dec) теперь поддерживает параметры len, dec любого значения. Например: ? str(val("7/3"),1000,990) Добавлена функция RSTR(val), которая выдает строку в виде "numerator/denominator", т.е. эту строку можно поместить в val() и при этом не получить никакой потери точности при преобразованиях!
? rstr(val("7/3")+val("5/6")) // ---->>> "19/6"
Хэш-код - число больше чем 1 000 000 000 - результат сворачивания строки в число по методу битовых операций с каждым байтом указанной строки. Для строк менее 20 символов вероятность получения одинаковых кодов для разных строк примерно 1/1000000. Для строк более длинного размера вероятноcть падает.
Компилятор понимает шестнадцатиричные числовые константы в виде 0x20, 0x30.
Ассоциативные массивы (AM) - подробнее в модели ОО. Если коротко, то обычный массив выглядит так: declare m[5] m[1]=11; m[2]=12; .... Ключами доступа к элементам являются непрерывный ряд чисел, ограниченный объявленным размером массива, в результате чего вставить в такой массив элемент с ключом 6 становиться проблемой и тормозом в прикладной программе. А у AM ключами являются строки, например: m=map() m:asdf=11; m:qwer=12 ? valtype(m) // 'O' //object ? len(m) // 2 ? m:qwer // 12 Фактически в качестве ключа доступа к элементу такого массива является хэш-код указанной строки, который вычисляется на этапе компиляции. Хэш-код может вычисляться компилятором еще и посредством конструкции `qwer` Например: ? m:qwer==m[`qwer`] // .t. В run-time хэш-код можно вычислить hashstr("QWER") ? m:qwer==m[hashstr("QWER")] // .t. обратите внимание на то, что QWER пишется большими буквами, это сделано из-за того, что компилятор не различает регистры букв.
Получить список ключей в АМ можно mapkeys(m) - возвращает массив с хэш-кодами, которые есть в массиве m mm=mapkeys(m) (вместо функции mapkeys() можно использовать специальный цикл, описание которого находится чуть выше)
? len(mm) // 2 for i to len(mm) ? mm[i] //вернет что-то типа 1233345677, 124321423 next Еще АМ характеризуется очень быстрым доступом к элементам, так как фактически поиск нужного элемента производиться по бинарному дереву. И вместо ascan(mm,`QWER`) лучше использовать `QWER` $ m // -> .t.
Модель ОО.

Для начала немного о встроенной ОО-модели клиппера. Она построена на основе обычных массивов и любое обращение типа

obj:attribute или obj:method() приводит к тому, что в массиве obj производится поиск элемента, у которого первый элемент совпадает с именем attribute или method, причем такой поиск производится линейно и практически является аналогом функции ascan(obj,{|x|x[1]=="attribut"}), что естественно очень плохо сказывается на производительности ОО-модели "чистого" CA-Clipper. Я конечно немного упрощаю для более простого понимания, но смысл остается такой, который я описал.

Надеюсь, теперь понятно, для чего сделаны ассоциативные массивы? ОО-модель на основе АМ работает быстрее на порядок!

При этом исчезает необходимость в конструкциях типа

obj:=tclass(class_name):new() и в самом классе tclass, который занимается тем, что обеспечивает run-time поддержку регистрации новых классов. А это увеличивает эффективность ОО-модели еще в несколько раз.

Как сделать свой класс? Очень просто:

function MyClassNew() obj:=map() // пустой объект clone(MyClass2New(),obj) // унаследовать структуру от MyClass2 clone(MyClass3New(),obj) // унаследовать структуру от MyClass3 // если уже имеются совпадающие атрибуты // или методы, то они будут заменены по // принципу "кто последний - тот и папа" obj:attribute1:=0 obj:attribute2:=date() obj:method1:=@func1() // присвоить методу указатель на функцию obj:method2:=@func2() // эти функции должны быть определены в этом же // prg-файле // если методы уже были унаследованы от других // классов - они будут переназначены на указанные. return obj // вернуть готовый объект static function func1 ::attribute1++ return NIL static function func2(self) self:attribute1-- return self Хотелось бы еще добавить два простых правила:
  • атрибут рождается при первом присвоении в него чего-нибудь, в том числе и NIL;
  • методом можно назначить или переназначить в любой момент в run-time любую функцию, объявленную как static function в этом модуле. Либо наследовать ее от другого объекта, как обычное присвоение значений. myObj1:metod1 := myObj2:=metodX
Как использовать объекты? Еще проще, вернее так же, как и в CA-Clipper. obj:=MyClassNew() obj:metod1() ? obj:attribute1 В объекте можно объявить метод destroy(), но это не совсем destructor, как это принято в языках 3 поколения. Есть переменная local myObj:=myclassNew() В ней лежит объект. При выходе из тела функции эта переменная будет уничтожена со всеми вложенными в ней данными. Берем пример, что в этой myObj имеется атрибут myObj:hFile:=fopen(trali_vali). При уничтожении myObj необходимо закрыть hfile, но компилятор об этом не знает, компилятор (вернее не компилятор, а виртуальная машина) знает только о том, что в hFile лежит число и уничтожит только число, а файл останется открытым! Вот для этого и делается метод destroy, который будет вызываться (если таковой имеется) перед уничтожением переменной myObj. static function my_destroy() fclose(::hFile) return
Контроль изменений аттрибутов в объекте.

Если надо контролировать изменения атрибутов объекта, то сделайте метод modify и установите mapmodify(map_obj, .t. ) . Тогда все изменения будут приходить сначала в метод modify и, только после этого, изменится значение атрибута, на то значение которое вернет метод modify()


Регенерация объектов.

CLIP умеет хранить в MEMO полях данные любого типа, в том числе и объекты. Но при записи объекта в БД не сохраняются методы объекта (потому что нет смысла хранить код с каждым экземпляром, да и методы имеют тенденцию к изменению).

А при восстановлении объекта происходит следующее: раскодируюся данные ( var ), если тип полученных данных - объект, то у объекта запрашивается атрибут CLASSNAME и делается попытка вызвать функцию _recover_&var:CLASSNAME(var), в которую передается восстановленный объект. Эта функция и должна назначить "утерянные" при записи методы.

Этим же механизмом можно пользоваться и для передачи объектов в виде строки. Например по почте или TCP-соединению :)

Вот пример использования:

x:=asdfNew() /* создается объект */ ? "x:m1",x:m1() /* проверка работоспособности */ ? "x:m2",x:m2() y:=var2str(x) /* объект кодируется в строку */ /* or field->memo_field:=x */ ? "y=",y z:=str2var(y) /* раскодируется обратно, при этом вызывается _recover_asdf() */ /* or z:=field->memo_field */ ? "z=",z ? "z:m1",z:m1() /* проверка на работоспособность */ ? "z:m2",z:m2() ? return function asdfNew() local o:=map() o:classname := "ASDF" o:a1 := "asdf" o:a2 := "qwer" _recover_asdf(o) return o function _recover_asdf(o) o:m1 :=@ asdf_1() o:m2 :=@ asdf_2() ? "recovering" return o static function asdf_1 ? "asdf_1",::a1 return ::a1 static function asdf_2 ? "asdf_2",::a2 return ::a1
Благодаря такому устройству ОО-модели и возможности компилировать быстрый код посредством трансляции в С-программу, появилась возможность написать стандартные классы TBrowse, Get на самом CA-Clipper.
При этом визуально производительность этих классов не хуже, чем написанные на чистом C в стандартном CA-Clipper.


© Ю.Хныкин, uri@itk.ru, 2001