C/C++ Objektmethoden

Gast #5280533
Lesenswert?

Hallo zusammen

Beim Recherchieren für das Programmieren von Mikrocontrollern stosse ich 
immer wieder auf verschiedene Syntax, wenn es um den Aufruf von 
Objektmethoden geht.

Viele Beispiele zeigen folgendes:
1
Objekt.Methode();
manche aber:
1
Objekt->Methode();

Worin liegt hier der Unterschied?
Gast #5280546
Lesenswert?

Stefan U. schrieb:
> Das erste ruft die Methode einer Objektreferenz auf. Das andere ruft die
> Methode über einen Zeiger auf, der auf die Objektinstanz zeigt.

Stefan, bitte schreibe mir in diesem Thread nicht mehr weiter. Ich habe 
schon einige deiner Kommentare gelesen... Leider meist unverständlich 
und zu hochstehend - wie dieser hier. Nichts für ungut.
Persönliche Seite #5280561
Lesenswert?

Hallo,

ich denke das ganze wird klarer, wenn man sich Variablen anschaut:

Wir haben eine Struktur/Klasse S:
1
struct S {
2
    int x;
3
};
und legen von dieser eine Variable an:
1
S variable;
Nun kann man ganz normal mittels "Punkt" auf die Struct-Variable 
zugreifen:
1
variable.x = 42;

Nun haben wir aber irgenwie einen Zeiger auf diesen Struct (z.B. durch 
dynamischen Speicher oder durch einen Parameter):
1
S *pointer;
Bevor man auf die Member-Variable zugreifen kann muss man erst einmal 
dereferenzieren:
1
(*var2).x = 42;
Diese Schreibweise (also mit Klammern) ist wegen der 
Operator-Reihenfolge nötig, aber umständlich, deswegen gibt es auch den 
kürzeren "Pfeil":
1
var2->x = 42;
Das ganze funktioniert dann komplett analog dazu.

Mit freundlichen Grüßen,
N.G.
#5280783
Lesenswert?

N. G. schrieb:
...
> Nun haben wir aber irgenwie einen Zeiger auf diesen Struct (z.B. durch
> dynamischen Speicher oder durch einen Parameter):S *pointer;
> Bevor man auf die Member-Variable zugreifen kann muss man erst einmal
> dereferenzieren:(*var2).x = 42;

Das wäre deutlich klarer, wenn du nicht mitten drin den Namen der 
(Pointer-)Variablen ändern würdest. Also besser:
1
S *pointer;

und dann
1
(*pointer).x= 42;

bzw. als abgekürzte Schreibweise
1
pointer->x= 42;
Persönliche Seite #5281240
Lesenswert?

Michael schrieb:
> Beim Recherchieren für das Programmieren von Mikrocontrollern stosse ich
> immer wieder auf verschiedene Syntax, wenn es um den Aufruf von
> Objektmethoden geht.
>
> Viele Beispiele zeigen folgendes:
>
> Objekt.Methode();
>
> manche aber:
>
> Objekt->Methode();
>
> Worin liegt hier der Unterschied?

...und für C++: Der Code ist 1. nicht Mikrocontroller-spezifisch und 2. 
sind die gegebenen Antworten in dem Sinne unvollständig, dass allein aus 
der Syntax nicht auf die Semantik der Code-Schnippel geschlossen werden 
kann. Insbesondere bedeuten weder "*Object" noch "Object->" dass 
"Object" ein Zeiger sein muss.

Daher kann
1
(*Object).Method();
etwas anderes sein als
1
Object->Method();

Beispiel:
1
struct B
2
{
3
  int value;
4
  B (int value) : value(-value) {}
5
  int Method () const
6
  {
7
    return 3 + value;
8
  }
9
};
10

11
struct A
12
{
13
  int value;
14
  A (int value) : value(value) {}
15

16
  B operator * () const
17
  {
18
    return B (value);
19
  }
20
  int Method() const
21
  {
22
    return value;
23
  }
24
  A* operator -> () const
25
  {
26
    return new A (2 + value);
27
  }
28
  
29
};
30

31
int testA1 (const A &Object)
32
{
33
  return Object.Method(); // Ok, return Object.value
34
}
35

36
int testA2 (const A &Object)
37
{
38
  return Object->Method(); // Ok, return 2 + Object.value
39
}
40

41
int testA3 (const A &Object)
42
{
43
  return (*Object).Method(); // Ok, return 3 - Object.value
44
}
45

46
int testA4 (const A *Object)
47
{
48
  return Object->Method(); // Ok, return Object.value
49
}

Sowohl prefix-* als auch -> können überladen werden, und ob das 
tatsächlich der Fall ist kann man nur wissen, wenn man die Definition 
von Object kennt.

Und Method braucht auch keine "Methode" zu sein, es kann eine Komponente 
sein, deren Klasse () überlädt.  Beispiel:
1
struct B
2
{
3
  int value;
4
  B (int value) : value(-value) {}
5
  int Method () const;
6
  int operator () () const
7
  {
8
    return 4 + value;
9
  }
10
};
11

12
struct A
13
{
14
  int value;
15
  B Method;
16
  A (int value) : value(value), Method(value) {}
17

18
  B operator * () const;
19
  A* operator -> () const;
20
};
In dem Falle hat man dann
1
int testA1 (const A &Object)
2
{
3
  return Object.Method(); // Ok, return 4 - Object.value
4
}
denn Object.Method liefert die Method-Komponente von A, welche vom Typ B 
ist, die wiederum () überlädt.
#5281250
Lesenswert?

Und all die tollen Antworten nur, um die einzig zielführende Antwort zu 
vermeiden:

Nimm dein C/C++ Buch, und lies nach, was es mit den Operatoren und all 
dem anderen auf sich hat. Ohne Kenntnis der allereinfachsten und 
grundlegenden Konzepte und Syntax einer Programmiersprache versteht man 
Quelltexte nunmal nicht.

Ja, dieses Forum ist manchmal Anfängerfeindlich, und ja, das muß 
manchmal sein.

Oliver
#5281257
Lesenswert?

@gjlayde
Wah.. den () Operator zu überladen dürfte zu dem verwirrendsten Dingen 
gehören die man in C++ machen "kann"...

@oliverso
Da ist was dran. Die Geschichte mit den Pointern zählt allerdings klar 
zu dem an schwersten zu begreifenden Aspekten von C und C++. Besonders 
wenn man von höheren Sprachen kommt.
Dabei ist es weniegr das Konzept an sich von Speicheraddressierung 8was 
in Büchern auch breit und lang behandelt wird), sondern die Syntax 
selbst.
Gast #5281264
Lesenswert?

Alex G. schrieb:
> Die Geschichte mit den Pointern zählt allerdings klar zu dem an
> schwersten zu begreifenden Aspekten von C und C++.

Hab nie verstanden warum. Man muss sich nur überlegen wie so ein 
Speicher strukturiert ist... Und schwerer zu begreifen als z.B. 
Metaprogrammierung mit variadischen Templates usw?

Alex G. schrieb:
> Wah.. den () Operator zu überladen dürfte zu dem verwirrendsten Dingen
> gehören die man in C++ machen "kann"...

Ist aber absolut gängig, um Funktionale zu definieren. Mittlerweile dank 
Lambdas etwas weniger.
Gast #5281288
Lesenswert?

> die man in C++ machen "kann"...

Gottseidank kann, nicht muss. Ich nutze dieses Feature nur sehr selten.

Wenn ich mir die ganzen langen und teils für mich verwirrenden Antworten 
anschaue, war meine erste Antwort wohl doch noch die Beste. Sie sollte 
nämlich nur die Stichworte nennen, mit denen man weiter suchen kann.
#5281294
Lesenswert?

Alex G. schrieb:
> Wah.. den () Operator zu überladen dürfte zu dem verwirrendsten Dingen
> gehören die man in C++ machen "kann"...

Kommt darauf an, wofür man es verwendet. Es ist z.B. ein unheimlich 
schicker Weg, einem Container einen bounds-check zu verpassen.

> Die Geschichte mit den Pointern zählt allerdings klar
> zu dem an schwersten zu begreifenden Aspekten von C und C++

Du solltest hier C und C++ nicht in einen Topf werfen. In C++ braucht 
man an sehr vielen Stellen keinen Pointer, wo man in C einen braucht. 
Und zwar weil C++ call by reference kann und auch richtige Strings hat. 
Nackte Pointer sollte man in einem C++ Programm auch nicht verwenden 
(oder brauchen). Dynamische Allozierungen verpackt man besser in 
Objekten, dann kann man das Aufräumen auch gleich in den Destruktor 
legen. Wenn überhaupt noch Pointer auf der Anwendungsebene, dann 
Smartpointer.
#5281295
Lesenswert?

Da stimme ich Stefan mal in beiden Punkte zu...

Programmeire C++ allerdings nur für PCs. Eventuell hat man durch solche 
Konstrukte, Vorteile in Sachen Geschwindigkeit?


Dr. Sommer schrieb:
Und schwerer zu begreifen als z.B.
> Metaprogrammierung mit variadischen Templates usw?
Wenn man in die herantritt, hat man in der Regel schon einige Monate 
intensiver Erfahrung mit C++ und ist abgehärtet :D
Gast #5281296
Lesenswert?

Stefan U. schrieb:
> Gottseidank kann, nicht muss.

Und wie würdest du dann eine Prädikats-Funktion an std::find_if 
übergeben, die sich zusätzliche Dinge merkt? Mit Lambda's geht's 
mittlerweile kompakter, aber das ist auch nicht immer geeignet.

Kaj G. schrieb:
> Schon mal einen ueberladenen []-Operator gesehen? Das ist nicht weniger
> verwirrend :D
Ja, bei std::vector. Funktionier dann genau wie bei Arrays. Super 
kompliziert!
Gast #5281326
Lesenswert?

Überladene Operatoren sind sicher ein Schock, für Leute, welche aus 
einer Sprachecke kommen, welche das nicht kann.
(z.B. ich)

Aber mittlerweile habe ich sie lieben gelernt.

Zum Beispiel ermöglichen sie mir den "Datenfluss" zu betonen. Die 
Fixierung auf den Programmfluss spiegelt oftmals nicht das wieder, was 
man erreichen möchte.
Gerade, wenn man lang laufende Schleifen/Zeitabläufe aufreißen muss, um 
(quasi) Nebenläufigkeiten zu erreichen.

Hier ein Beispiel aus meiner Bastelstube, welches einfach nur 
Tastendrücke zählt.
1
void loop() 
2
{
3
  counter = flankenerkennung = entprellen = taster;
4
}

Wie gesagt, hier liegt die Betonung auf dem Datenfluss.
Der Programmfluss ist verborgen in den Klassen.
Auch die innere zeitliche Abfolge des entprellens ist unter dem Deckel.
Gast #5281340
Lesenswert?

> counter = flankenerkennung = entprellen = taster;

Oha, wie schrecklich!

> Überladene Operatoren sind sicher ein Schock, für Leute, welche aus
> einer Sprachecke kommen, welche das nicht kann.

Jawohl
#5281345
Lesenswert?

Arduino F. schrieb:
> Wie gesagt, hier liegt die Betonung auf dem Datenfluss.
> Der Programmfluss ist verborgen in den Klassen.
> Auch die innere zeitliche Abfolge des entprellens ist unter dem Deckel.

Macht es dafür Sinn einen Operator so zweck zu entfremden?

Das problem ist eben, wenn jemand anders auf deinen Code da blickt, ohne 
diese Hintergründe zu kennen, dann denkt er erstmal "wieso werden da 
alle variablen auf den selben Wert gesetzt"?

Sieht sicherlich schön aus, aber wenn man nicht nur für sich selbst 
programmeirt, ist das eher ein graus.
Ich hätte für den Zweck eher versucht Java's "Streams" zu immitieren, 
damit es in etwa so aussieht:
1
counter = taster.entprellen().flankenerkennung().zaehlen();

Das wäre viel eindeutiger.
#5281349
Lesenswert?

Weil man alles kann (aber nicht muß), gibt’s ja inzwischen die C++ Core 
guidelines (denen man folgen kann, aber nicht muß) Und deren erste Regel 
zum Thema Operator-überladen lautet:

http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Ro-conventional

Der Zuweisungsoperator = sollte grundsätzlich und immer eine Zuweisung 
ausführen, in der Form, daß nach a=b a==b true liefert.

Alles andere ist nur was für den Confuscated C Contest.

Oliver
Gast #5281374
Lesenswert?

Nunja...
Muss ja keinem schmecken.

Gibt ja auch durchaus noch andere Notationen!
Meine Klassen sind in der Hinsicht recht vielgesichtig.

Alternative zu:
> counter = flankenerkennung = entprellen = taster;

Dies:
counter(flankenerkennung(entprellen(taster())));

Oder:
counter.doTrigger(flankenerkennung.doTrigger(entprellen.doTrigger(taster 
.isHigh())));

Alles das gleiche.
Frisst kein Byte mehr oder weniger.
Gast #5281387
Lesenswert?

Alex G. schrieb:
> Ich hätte für den Zweck eher versucht Java's "Streams" zu immitieren,
> damit es in etwa so aussieht:
> counter = taster.entprellen().flankenerkennung().zaehlen();

Da findet ein Fluss von links nach rechts statt. Es werden Referenzen 
übergeben.
Macht aus meiner Sicht keinen Sinn hier.
Ich möchte ja Zustände weiter reichen/zuweisen.

An anderer Stelle setze ich das gerne ein!
Auch dazu gerne ein Beispiel:
1
void setup() 
2
{
3
   Serial.begin(9600);
4

5
   adc  .enable()
6
        .setReference(Adc::REF_11)
7
        .setClockDivisor(Adc::DIV_128)
8
        .setSource(Adc::MUX_THERMO)
9
        .setCallBack(adcCallBack)
10
        .enableAutoTrigger()
11
        .setTrigger(Adc::TRIG_FREERUNNING)
12
        .enableIrq()
13
        .startConversion();
14
}
Gast #5281389
Lesenswert?

Alex G. schrieb:
> Es geht nicht um Bytes sondern um Leserlichkeit...
Genau!
Und die erste Variante ist (für mich) leserlicher.
Verschachtlungen und Klammern zählen hasse ich wie die Pest.

Wenn man mal kapiert hat, worum es sich dreht, auch für andere.
(die Hoffnung stirbt zuletzt)
#5281432
Lesenswert?

Arduino F. schrieb:
> Alex G. schrieb:
>> Ich hätte für den Zweck eher versucht Java's "Streams" zu immitieren,
>> damit es in etwa so aussieht:
>> counter = taster.entprellen().flankenerkennung().zaehlen();
>
> Da findet ein Fluss von links nach rechts statt. Es werden Referenzen
> übergeben.
> Macht aus meiner Sicht keinen Sinn hier.
> Ich möchte ja Zustände weiter reichen/zuweisen.

Du hättest da durchaus den .-operator überladen können, wenn du keine 
Objektorientierung für diese kleine Sache willst, denn der 
offensichtliche Zweck des . - oeprators wäre gleich geblieben.
Beim Üebrladen von =, hast du den erheblich verändert.

Wieso ist das "Weiterreichen von Zuständen" denn deiner Meinung nach 
kein Fluss?

Du hast den Taster.
Der Taster wird entprellt.
Auf den entprellten Taster wird Flankenerkennung angewendet.
Die erkannten Flanken werden gezählt.
#5281447
Lesenswert?

Arduino F. schrieb:
> adc  .enable()
>         .setReference(Adc::REF_11)
>         .setClockDivisor(Adc::DIV_128)
>         .setSource(Adc::MUX_THERMO)
>         .setCallBack(adcCallBack)
>         .enableAutoTrigger()
>         .setTrigger(Adc::TRIG_FREERUNNING)
>         .enableIrq()
>         .startConversion();

Hier versteht jeder sofort was abgeht.



Arduino F. schrieb:
> counter = flankenerkennung = entprellen = taster;

und hier versteht (wenn überhaupt) nur derjenige der es geschrieben hat 
was abgeht. Bitte nimm es nicht persönlich aber ich sehe vor allem in 
der Arduino-Welt die kriminellsten Dinger wenn es um das überladen von 
Operatoren oder Funktionen geht. Das es funktioniert ist ja gar keine 
Frage aber der Code taugt dann nur noch für Obfuscation-Contests.
Gast #5281475
Lesenswert?

Alex G. schrieb:
> Du hättest da durchaus den .-operator überladen können, wenn du keine
> Objektorientierung für diese kleine Sache willst, denn der
> offensichtliche Zweck des . - oeprators wäre gleich geblieben.
> Beim Üebrladen von =, hast du den erheblich verändert.


Da hätte ich doch mal gerne eine Vorführung gesehen!
Ein Beispiel.

Habe mir die überladbaren Operatoren ausführlich angesehen, aber der . 
ist mir dabei noch nicht unter gekommen.

http://en.cppreference.com/w/cpp/language/operators gibt das nicht her.
Und meine beiden C++ Bücher auch nicht.

Der Compiler schreit bei dem Versuch:
>  error: expected type-specifier before '.' token
ok, ok, da mag ich noch was falsch gemacht haben.

Was solls, ist ein Grund, nochmal auf die Suche zu gehen.....

---
2 Probleme sehe ich jetzt schon leuchten...

1. Bisher brauche ich für den Taster kein Byte Ram. Die definierte 
Instanz wird vollständig weg optimiert. Es bleibt nur die Inline 
Funktionalität im resultierenden Code erhalten. Ob mir das dann so 
erhalten bleibt? Wenn ich da mit Zeigern und Referenzen spielen muss?

2. Der Dot Operator würde mir die Initialisierung kaputt machen. 
taster.init(). Ohne Extrawürste wirds dann nicht gehen.

Wenn sich die beiden Sorgen ohne Probleme aushebeln lassen, wäre ich 
sehr erfreut. Muss es aber erst sehen und fühlen.

Ob ich
> counter = flankenerkennung = entprellen = taster
oder
> taster.entprellen.flankenerkennung.counter
schreibe ist mir echt egal...
Gast #5281590
Lesenswert?

Alex G. schrieb:
> Oops, mein Fehler. Der Punkt ist tatsächlich eine Ausnahme.
> Geht doch nicht alls in C++.
Danke, für die Rückmeldung!

Und ich dachte schon...

Dann werde ich erstmal den Zuweisungsoperator beibehalten. Denn die 
anderen Operatoren sind noch "absurder" an der Stelle.

Werde dann warten, bis C++ mir den <- Operator erlaubt

counter<-flankenerkennung<-entprellen<-taster;
Gast #5281606
Lesenswert?

> ich würde den §-Operator vorschlagen

Was macht man damit?

text=GrundGesetz§3.4;

> Und warum nicht den Schiebeoperator '<<'?

Das war auch mein spontaner Einfall dazu, schließlich wird der auch bei 
std::cout "missbraucht".
Gast #5281650
Lesenswert?

Andreas M. schrieb:
> Und warum nicht den Schiebeoperator '<<'? Würde sogar etwas Sinn
> ergeben.

Naja....

Habe ich drüber nachgedacht!
Und auch implementiert gehabt.

Würde dann im Extremfall zu so einem Bild führen:
1
void loop() 
2
{
3
 Serial << (counter << flankenerkennung << entprellen << taster) << endl;
4
}

Oder etwas klarer:
1
void loop() 
2
{
3
 counter << flankenerkennung << entprellen << taster;
4
 Serial << counter << endl;
5
}

Schien mir dann doch nicht angemessen zu sein.
Gast #5282801
Lesenswert?

Da widerspricht sich der Autor aber selbst mit seinem Beispiel:
1
Use an operator for an operation with its conventional meaning
2

3
std::cout << /* class members here */;

Schauen wir doch mal, wozu der << Operator spezifiziert ist:
1
Bitwise left shift  a << b
(https://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B)

Da hat er die Ausnahme einfach zur Regel gemacht. Dumm gelaufen, würde 
ich mal sagen.
Persönliche Seite #5282847
Lesenswert?

Vincent H. schrieb:
> Deine operator overloads verstoßen allesamt gegen das "principle of
> least astonishment".

Was aber nichts daran ändert, dass man die Semantik der vom OP gezeigten 
2 Codezeilen nicht wissen kann, ohne die Definition der 
zugrundeliegenden Deklarationen und Definitionen zu kennen.

Ich würd auch jetzt nicht sagen, dass "->" zu überladen weniger oder 
mehr überraschend ist als die Default-Bedeutung von "->".

Dito für andere Features wie Ableitung etc.
Gast #5283048
Lesenswert?

> Ich würd auch jetzt nicht sagen, dass "->" zu überladen weniger oder
> mehr überraschend ist als die Default-Bedeutung von "->".

Wohl wahr, einen Schönheitswettbewerb würde C++ nie gewinnen.
Gast #5283057
Lesenswert?

Stefan U. schrieb:
> einen Schönheitswettbewerb würde C++ nie gewinnen.

Mir wäre ja auch Forth recht...
Aber das fühlt sich auf einer Von Neumann Maschine wohler.
Und irgendein Massenspeicher um das Image zu laden wäre auch schön.

Obwohl, einen "Schönheitswettbewerb" gewinnt man damit auch nicht.
Aber zumindest kann man sich da einen <- Operator bauen (ohne dass man 
erschlagen wird), wenn man meint einen zu brauchen.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren