Operatoren Operatoren - Berechnung

beatles

Mitglied
Hallo Zusammen

Für die Ausführung von Operatoren gibt es ja Regeln, welcher Operator zuerst betrachtet werden muss.
Folgendes Beispiel:

Java:
public class Test {
   private int i, j, k;

   public static void main(String[] args) {
      int i =5;
      int j =7;
      int k =3;
      System.out.println(k * = --i * j++);
   }
}

Wie muss ich das Schritt für Schritt betrachten? Das Resultat wird in Eclipse als 84 ausgegeben...

Besten Dank für Hinweise
 
Zuletzt bearbeitet von einem Moderator:
Ist doch ganz einfach, in der Reihenfolge, wie sie da steht: Dekrementierung, Inkrementierung, Multiplikation, Multiplikation.

Es gibt bestimmte Regeln, was bei so einem Ausdruck/Term/Algebra geschieht, aber das will ich jetzt nicht kopieren.

Wie du (nebenbei) siehst, spielen Whitespaces keine Rolle.
 
Dekrementierung, Inkrementierung, Multiplikation, Multiplikation.

Also blicke da noch nicht wirklich durch...

Wenn ich die Werte einmal einsetze:
(3 * = --5 * 7++)

Wenn ich das mit der erwähnten Reihenfolge mache:
--5 = 4
7++ = 8

4*8 =32 * 3 = 96

??
 
So einfach dann offensichtlich doch nicht.
Zuerst die Dekrementierung - das ist richtig. --i wird zuerst gemacht (i = 4).
Dann kommt aber erst die Multiplikation: i * j = 4 * 7 = 28
Dann kommt die nächste Multiplikation: k * 28 = 3 * 28 = 84
Und jetzt kommt das j++ - dadurch ist j dann jetzt 8.

Also --Variable wird zuerst gemacht (Vor der ganzen Operation)
Und Variable++ wird am Ende gemacht (Nach der ganzen Operation)

Was dann aber auch hier klar zeigt: Sowas ist absolut unleserlich! Daher sind diese Operationen nicht wirklich so zu verwenden. Üblich ist, dass die ++ und -- Operationen nur als komplette Operation benutzt werden. Also i++;

Edit: Typo - Musste Variable++ sein und nicht ++Variable!
 
Also um es noch einmal klar zu sagen:
--Variable wird ausgeführt, ehe die Variable benutzt wird. Also in dem Beispiel ist --i enthalten. Ehe die Variable i verwendet wird, wird i reduziert.
Variable++ wird erst nach Benutzung erhöht. Also in dem Beispiel ist das j++. Hier wird die Berechnung mit j erst durchgeführt und danach wird dann j erst erhöht.
 
public static void main(String[] args) {
int i =5;
int j =7;
int k =3;
Sysstem.out.println(k * = --i * j++);
}
}

Das kann auch so nicht funktionieren, erstmal Sysstem dann * = (Leerzeichen).

Schwieriger wird's dann so:
Java:
        int i = 5;
        int j = 7;
        int k = 3;
        System.out.println(j *= --i * j++);
 
Also der Ausdruck j *=--i * j++ ist nicht definiert, da nicht definiert ist, wann die Zuweisung zu j und wann die j++ Operation durchgeführt wird. Dies ist Abhängig von der genauen Implementation.

Ebenso gibt es Probleme, wenn eine Variable mehrfach verändert wird oder bei der Zuweisung eine Rolle spielt.

Also daher macht es auf jeden Fall Sinn, dass man sich hier doch stärker zurück hält.

Siehe z.B. auch http://stackoverflow.com/questions/6457130/pre-post-increment-operator-behavior-in-c-c-java-c-sharp
 
In C/C++ ist es mWn. nicht definiert/Zufall. In Java mWn. aber nicht.
Aber du magst recht haben.
Hier noch was Verrücktes:
Java:
        int j = 7;
        System.out.println(j * --j * j++);
        System.out.println(j *= --j * j++);
 
das heisst aber man kann sich nicht zwingend auf die angegebenen Reihenfolgen stützen (siehe Bild).
wertigkeit_operatoren.gif
 
Ich konnte unter diesem Link aber nicht herauslesen dass es in Java undefiniert ist. Es stand doch da dass der Ausdruck einfach von links nach rechts abgearbeitet wird. Und es kommt dann wie erwartet 196 raus

Edit: es waren hier ja nur zwei bzw. drei Multiplikationen auszuführen, das heisst Vorrangregeln spielten keine Rolle. Wenn die noch ins Spiel kommen geht das ganze aber auch straight forward.
 
"Java and C# evaluate expressions from left to right, and the side-effects are visible immediately."

Also side-effects kommen da dann ins Spiel. Mag sein, dass es in Java nicht undefiniert ist, da klar definiert ist, wie die genaue Implementation auszusehen hat. Dies kann ich jetzt nicht beurteilen, denn dazu müsste man sich die Spezifikationen im Detail ansehen und dann prüfen, ob dies wirklich genau so festgelegt wurde. Ansonsten sind unterschiedliche Implementationen denkbar. Mag bei Java uninteressant sein, wenn es kaum Implementationen gibt (Was gibt es da? Oracle und OpenJDK?). Bei anderen Sprachen kann das interessanter sein, wenn es da hunderte von verschiedenen Implementationen gibt.
 
Also ich hab mal versucht den ursprünglichen Term in Einzelschritte zu zerlegen:
Java:
--i;
int j_alt = j;
++j;
int temp = i * j_alt;
k = k * temp;
System.out.println(k); // Ergebnis 84
Eigentlich alles so wie man es erwartet.

Ist tatsächlich interessant, wusste gar nicht daß das zwischen C und Java unterschiedlich gehandhabt wird. Der Unterschied ist anscheinend der, daß in C beide male zuerst auf 'a' zugegriffen wird und am Ende das Inkrement ausgeführt wird. Ein Inkrement fällt dabei unter den Tisch.
Aber Zufall ist da keiner im Spiel.
 
Für den TE ist es wichtig zu wissen: Klar gelten die Vorrangregeln, Ausdrücke werden von links nach rechts gemäss den Prioritätsregeln abgearbeitet, Seiteneffekte werden sofort wirksam ....
 
Das ist erst einmal nur eine Aussage von einem Benutzer. Und wenn es so trivial ist, dann frage ich mich, was die "Seiteneffekte" sind. Wenn aber jemand von Seiteneffekten spricht, die man dann schon sehen wird, dann ist die Frage, ob dies durch eine klare Regel definiert ist oder eben nicht.

Man kann jetzt die Spezifikationen wälzen. Und da findet man dann wohl Prioritäten wie hier schon gepostet sind. Aber da ist dann die Zuweisung nicht mit drin. Es geht dann halt teilweise doch ins Detail.

Bei j = a + j++; stellt sich die Frage, in welcher Reihenfolge denn alles genommen wird. Da ist dann halt die Frage, auf welches j sich das ++ dann beziehen wird.
Das kann dann halt mehrere Implementations-Reihenfolgen geben:
- Eval right side
- Eval ++ operation
- Assign to left value

Oder
- Eval right side
- Assign to left value
- Eval ++ operation

Oder wenn da dann auf CPU Ebene gegangen wird - die Zuweisung in j - ist das denn schon betrachtet? Droht da evtl. eine Optimierung durch Compiler und JIT, dass da ja ein j++ steht und da das j dann in einem Register für später verblieben ist und dies dann erhöht wird? Das nur als grobe Ansicht, da ich hier die Implementationen im Detail nicht kenne und nicht einmal kennen will.

Fakt ist: Ist es genau definiert worden, dann kann es verwendet werden. Ist es nicht genau definiert worden, dann ist die Frage, ob das Ergebnis, das Du derzeit hast bei anderen auch kommt (die evtl. andere Implementationen auf anderen Architekturen einsetzen) und ob das Ergebnis in Zukunft auch noch gleich ist (Wenn da evtl. bei Implementation etwas geändert wird).

Es mag also sein, dass es bei Java exakt spezifiziert wurde. Ich bezweifle es, wenn von Seiteneffekten geredet wird. Aber die Spezifikation kann uns hier nur diese Frage im Detail beantworten. Diese Arbeit werde ich mir aber nicht machen und einfach davon ausgehen, dass es nicht spezifiziert ist. Denn benutzen würde ich es ehh nicht.
 
Aber Zufall ist da keiner im Spiel.

Mit Zufall meinte ich, es ist (in C/C++) nicht definiert, d. h. jeder Hersteller kann es sich aussuchen, d. h. es ist Zufall, was gerade passiert.

Für den TE ist es wichtig zu wissen: Klar gelten die Vorrangregeln, Ausdrücke werden von links nach rechts gemäss den Prioritätsregeln abgearbeitet, Seiteneffekte werden sofort wirksam ....

Die Vorfahrtsegeln gelten, so viel steht fest!.

In diesem Fall,
j *= --j * j++
, wird aber nicht 6 * 6 * 6 gerechnet, wie zu erwarten wäre, sondern 6 * 6 * 7. Wieso?

Weiterhin ist meine Argumentation nicht ausführlich, deshalb halte ich mich jetzt zurück.

Edit: (Beispiel geändert)
 
Zuletzt bearbeitet von einem Moderator:
Einfach mal ein Beispiel:
int j = 5;
j = 10 + j++;

Der Ausdruck rechts vom = wird ausgerechnet - das ist einfach und 15.
Aber was kommt nun zuerst? Das j++ oder das zuweisen des Ergebnisses zu j?

Ist in der Java Spezifikation klar definiert, dann die Veränderung von j durch das j++ nun zuerst kommt? Also dass erst j zu 6 wird ehe dann j das Ergebnis 15 zugewiesen bekommt?

Das wäre in meinen Augen die entscheidene Frage.

Für die Praxis ist dies aber aus meiner Sicht nicht relevant, da solche Konstruktionen wohl für jeden als Unleserlich angesehen werden dürften. Und da ich auch andere Sprachen beruflich nutze (c# und c++) ist das für mich ehh ein no go.
 
Aber was kommt nun zuerst?
Wieso denkst du dass das schwierig ist ? Der Ausdruck rechts vom Gleichheitszeichen wird ausgewertet und ergibt 15. Dann wird zwar j noch mal inkrementiert aber das Ergebnis des Ausdrucks steht ja schon fest. Also ergibt das ganze 15.


Für die Praxis ist dies aber aus meiner Sicht nicht relevant
Ich denke dass es hier dem TE nicht um den Einsatz in der Praxis geht sondern er hat eine Übungsaufgabe oder Prüfungsfrage und soll zeigen dass er die ganzen Operatoren und deren Einsatz verstanden hat.
 
Das Problem vom TE war ja auch unproblematisch und wurde erläutert. Da war ja auch auf der linken Seite kein Ausdruck, bei dem auf der rechten Seite ein Post-Increment gemacht wurde.

Und die Probleme, die ich sehe, habe ich mehrfach ausgedrückt. Weitere Wege es auszudrücken sehe ich nicht.
 
Wieso denkst du dass das schwierig ist ? Der Ausdruck rechts vom Gleichheitszeichen wird ausgewertet und ergibt 15. Dann wird zwar j noch mal inkrementiert aber das Ergebnis des Ausdrucks steht ja schon fest. Also ergibt das ganze 15.
Dass der Ausdruck 15 ergibt ist glaube ich unstrittig. Aber ist wirklich sicher, dass zuerst j++ durchgeführt wird und dann die Zuweisung erfolgt, so dass schließlich j=15 ist? Oder kann es Implementierungen geben, bei denen erst die Zuweisung erfolgt und dann die Inkrementierung, so dass dann j=16 ist?
 
Dass der Ausdruck 15 ergibt ist glaube ich unstrittig
Es geht gar nicht ums streiten ich versteh nur nicht wo da ein Problem ist. 😉😉 Wenn die Definition so ist dass der Ausdruck rechts von links nach rechts ausgewertet wird (Prios von Operatoren mal ausser acht gelassen) dann wird natürlich auch zuerst das j++ ausgeführt ! Warum sollte man denn mitten in der Berechnung des Ausdrucks abbrechen und die Zuweisung durchführen ?
 
Folgendes Beispiel:

public class Test {
private int i, j, k;

public static void main(String[] args) {
int i =5;
int j =7;
int k =3;
Sysstem.out.println(k * = --i * j++);
}
}

Wie muss ich das Schritt für Schritt betrachten? Das Resultat wird in Eclipse als 84 ausgegeben...

k *= --i * j++ wird zu k = k * --i * j++,
jetzt haben wir eine Zuweisung und einen arithmetischen Ausdruck mit Inkrementierung, Dekrementierung usw.,
für genau diesen Ausdruck (Edit: für andere Ausdrücke auch...) gibt es wohldefinierte Operator-Prioritäten, s. d. bei kneitzel und JStein52 "dasselbe" rauskommt, obwohl sie ja unterschiedliche Rechner/Maschinen haben,
diese Operator-Prioritäten und was bei der Evaluierung eines Ausdrucks geschieht, müsste ich jetzt wortwörtlich/buchstäblich zitieren,
aber das hast du schon getan, deshalb nur:
zuerst k, dann i dekrementieren, dann i und dann j (ohne inkrementieren).
 
Zuletzt bearbeitet von einem Moderator:
Warum sollte man denn mitten in der Berechnung des Ausdrucks abbrechen und die Zuweisung durchführen ?
Es geht eigentlich nicht darum, ob die Berechnung des Ausdrucks abgebrochen wird, sondern darum, ob die implizite Zuweisung der ++-Operation auch unmittelbar durchgeführt wird (also Bestandteil der Auswertung ist), oder erst nach Abschluss der gesamten Auswertung. Ich denke auch, dass es unmittelbar durchgeführt wird, denn ich möchte, dass bei folgendem Beispiel immer 21 heraus kommt:
Java:
int j = 5;
j = 10 + j++ + j++;
Aber ich weiß nicht, ob es wirklich Bestandteil der Spezifikation ist und auch 20 heraus kommen darf.
 
Sorry, aber ich verstehe das Problem wohl nicht. Ist denn deiner meinung nach das ++ beim j kein Teil des Ausdrucks auf der rechten Seite ?? Ja wohl schon. Und deshalb muss auch dies abgearbeitet werden bevor der Ausdruck komplett ist. und es kommt natürlich 21 raus
 
Und wie bitte sollte denn da 20 herauskommen ? wie @kneitzel schon zitiert hat, Seiteneffekte wirken sich sofort aus, d.h. beim ersten j++ wird dies durchgeführt und ist beim nächsten Schritt wirksam.
 
An der Stelle verstehe ich z. B. nicht mehr, warum 21 raus kommt.
1.) Im Produktivcode sollte man das nicht einsetzen, andere müssen das auch noch verstehen können.
2.) Wahrscheinlich ist es wohldefiniert, weil man Fehler aus C/C++ vermeiden wollte, aber es ist zu kompliziert auch für eine Prüfung.
 
Und wie bitte sollte denn da 20 herauskommen ?
Falls die Reihenfolge so wäre, dass erst der Ausdruck berechnet wird, dann die impliziten Zuweisungen aus j++ erfolgen und schließlich die "normale" Zuweisung j wieder überschreibt.
wie @kneitzel schon zitiert hat, Seiteneffekte wirken sich sofort aus, d.h. beim ersten j++ wird dies durchgeführt und ist beim nächsten Schritt wirksam.
Ich hatte ihn so verstanden, dass er nicht sicher war, was die Spezifikation sagt, weil er das Zitat aus einer anderen Quelle hat. Kann aber auch gut sein, dass ich da etwas durcheinander gebracht habe.

Inzwischen bin ich ich ohnehin der Meinung, dass bei meinem Beispiel nur 21 ein sinnvolles Ergebnis ist und vermute, dass es auch entsprechend spezifiziert ist. Produktiv nutzen werde ich es ohnehin nie, deshalb genügt mir das auf jeden Fall.😉
 
Stimmt schon was JStein sagt

jls §15.14.2 hat gesagt.:
At run time, if evaluation of the operand expression completes abruptly, then the postfix increment expression completes abruptly for the same reason and no incrementation occurs. Otherwise, the value 1 is added to the value of the variable and the sum is stored back into the variable. Before the addition, binary numeric promotion (§5.6.2) is performed on the value 1 and the value of the variable. If necessary, the sum is narrowed by a narrowing primitive conversion (§5.1.3) and/ or subjected to boxing conversion (§5.1.7) to the type of the variable before it is stored. The value of the postfix increment expression is the value of the variable before the new value is stored.
 
Java:
public class TempKlasse {
	public static void main(String[] args) {
		int j = 5;
		j = 10 + j++ + j++;
		System.out.println("j = " + j);
	}
}

Code:
Compiled from "TempKlasse.java"
public class TempKlasse {
  public TempKlasse();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_5
       1: istore_1
       2: bipush        10
       4: iload_1
       5: iinc          1, 1
       8: iadd
       9: iload_1
      10: iinc          1, 1
      13: iadd
      14: istore_1
      15: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      18: new           #3                  // class java/lang/StringBuilder
      21: dup
      22: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      25: ldc           #5                  // String j =
      27: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      30: iload_1
      31: invokevirtual #7                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      34: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      37: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      40: return
}
 
Code:
       0: iconst_5
       1: istore_1
       2: bipush        10
       4: iload_1
       5: iinc          1, 1
       8: iadd
       9: iload_1
      10: iinc          1, 1
      13: iadd
      14: istore_1

13: iadd wird zwar noch ausgeführt, hat aber keinen Einfluss/Seiteneffekt auf 14: istore_1 - anders kann ich mir das im Moment auch nicht erklären.
 
Bytecode muss man dann auch lesen können..

Code:
       0: iconst_5 // push 5 auf stack
       1: istore_1 // speichere stack in local var 1
       2: bipush       10 // pushe 10 auf stack
       4: iload_1 // lade local var 1 auf den stack
       5: iinc          1, 1 // local var 1 inkrementieren
       8: iadd // addieren der obersten 2 auf dem Stack (NICHT die lokale Variable) und zurück auf den Stack pushen
       9: iload_1 // local var 1 auf den stack
      10: iinc          1, 1 // local var 1 inkrementieren
      13: iadd // addieren der obersten 2 auf dem Stack (NICHT die lokale Variable) und zurück auf den Stack pushen
      14: istore_1 // Wert vom Stack in local var 1 speichern
iadd führt die operation auf den Stack aus
 

Neue Themen


Zurück
Oben