Forum: Mikrocontroller und Digitale Elektronik C: Zuweisung von 2 Variablen auf einmal


von Martin M. (gga)


Lesenswert?

was macht folgender Code:
1
 Parameter1 = Parameter2 = 4;

Was steht am Ende der Befehlsausführung in "Parameter1"
* der alte Wert von Parameter2?
* 4?

Ich habe zwar den Quellcode, kann aber nicht debuggen um zu schauen. was 
der CooCox-Compiler daraus macht.

: Verschoben durch Moderator
von jemand (Gast)


Lesenswert?

Schaue damit: https://www.onlinegdb.com/

Parameter1 hat dann den neuen Wert von Parameter2 (der nicht 
zwangsläufig der Wert ganz rechts sein muss).

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Martin M. schrieb:
> Was steht am Ende der Befehlsausführung in "Parameter1"

Der neue Wert von Parameter 2.

Die Ausführung ist *):
1
Parameter2 = 4;
2
Parameter1 = Parameter2;

Die Unterscheidung ist dahingehend wichtig: wenn die erste Zuweisung 
irgendwas am Wert der rechten Seite verändert hat (weil bspw. Parameter2 
einen kleineren Wertebereich hat als die rechte Seite – OK, bei "4" eher 
nicht ;), dann übernimmt die zweite Zuweisung diese Änderung. Besonders 
gravierend ist das bei einigen AVRs, wenn man sowas schreibt:
1
  DDRA = DDRB = DDRC = DDRE = DDRF = DDRG = 0xFF; // alle Ports auf Ausgang

aber dann DDRG gar nicht die vollen 8 Bits hat (Hardware-Register). Dann 
liest bereits die zweite Zuweisung was anderes als 0xFF aus (bspw. 
0x3F). :-o  Dies wandert dann durch die komplette linke Seite weiter. 
(Davon abgesehen ist es völlig suboptimal, da alle diese 
Hardwareregister "volatile" sind und der soeben geschriebene Wert halt 
aus jedem wieder rückgelesen werden muss für die nächste Zuweisung.)

*) Nicht völlig äquivalent, weil die ausführliche Variante dazwischen 
noch einen Sequenzpunkt beinhaltet, die andere nicht. Das ist aber hier 
eher nebensächlich.

von jemand (Gast)


Lesenswert?

Eine anderer Fall für unerwartetes Verhalten wäre so etwas (was einem 
aber hoffentlich nicht so passiert ;) ):
1
#include <stdbool.h>
2
3
// ...
4
5
bool b;
6
int i;
7
8
i = b = 42;

// ich weiß gerade nicht, ob das implementation defined ist, aber bei 
einem kurzen Test war i == 1

von Falk B. (falk)


Lesenswert?

Martin M. schrieb:
> Parameter1 = Parameter2 = 4;

Eine der vielen, sinnlosen Fallstricke in C, die man tunlichst meiden 
sollte. Bringt kaum Gewinn, dafür aber Verwirrung.

von Georg (Gast)


Lesenswert?

Falk B. schrieb:
> Bringt kaum Gewinn, dafür aber Verwirrung

Für viele traditionelle C-Programmierer ist gerade die Verwirrung der 
Gewinn - Hauptsache mein Chef versteht meine Programme nicht, dann kann 
er mich nicht entlassen.

Georg

von Einer K. (Gast)


Lesenswert?

Falk B. schrieb:
> Eine der vielen, sinnlosen Fallstricke in C, die man tunlichst meiden
> sollte. Bringt kaum Gewinn, dafür aber Verwirrung.

So pauschal, müsste deine Aussage auch dann für C++ Gültigkeit besitzen.

Meine Meinung:
Es ist eine wohldefinierte Spracheigenschaft.
Somit also auch überall einsetzbar, wo es Sinn macht.

Ein Beispiel, für einen Ereigniszähler in C++.
Es ist jetzt vielleicht kein Performance Wunder, aber tut genau das was 
es soll.
Oder einfach nur ein Einblick in meine kleine Arduino Welt.
1
// Vorlage: https://forum.arduino.cc/t/variable-bei-langerem-tastendruck-schnell-hochzahlen/645342/5?u=combie
2
// dort finden sich auch die Libs.
3
4
#include <CombiePin.h>
5
#include <CombieTimer.h>
6
7
8
#include <CombieTypeMangling.h>
9
using namespace Combie::Millis;
10
11
#include <CombieTools.h>
12
using CounterType = Combie::Tools::Counter<unsigned long>;
13
14
CounterType                      counter;
15
Combie::Pin::TasterGND<3>        taster; // Taster gegen GND geschaltet
16
Combie::Timer::EntprellTimer     entprellen {20_ms};
17
Combie::Tools::FlankenErkennung  flankenerkennung;
18
19
void setup() 
20
{
21
    Serial.begin(9600);
22
    taster.initPullup();
23
    counter.onCount([](CounterType &counter)
24
                    { 
25
                      Serial.print("Ereignis Nr: ");
26
                      Serial.println(counter);
27
                    });
28
}
29
30
void loop() 
31
{ 
32
  counter = flankenerkennung = entprellen = taster;
33
}

von Stefan F. (Gast)


Lesenswert?

Georg schrieb:
> Hauptsache mein Chef versteht meine Programme nicht, dann kann
> er mich nicht entlassen.

So eine Einstellung hatten wir mal bei einem Berater der für Vodafone 
entwickelte - bis der Abteilungsleiter dahinter kam (bzw. es endlich 
glaubte). Danach hatte dieser Berater nie wieder für die Abteilung 
gearbeitet.

von Peter D. (peda)


Lesenswert?

Arduino Fanboy D. schrieb:
> counter = flankenerkennung = entprellen = taster;

Ja, in C++ kann man noch viel besser unleserlichen und fehlerträchtigen 
Code schreiben.
Das brauchst Du nicht ständig zu wiederholen.

Das obige Konstrukt mag ganz früher bei einem Interpreter oder bei 
knappem RAM für den Editor seine Berechtigung gehabt haben.
Heutzutage ist es nur ein Ausdruck für schlechten Programmierstil.

von mh (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> counter = flankenerkennung = entprellen = taster;

Wie weist man einen TasterGND einem EntprellTimer zu? Wie weist man 
einen EntprellTimer einer FlankenErkennung zu? Und wie weist man eine 
Flankenerkennung einem Counter zu? Du hast hier ein perfektes Beispiel 
geliefert, warum

Falk B. schrieb:
> Martin M. schrieb:
>> Parameter1 = Parameter2 = 4;
>
> Eine der vielen, sinnlosen Fallstricke in C, die man tunlichst meiden
> sollte. Bringt kaum Gewinn, dafür aber Verwirrung.

auch in C++ zutrifft.

von Einer K. (Gast)


Lesenswert?

mh schrieb:
> Du hast hier ein perfektes Beispiel
> geliefert,

Ich weiß!
> counter = flankenerkennung = entprellen = taster;

Die/Eine alternative Schreibweise wäre:
1
  counter.doTrigger(flankenerkennung.doTrigger(entprellen.doTrigger(taster.pressed())));

Was ist "schöner"?

von Gerald K. (geku)


Lesenswert?

C Programm :
1
 #include <stdio.h>
2
 
3
int parameter1;
4
 int parameter2;
5
6
 int main()
7
 {
8
    parameter2 = parameter1 = 4;
9
     printf("p1: %d, p2: %d\r\n",parameter1,parameter2);
10
}
Assembler :
1
main:
2
     push    {fp, lr}
3
     add fp, sp, #4
4
5
     ldr r3, .L3
6
     mov r2, #4 ; Konstante 4 ist nur einmal vorhanden
7
     str r2, [r3]  ; speichern Parameter1
8
     ldr r3, .L3
9
     ldr r3, [r3]
10
     ldr r2, .L3+4
11
     str r3, [r2]   ; speichern Parameter2
12
     ldr r3, .L3
13
     ldr r1, [r3]
14
     ldr r3, .L3+4
15
     ldr r3, [r3]
16
     mov r2, r3
17
     ldr r0, .L3+8
18
     bl  printf
19
20
     mov r3, #0
21
     mov r0, r3
22
     pop {fp, pc}

Konstante 4 wird in Register r2 gehalten

: Bearbeitet durch User
von quotendepp (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> Was ist "schöner"?

das zweite mit den funktionsaufrufen (wenn man es schöner formatiert).

von Stefan F. (Gast)


Lesenswert?

Gerald K. schrieb:
> Konstante 4 ist nur einmal vorhanden

Ich wette, dass sie auch nur einmal vorhanden sein wird, wenn du das in 
zwei Zeilen aufteilst (ausgenommen, wenn man die Optimierung ganz 
deaktiviert).

von Gerald K. (geku)


Lesenswert?

Parameter getrennt auf 4 gesetzt:
1
main:
2
  push    {fp, lr}
3
  add fp, sp, #4
4
  ldr r3, .L3
5
  mov r2, #4
6
  str r2, [r3]
7
   ldr r3, .L3+4
8
   mov r2, #4
9
   str r2, [r3]
10
   ldr r3, .L3
11
   ldr r1, [r3]
12
   ldr r3, .L3+4
13
   ldr r3, [r3]
14
   mov r2, r3
15
   ldr r0, .L3+8
16
   bl  printf

von Stefan F. (Gast)


Lesenswert?

Hmm, diese Wette habe ich verloren

von Gerald K. (geku)


Lesenswert?

Stefan ⛄ F. schrieb:
> Hmm, diese Wette habe ich verloren

Der Aufruf  war in beiden Fällen:     gcc -S test.c

Habe mir auch gedacht, dass das Ergebnis gleich sein wird.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Gerald K. schrieb:
> Der Aufruf  war in beiden Fällen:     gcc -S test.c

dann füg mal noch ein -O ein

von Gerald K. (geku)


Lesenswert?

Jörg W. schrieb:
> dann füg mal noch ein -O ein

Parameter getrennt auf 4 gesetzt:
1
main:
2
        push    {r4, lr}
3
        mov     r1, #4
4
        ldr     r3, .L3
5
        str     r1, [r3]
6
        ldr     r3, .L3+4
7
        str     r1, [r3]
8
        mov     r2, r1
9
        ldr     r0, .L3+8
10
        bl      printf
11
        mov     r0, #0
12
        pop     {r4, pc}
13
14
statt :
15
16
main:
17
        push    {fp, lr}
18
        add     fp, sp, #4
19
        ldr     r3, .L3
20
        mov     r2, #4
21
        str     r2, [r3]
22
        ldr     r3, .L3+4
23
        mov     r2, #4
24
        str     r2, [r3]
25
        ldr     r3, .L3
26
        ldr     r1, [r3]
27
        ldr     r3, .L3+4
28
        ldr     r3, [r3]
29
        mov     r2, r3
30
        ldr     r0, .L3+8
31
        bl      printf
32
        mov     r3, #0
33
        mov     r0, r3
34
        pop     {fp, pc}

: Bearbeitet durch User
von mh (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> Die/Eine alternative Schreibweise wäre:
> counter.doTrigger(flankenerkennung.doTrigger(entprellen.doTrigger(taster 
.pressed())));
>
> Was ist "schöner"?

Die hier gezeigte Alternative. Wenn du schon einen Operator 
vergewaltigen musst, nimm "<<" (stream) oder "|" (pipe), die werden 
heufiger auf diese Art genutzt, allerdings auch nicht ohne berechtigete 
Kritik. Das was du da machst hat nichts mit einer Zuweisung zu tun, 
sollte also nicht durch den Zuweisungsoperator erledigt werden.

von Einer K. (Gast)


Lesenswert?

mh schrieb:
> Das was du da machst hat nichts mit einer Zuweisung zu tun,

Nunja....
Eine Zuweisung ist im Kern auch nur der Transport einer 
Informationseinheit, von  rechts nach links.
Auch gerne mal mit einer Transformation verbunden
b sei vom Type float
a vom Type int
b = a;

mh schrieb:
> nimm "<<" (stream) oder "|" (pipe), die werden
> heufiger auf diese Art genutzt, allerdings auch
> nicht ohne berechtigete
Kritik.
Ja...
Ist auch nicht "schöner"

Leider kennt C++ keinen Operator um den Datenfluss besser abzubilden, 
als die Zuweisung.

Mit Ausnahme vielleicht von << oder >> aber die haben schon eine "Zweit" 
Verwendung, das Streaming.

von Stefan F. (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> counter = flankenerkennung = entprellen = taster;

Das ist ein perfektes Beispiel dafür, warum ich Operator-Überladung 
ablehne. Da erkennt man überhaupt nicht mehr, was passiert.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Arduino Fanboy D. schrieb:
> counter = flankenerkennung = entprellen = taster;

Mich wundert, dass die Entwickler der C++-Standardbibliothek nicht schon
längst auf diese geniale Idee gekommen sind:

1
#include <iostream>
2
#include <cmath>
3
4
class NewSin {
5
  public:
6
    double operator=(double x) {
7
      return sin(x);
8
    }
9
};
10
11
12
int main() {
13
  // computing sin(0.2) in two ways
14
15
  // classic style with ugly parentheses
16
  double y1 = sin(0.2);
17
18
  // modern style by Arduino Fanboy, no need for parentheses
19
  NewSin sin;
20
21
  double y2 = sin = 0.2;
22
23
  // results are the same
24
  std::cout << y1 << ' ' << y2 << '\n';
25
26
  return 0;
27
}

von Falk B. (falk)


Lesenswert?

Yalu X. schrieb:
> // modern style by Arduino Fanboy, no need for parentheses
>   NewSin sin;

Hehe, da hast du wohl unbewußt die richtige Funktion gewählt, sin . . .
;-)

von c-hater (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:

> Das ist ein perfektes Beispiel dafür, warum ich Operator-Überladung
> ablehne. Da erkennt man überhaupt nicht mehr, was passiert.

Also generell würde ich Operator-Überladung nicht ablehnen. Die kann 
schon Sinn ergeben. Aber eher nur bei numerischen Operationen mit 
"erweiterten" Typen. Klar, ganz klassisches Beispiel: Rechnen mit 
komplexen Zahlen.

Oder auch mit rationalen Zahlen, die intern durch Brüche ganzer Zahlen 
dargestellt werden.

Beides ist in vielen praktischen Anwendungen eine nützliche Sache und 
wenn man einfach im Quelltext die Operation mit den üblichen Zeichen 
darstellen kann, halt ich das durchaus für sehr nützlich, weil: das 
fördert das Verständnis.

Die armselige "Anwendung" des Fanboys hingegen... Naja...

von Stefan F. (Gast)


Lesenswert?

c-hater schrieb:
> Also generell würde ich Operator-Überladung nicht ablehnen. Die kann
> schon Sinn ergeben. Aber eher nur bei numerischen Operationen

Genau. "+" dient der Addition und nicht dazu irgendwo etwas 
hinzuzufügen. Wenn wir uns auf dieses Prinzip beschränken fühle ich mich 
wohl.

Was ich von
> cout << "Hello" #
halte, sollte dann klar sein: Wenig. Es geht noch schlimmer:
> cout << setw(6) << -17 << endl << flush;
Da sind die Macher von C++ bereits mit einem sehr schlechten Beispiel 
voran gegangen.

von Yalu X. (yalu) (Moderator)


Lesenswert?

c-hater schrieb:
> Also generell würde ich Operator-Überladung nicht ablehnen. Die kann
> schon Sinn ergeben. Aber eher nur bei numerischen Operationen mit
> "erweiterten" Typen. Klar, ganz klassisches Beispiel: Rechnen mit
> komplexen Zahlen.
>
> Oder auch mit rationalen Zahlen, die intern durch Brüche ganzer Zahlen
> dargestellt werden.

Wow, da sind wir ja ausnahmsweise mal exakt derselben Meinung :)

IMHO sollte nämlich auch hier das Prinzip der geringsten Überraschung
gelten.

Das Problem bei C++ ist, dass schon der Herr Stroustrup mit ganz
schlechtem Beispiel vorangegangen ist, indem er die Bit-Shift-Operatoren
für die Ein-/Ausgabe missbraucht hat.

Positiv gesehen hat dies dazu geführt, dass für C++-Programmierer die
meisten Überraschungen gar keine mehr sind, so dass das o.g. Prinzip
automatisch immer erfüllt ist :D

von Rolf M. (rmagnus)


Lesenswert?

jemand schrieb:
> Eine anderer Fall für unerwartetes Verhalten wäre so etwas (was einem
> aber hoffentlich nicht so passiert ;) ):
> #include <stdbool.h>
> // ...
> bool b;
> int i;
> i = b = 42;
>
> // ich weiß gerade nicht, ob das implementation defined ist, aber bei
> einem kurzen Test war i == 1

Das ist nicht implementation-defined. Eine Konvertierung von bool nach 
int hat immer 1 für true und 0 für false als Ergebnis. Umgekehrt wird 
bei Konvertierung von int nach bool aus der 0 ein false und aus allem 
anderen ein true. Das heißt, am Ende ist bei einem konformen Compiler 
garantiert, dass nachher b==true und i==1 ist.

mh schrieb:
> Arduino Fanboy D. schrieb:
>> Die/Eine alternative Schreibweise wäre:
>> counter.doTrigger(flankenerkennung.doTrigger(entprellen.doTrigger(taster
> .pressed())));
>>
>> Was ist "schöner"?
>
> Die hier gezeigte Alternative. Wenn du schon einen Operator
> vergewaltigen musst, nimm "<<" (stream) oder "|" (pipe), die werden
> heufiger auf diese Art genutzt, allerdings auch nicht ohne berechtigete
> Kritik. Das was du da machst hat nichts mit einer Zuweisung zu tun,
> sollte also nicht durch den Zuweisungsoperator erledigt werden.

Arduino Fanboy D. schrieb:
> mh schrieb:
>> Das was du da machst hat nichts mit einer Zuweisung zu tun,
>
> Nunja....
> Eine Zuweisung ist im Kern auch nur der Transport einer
> Informationseinheit, von  rechts nach links.

Der Wert des Objekts auf der rechten Seite wird in das Objekt auf der 
linken Seite kopiert.

> Auch gerne mal mit einer Transformation verbunden
> b sei vom Type float
> a vom Type int
> b = a;

Ja, eine Konvertierung des Wertes findet statt. Nachher hat b den selben 
Wert wie a, sofern es diesen aufnehmen kann. Das ist bei dir eher nicht 
so. Damit verstößt der Code gegen das Prinzip der geringsten 
Überraschung. Man erwartet nicht, dass sich hinter einer Zuweisung eine 
Entprellung versteckt, und es irritiert, dass du ein Objekt vom Typ 
FlankenErkennung einem Zähler zuweist, weil das eigentlich gar keinen 
Sinn ergibt. Das würde höchstens dann einen Sinn ergeben, wenn die 
FlankenErkennung selbst schon eine Art von Zähler ist, dessen Wert dann 
eben in den Counter kopiert wird. Aber wozu braucht man den dann noch? 
Eher verständlich wäre da noch ein counter += flankenerkennung …

> mh schrieb:
>> nimm "<<" (stream) oder "|" (pipe), die werden
>> heufiger auf diese Art genutzt, allerdings auch
>> nicht ohne berechtigete
> Kritik.
> Ja...
> Ist auch nicht "schöner"

Naja, es ist auch nicht schön, aber etwas weniger schlimm. Die Zuweisung 
finde ich besonders unpassend.

von Einer K. (Gast)


Lesenswert?

c-hater schrieb:
> Die armselige "Anwendung" des Fanboys hingegen... Naja...

Hmmm....

Anhänger des Programmflusses, malen Kontrollflussdiagramme.
Gespickt mit Bedingungen und Schleifen

Die OOPler malen gerne(?) in UML
Die Objekte kommunizieren über Nachrichten.

Und bei mir ist es am Datenfluss orientiert.
Die Information "fließt" durch Filter/Konverter vom Taster bis in den 
Counter

Oder anders:
Die Betonung auf den Datenfluss wird häufig unterschätzt.

Musste damals in meiner Ausbildung Datenflussdiagramme malen. Das 
"armselige" ist einfach nur die Umsetzung in C++, des damals gelernten.

von Stefan F. (Gast)


Lesenswert?

Ist halt Geschmacksache. Man hat in C++ vieles eingebaut, um für alle 
Eventualitäten gerüstet zu sein. Leider bedeutet das zugleich eine 
Abkehr von "Keep it Stupid Simple".

Naja, die Würfel sind schon lange gefallen. Das wird keiner mehr ändern.

von Rolf M. (rmagnus)


Lesenswert?

Arduino Fanboy D. schrieb:
> Und bei mir ist es am Datenfluss orientiert.
> Die Information "fließt" durch Filter/Konverter vom Taster bis in den
> Counter

Das soll sie ruhig tun, aber doch nicht per Zuweisungsoperator.

von Einer K. (Gast)


Lesenswert?

Rolf M. schrieb:
> Das soll sie ruhig tun, aber doch nicht per Zuweisungsoperator.
Das wurde doch erwähnt, dass Operatorenüberladung unter Generalablehnung 
steht.
Alleine darum schon, ist die Verwendung des Zuweisungsoperators nur ein 
kleines Detail.
Irgendwie unwichtig, weil auch relativ leicht zu ändern.
z.B -> wäre auch ein lustiger Kandidat.
Lieber wäre mir ein eigener Operator, z.B. <-

Der Zuweisungsoperator hat schon den Vorteil, dass er von rechts nach 
links ausgewertet wird.

Alle anderen von links nach rechts, bei gleichem Rang.
Das bedingt, dass die zu verknüpfenden Elemente gegenseitig ihr 
Interface kennen müssen. Im schlimmsten Fall gar ein Stapel auf und 
abgebaut werden müsste.

Mag ja sein, dass der = Operator ungünstig erscheint.
Aber einen Besseren sehe ich nicht.
Denke schon, dass ich alle sinnvollen Möglichkeiten durchprobiert habe.

z.B. der | oder or Operator verbindet zwei Werte zu einem neuen.
Das ist hier nicht gegeben. Bei mir ist es ein "Informations Transport"
Das ist dem = näher, als dem |, welcher mit Transport nix am Hut hat.

von Stefan F. (Gast)


Lesenswert?

> counter = flankenerkennung = entprellen = taster;

Was machst du, wenn mehrere Quellen kombiniert werden müssen? Etwa:

kuchen = backen = mischen = mehl = milch = eier = zucker;

Oder:

kuchen = backen << mischen << (mehl + milch + eier + zucker);

Besser nicht.

Das wäre in meinen Augen kein C++ sondern ein U-Boot, das einem 
irgendwann um die Ohren fliegt.

von c-hater (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:

> Und bei mir ist es am Datenfluss orientiert.
> Die Information "fließt" durch Filter/Konverter vom Taster bis in den
> Counter

Das ist sicher so. Aber wohl genauso sicher nicht in dem Sinne, wie man 
üblicherweise das erwartete Verhalten eine Zuweisung beschreiben würde.

Denn: es wird keinesfalls die gesamte Information zugewiesen, sondern 
nur der Inhalt von deren Standardeigenschaft.

Wenn man das schon unbedingt mittels Operatoren abbilden will, dann wäre 
das Piping-Symbol das Mittel der Wahl gewesen, denn das impliziert ja 
einen Filter zwischen den Pipes.

Insofern ist allerdings auch schon die C-Geschichte unlogisch. Denn auch 
hier passiert ja u.U. eine Filterung der Information bei der 
Mehrfach-Zuweisung.

Wie ich immer sage: C ist Dreck und C++ ist noch größerer Dreck. Erbt 
allen Dreck von C und fügt noch mehr Möglichkeiten hinzu, beschissen 
lesbaren Code zu produzieren...

Allerdings: Man muss all diese Möglichkeiten ja NICHT zwingend 
benutzen! Nicht in C und auch nicht in C++...

von A. S. (Gast)


Lesenswert?

Gerald K. schrieb:
> Habe mir auch gedacht, dass das Ergebnis gleich sein wird.

Naja, mit zwei = in einer Zeile ist halt aufwändiger. Und bei. volatile 
würde nichtmal Optimierung helfen.

Bei den 2 Zeilen darf er die aufwändige Variante hingegen gar nicht 
verwenden.

von Rolf M. (rmagnus)


Lesenswert?

Arduino Fanboy D. schrieb:
> Rolf M. schrieb:
>> Das soll sie ruhig tun, aber doch nicht per Zuweisungsoperator.
> Das wurde doch erwähnt, dass Operatorenüberladung unter Generalablehnung
> steht.

Nein, nur die missbräuchliche.

> Der Zuweisungsoperator hat schon den Vorteil, dass er von rechts nach
> links ausgewertet wird.
>
> Alle anderen von links nach rechts, bei gleichem Rang.
> Das bedingt, dass die zu verknüpfenden Elemente gegenseitig ihr
> Interface kennen müssen. Im schlimmsten Fall gar ein Stapel auf und
> abgebaut werden müsste.

Warum? du musst die Kette ja nicht unbedingt rückwärts aufschreiben. 
Kannst ja auch schreiben:
1
taster >> entprellen >> flankenerkennung >> counter;
Das finde ich zwar auch nicht toll, aber immer noch besser als die 
Zuweisung.

> Mag ja sein, dass der = Operator ungünstig erscheint.
> Aber einen Besseren sehe ich nicht.
> Denke schon, dass ich alle sinnvollen Möglichkeiten durchprobiert habe.
>
> z.B. der | oder or Operator verbindet zwei Werte zu einem neuen.

Von Shellskripten her ist er auch bekannt als Pipe-Operator, der den 
Output eines Verarbeitungsschrittes an den nächsten als Input 
weiterleitet. Das trifft das, was du hier machst, ganz gut.

> Das ist hier nicht gegeben. Bei mir ist es ein "Informations Transport"
> Das ist dem = näher, als dem |, welcher mit Transport nix am Hut hat.

= ersetzt den Wert des Ziels du den (ggf. konvertierten) aus der Quelle. 
Das trifft conuter = flankenerkennung gar nicht. Dass irgend eine Art 
von Information von A nach B fließt, reicht für mich nicht als 
Rechtfertigung dafür, = zu verwenden.

: Bearbeitet durch Moderator
von Einer K. (Gast)


Lesenswert?

Rolf M. schrieb:
> Von Shellskripten her ist er auch bekannt als Pipe-Operator, der den
> Output eines Verarbeitungsschrittes an den nächsten als Input
> weiterleitet. Das trifft das, was du hier machst, ganz gut.
Das ist eine andere Sprache.



Rolf M. schrieb:
> Warum? du musst die Kette ja nicht unbedingt rückwärts aufschreiben.
> Kannst ja auch schreiben:
> taster >> entprellen >> flankenerkennung >> counter;
> Das finde ich zwar auch nicht toll, aber immer noch besser als die
> Zuweisung.

Nicht wirklich!

Das Problem, die Auswertung von links nach rechts.
Es baut sich auf dem Stack die Aufrufreihenfolge auf.
Das ist recht ungünstig, oder?
Naja...

von Einer K. (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:
> Was machst du, wenn mehrere Quellen kombiniert werden müssen? Etwa:
>
> kuchen = backen = mischen = mehl = milch = eier = zucker;

Das ist etwas komplizierter, weil einige der Sachen Dinglich sind und 
andere Tätigkeiten.
1
Mischung mischung = Mischung() + Eier(3) + Butter(500_gr) + Milch(0.3_ltr);
2
mischung += Mehl(0.5_pfund);
3
michung.mischen();

Doof nur, dass es meinem Taster und seinem Kumpels nix ausmacht, wenn 
sie 100000 mal pro Sekunde dran kommen.
Bei einem Backofen sieht das anders aus, wenn er voll ist.
Von daher ist es eben etwas komplizierter, dein Beispiel.

von Gerald K. (geku)


Lesenswert?

Martin M. schrieb:
> Ich habe zwar den Quellcode, kann aber nicht debuggen um zu schauen. was
> der CooCox-Compiler daraus macht.

Ist gar nicht notwendig den Code zu exekutieren. Es reicht beim 
Compilieren einen Assemblerzwischencode zu erzeugen. Beim GCC kompiliert 
man mit dem Parameter -S.

von Klaus W. (mfgkw)


Lesenswert?

Arduino Fanboy D. schrieb:
> Rolf M. schrieb:
>> Von Shellskripten her ist er auch bekannt als Pipe-Operator, der den
>> Output eines Verarbeitungsschrittes an den nächsten als Input
>> weiterleitet. Das trifft das, was du hier machst, ganz gut.
> Das ist eine andere Sprache.

Gut, dann halt C++:
Ab C++ 20 gibt es sowas wie eine pipe für ranges, letztlich also für 
Container wie list, vector etc..

Da kann man eine Art pipe bauen mit dem |-Operator. Links geht eine 
range rein (also z.B. eine ganze Liste), alle Elemente werden durch 
filter entfernt oder behalten und mit transform manipuliert, und rechts 
purzelt wieder eine Sammlung von Werten raus.

Z.B. aus 
https://www.heise.de/developer/artikel/C-20-Die-Ranges-Bibliothek-4661566.html 
:
1
 std::vector<int> numbers = {1, 2, 3, 4, 5, 6};
2
  
3
 auto results = numbers | std::views::filter([](int n){ return n % 2 == 0; })
4
                        | std::views::transform([](int n){ return n * 2; });
5
                           
6
    for (auto v: results) std::cout << v << " ";     // 4 8 12

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Arduino Fanboy D. schrieb:
> Rolf M. schrieb:
>> Von Shellskripten her ist er auch bekannt als Pipe-Operator, der den
>> Output eines Verarbeitungsschrittes an den nächsten als Input
>> weiterleitet. Das trifft das, was du hier machst, ganz gut.
> Das ist eine andere Sprache.

= macht in C++ normalerweise auch nicht das, was du damit tust. Mir 
fällt gar keine Sprache ein, die sowas regulär so macht.

> Das Problem, die Auswertung von links nach rechts.
> Es baut sich auf dem Stack die Aufrufreihenfolge auf.
> Das ist recht ungünstig, oder?

Ich verstehe nicht, was du damit meinst und warum von rechts nach links 
besser sein soll.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Arduino Fanboy D. schrieb:
> Und bei mir ist es am Datenfluss orientiert.
> Die Information "fließt" durch Filter/Konverter vom Taster bis in den
> Counter

Die Idee an sich finde ich prinzipiell sehr gut. Leider ist in C++ keine
(für meinen Geschmack) saubere Umsetzung möglich, da es keinen Operator
gibt, der bereits eine ähnliche Funktion hat. Am ehesten wäre noch das
ohnehin schon aufgeweichte << geeignet. Der passt aber nicht wegen
seiner Linksassoziativität.

Es wäre auch nicht schlecht, wenn man den Datenfluss – wie im Abendland
üblich – von links nach rechts lesen könnte, also bspw.

1
  taster >> entprellen >> flankenerkennung >> counter;

Hier ist die Linksassoziativität in Ordnung, aber wie du oben schon
schriebst, erfordert dies,

Arduino Fanboy D. schrieb:
> dass die zu verknüpfenden Elemente gegenseitig ihr Interface kennen

Andere Programmiersprachen wie bspw. Haskell sind für solche Späße
besser geeignet als C++. In Haskell wird der Missbrauch existierender
Operatoren für unpassende Zwecke absichtlich erschwert, dafür wird die
Definition neuer Operatoren unterstützt. Die Definition eines Operators
besteht dabei aus dem Symbol, dem Funktionsrumpf, dem Rang und der
Assoziativität.

Einen Operator, der etwas Ähnliches tut wie in deiner Anwendung, gibt es
sogar schon in der Standardbibliothek, nämlich >>=. Aus der Doku:
"Sequentially compose two actions, passing any value produced by the
first as an argument to the second."

Ein Programm, das eine Textzeile von stdin einliest, sie spiegelt und 
auf
stdout ausgibt, kann bspw. so aussehen:

1
main =
2
  getLine  >>= (return . reverse) >>= putStrLn

Das "return ." vor dem "reverse" hat nichts mit dem return in C zu tun,
sondern macht – vereinfacht ausgedrückt – aus einer gewöhnlichen
Funktion eine Action.

Wenn – wie in Stefans Kuchenbeispiel – mehrere Werte zwischen den
Actions weitergereicht werden sollen, werden die entsprechenden
Ausdrücke mit >>= schnell unhandlich. Für diese Fälle bietet die Sprache
als syntaktischen Zucker die do-Notation an. Das obige Beispiel würde
man damit so schreiben:

1
main = do
2
         inStr <- getLine
3
         let outStr = reverse inStr
4
         putStrLn outStr

inStr und outStr sind dabei Variablennamen. Der Compiler setzt dieses
Beispiel in den obigen >>=-Ausdruck um.

Das ist schon ziemlich cool. Noch cooler ist aber, dass das in ähnlicher
Form auch in C++ möglich ist. Dein ursprüngliches Beispiel würde damit
so aussehen:

1
btPressed           = taster.pressed();
2
btPressedDebounced  = entprellen.doTrigger(btPressed);
3
btEdge              = flankenerkennung.doTrigger(btPressedDebounced);
4
btEdgeCount         = counter.doTrigger(btEdge);

Langer Rede kurzer Sinn: Dieser Schreibweise würde ich in C++ den
Vorzug geben :)


Zum Schluss noch etwas zum Ursprungsthema:

Mehrfachzuweisungen der Form a = b = c = x habe ich schon immer (anfangs
eher unbewusst) nur dann verwendet, wenn a, b und c exakt vom gleichen
Typ sind. Soll bspw. einer char-, einer int- und einer double-Variable
jeweils der Wert 0 zugewiesen werden, schreibe ich dafür drei einzelne
Zuweisungen.

von mh (Gast)


Lesenswert?

Yalu X. schrieb:
> Es wäre auch nicht schlecht, wenn man den Datenfluss – wie im Abendland
> üblich – von links nach rechts lesen könnte, also bspw.
>
>   taster >> entprellen >> flankenerkennung >> counter;
>
Dann hat man das Ergebnis aber rechts und nicht wie in C/C++ üblich 
links. Das sorgt also für einiges an überraschung.

> Hier ist die Linksassoziativität in Ordnung, aber wie du oben schon
> schriebst, erfordert dies,
>
> Arduino Fanboy D. schrieb:
>> dass die zu verknüpfenden Elemente gegenseitig ihr Interface kennen
Da man das Interface von operator<< selbst festlegen kann, müsste es 
auch linksassoziativ möglich sein. Erforder vermutlich ein mini 
Expressiontemplate.

von Einer K. (Gast)


Lesenswert?

Rolf M. schrieb:
> Ich verstehe nicht, was du damit meinst
Stimmt das etwa nicht, mit dem Stack?
Oder anders: Wie kann man das vermeiden?

Yalu X. schrieb:
> Leider ist in C++ keine
> (für meinen Geschmack) saubere Umsetzung möglich, da es keinen Operator
> gibt, der bereits eine ähnliche Funktion hat.
Ja, das ist schade....

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.