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

Сделан новый оператор 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