Operatoren

Mittlerweile sind Operatoren in C4Script auf die gleiche Weise benutzbar wie aus diversen Programmiersprachen bzw. der Mathematik bekannt.
Es gelten also insbesondere die üblichen Prioritätsregeln (Punkt-Vor-Strich und Verwandte). Genaueres kann der unten aufgeführten Operatorenliste entnommen werden.
Um die aufeinanderfolgende Berechnung bestimmter Ausdrücke zu erzwingen, können auch Klammern gesetzt werden.
Folgende Operatoren werden unterstützt:
Priorität Operator Beschreibung Standort Typen (Rückgabe, erwartet)
17 ++ Erhöht den Wert der vorangestellten Variable um 1. postfix (nur 1 Parameter) int, Referenz
17 -- Vermindert den Wert der vorangestellten Variable um 1. postfix (nur 1 Parameter) int, Referenz
16 ++ Erhöht den Wert der nachgestellten Variable um 1. prefix Referenz, Referenz
16 -- Vermindert den Wert der nachgestellten Variable um 1 prefix Referenz, Referenz
16 ~ Negiert den nachgestellten Wert bitweise prefix int, int
16 ! Negiert den nachgestellten Wert logisch prefix bool, bool
16 + macht nichts (für Abwärtskompatibilität zu Schreibweisen wie "+5") prefix int, int
16 - bildet die Gegenzahl zum nachgestellten Wert prefix int, int
15l ** Potenziert zwei Werte postfix int, int/int
14l / Dividiert zwei Werte durcheinander postfix int, int/int
14l * Multipliziert zwei Werte miteinander postfix int, int/int
14l % gibt den Divisionrest der Division zweier Werte zurück postfix int, int/int
13l - subtrahiert zwei Werte postfix int, int/int
13l + addiert zwei Werte postfix int, int/int
12l << führt eine Links-Bitschiebe-Operation aus postfix int, int/int
12l >> führt eine Rechts-Bitschiebe-Operation aus postfix int, int/int
11l < gibt zurück, ob der erste Wert kleiner ist als der zweite postfix bool, int/int
11l <= gibt zurück, ob der erste Wert kleiner oder gleich dem zweiten ist postfix bool, int/int
11l > gibt zurück, ob der erste Wert größer als der zweite ist. postfix bool, int/int
11l >= gibt zurück, ob der erste Wert größer oder gleich dem zweiten ist. postfix bool, int/int
10l .. Hängt die zwei Strings (falls nötig konvertiert) aneinander. postfix string, string|bool|int|id/string|bool|int|id
Hängt zwei Arrays aneinander. array, array/array
Ergänzt die vorangestellte Map mit Werten der nachgestellten Map bzw. überschreibt bestehende Werte. map, map/map
9l == gibt zurück, ob zwei Werte gleich sind postfix bool, any/any
9l != gibt zurück, ob zwei Werte nicht gleich sind. postfix bool, any/any
9l S= gibt zurück, ob zwei Strings gleich sind postfix bool, String/String
9l eq gibt ebenfalls zurück, ob zwei Strings gleich sind postfix bool, String/String
9l ne gibt ebenfalls zurück, ob zwei Strings nicht gleich sind postfix bool, String/String
8l & führt ein bitweises And aus postfix int, int/int
6l ^ führt ein bitweises XOr aus postfix int, int/int
6l | führt ein bitweises Or aus postfix int, int/int
5l && führt ein logisches And aus postfix any, any/any
4l || führt ein logisches Or aus postfix any, any/any
3l ?? Liefert den nachgestellten Wert falls der vorangestellte nil ist, den vorangestellte andernfalls. ab #strict 3 postfix any, any/any
2r **= potenziert die vorangestellte Variable mit dem nachgestellten Wert postfix Referenz, Referenz/int
2r *= vervielfacht die vorangestellte Variable mit dem nachgestellten Wert postfix Referenz, Referenz/int
2r /= teilt den Wert der vorangestellten Variable durch den nachgestellten Wert postfix Referenz, Referenz/int
2r %= Schreibt den Divisionrest der Division des Wertes vorangestellten Variable mit dem nachgestellten Wert in die Variable postfix Referenz, Referenz/int
2r += erhöht die vorangestellte Variable um den nachgestellten Wert postfix Referenz, Referenz/int
2r -= vermindert den Wert der vorangestellten Variable um den nachgestellten Wert postfix Referenz, Referenz/int
2r <<= führt eine Links-Bitschiebe-Operation auf der vorangestellten Variable um den nachgestellten Wert aus postfix Referenz, Referenz/int
2r >>= führt eine Rechts-Bitschiebe-Operation auf der vorangestellten Variable um den nachgestellten Wert aus postfix Referenz, Referenz/int
2r ..= Hängt den nachgestellten String an den vorangestellten (beide falls nötig konvertiert). postfix Referenz, Referenz/string|bool|int|id
Hängt das nachgestellte Array an das vorangestellte. Referenz, Referenz/array
Ergänzt die vorangestellte Map mit Werten der nachgestellten Map bzw. überschreibt bestehende Werte. Referenz, Referenz/map
2r &= führt ein bitweises And aus postfix Referenz, Referenz/int
2r |= führt ein bitweises Or aus postfix Referenz, Referenz/int
2r ^= führt ein bitweises XOr aus postfix Referenz, Referenz/int
2r ??= Weist der vorangestellten Variable den nachgestellten Wert zu, falls, die vorangestellte Variable nil ist. ab #strict 3 postfix Referenz, Referenz/any
2r = weist der vorangestellten Variable den nachgestellten Wert zu postfix Referenz, Referenz/any

Erklärungen und Beispiele:

Postfix oder prefix?

Diese Eigenschaft eines Operators gibt an, ob er vor(pre) oder nach(post) seinem ersten Parameter steht.
Prefix - Operatoren haben immer nur einen Parameter (nach dem Operator), während Postfix - Operatoren in der Regel 2 Parameter haben, einen vor und einen danach. (siehe auch Operatoren ++/--)

Beispiel 1:

Log(" Ergebnis: %d", 5 + 5);
Log(" Ergebnis: %d", 12 - 5);
Log(" Ergebnis: %d", 5 * 5);

Ausgabe:

 Ergebnis: 10
 Ergebnis: 7
 Ergebnis: 25
Plus, Minus, Multiplikation u.ä. Operatoren bilden die Klasse der Postfix - Operatoren. Es werden jeweils zwei Werte (die vor bzw. nach dem Operator stehen) verrechnet.

Beispiel 2:

Log(" Ergebnis: %d", -(5 + 5));
Log(" Ergebnis: %d", ~7);
Log(" Ergebnis: %d", !0);

Ausgabe:

 Ergebnis: -10
 Ergebnis: -8
 Ergebnis: 1
Bei diesen Operatoren handelt es sich um sogenannte Prefix - Operatoren. Sie stehen jeweils vor dem zu verarbeitenden Wert.

Die Operatoren ++ und --

Die Operatoren ++ bzw. -- existierten sowohl als postfix- als auch als prefix-Operatoren. Dabei haben die postfix-Varianten noch die Besonderheit, dass sie keinen nachgestellten Wert benötigen.

Beispiel: (postfix)

Var(0) = 0;
while(Var(0)++ < 10)
  Log("%d", Var(0) )

Beispiel 2: (prefix)

Var(0) = 0;
while(++Var(0) < 10)
  Log("%d", Var(0) )
Diese beiden Beispiele sind fast identisch. In jedem Fall wird bei jeder Schleifenwiederholung Var(0) um 1 erhöht. Das Ergebnis wird jeweils mit 10 verglichen.
Doch eine wichtige Feinheit unterscheidet die beiden Operatorenvarianten: wird der postfix-Operator benutzt, so wird jeweils alte Wert der Variable zurückgegeben. D.h. das Ergebnis der Schleife im 1. Beispiel wäre eine Zahlenreihe von 1 bis 10. Denn bei dem letzten Schleifendurchlauf ist Var(0) am Anfang 9. Dann wird sie um eins erhöht (Var(0) ist dann 10), es wird aber der alte Wert (9) zurückgegeben und mit der 10 verglichen. Die Schleife wird also ein weiteres Mal durchlaufen, und es wird der Wert 10 ausgegeben.
Anders beim 2. Beispiel. Hier läuft die Schleife von 1 bis 9. Denn wenn Var(0) 9 ist und erhöht wird, so wird der neue Wert, eben 10, zurückgegeben. Dieser Wert ist nicht kleiner als 10, deshalb wird die Schleife abgebrochen.

Die Operatoren && und ||

Diese beiden Operatoren haben (ab #strict 2) eine Besonderheit. Wenn das Ergebnis bereits nach Auswertung des ersten Parameter feststeht, wird der zweite Parameter gar nicht erst ausgewertet. Beispielsweise explodiert ein Objekt durch dieses Script nicht, weil das Ergebnis 0 wäre, egal was Explode zurücklieferte:
0 && Explode(20);
Außerdem ist das Ergebnis des Operators der Wert des ersten oder zweiten Parameters, je nachdem ob einer oder beide ausgewertet wurden. Beispielsweise kann man so wenn möglich einen Ritter, und ansonsten einen Clonk erzeugen:
CreateObject(KNIG,0,0,GetOwner()) || CreateObject(CLNK,0,0,GetOwner())

Die Operatoren ?? und ??=

?? ist in manchen Sprachen als nil-coalescing Operator bekannt. Beide Operatoren prüfen, ob der vorangestellte Wert nil ist. Nur falls dieser nil ist wird der nachgestellte Ausdruck ausgewertet und das Ergebnis davon geliefert bzw. zugewiesen. Andernfalls liefert ?? den vorangestellten Wert bzw. ??= verändert die Variable nicht. Dies wird typischerweise für Arbeiten mit Default-Werten oder für Ersatzwerte von möglicherweise nil zurückgebenden Funktionsaufrufen verwendet. Im Gegensatz zu || wird explizit auf nil geprüft ohne den Wert in einen bool zu verwandeln.
Die Operatoren sind so wie nil nur ab #strict 3 verfügbar.

Beispiele:

1337 ?? 42; // = 1337
nil ?? 42; // = 42
0 ?? 42; // = 0
0 || 42; // = 42

// Standardwert für x ist 10, 0 kann aber explizit übergeben werden.
func Foo(int x)
{
	x ??= 10;
}
				

Die Operatoren .. und ..=

Diese beiden Operatoren haben abhängig von den Typen der Parameter unterschiedliche Bedeutung. .. und ..= funktionieren exakt gleich, außer dass letzterer die links angegebene Variable direkt modifiziert.

String-Konkatenation

Für Parameter der Typen string, bool, int und id wird eine String-Konkatenation durchgeführt. ints und ids werden dabei in ihre normal übliche textuelle Representation umgewandelt, während bools ebenfalls wie ints dargestellt werden. Das folgende Beispiel loggt "Lesefortschritt: 40 %":
Log("Lesefortschritt: " .. 3 + 1 .. 0 .. " %");

Array-Konkatenation

Falls beide Parameter Arrays sind werden sie zusammengehängt. Das folgende Beispiel ergibt [1, 2, 3, 10, 20, 30]:
[1, 2, 3] .. [10, 20, 30]

Map-Kombination

Falls beide Parameter Maps sind, wird die vorangestellte Map um die nachgestellte ergänzt. Das heißt, dass Schlüssel die in der vorangestellten Map nicht vorhanden sind um die der nachgestellten ergänzt werden, während in beiden Maps existierende Schlüssel auf die Werte der nachgestellten Map gesetzt werden. Damit ist es unter anderem möglich, Default-Werte vorzugeben:
var defaults = { Message = "To lazy to think of my own!", Color = RGB(255) };
var settings = defaults .. { Message = "Hello there!", Italic = true };
// settings will be { Message = "Hello there!", Color = RGB(255), Italic = true }

Prioritäten und Assoziativität

Dieses Thema ist nicht unbedingt zum Verständnis der Operatoren vonnöten. Es soll nur zeigen, wie die Mechanismen wie Punkt-vor-Strich u.ä. im Detail funktionieren.
Um einen längeren Ausdruck mit mehreren Operatoren ausrechnen zu können, muss entschieden werden, in welcher Reinfolge die Operatoren ausgeführt werden und welches Ergebnis welchem anderem Operator zu übergeben ist.
Allgemein gilt: Operatoren mit höherer Priorität werden vor Operatoren mit niedriger Priorität ausgeführt. Anmerkung: das gilt nicht für die Reihenfolge der Ausführung der Parameter. Diese werden ganz "normal" von links nach rechts ausgeführt.

Beispiel:

Log("%d", 5 * 4 + 3 < 6);
entspricht:
Log("%d", (((5 * 4) + 3) < 6));
Dabei taucht aber noch ein Problem auf: was ist zu tun, wenn "benachbarte" Operatoren dieselbe Priorität haben? Ist dann der linke oder der rechte Ausdruck zu klammern? Über diese Frage entscheidet die sog. Assoziativität. Ist ein Operator links-assoziativ, so hat jeweils der linke Operator Priorität (übrigens auch der Regelfall, denn meistens wird von links nach rechts gerechnet). Anders bei den rechts-assoziativen Operatoren, wo jeweils der rechte Operator Priorität hat.

Beispiel:

Var(0) = Var(1) = 1 + 2 + 3 + 4;
entspricht:
Var(0) = (Var(1) = (((1 + 2) + 3) + 4) );
Hier sieht man deutlich, dass der Operator "+" links-assoziativ ist, der Ausdruck "1 + 2 + 3" wird zu "(1 + 2) + 3".
Dagegen wird der Ausdruck "Var(0) = Var(1) = x" zu "Var(0) = (Var(1) = x)", da der Operator "=" rechts-assoziativ ist.
Die Angaben über Prioritäten bzw. Assoziativitäten finden sich in der obigen Liste.

Bitweise Operatoren

In der Liste der Operatoren fallen einige Operatoren auf, die bitweise Opererationen oder Bitschieben durchführen.
Erst mal eine kurze Beschreibung von Bits: der Computer rechnet intern im sog. binären Zahlensystem. In diesem System gibt es nur zwei Ziffern, nämlich 0 und 1. (wir rechnen üblicherweise im 10er-System, wir haben 10 Ziffern von 0 bis 9). Damit lassen sich die Zahlen folgendermaßen darstellen:

Das Binärsystem

(jeweils erst im Zehnersystem, dann im Binärsystem)
1 = 00000001
2 = 00000010
4 = 00000100
8 = 00001000
6 = 00000110
7 = 00000111
Dabei wird jede Ziffer als "Bit" bezeichnet. Eine Folge von 8 Bits (s.o.) nennt man ein "Byte".
Bitweise Operatoren bearbeiten Zahlen nun bitweise.

Beispiel:

~00000000 = 11111111
~00000100 = 11111011
~01100000 = 10011111
In diesem Beispiel handelt es sich um bitweises Not. D.h. jedes Bit wird einzeln negiert (aus 0 wird 1, aus 1 wird 0).
Bitweise Operatoren mit 2 Parametern verrechnen nun jeweils die Bits zweier Zahlen.

Beispiele:

10010010
& 01110111
= 00010010
10010010
| 01110111
= 11110111
10010010
^ 01110111
= 11100101
Diese drei Operatoren sind (der Reihe nach) Und (&), Oder (|) und Exklusiv-Oder (^).
Bei dem ersten Operator (Und) steht beim Ergebnis nur dann eine 1, wenn in beiden Eingaben an der entsprechenden Stelle ebenfalls eine 1 steht. "Und" hat also die folgende Wahrheitstabelle (senkrecht bzw. waagerecht sind die beiden möglichen Parameter jeweils gelistet, in der Tabelle selbst das Ergebnis):
& 0 1
0 0 0
1 0 1
Der Operator "Oder" gibt 1 zurück, wenn eins (oder beide) der Eingaben 1 sind:
| 0 1
0 0 1
1 1 1
Der Operator "Exklusiv-Oder" (XOr) verhält sich wie das "Oder", doch wird 0 zurückgegeben, wenn beide Parameter 0 sind.
^ 0 1
0 0 1
1 1 0

Anwendungen

In Clonk werden häufig Bitoperationen benutzt, wenn es darum geht, verschiedene Eigenschaften eines Objekts in einem Wert zu speichern. Das prominenteste Beispiel sind hier wohl die C4D(Category)-Werte. Hier repräsentiert jedes Bit des C4D-Wertes eine bestimmte Eigenschaft (für Einzelheiten siehe Entwickler-Dokumentation).
Auf diese Bits kann man recht bequem über die bitweisen Operatoren zugreifen. Ein C4D-Wert kann z.B. folgendermaßen aussehen:

Beispiel: (Wipf)

Category = 69640
Dieser Wert ergibt im Binär-System: (kann mit Windows-Taschenrechner umgerechnet werden)
10001000000001000
Hier sieht man, dass Bit 3, Bit 12 und Bit 16 gesetzt sind (es wird von rechts gezählt und mit 0 begonnen)
Dies entspricht der Reihe nach den C4D-Werten C4D_Living (Objekt ist ein Lebewesen), C4D_SelectAnimal (Objekt kann im Menu für Lebewesen hinzugefügt werden) und C4D_TradeLiving (Objekt ist ein Lebewesen, darf aber trotzdem verkauft werden).
Im Script kann nun recht einfach geprüft werden, ob ein bestimmtes Bit gesetzt ist: man benutzt den Operator "Und" (&), dem man den Wert und die "Maske" übergibt. In der Maske ist nur das Bit gesetzt, dass von Interesse ist. Auf diese Weise müssen im Ergebnis alle Bits, die nicht interessieren, 0 sein (denn sie sind es in der Maske). Ist nun das Bit, das interressiert, 1, so wird diese in das Ergebnis übernommen, ist es 0, so wird im Ergebnis dort auch 0 stehen. Im letzteren Fall ist sogar das gesamte Ergebnis 0, da alle anderen Bits ja auch 0 sind. Es genügt also zu überprüfen, ob das Ergebnis von [Val] & [Mask] ungleich null ist, um herauszufinden, ob ein bestimmtes Bit gesetzt ist.

Beispiel:

10001000000001000 (Wert)
& 00001000000000000 (Maske)
= 00001000000000000
Das Ergebnis ist in diesem Fall nicht 0, also ist das Bit, dass in der Maske gesetzt ist, auch im Wert gesetzt.
10001000000001000 (Wert)
& 00000000010000000 (Maske)
= 00000000000000000
Hier ist das Ergebnis 0, denn das Bit in der Maske ist nicht beim Wert gesetzt.
Es bleibt noch die Frage zu klären, wo man die entsprechende Maske denn herbekommt. Im Fall der Category-Werte sind diese bereits mit Namen mitgeliefert, sie können über C4D_[XXX] abgerufen werden. Wollte man z.B. herausfinden, ob es sich bei einem Objekt um ein Lebewesen handelt, so sähe der Script folgendermaßen aus:
if (GetCategory() & C4D_Living)
  ;...;
Auf diese Weise kann man also einzelne Bits gezielt prüfen. Doch wie soll man diese nun bearbeiten? Will man ein einzelnes Bit setzen, so wird der Operator "Oder" (|) benutzt. Dieser wird wieder auf Wert und Maske angewendet:
10001000000001000 (Wert)
| 00000000010000000 (Maske)
= 10001000010001000 (neuer Wert)
Auf diese Weise ist es allerdings immer nur möglich, ein bestimmtes Bit auf 1 zu setzen. War dieses bereits gesetzt, so bleibt der Wert unverändert. Will man nun ein bestimmtes Bit auf 0 setzen, so benutzt man wieder den "Und"-Operator und die inverse(logisch Not) Maske, also [Wert] & ~[Maske]:
10001000010001000 (Wert)
& 11111111101111111 (= ~00000000010000000)
= 10001000000001000 (neuer Wert)
Es ist auch möglich, ein bestimmtes Bit gezielt umzuschalten, also von 1 auf 0 und von 0 nach 1. Dies leistet der bitweise "Exklusiv-Oder"-Operator (^):
10001000000001000 (Wert)
^ 00000000010000000 (Maske)
= 10001000010001000 (neuer Wert)
10001000010001000 (Wert)
^ 00000000010000000 (Maske)
= 10001000000001000 (neuer Wert)

Bitschiebereien

Außer den Operatoren, die bitweises Manipulieren von Werten erlauben (s.o.) gibt es noch sog. Bitschiebe-Operatoren. Die Funktion dieser Operatoren beschränkt sich darauf (anschaulich gesprochen), an einen Bit-Wert Nullen anzuhängen (<<) bzw. Ziffern abzuschneiden (>>).

Beispiel:

  00001000000001000 << 2
= 00100000000100000

  00001000000001000 >> 2
= 00000010000000010
Mathematisch entspricht der Operator << der Multiplikation mit 2 ^ X (genau wie im Zehnersystem das Anhängen einer Null mit einer Multiplikation mit 10 gleichkommt) und der Operator >> einer Division mit 2 ^ X (mit anschließendem Abrunden).
Mit den Operatoren können vor allem Masken erstellt (1 << BitNr), und RGB-Farbwerte zusammengesetzt und auseinandergenommen werden.
PeterW,