Zeiger als Parameter von Funktionen
-
Hallo!
Ich habe da ein kleines Verständnisproblem was Zeiger als Parameter von Funktionen angeht.
Hier einmal 4 Prototypen und der Funktionsaufruf:
Beispiel1:
void increment(int *intZeiger); increment(&value);Das erscheint mir noch logisch. Beim Aufruf wird der Funktion die Speicheradresse von value übergeben. Diese wird dann in den Zeiger *intZeiger kopiert.
Beispiel2:
void increment(int *intZeiger); increment(valueZeiger);valueZeiger ist hier ein Zeiger auf einen Integer, dessen Adresse wird beim Aufruf übergeben und anschließend in *intZeiger gespeichert. Soweit ist auch noch alles klar. Verwirrend finde ich, dass valueZeiger ja die Speicheradresse meint und *intZeiger den Wert, welcher gespeichert wurde. Auch wenn das * verwirrend ist, muss ja klar sein, dass es sich um einen Zeiger handelt.
Beispiel3:
void increment(int &intParameter); increment(value);Hier wird bei Aufruf der Integer übergeben. Was hier beim Aufruf wohin kopiert ist mir ein Rätsel. Für mich sieht es so aus, als würde der Intwert von value in eine Adresse kopiert werden (wegen dem Ampersand).
Beispiel4:
void increment(int **intZeiger); increment(&valueZeiger);Was hier geschieht ist mir ein totales Rätsel. Die Adresse eines Zeigers wird in den Zeiger auf einen Zeiger gespeichert?!
Mein Problem: Beispiel 3 und Beispiel 4 kann ich mir nicht wirklich erklären. Könnte mir vllt. jemand helfen und mir sagen, was da auf dem Stack los ist? Ich komme eigtl. aus dem Bereich Java, daher habe ich recht wenig bis keine Erfahrungen mit Zeigern.
Als Anhang Poste ich noch einmal alle Programme.
Beispiel 1: #include <cstdlib> #include <iostream> using namespace std; void increment(int *intZeiger) { (*intZeiger)++; } int main(int argc, char *argv[]) { int value = 1; increment(&value); cout << "Value: " << value << endl; system("PAUSE"); return EXIT_SUCCESS; } Beispiel 2: #include <cstdlib> #include <iostream> using namespace std; void increment(int *intZeiger) { (*intZeiger)++; } int main(int argc, char *argv[]) { int value = 1; int *valueZeiger = 0; valueZeiger = &value; increment(valueZeiger); cout << "Value: " << value << endl; system("PAUSE"); return EXIT_SUCCESS; } Beispiel 3: #include <cstdlib> #include <iostream> using namespace std; void increment(int &intZeiger) { intZeiger++; } int main(int argc, char *argv[]) { int value = 1; increment(value); cout << "Value: " << value << endl; system("PAUSE"); return EXIT_SUCCESS; } Beispiel 3: #include <cstdlib> #include <iostream> using namespace std; void increment(int **intZeiger) { (**intZeiger)++; } int main(int argc, char *argv[]) { int value = 1; int *valueZeiger = 0; valueZeiger = &value; increment(&valueZeiger); cout << "Value: " << value << endl; system("PAUSE"); return EXIT_SUCCESS; }Vielen Dank !
-
mawilab schrieb:
Verwirrend finde ich, dass valueZeiger ja die Speicheradresse meint und *intZeiger den Wert, welcher gespeichert wurde.
Das ist komplizierter, als es sich anhört. Tatsächlich geht es immer darum, wie du die Bytes, auf die diese Zeiger zeigen, interpretierst. Daher sind Zeiger-Casts, sprich Casts, die den Typ auf den der Zeiger zeigt ändern, sehr gefährlich. Das kann schnell zum Programmabsturz führen.
mawilab schrieb:
Auch wenn das * verwirrend ist, muss ja klar sein, dass es sich um einen Zeiger handelt.
Du redest jetzt von der Dereferenzierung, und nicht von der Deklaration, oder? Aber was ist daran verwirrend?
mawilab schrieb:
Beispiel3:
void increment(int &intParameter); increment(value);Hier wird bei Aufruf der Integer übergeben. Was hier beim Aufruf wohin kopiert ist mir ein Rätsel. Für mich sieht es so aus, als würde der Intwert von value in eine Adresse kopiert werden (wegen dem Ampersand).
Nein. Das Ampersand, so wie es hier benutzt wird, macht aus
intParametereine Referenz, das hat mit Zeigern direkt nichts zu tun.mawilab schrieb:
Beispiel4:
void increment(int **intZeiger); increment(&valueZeiger);Was hier geschieht ist mir ein totales Rätsel. Die Adresse eines Zeigers wird in den Zeiger auf einen Zeiger gespeichert?!
Ja... ^^
mawilab schrieb:
Mein Problem: Beispiel 3 und Beispiel 4 kann ich mir nicht wirklich erklären. Könnte mir vllt. jemand helfen und mir sagen, was da auf dem Stack los ist? Ich komme eigtl. aus dem Bereich Java, daher habe ich recht wenig bis keine Erfahrungen mit Zeigern.
Das erklärt alles.

Rohe Zeiger wirst du in C++ fast nie finden. Gerade mehrfache Indirektion wird in C++ eigentlich gar nicht verwendet. Das ist eine C-Sache; mehrfache Indirektion wurde da für mehrdimensionale Arrays verwendet. In C++ gibt es dafür viel bessere Lösungen.
Trotzdem ist es natürlich gut, bzw. wichtig Zeigerarithmetik zu verstehen.
Aber das du nicht weißt, was Referenzen sind ist kritisch. Da empfehle ich dir ein Grundlagenbuch.
-
Zu Beispiel 3:
Die Funktion erwartet eine Referenz auf einen int.
Referenzen sind wie Zeiger, nur braucht man für den Wertzugriff nicht den *-Operator, sondern kann einfach den Namen der Variablen verwenden (wie bei normalen ints auch).
Zur Übergabe muss man auch nicht die Adresse einer Variable angeben, sondern der Name reicht. Das ist zum einfacherern Übergeben Call by Reference.Zu Beispiel 4:
int *zeiger, ist ein Zeiger auf int.
int **zeiger, ist ein Zeiger, der auf einen Zeiger zeigt, der auf ein int zeigt.
int ***zeiger, ist ein Zeiger, der auf einen Zeiger zeigt, der auf einen Zeiger zeigt, der auf einen int zeigt.
int *zeiger = &wert, initalisiert einen Zeiger mit der Adresse von wert.
int *zeigeraufzeiger = &zeiger, initialisiert einen Zeiger auf einen Zeiger mit der Adresse von zeiger.
int *zeigeraufzeigeraufezeiger,...
Übergeben wird einfach nur die Adresse eines Zeigers an einen solchen Zeiger.
*zeiger = 5, greift auf den Wert von zeiger zu.
*zeigeraufzeiger = ..., greift auf den Wert eines Zeigers auf einen Zeiger zu.
*(*zeigeraufzeiger) = ..., greift auf den Wert zu auf den der zeigeraufzeiger zeigt (also auf einen zeiger), auf das Ergebnis wird wiederum * angewandt und man greift auf den Wert des zeigers, auf den zeigeraufzeiger zeigt zu.
-
int *zeigeraufzeiger = &zeigerHast du da nicht ein Sternchen vergessen, und beim nächsten Beispiel auch zwei?

-
Nathan schrieb:
Referenzen sind wie Zeiger
Ganz so einfach ist es auch nicht ...
-
Belli schrieb:
Nathan schrieb:
Referenzen sind wie Zeiger
Ganz so einfach ist es auch nicht ...
Oh Mist, hab ich übersehen.
Das ist natürlich Blödsinn.
-
Ich bin beruhigt, dass Du das noch mal bestätigst ...
-
Danke schonmal für die Antworten
!Ich arbeite mich gerade durch ein Grundlagenbuch durch. Dabei haben sich diese Fragen ergeben, besonders der Zeiger auf einen Zeiger. In einer Musterlösung zu einer Aufgabe wird dieser verwendet, aber vorher und nacher nicht richtig erklärt. Da steht nur, dass der doppelstern benötigt wird, damit die Änderung innerhalb der Funktion auf die Variable durschlägt. So oder so ähnlich stand es da. Den Abschnitt zu den Parametern (zwei seiten weiter vorne) habe ich mir gerade noch einmal durchgelesen.
Ok 1, 2 und 3 habe ich nun verstanden denke ich.
Beispiel 4 hat sich in meinem Kopf auch entknotet. Ich finde es aber immernoch verwirrend.
Daraus ergibt sich eine neue Frage die zum Teil ja schon beantwortet wurde: Wo brauchst man das bzw. wo finden die Beispiele Anwendung. Es führen ja alle zum selben Ergebnis, warum sollte man welche Variante verwenden und warum nicht.
LG
-
Ich arbeite mich gerade durch ein Grundlagenbuch durch. Dabei haben sich diese Fragen ergeben, besonders der Zeiger auf einen Zeiger. In einer Musterlösung zu einer Aufgabe wird dieser verwendet, aber vorher und nach[h]er nicht richtig erklärt.
oO
Ich glaube mittlerweile müssten wir mal eine Bücherverbrennung für Programmierfachliteratur organisieren.
Daraus ergibt sich eine neue Frage die zum Teil ja schon beantwortet wurde: Wo brauchst man das bzw. wo finden die Beispiele Anwendung. Es führen ja alle zum selben Ergebnis, warum sollte man welche Variante verwenden und warum nicht.
Rohe Zeiger sollten gänzlich vermieden werden. Es heißt nicht, dass man in der Praxis keinen Gebrauch davon macht. Im Gegenteil. Aber man benutzt Referenzen eher als Standard, und Zeiger nur wenn man dazu gezwungen wird.
P.S.: call-by-value ist auch immer eine Möglichkeit
aber es kommt eben drauf an
-
mawilab schrieb:
Beispiel 4 hat sich in meinem Kopf auch entknotet. Ich finde es aber immernoch verwirrend.
void f(){ int value = 1; int* value_zeiger = &value; int** value_zeiger_zeiger = &value_zeiger; } /* +-----+ +--------------+ +---------------------+ |value| | value_zeiger | | value_zeiger_zeiger | +-----+ +--------------+ +---------------------+ | int | <------| int* | <----------| int** | +-----+ +--------------+ +---------------------+ | 1 | | &value | | &value_zeiger | +-----+ +--------------+ +---------------------+ */ void increment(int** ppi){ /* ppi ist eine Kopie des Funktionsarguments / z.B. bei increment(value_zeiger_zeiger): +-------------------+ | ppi | +-------------------+ | int** | +-------------------+ | &value_zeiger | +-------------------+ }Wofür man das braucht?
Jedesmal, wenn Du den Wert einer Variable (dritte Zeile in der Grafik oben) in einer Funktion ändern willst. Allerdings sind dafür Referenzen kompakter und weniger fehlerträchtigvoid bieg_zeiger_um(int*& p, int* pp){ p=pp; } void bieg_zeiger_um(int** p, int* pp){ if(p != nullptr) *p=pp; } void g(){ int a = 0; int b = 1; int* p = &a; bieg_zeiger_um(p, &b); // hin... bieg_zeiger_um(&p, &a); // und her... }Ich muss weg: die lokale Hausbrauerei wartet!

-
@Sone, Belli:
Mir ist schon klar, dass Referenzen nicht wie Zeiger sind.
Da die Verwendung in diesem Zweck ähnlich ist, wollte ich den TE nicht weiter verwirren.
-
Wofür man das braucht?
Jedesmal, wenn Du den Wert einer Variable (dritte Zeile in der Grafik oben) in einer Funktion ändern willst. Allerdings sind dafür Referenzen kompakter und weniger fehlerträchtigAber das funktioniert doch auch so wie im Beispiel 1 oder 2. Warum also so umständlich?
Schade, dass ich schon auf Seite 173 bin und noch nie die Bezeichnung "Rohe Zeiger" gehört habe, scheint wichtig zu sein. Ich werde mir merken Referenzen zu verwenden, welchen bislang zwei Seite gewidmet wurden. Naja es folgen ja noch ein paar Seiten evtl. wird ja noch einiges erklärt, sonst stelle ich mein Exemplar gern der erwähnten Verbrennung zur Verfügung!
LG
-
Kannste schon dyn. Speicherreservierung?
Wenn notwendig, dann das ganze in einen std::unique_ptr packen oder in einen std::auto_ptr, falls dein Compiler kein C++11 unterstützt.
-
Sone schrieb:
Rohe Zeiger wirst du in C++ fast nie finden. Gerade mehrfache Indirektion wird in C++ eigentlich gar nicht verwendet. Das ist eine C-Sache; mehrfache Indirektion wurde da für mehrdimensionale Arrays verwendet. In C++ gibt es dafür viel bessere Lösungen.
Warum soll so ein Schwachsinn (Doppelzeiger für Arrays) in C auf einmal akzeptabel sein?
-
Mehrdimensionale Arrays gehen nicht anders?!
-
mawilab schrieb:
Wofür man das braucht?
Jedesmal, wenn Du den Wert einer Variable (dritte Zeile in der Grafik oben) in einer Funktion ändern willst. Allerdings sind dafür Referenzen kompakter und weniger fehlerträchtigAber das funktioniert doch auch so wie im Beispiel 1 oder 2. Warum also so umständlich?
Die Beispiele sind natuerlich gekuenstelt. Aber der Groschen ist gefallen?!
Einfache Aufgabe:
Eine bestimmte Bakterienkultur verdoppelt sich jeden Zyklus.
Schreib mir doch bitte eine(zwei) Funktion, die die "population" jeweils verdoppelt, so dass das folgende Programm 16 ausgibt.#include <iostream> int main(){ int population=1; for(int i=0; i<4; ++i) /* hier */ std::cout << population << '\n'; }Du wirst merken, dass Du entweder Zeiger, oder Referenzen brauchst.
-
Ok, hier die erste Lösug ohne zeiger oder Referenz die 16 Aaugibt:
#include <cstdlib> #include <iostream> using namespace std; int verdoppel(int population) { return population * 2; } int main(int argc, char *argv[]) { int population=1; for(int i=0; i<4; ++i) population = verdoppel(population); std::cout << population << '\n'; system("PAUSE"); return EXIT_SUCCESS; }Bischen gemein, da ich es mit dem Rückgabewert gemacht habe
!Nun zu der zweiten Lösung:
#include <cstdlib> #include <iostream> using namespace std; void verdoppel(int *popZeiger) { *popZeiger = *popZeiger * 2; } int main(int argc, char *argv[]) { int population=1; int *popZeiger = 0; popZeiger = &population; for(int i=0; i<4; ++i) verdoppel(popZeiger); std::cout << population << '\n'; system("PAUSE"); return EXIT_SUCCESS; }Das ist eigtl. klar, die Frage ist warum man es so machen sollte:
#include <cstdlib> #include <iostream> using namespace std; void verdoppel(int **popZeiger) { **popZeiger = **popZeiger * 2; } int main(int argc, char *argv[]) { int population=1; int *popZeiger = 0; popZeiger = &population; for(int i=0; i<4; ++i) verdoppel(&popZeiger); std::cout << population << '\n'; system("PAUSE"); return EXIT_SUCCESS; }Also mit einem Zeiger auf einen Zeiger

LG
-
TyRoXx schrieb:
Sone schrieb:
Rohe Zeiger wirst du in C++ fast nie finden. Gerade mehrfache Indirektion wird in C++ eigentlich gar nicht verwendet. Das ist eine C-Sache; mehrfache Indirektion wurde da für mehrdimensionale Arrays verwendet. In C++ gibt es dafür viel bessere Lösungen.
Warum soll so ein Schwachsinn (Doppelzeiger für Arrays) in C auf einmal akzeptabel sein?
Na, für dynamische mehrdimensionale Arrays bleibt dir in C doch nichts übrig?
Sry, ich bin kein Cler
aber wie denn sonst?
-
mawilab schrieb:
Bischen gemein, da ich es mit dem Rückgabewert gemacht habe

Naja, gemein ist das nicht, und jeder hier würde die Funktion auch genau so schreiben. Das ist in Java so und in C++ nicht anders.
mawilab schrieb:
Nun zu der zweiten Lösung:
void verdoppel(int *popZeiger) { *popZeiger = *popZeiger * 2; }Ja. Das war eine Lösung, die ich mir vorgestellt habe: Du willst den Wert von
populationändern, also musst Du einen Zeiger aufpopulationbenutzen.mawilab schrieb:
Das ist eigtl. klar, die Frage ist warum man es so machen sollte:
void verdoppel(int **popZeiger) { **popZeiger = **popZeiger * 2; }Also mit einem Zeiger auf einen Zeiger

Gibt keinen Grund. Zeiger auf Zeiger sind eher selten. Wie Sone schon angemerkt hat kommen sie bei mehrdimensionalen (ragged) Arrays vor. Manchmal wird argv so deklariert:
int main(int argc, char** argv){ // argv[0] bis argv[argc-1] enthalten die Kommandozeilenparameter }Aber um beim Beispiel mit
verdoppelzu bleiben: Du brauchst keinenint**um einenintzu ändern - das ist einfach unnötig kompliziert. Siehe mein früheres Beispiel mitbieg_zeiger_um()- da war ein Zeiger auf Zeiger "nötig".Um die Referenz hast Du Dich gedrückt - dabei ist das noch "die natürlichste" Art,
verdoppelin C++ zu schreiben.
-
Doppelzeiger "braucht" man in C auch noch, wenn man eine Funktion hat, die einen Zeiger auf ein gültiges Objekt erzeugen soll.
err_code_t create_some_type_obj(some_type ** p) { some_type * zeiger_auf_gueltiges_some_type_objekt = malloc(...); *zeiger_auf_gueltiges_some_type_objekt = irgendwelche_daten; //ggf. mehr zeugs *p = zeiger_auf_gueltiges_some_type_objekt; } //... some_type * p = 0; err_code_t err = create_some_type_obj(&p); //hier wird ein some_type Objekt erzeugt //wenn bei create_some_type_obj alles gut gegangen ist, zeigt p nun nicht mehr auf 0, sondern auf ein gueltiges ObjektIch finde das zwar nicht schön, und würde sofern möglich, mit Rückgabewerten arbeiten, aber in vielen C-APIs sieht man sowas. In C++ braucht man das aber nicht mehr. Da kann man Referenzen benutzen.