Hallo,
bei der Programmierung in C++ besteht ja die Möglichkeit,
Funktionsparameter per Referenz zu übergeben.
Von der Lesbarkeit finde ich dieses ja besser. Mich würde nur mal
interessieren, welche Version effizienter umgesetzt wird.
Auf einem AVR könnte ich mir ja den Assemblertext ansehen und
vergleichen, aber auf dem PC steig ich da nicht so durch.
Macht der Kompilier aus beiden Versionen der Funktion den gleichen
Assemblercode?
Beispiel:
Ich habe ein C++ Buch in dem als Beispiel Objekte "Datum" vorhanden
sind.
Für diese Klasse gibt es dann eine Funktion exchange, die die Inhalte
zweier Objekte tauscht.
In der Beschreibung steht, dass "in diesem Fall mit Zeigern gearbeitet
werden sollte weil beim Aufruf der Funktion dann die Adresse des Objekts
angegeben werden muss".
Warum man das sollte steht da leider nicht.
Hier die Methode:
1
voidDatum::exchange(Datum*pd)
2
{
3
Datumtemp=*pd;
4
*pd=*this;
5
*this=temp;
6
}
Vom Prinzip her ist das dann doch ähnlich meiner beiden "tausch"
Funktionen oben, oder?
Manche C++-Programmierer finden es besser, Parameterwerte, die durch die
Funktion verändert werden, per Zeiger zu übergeben statt per Referenz,
da man sonst beim Aufruf der Funktion überhaupt nicht mehr sieht, daß
sie den Wert verändert.
Der schöne Vorteil bei Referenzen ist, dass man innerhalb der Funktion
nicht ständig dereferenzieren muss.
> Manche C++-Programmierer finden es besser, Parameterwerte, die durch die> Funktion verändert werden, per Zeiger zu übergeben statt per Referenz,> da man sonst beim Aufruf der Funktion überhaupt nicht mehr sieht, daß> sie den Wert verändert.
Parameter gehören ja ohnehin dokumentiert, um es nicht dem Rätselraten
zu überlassen, ob sich was ändert oder nicht. Per Pointer übergebene
Sachen könnten sich ja beispielsweise auch nicht ändern.
> Manche C++-Programmierer finden es besser, Parameterwerte, die durch die> Funktion verändert werden, per Zeiger zu übergeben statt per Referenz,> da man sonst beim Aufruf der Funktion überhaupt nicht mehr sieht, daß> sie den Wert verändert.> Parameter gehören ja ohnehin dokumentiert, um es nicht dem Rätselraten> zu überlassen, ob sich was ändert oder nicht. Per Pointer übergebene> Sachen könnten sich ja beispielsweise auch nicht ändern.
Najaaa....da halt ich mich also lieber an die Funktionsprototypen*:
void foo(const std::string & s);
void bar(std::string & s);
Bei foo kann s nicht verändert werden (ausser der Halbschuh der foo
programmiert hat casted irgendwo die Konstantheit weg), bei bar kann s
verändert werden, da s nicht als konstante Referenz deklariert ist (und
soll vermutlich verändert werden, weil sonst wär s konstant...)
*) Und sonst les ich den Code wenn ich befürchte dass etwas nicht
stimmt. Die Dokumentation lügt sowieso bzw. ist veraltet bzw. ist nicht
vorhanden :D
Zunächst mal muss unterschieden werden:
Soll die Funktion den Parameter beim Aufrufer ändern können
oder nicht?
Falls sie nicht ändern können soll:
Handelt es sich beim Argument um einen int, char, bool, double
oder einen sonstigen sog. eingebauten Datentyp?
wenn ja: übergib das Argument direkt
bsp: void foo( int i );
wenn nein: übergib eine konstante Referenz
bsp: void foo( const std::string& i );
Falls die Funktion das Argument beim Aufrufer ändern können soll:
Übergib eine Referenz
bsp: void foo( int& i );
void foo( std::string& i );
In C++ werden Pointer wesentlich seltener eingesetzt als in C.
Stattdessen nimmt man lieber Referenzen.
(Die Unterscheidung im Fall 1 zwischen eingebauten Datentypen
und Klassen beruht darauf, dass eine Kopie eines int zu erzeugen
eine billige Operation ist, während das Erzeugen einer Kopie
eines Klassenobjektes normalerweise eine teure Operation ist)
> In der Beschreibung steht, dass "in diesem Fall mit Zeigern gearbeitet> werden sollte weil beim Aufruf der Funktion dann die Adresse des Objekts> angegeben werden muss".> Warum man das sollte steht da leider nicht.
Es ist auch Unsinn. Der Autor hat den Schritt von C zu C++
noch nicht wirklich konsequent vollzogen.
void Datum::exchange( Datum& With )
{
Datum temp = With;
With = *this;
*this = temp;
}
Rolf Magnus wrote:
> Manche C++-Programmierer finden es besser, Parameterwerte, die durch die> Funktion verändert werden, per Zeiger zu übergeben statt per Referenz,> da man sonst beim Aufruf der Funktion überhaupt nicht mehr sieht, daß> sie den Wert verändert.
Dieses Argument kommt immer wieder. Ich halte es allerdings
für ein NULL-Argument.
Klar. Bei Beispielen die Funktionsnamen wie foo() oder bar()
verwenden, stimmt das tatsächlich.
In der Praxis hat man aber vernünftige Funktions und Argumentnamen
und bei denen stellt sich diese Fragestellung normalerweise nicht
bzw. nur sehr selten.
Und wie soll so ein Name heißen, der darauf hinweißt, dass der
Vatriableninhalt verändert wird?
pArgument gibts ja net mehr - is ja kein Pointer.
Die Praxis... jaa.. ich sehs überall. Jegliche APIs verwenden
Referenzen... ohne Ende. ;-)
Ich verwende Referenzen nur, wenn der Variableninhalt von der Funktion
nicht verändert wird. Da dies bei dieser Art der Parameterübergabe
schlichtweg nicht klenntlich gemacht werden kann, ob eine veränderung
des Inhalts ansteht, verschlechtert sich nur die Klarheit im Code.
Der Sinn der Referenzen an dieser stelle ist der schnellere Aufruf der
Funktion. Große Strukturen oder Klassen ersparen sich damit den Aufruf
des Copy-Ctors und pumpen den Stack nicht sinnlos zu.
MFG
Erstmal Danke für die Infos.
So wie es scheint, ist es mehr oder weniger Geschmackssache ob ich nun
einen Pointer oder eine Referenz verwende.
Obwohl ich sagen muss, dass ich das Argument einen Pointer zu verwenden,
um kenntlich zu machen dass der Wert verändert wird, gar nicht mal
schlecht finde.
Allerdings habe ich bis jetzt bei meinen Referenzen die nicht geändert
werden mit einem "const" gekennzeichnet. Dies ist im Gegensatz zum
Pointer beim Funktionsaufruf aber nicht mehr sichtbar.
Gibt es denn überhaupt einen funktionalen Unterschied? Gut,
Pointerarithmetik wird es wohl nicht mehr geben ;-)
Toll.. wirklich super. wrote:
> Und wie soll so ein Name heißen, der darauf hinweißt, dass der> Vatriableninhalt verändert wird?> pArgument gibts ja net mehr - is ja kein Pointer.
Ich spreche nicht von ungarischer Notation.
Wenn du eine Funktion aufrufst, die 2 Werte vertauscht:
swap( Wert1, Wert2 );
werden dann die Argumente verändert?
Na sicher doch, das ist ja der Sinn der Funktion.
Wenn du einen Aufruf siehst:
NettoBetrag = CalcNetto( BruttoBetrag, Prozent );
wird da in CalcNetto eines der beiden Argumente verändert?
Sicher nicht. Das wäre wohl ziemlich kontraproduktiv und
auch nicht Aufgabe der Funktion. Die Funktion soll einen
Nettobetrag ausrechnen, drumm heist sie auch CalcNetto.
Dazu muss keines der Argumente verändert werden.
Was ist mit
Withdraw( UserAccount, Amount );
welches der beiden Argumente wird wohl von der Funktion
verändert werden?
genau: UserAccount wird sich verändern, Amount wird sich nicht
verändern.
Funktionen sollen das tun was aussen drauf steht (wie sie heissen).
Nicht mehr und nicht weniger. Und daraus ergibt sich automatisch
welche Argumente verändert werden können und welche nicht.
> Die Praxis... jaa.. ich sehs überall. Jegliche APIs verwenden> Referenzen... ohne Ende. ;-)
APIs muessen viele Programmiersprachen unterstützen. Da muss
man den kleinsten gemeinsamen Nenner nehmen. Und der ist nun
mal: Eine Adresse wird übergeben.
Thomas wrote:
> Gibt es denn überhaupt einen funktionalen Unterschied? Gut,> Pointerarithmetik wird es wohl nicht mehr geben ;-)
Kannst du genauso machen.
Eine Referenz ist ja nichts anderes als ein 'neuer Name
für ein ansonsten existierendes Objekt'. Von dem Objekt
(über die Referenz) kann man die Adresse bestimmen und damit
Pointerarithmetik machen.
Aber es gibt tatsächlich einen qualitativen Unterschied.
Wenn deine Schnittstelle die Möglichkeit haben muss, den
Sachverhalt 'kein Objekt' auszudrücken, dann kommst du um
einen Pointer nicht drumherum. Das geht nicht mit Referenzen.
> Von dem Objekt (über die Referenz) kann man die Adresse bestimmen und> damit Pointerarithmetik machen.
Auf sowas wird man aber in der Regel tunlichst verzichten. Wenn eine
Funktion eine Referenz auf ein Objekt will, würde ich nicht annehmen,
daß sie das als Element eines Arrays ansieht und einen Zeiger bildet, um
darüber auf die anderen Elemente zuzugreifen.
Eigentlich nimmt man keines von beiden, sondern die
swap-Templatefunktion aus der C++ Standardbibliothek.
1
#include<algorithm>
2
...
3
4
inta;
5
intb;
6
std::swap(a,b);
7
8
std::stringsa;
9
std::stringsb;
10
std::swap(sa,sb);
11
12
// Und dank template-Spezialisierung
13
std::list<Irgendwas>la;
14
std::list<Irgendwas>lb;
15
std::swap(la,lb);
Beachte: bei deiner Implementierung würden beim letzten swap die (evtl
mehrere MB grossen) Listen dreimal umkopiert werden, mit std::swap
werden nur ein paar interne Pointer in den Listen angepasst (ruft intern
std::list::swap auf).
/Ernst
Peter, du enttäuschst mich. Von dir hätte ich jetzt den Kommentar
erwartet, dass man Namespaces überhaupt nicht brauchen tut, weils
1. Lesbarer ist und
2. Man dann den Code auch durch den C Compiler jagen kann
> ...und man benutzt> using namespace std;> damit man nicht 1000 mal std:: hinschreiben muss ;-)
Am besten in jedem Header, um möglichst viele Namenskonflikte zu
provizieren so das Konzept von Namespaces nutzlos (useless namespace
std) zu machen.
namenskonflikte sind nicht schlimm, die meldet der compiler sofort und
dann kann man es immer noch ändern. sinnlose schreibarbeit sparen wäre
mir wichtiger.