.. highlightlang:: us .. index:: CreateObject .. index:: Excel, Workbooks .. _uniscript-objekte: .. _uniscript-objects: UniScript-Objekte ================= In Kapitel :ref:`activex-schnittstelle` wurde die Verwendung von ActiveX-Objekten in UniScript gezeigt. :: xls = CreateObject("Excel.Application") xls.Visible = 1 xls.Workbooks.Add() xls.ActiveSheet.Range("b1").Value = "sin(x)" y = sin(linspace(0, 2*PI)) xls.ActiveSheet.Range("b2:b101").Value = y' In den folgenden Abschnitten soll gezeigt werden, wie Objekte mit UniScript erzeugt und verwendet werden. .. index:: obj_create .. _einfuhrung-objects: .. _introduction-objects: Einführung ---------- UniScript-Objekte werden mit der Funktion :ref:`obj_create` erzeugt. Diese Funktion erzeugt ein leeres Objekt. Diesem Objekt können anschließend Variablen und Funktionen zugefügt werden. :: c = obj_create() c.unit = "Nm" c.val = [1,2,3,4] Das Objekt *c* hat nun zwei Variablen *unit* und *val*. Alternativ kann die kürzere Objekt-Konstruktor-Schreibweise verwendet werden. :: c = [. unit = "Nm", val = [1,2,3,4] ] Der Punkt hinter der öffnenden eckigen Klammer ("[") hat den Zweck, dass der Interpreter (und der Programmierer) erkennen kann, dass es sich nicht um ein gewöhnliches Array handelt. Ohne Punkt würde in diesem Fall eine Variant-Matrix mit 5 Elementen erzeugt: :: c = [unit = "Nm", val = [1,2,3,4] ] // c ist hier kein Objekt Objekte können an Variablen zugewiesen werden. Die Anweisung :: b = c; bewirkt, dass *b* und *c* auf das selbe Objekt zeigen. Die Anweisung ``b = c;`` kopiert also kein Objekt. Das Objekt kann über den Zeiger *b* oder *c* geändert werden. :: b.unit = "kg"; print c.unit // => "kg" Bei gewöhnlichen UniScript-Vektoren und Matrizen, werden die Elemente dagegen kopiert: :: v1 = [1,3,7,123] v2 = v1 v2[1] = 11 print v1[1] // => 1 Schreibt man nun ``c = 0``, zeigt *c* nicht mehr auf das Objekt, es ist nur noch über *b* erreichbar. ``b = 0`` führt dazu, dass das Objekt gar nicht mehr erreichbar ist. Es wird dann vom sogenannten garbage collector aus dem Speicher entfernt. Der garbage collector kann auch vom Programmierer aufgerufen werden (siehe :ref:`gc` im Index der Online-Hilfe). Variablen können anstatt über die Schreibweise :: Zeiger-auf-Objekt "." Name = Wert auch über die Indexschreibweise erzeugt oder geändert werden. Anstatt :: c.unit = "Nm" kann auch :: c["unit"] = "Nm" geschrieben werden. Falls der Variablen-Name kein gültiger UniScript-Bezeichner ist, z. B. weil er Leerzeichen, Sonderzeichen oder Umlaute enthält, oder der Bezeichner ein UniScript-Schlüsselwort ist, ist nur die zweite Schreibweise möglich. Ein Objekt führt intern zwei Tabellen, eine mit Namen (Strings) und eine mit Zahlen-Schlüsseln. :: c = obj_create() c[1] = 123 c["1"] = 456 *c* enthält nun zwei Key-Wert-Paare, eins in der Tabelle mit Zahlenschlüsseln und das andere in der Tabelle mit den Zeichenketten-Schlüsseln (String-Keys). In der Objekt-Konstruktor-Schreibweise kann dies auch in der Form :: c = [. [1] = 123, ["1"] = 456] geschrieben werden. Die Zahlenkeys sind vom Typ ``double``. Sie können auch negativ sein und brauchen auch keine ganzen Zahlen zu sein:: c[-4] = "hello" c[0] = 1+2i c[16237623] = -11 c[-1.234] = 1.234 c[1e200] = 123 UniScript-Objekte enthalten Funktionen, die auch als Member-Funktionen oder Methoden des Objekts bezeichnet werden. :: k = c.keys(-1e308, +1e308) liefert alle Schlüssel des Objekts *c* aus der Tabelle mit Zahlenschlüsseln. *k* ist ein Vektor mit den Schlüsselwerten. In der Online-Hilfe finden Sie die Beschreibung aller Methoden unter den Namen ``obj_xxx``, also z. B. :ref:`obj_keys`. Es ist kein Fehler auf einen Index zuzugreifen, für den kein Wert existiert: ``a = c[111]`` liefert 0.0. Mit der Methode ``has_key`` (:ref:`obj_has_key`) kann geprüft werden, ob der Key existiert: ``c.has_key(123)`` liefert TRUE (1), wenn ``c[123]`` existiert und FALSE (0), wenn ``c[123]`` nicht existiert. Objekte können als Variablen Zahlen, komplexe Zahlen und Strings, sowie Vektoren und Matrizen dieser drei Typen enthalten. Außerdem Zeiger auf ActiveX-Objekte (siehe :ref:`activex-schnittstelle`) und Variant-Matrizen. Objekte können auch Zeiger auf Objekte enthalten. Dadurch sind Datenstrukturen wie Listen möglich: :: list = obj_create(); list.val = 123; list.next = obj_create(); list.next.val = 456; list.next.next = 0; bzw. in Objekt-Konstruktor-Schreibweise: :: list = [. val = 123, next = [. val = 456, next = 0]] .. _objekt-konstruktor-schreibweise: .. _object-constructor-syntax: Objekt-Konstruktor-Schreibweise ------------------------------- **Grammatik** :: prop_val: NUMBER { counter = counter + 1; } /* Regel 1 */ |STRING { counter = counter + 1; } /* R2 */ | "(" exp ")" { counter = counter + 1; } /* R3 */ | NUMBER "=" exp { counter = NUMBER + 1; } /* R4 */ | identifier "=" exp /* R5 */ | "[" exp "]" "=" exp /* R6 */ | STRING "=" exp /* R7 */ ; prop_list: /* empty */ /* R8 */ | prop_val /* R9 */ | prop_list "," prop_val /* R10 */ ; constructor: "[." { counter = 1; } /* Regel 11 */ prop_list "]" ; Beispiele: :: [.] // Regel 11, Regel 8 [.123] // R11, R9, R1 [.(1+2i)] // R11, R9, R3 [. "test 1" = 123] // R11, R9, R7 [. a = 1, b = 2] // R11, R9, R10, R5 Weitere Beispiele:: a = [. 1, 2, 3] entspricht:: a = [.]; a[1] = 1; a[2] = 2; a[3] = 3; :: a = [. 5 = 1, 2, 3] entspricht:: a = [.]; a[5] = 1; a[6] = 2; a[7] = 3; :: a = [. [5] = 1, 2, 3] entspricht:: a = [.]; a[5] = 1; a[1] = 2; a[2] = 3; .. index:: obj_copy, obj_count, obj_count_num, obj_count_str, obj_create .. index:: obj_has_key, obj_info, obj_keys, obj_load, obj_lookup, obj_parent .. index:: obj_remove, obj_save, obj_set_at, obj_set_methods, obj_set_parent .. index:: obj_set_str_return, set_method_table .. _standard-methoden: .. _standard-methods: Standard-Methoden ----------------- Die folgenden Funktionen können immer in der Form:: obj_xxx(o, ...) oder, kürzer, in der Form:: o.xxx(...) geschrieben werden. Also z. B. ``a = o.lookup("key")`` anstatt ``a = obj_lookup(o, "key")``. Hat die Funktion nur einen Parameter (*obj*) können bei der Punktschreibweise die Klammern weg gelassen werden:: n = o.count; oParent = o.parent; .. us.makeindex obj, UniScript-Objekt-Funktionen .. include:: ../ftab/obj.ftab .. _methoden-fur-objekte: .. _methods-for-objects: Methoden für Objekte -------------------- Neben den Standard-Methoden wie ``copy``, ``keys``, ``set_at``, deren Beschreibung Sie in der Online-Hilfe unter :ref:`obj_copy`, :ref:`obj_keys`, :ref:`obj_set_at` finden, können einem Objekt weitere Methoden zugefügt werden. Um ein Objekt um Funktionen (auch Member-Funktionen oder Methoden genannt) zu erweitern muss ein Objekt erzeugt werden, dass die Namen der Methoden den UniScript-Funktionsnamen zuordnet. Beispiel:: def my_methods() { return [. test = "my_test"]; } def my_create() { this = [.]; set_method_table(this, "my"); return this; } Die Funktion "my_test" kann folgendermaßen geschrieben werden:: def my_test(this, a, b) { return a+b; } Falls nun die Anweisungen :: obj = my_create(); obj.test(3,4); ausgeführt werden, versucht der UniScript-Interpreter die Funktion "my_test" aufzurufen. Der Interpreter übergibt der Funktion "my_test" als ersten Parameter zusätzlich das Objekt *obj* als ersten Parameter. Die Funktion ``my_create`` bezeichnet man als Konstruktor-Funktion, da sie ein Objekt konstruiert. Sie erzeugt zunächst ein UniScript-Objekt:: this = [.]; Der Aufruf :: set_method_table(this, "my"); bewirkt, dass die Funktion ``my_methods`` aufgerufen wird. ``my_methods`` erzeugt ein Objekt mit den Methodennamen. :ref:`set_method_table` kopiert dieses Objekt in eine globale Tabelle (``g_method_table``) und setzt das Objekt mit den Methoden für *this* mit der Funktion :ref:`obj_set_methods`. Alle mit ``my_create()`` erzeugten Objekte erhalten das selbe Objekt mit den Methoden, d. h. ``my_methods()`` wird nur einmal aufgerufen. Zum Schluß gibt der Konstruktor das erzeugte Objekt zurück:: return this; .. index:: Überladen (Operatoren) .. _uberladen-von-operatoren: Überladen von Operatoren ------------------------ Die meisten Operatoren können nicht direkt auf Objekte angewendet werden. Die folgenden Zeilen führen zu einem Laufzeitfehler "+: Operand type incorrect":: a = [. val = 123]; b = [. val = 456]; c = a + b; Die Anweisung :: a == b liefert den Wert 0, d.h. *a* ist nicht gleich *b*. Setzt man ``b.val = 123`` liefert ``a == b`` immer noch FALSE. Der Grund ist, das der Gleichheitsoperator nur überprüft ob *a* und *b* auf das selbe Objekt zeigen. D.h. nach den Anweisungen :: a = obj_create(); a.val = 123; b = a; bzw. :: a = [.val = 123]; b = a; ist der Ausdruck ``a == b`` erfüllt. Entsprechend verhält sich der Ungleich-Operator (a != b). Der Not-Operator (!a) dient dazu festzustellen, ob die Objekt-Erzeugung erfolgreich war. :: a = [.]; if (!a) { error() } Man könnte auch schreiben:: a = [.]; if (a == 0) { error() } Ein Zeiger auf ein Objekt kann also auch mit einer Zahl verglichen werden, wobei die 0 die einzige sinnvolle Zahl ist. Die Bedeutung dieser Operatoren kann umdefiniert werden. Beispiel:: def my_add(this, b) { result = this.copy(); result.val = this.val + b.val; return result; } def my_add2(this, b) { result = this.copy(); result.val = b.val + this.val; return result; } def my_print(this) { print this.val; } def my_format(this) { return sprintf("%s=%f", this.type, this.val); } def my_tonumber(this) { return this.val; } def my_methods() { return [. tonumber = "my_tonumber", __print__ = "my_print", __format__ = "my_format", __add__ = "my_add", __add2__ = "my_add2" ]; } def my(num) { this = [. type = "my"]; set_method_table(this, "my"); if (nargsin() == 1) { this.val = num; } else { this.val = 0; } return this; } a = my(2); b = my(3); d = my(3); printf("\na + b = %f\n", (a+b).tonumber()); .. index:: __destructor__, __print__, __format__, __setproperty__ .. index:: __getprop__, __setprop__, __veceval__, __vecassign__ .. index:: __catcol__, __catcol2__, __catrow__ .. list-table:: :header-rows: 1 * - Operator - Anmerkung * - ``__destructor__`` - Destruktor. Diese Funktion wird aufgerufen, wenn der Garbage-Collector ein Objekt zerstört. * - ``__print__`` - print a. * - ``__format__`` - Zeichenkette für Debugger erzeugen. * - ``__getproperty__`` - v = a.b auswerten. * - ``__setproperty__`` - a.b = v auswerten. * - ``__getprop__`` - v = a.b auswerten. * - ``__setprop__`` - a.b = v auswerten. * - ``__veceval__`` - a[idx] auswerten. * - ``__vecassign__`` - a[idx] = v auswerten. * - ``__catcol__`` - Verkettung ([a,b]). * - ``__catcol2__`` - Verkettung ([a,b]). Wird aufgerufen, wenn a keine ``__catcol__``-Methode hat. * - ``__catrow__`` - Verkettung ([a;b]). * - ``__catrow2__`` - Verkettung ([a;b]). Wird aufgerufen, wenn a keine ``__catrow__``-Methode hat. ``__setprop__`` Mit ``__setprop__`` kann eine UniScript-Funktion aufgerufen werden, wenn eine Anweisung der Form ``obj.prop = value`` ausgeführt wird. Beispiel:: def my_setprop(obj, val) { obj["prop"] = val; } Innerhalb der property-set-Funktion darf die Form ``obj.property = value`` nicht verwendet werden, sonst würde sich die Funktion endlos rekursiv aufrufen. Es sollte eine der Formen :: obj["prop_name"] = val; obj.set_at("prop_name", val); obj_set_at(obj, "prop_name", val); verwendet werden. ``__setproperty__`` ``__setproperty__`` hat einen Parameter zusätzlich: *ssProp*. Beispiel:: def my_setprop(obj, val, ssProp) { obj[ssProp] = val; } ``__getprop__`` ``__getprop__`` wird verwendet, wenn ein Ausdruck der Form ``obj.prop`` auf der rechten Seite verwendet wird: ``val = obj.prop``. :: def my_getprop(obj) { return obj["prop_name"]; } .. index:: __getproperty__ ``__getproperty__`` :: def my_getproperty(obj, ssProp) { return obj[ssProp]; } ``__veceval__`` Wird bei Ausdrücken wie ``v = obj[index]`` aufgerufen. :: def my_veceval(obj, index) { return obj.lookup(index); } ``__vecassign__`` Wird bei Ausdrücken wie ``obj[index] = val`` aufgerufen. :: def my_vecassign(this, index, val) { this.set_at(index, val); } Beispiel: :: def my_getprop(this) { print "in getprop" return this.lookup("prop"); } def my_getproperty(this, ssProp) { print "in getproperty" return this.lookup(strupper(ssProp)); } def my_setprop(this, val) { print "in setprop" this.set_at("prop", val); } def my_setproperty(this, val, ssProp) { print "in setproperty" return this.set_at(strupper(ssProp), val); } def my_veceval(this, idx) { print "in veceval" return this.lookup(idx); } def my_vecassign(this, idx, val) { print "in vecassign" this.set_at(idx, val); } def my_methods() { return [. __setprop__ = [. prop = "my_setprop"], __getprop__ = [. prop = "my_getprop"], __getproperty__ = "my_getproperty", __setproperty__ = "my_setproperty", __veceval__ = "my_veceval", __vecassign__ = "my_vecassign"] } def my(num) { this = [.type = "my"]; set_method_table(this, "my", 1); if (nargsin() == 1) { this[1] = num; } else { this[1] = 0; } return this; } a = my(11) a.prop = 123 print a.prop a.xyz = 456 print a.xyz a[11] = 12 print a[11] .. index:: __add__, __sub__, __div__, __ediv__, __ldiv__, __mod__ .. index:: __mul__, __emul__, __power__, __epower__, __shl__, __shr__ .. index:: __and__, __or__, __xor__, __bitand__, __bitor__ **Arithmetische binäre Operatoren** .. list-table:: :header-rows: 1 * - Operator - Anmerkung * - ``__add__`` - Additions-Operator (a + b). * - ``__sub__`` - Subtraktions-Operator (a - b). * - ``__div__`` - Divisision (a/b). * - ``__ediv__`` - Elementweise Divisision (a ./ b). * - ``__ldiv__`` - Links-Division (a \ b). * - ``__mod__`` - Modulus-Operator (a % b). * - ``__mul__`` - Multiplikation (a \* b). * - ``__emul__`` - Elementweise Multiplikation (a .\* b). * - ``__power__`` - Exponentiation (a ^ b). * - ``__epower__`` - Elementweise Exponentiation (a .^ b). * - ``__shl__`` - Shift-Links (a << b). * - ``__shr__`` - Shift-Rechts (a >> b). * - ``__and__`` - Und-Operator (a && b). * - ``__or__`` - Oder-Operator (a || b). * - ``__xor__`` - Exklusiv-Oder-Operator (a @ b). * - ``__bitand__`` - Bitweises And (a & b). * - ``__bitor__`` - Bitweises Or (a | b). Von allen arithmetischen binäre Operatoren existiert die Form ``__xxx2__``, z. B. ``__add2__``. Diese Funktionen werden aufgerufen, wenn der linke Operand keine ``__add__``-Funktion besitzt. **Arithmetische unäre Operatoren** .. list-table:: :header-rows: 1 * - Operator - Anmerkung * - ``__uminus__`` - Vorzeichen-Minus (-a). * - ``__trans__`` - Transponierung (a'). * - ``__etrans__`` - Elementweise Transponierung (a.'). * - ``__bitcmpl__`` - Bitweises Complement (~a). **Vergleichsoperatoren** Die Operatoren ``==``, ``!=`` und ``!`` haben Default-Operatoren, die die Adressen der Objekte vergleichen. .. list-table:: :header-rows: 1 * - Operator - Anmerkung * - ``__lt__`` - Kleiner-Als-Operator (a < b). * - ``__le__`` - Kleiner-Gleich-Operator (a <= b). * - ``__eq__`` - Gleichheitsoperator (a == b). * - ``__ne__`` - Ungleich-Operator (a != b). * - ``__gt__`` - Größer-Als-Operator (a > b). * - ``__ge__`` - Größer-Gleich-Operator (a >= b). * - ``__not__`` - Nicht-Operator (!a). Von allen binären Vergleichsoperatoren existiert die Form ``__xxx2__``, z. B. ``__gt2__``. Diese Funktionen werden aufgerufen, wenn der linke Operand keine ``__gt__``-Funktion besitzt. .. index:: Vererbung .. _vererbung: Vererbung --------- Manchmal hat man eine Gruppe von Objekten, die gemeinsame Eigenschaften haben. Beispiel: Linie, Kreis, Rechteck. Die gemeinsamen Eigenschaften sind z. B. die Position, oder die Dicke der Randlinie. Die individuellen Eigenschaften wären beim Rechteck die Seitenlängen a und b, beim Kreis der Durchmesser d und bei der Linie die Endposition p2. Es wäre dann sinnvoll ein Objekt zu erzeugen mit den gemeinsamen Eigenschaften, das als Basis-Objekt bezeichnet wird. Die Child-Objekte erben die Eigenschaften des Basis-Objekts (auch Parent-Objekt oder Superclass-Objekt genannt). In diesem Beispiel soll das Basis-Objekt ``shape`` genannt werden und die Objekte, die die Eigenschaften von shape erben sollen, heissen circle und rect. :: def shape_set_name(this, s) { this.name = s; } def shape_methods() { return [. set_name = "shape_set_name"]; } def shape(x, y) { this = [. x = x, y = y, name = ""]; set_method_table(this, "shape"); return this; } def circle_plot(this) { printf("\n", this.x, this.y, this.d); } def circle_methods() { return [. plot = "circle_plot"]; } def circle(x, y, d) { this = [. __parent__ = shape(x, y), d = d]; return set_method_table(this, "circle"); } def rect_plot(this) { printf("\n", this.x, this.y, this.a, this.b); } def rect_set_name(this, s) { this.name = strupper(s); } def rect_methods() { return [. plot = "rect_plot", set_name = "rect_set_name"]; } def rect(x, y, a, b) { this = [. __parent__ = shape(x, y), a = a, b = b]; return set_method_table(this, "rect"); } .. _funktionsaufrufe-mit-uniscript-objekten: Funktionsaufrufe mit UniScript-Objekten --------------------------------------- Einige Programmiersprachen haben bei Funktionsaufrufen die folgenden Möglichkeiten: * Benannte Argumente * Argumente mit Defaultwerten * Variable Länge der Parameterliste In UniScript sind diese Techniken nicht möglich. Sie können jedoch mit Hilfe von Objekten simuliert werden. Benannte Argumente:: def test_named(arg) { return arg.a + arg.b; } test_named([.b = 1, a = 2]) test_named([.a = 2, b = 1]) Argumente mit Defaultwerten:: def test_default(arg) { d = [.a = 11, b = 22]; for (i in arg.keys) d[i] = arg[i]; return d.a + d.b; } test_default([.a = 1000]) test_default([.b = 1000]) test_default([.a = 1, b = 2]) test_default([.]) Variable Länge der Parameterliste:: def test_var(args) { print "test_var" for (i in args.keys(1)) { print args[i] } } test_var([. 11111, "Hallo", ([1,2,3]), (2+3i)]); test_var([. 1,2,3,4]); .. _beispiel-objects: Beispiel -------- :: def file_methods() { return [. read = "file_read", write = "file_write", name = "file_get_name", close = "file_close", __shl__ = "file_shl", __destructor__ = "file_close", __format__ = "file_format"]; } def file_open(ssFileName, ssMode) { fp = fopen(ssFileName, ssMode); if (!fp) { return 0; } this = [. fp = fp, name = ssFileName]; set_method_table(this, "file"); return this; } def file_format(file) { return sprintf("fp = %d\nname = %s", file.fp, file.name); } def file_read(file, ssType, n) { if (nargsin() == 2) { return fread(file.fp, ssType); } else { return fread(file.fp, ssType, n); } } def file_write(file, ssType, dat) { return fwrite(file.fp, ssType, dat); } def file_get_name(file) { return file["name"]; } def file_close(file) { fclose(file.fp); } // file << val def file_shl(this, other) { if (type(other) == "real") { this.write("double", other); } else if (type(other) == "string") { this.write("char", other); } else { other.write(this); } return this; } def f_int32_write(this, file) { file.write("int", this.val); } def f_int32_methods() { return [. write = "f_int32_write"]; } def f_int32(v) { return set_method_table([. type = "f_int32", val = v], "f_int32"); } def test() { file = file_open("d:/test.txt", "wb"); file << [1, 3] << f_int32([4,5,6]) << "This is a test!"; file.close(); file = file_open("d:/test.txt", "rb"); print file.read("double", 2); print file.read("int", 3); print file.read("char"); file.close(); } :sub:`id-528674`