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):
Der Operator "Oder" gibt 1 zurück, wenn eins (oder beide) der Eingaben 1 sind:
Der Operator "Exklusiv-Oder" (XOr) verhält sich wie das "Oder", doch wird 0 zurückgegeben, wenn beide Parameter 0 sind.
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,