Hallo, ich hätte eine Frage, und zwar hab ich vor einigen Tagen mit C
begonnen!(Hab davor schon ein bisschen in anderen Sprachen programmiert)
Und hab mir für mein Selbststudium ein Lehrbuchgekauft: C von Kopf bis
Fuß!
Jetzt hab ich mich bis zu den Zeigern und Arrays vorgearbeitet und das
eigentlich auch mit Erfolg (Ich verstehe das meiste)!
Nun bin ich zu dem "Zeiger und Array" Kapitel gekommen und bin dort auf
folgendes Problem gestoßen!
In dem Buch wird beschrieben das man in Arrays auf das erste Element mit
Hilfe der eckigen Klammern bzw mit dem *-Operator zugreifen kann!
Als Beispielcode schreibt der Autor folgendes Beispiel:
1
intdrinks[]={4,2,3};
2
printf("1. Gang: %i drinks\n",drinks[0]);
3
printf("1. Gang: %i drinks\n",*drinks);
Hab ich eigentlich auch verstanden, nur dann kommt ein Übungsbeispiel
zum selbst ausfüllen und dort stehe ich an:
1
voidanrufen(char*mld)
2
{
3
puts(SELBSTAUSZUFÜLLEN!!!!!);
4
}
5
6
intmain()
7
{
8
char*mld_von_marie="Nicht anrufen";
9
anrufen(mld_von_marie);
10
return;
11
}
Ich habe in das Feld *mld+6 geschrieben! Kompiliert aber ging nicht,
laut Buch gehört dort mld+6 rein, wieder kompiliert und siehe da,
funktioniert!
Nur verstehe ich nicht ganz warum! Ich möchte doch die Adresse so ändern
das das Programm die ersten sechs Zeichen überspringt.. doch die Adresse
ist doch in der Zeigervariable *mld gespeichert oder?!?
Ich bin ehrlich am verzweifeln, da ich mir dachte ,dass ich es
verstanden habe.. doch irgendwie stehe ich an!
Bitte um Hilfe!!
michael schrieb:> In dem Buch wird beschrieben das man in Arrays auf das erste Element mit> Hilfe der eckigen Klammern bzw mit dem *-Operator zugreifen kann!
Das funktioniert deshalb, weil die [] Operation in C so definiert ist
*( p + o ) <====> p[o]
> Als Beispielcode schreibt der Autor folgendes Beispiel:>>
1
>intdrinks[]={4,2,3};
2
>printf("1. Gang: %i drinks\n",drinks[0]);
3
>printf("1. Gang: %i drinks\n",*drinks);
4
>
Genau.
*drinks ist dasselbe wie *(drinks + 0) und das ist wiederrum anhand der
Äquivalenz identisch zu drinks[0].
> void anrufen(char *mld)> {> puts(SELBST AUSZUFÜLLEN!!!!!);
welchen Datentyp will puts gerne haben?
Welchen Datentyp hast du zur Verfügung?
> Nur verstehe ich nicht ganz warum! Ich möchte doch die Adresse> so ändern das das Programm die ersten sechs Zeichen überspringt..> doch die Adresse ist doch in der Zeigervariable *mld gespeichert> oder?!?
Nein.
Wenn mld ein Zeiger auf eine Abfolge von Charactern ist, dann ist *mld
ein einzelner Character. Das ist also dann schon der Buchstabe selber
und nicht mehr seine Adresse.
puts kann aber mit einem einzelnen Character nichts anfangen. puts will
eine Adresse, ab der es den String ausgeben soll. mld ist diese Adresse.
Und die Adresse des 6. Buchstabens hinter dem Beginn des Strings ist
dann mld+6
puts will einen char*. Wenn du also mld+6 machst, hast Du immer noch
einen char*, einfach um 6 Buchstaben verschoben.
Wenn Du aber *mld+6 machst, dereferenzierst Du Deinen char* und hast nur
noch ein char (ohne Pointer).
puts(char*) != puts(char)
Grüsse,
R.
Danke für die schnelle Antwort!
Könntest du mir, *( p + o ) <====> p[o], ein bisschen erläutern?
Und zu dem Beispiel kann ich leider nicht viel sagen, hab keine weitern
Informationen dazu!
Aber ich versteh nicht ganz warum ich mit "*mld + 6" die Adresse nicht
um 6 erhöhe, dass die ersten Buchstaben übersprungen werden!
michael schrieb:> Aber ich versteh nicht ganz warum ich mit "*mld + 6" die Adresse nicht> um 6 erhöhe, dass die ersten Buchstaben übersprungen werden!
Man sagt, "*" bindet stärker als "+". Das heißt, bei "*mld + 6" wertet C
zunächst "*mld" aus und erhält den char an Position mld[0]. Dieser wird
dann um 6 erhöht (was aus 'a' z.B. 'g' macht, aber die Position in mld
nicht mehr verändert).
Was du meinst, ist "*(mld + 6)". Dafür sind aber die Klammern
ausdrücklich nötig: zunächst wird die Adresse 6 Felder nach mld[0]
berechnet, diese wird dann mit "*" dereferenziert.
Aber wie die anderen schon geschrieben haben, erwartet puts weiterhin
einen Zeiger, d.h. "*" kannst du (und musst sogar) dir sparen.
michael schrieb:> Danke für die schnelle Antwort!>> Könntest du mir, *( p + o ) <====> p[o], ein bisschen erläutern?
Da gibts nicht viel zu erläutern. So ist das in C definiert.
> Aber ich versteh nicht ganz warum ich mit "*mld + 6" die Adresse nicht> um 6 erhöhe
wegen dem *
Wenn
int drinks[] = {4, 2, 3};
drinks ein Array ist, mit 3 Zahlen, dann ist der Name des Arrays alleine
(also drinks) die Adresse der ersten Zahl. D
drinks + 0 Adresse der ersten Zahl
drinks + 1 Adresse der zweiten Zahl
drinks + 2 Adresse der dritten Zahl
Der * Operator nimmt eine Adresse und besorgt den Wert der an dieser
Stelle gespcihert ist. Daher ergibt
*( drinks + 0 ) -> 4
*( drinks + 1 ) -> 2
*( drinks + 2 ) -> 3
(oder natürlich auch drinks[0], drinks[1], drinks[2]
wegen der Äquivalenz da weiter oben)
du hast einen String "Juhu" und einen Pointer drauf.
Ptr
+----+
| o |
+-|--+
|
v
+---+---+---+---+---+
| J | u | h | u | \0|
+---+---+---+---+---+
Ptr enthält die Adresse im Speicher, an der der String beginnt. Der *
Operator derefenziert diese Adresse.
*Ptr würde also das 'J' sein.
*(Ptr + 1) -> u
*(Ptr + 2) -> h
*(Ptr + 3) -> u
*(Ptr + 4) -> \0
Aber:
Ptr alleine, ist die Adresse an der das J steht
Ptr + 1 alleine, ist die Adresse an der das u steht
Ptr + 2 alleine, ist die Adresse an der das h steht
Ptr + 3 alleine, ist die Adresse an der das 2.te u steht
Ptr + 4 ist die Adresse an der das \0 steht
Das du diese Adresse, und zwar die Startadresse, an eine Funktion
übergibst, ändert daran ja nichts.
Karl Heinz Buchegger schrieb:> drinks + 0 Adresse der ersten Zahl> drinks + 1 Adresse der zweiten Zahl> drinks + 2 Adresse der dritten Zahl>> Der * Operator nimmt eine Adresse und besorgt den Wert der an dieser> Stelle gespcihert ist. Daher ergibt>> *( drinks + 0 ) -> 4> *( drinks + 1 ) -> 2> *( drinks + 2 ) -> 3>> (oder natürlich auch drinks[0], drinks[1], drinks[2]> wegen der Äquivalenz da weiter oben)
Aber:
*drinks + 1 würde 5 ergeben.
Denn *drinks ist ja nichts anderes als drinks[0]. Und drinks[0] ergibt
den Wert 4. Da dann noch 1 dazu gezählt macht das 5.
michael schrieb:> Danke für die schnelle Antwort!>> Könntest du mir, *( p + o ) <====> a[o], ein bisschen erläutern?
Du musst Dir die Typen angucken.
In deinem ursprünglichen Beispiel sieht man das vielleicht etwas klarer:
1
intdrinks[]={4,2,3};
"drinks" ist vom Typ 'int *', während "drinks[2]" vom Typ 'int' ist. Das
sind zwei unterschiedliche Typen die nicht austauschbar sind.
"drinks + 2" ist vom Typ 'int *' und ist ein Pointer der auf das letzte
Element zeigt. "*(drinks + 2)" dereferenziert den Pointer und hat daher
den Typ 'int'. Es macht genau das gleiche wie "drinks[2]" - wenn man so
will kann man also "drinks[2]" als Abkürzung von "*(drinks+2)"
auffassen.
(Hier ist es sehr wichtig, dass man das "+2" als Pointerarithmetik
auffasst: Der Pointer wird jeweils um die Größe des Zieltyps
inkrementiert!)
Bei dem char * ist es genau das gleiche: Man muss zwischen dem
Pointertyp auf ein Char und dem Char selber unterscheiden. "puts"
erwartet ein character-array, was in C quasi der gleiche Typ ist wie ein
Pointer auf ein einzelnes char. Wenn Du da einen char (und keinen
Pointer) hinpackst, wird er bestenfalls das falsche tun, wahrscheinlich
aber abstürzen (deswegne beklagt sich da auch schon der compiler).
Mit *mld + 6 übergibst Du aber ein Char. Das ist für puts aber falsch.
Deshalb entweder "mld + 6" (vom Typ char *) oder aber - als
Alternativnotation für genau das gleiche - "&(mld[6])". Wenn Du
verstanden hast, warum das das gleiche darstellt, dann bist Du schon
eine ganze Ecke weiter :-)
Viele Grüße,
Simon
Noch ein Versuch es einfach zu erklären:
Wenn du definiert hast
char *mld
dann ist mld vom Typ 'char *'
Also ein 'Zeiger auf Character'
*mld ist dann das Zeichen selbst.
Das * hat zwei Bedeutungen.
In der Definition der Variablen sagt es dem Compiler, daß du einen
Pointer willst.
Und in der Verwendung beim Dereferenzieren sagt es dem Compiler, daß du
den Wert willst auf den der Zeiger zeigt und nicht den Zeiger selbst.
Danke für die vielen Ansätze mir das ganze verständlich zu erklären!
Ich muss jetzt leider schnell nach Hause, und dort werde ich mir die
ganzen Posts nocheinmal durchlesen und wenns sein muss das Kapitel auch
nocheinmal!
Ich werde mich dann nocheinmal melden ob ich es denn letztendlich doch
"geschnallt" hab :-)
lg Michael
michael schrieb:> Ich stell mich grad sehr sehr blöd an, verstehe nicht warum ich so auf> der Leitung stehe!
Gehen wir das Programm mal durch.
main beginnt.
Durch
1
char*mld_von_marie="Nicht anrufen";
wird ein String erzeugt und in einer Variablen namens mld_von_marie wird
die Startadresse des Strings eingetragen
1
mld_von_marie
2
+---+
3
| o-----+
4
+---+ |
5
|
6
v
7
+---+---+---+---+---+---+---+---+---+---+---+ ...
8
| N | i | c | h | t | | a | n | r | u | f | ...
9
+---+---+---+---+---+---+---+---+---+---+---+ ...
Soweit so gut.
Jetzt kommt der Funktoinsaufruf
1
anrufen(mld_von_marie);
Die Funktion wird also aufgerufen und der Funktion wird der WErt von
mld_von_marie mitgegeben. Der WErt von mld_von_marie, das ist aber
nichts anderes als die Startadresse des Strings im Speicher. Die
Funktion kriegt also in der Zeichnung nix anderes, als die Angabe, wo
der Zeiger in der ZEichnung hinzeigt ....
1
voidanrufen(char*mld)
.... und dies Funktion legt sich selbst eine Variable namens mld an, in
der diese Information gespeichert wird. Die Funktion richtet sich also
selbst einen Zeiger ein, der auf genau die gleiche Stelle zeigt.
Achtung: Der * hier besagt nichts anderes, als das mld vom Typ char*
ist. mld ist also eine Pointervariable. Das hat nichts mit der
Dereferenzierung durch den * zu tun!
So.
Die Funktion möchte den String beginnend mit dem 6. Zeichen ausgeben und
puts will die Adresse davon haben. Also muss zu mld, welches ja auf den
Anfang des Strings zeigt, noch 6 dazugezählt werden ....
Marwin schrieb:> Udo Schmitt schrieb:>> char *mld>> dann ist mld vom Typ 'char *'>> Weswegen es sinnvoller ist, char* zu schreiben.
Uh, Ansichtssache:
1
char*mld,fump;
fump ist ein char, während mld ein char* ist. Ok, wenn man nur den Typ
in fließtext hinschreibt dann ja, aber in einer Deklaration wie eben ist
das u.U. missverständlich zu lesen.
Viele Grüße,
Simon
> fump ist ein char, während mld ein char* ist. Ok, wenn man nur den Typ> in fließtext hinschreibt dann ja, aber in einer Deklaration wie eben ist> das u.U. missverständlich zu lesen.
Richtig.
Daher wird in C++ (in der die Schreibweise char* var wesentlich
gebräuchlicher ist als char *var) auch dringenst davon abgeraten in
einer Defintion Datentypen zu mischen und mehr als 1 Pointer in einer
Defintion zu definieren. In C++ bevorzugt man daher
1
constchar*a="Hallo";
2
constchar*b="world";
und nicht
1
constchar*a="Hallo",*b="world";
auch wenn beides identisch ist.
Am ANfang kommt einem das als eine eher willkürliche Selbstkasteiung
vor. Aber zumindest mir ist es so ergangen, dass ich nach kurzer Zeit
C++ ein
1
inti,j,k,m,*Ptr,Mwst;
als unnatürlich und unübersichtlich empfunden habe.
Und spätestens, wenn dann modifier wie const, mutable, volatile auf der
Bühne erscheinen, erweist sich die von K&R eingeführte Schreibweise als
Boomerang. Sie war IMHO keine gute Idee.
Hallo, wollte nur kurz Rückmeldung darüber geben wie es mir mit den
Zeigern und Arrays so geht!
Mit eurer Hilfe deutlich besser, verstehe jetzt den Unterschied
zwischen:
char* x, *x, &x, x
Find ich sehr gut :-), danke nochmal für eure Geduld!
jetzt hat sich nur noch eine kleine Frage aufgetan, und ich würde gerne
wissen ob ich mit meiner Annahme recht habe!
1
#include<stdio.h>
2
#include<string.h>
3
4
chartracks[][80]={
5
"I left my heart in Havard Med School",
6
"Newark, Newark - a wonderful town",
7
"Dancing with a Dork",
8
"From here to maternity",
9
"The girl from Iwo Jima",
10
};
11
12
voidtrack_suchen(chargesucht[])
13
{
14
inti;
15
for(i=0;i<6;i++){
16
if(strstr(tracks[i];gesucht))
17
print("Track %i: '%s'\n",i,tracks[i]);
18
}
19
}
20
21
intmain()
22
{
23
chargesucht[80];
24
printf("Suchen nach: ");
25
scanf("%79s",gesucht);
26
track_suchen(gesucht);
27
return0;
28
}
So nun zu meiner Frage, ich könnte doch auch in der Funktion schreiben:
void track_suchen(char* gesucht)
und in der if könnte ich doch auch schreiben:
if(strstr(tracks+1; gesucht))
print("Track %i: '%s'\n", i, tracks+1);
Geht das so oder habe ich da schon wieder einen Wurm reingebracht?
lg Michael
Das mit gesucht geht, das mit tracks nicht.
Auch das 2D-Array ergibt nur einen einfachen Zeiger auf den
Speicherbereich.
Du kannst statt mit tracks[i][x] auch mit *(tracks+i*80+x) darauf
zugreifen.
Der Compiler macht aus dem tracks[i] ein *(tracks+i*80), was ja auch an
dieser Stelle sehr sinnvoll ist.
Nebenbei sollte deine Schleife nur bis < 5 laufen.
michael schrieb:> So nun zu meiner Frage, ich könnte doch auch in der Funktion schreiben:>> void track_suchen(char* gesucht)
Der wichtige Punkt:
Die Schreibweise
1
voidtrack_suchen(chargesucht[])
ist das, was man 'syntaktischen Zucker' nennt. D.h. das ist eine
Schreibweise, die dem einen oder anderen gefälliger vorkommt.
Mehr aber auch nicht!
Auch wenn du da in der Funktionsdefinition die Array-[] benutzt, DA WIRD
TROTZDEM EIN POINTER ÜBERGEBEN!
Die [] haben nichts zu sagen. Diese Schreibweise ist absolut identisch
zu
1
voidtrack_suchen(char*gesucht)
und zwar in allen Belangen!
(Und genau deswegen sind viele der Meinung, dass diese Schreibweise
nicht so gut ist. Denn sie suggeriert etwas, was nicht vorhanden ist.
Nämlich, dass da irgendwas magisches mit Arrays passieren würde)
> if(strstr(tracks+1; gesucht))
i. Wenn schon dann i.
Und wenn du dir von oben noch mal die Äquivalenz holst
*(a+o) <==> a[o]
und im Original ein tracks[i] stand, dann sollte unmittelbar klar sein,
wogegen du das in Pointer-Schreibweise austauschen musst :-)
Übrigens:
Die Pointer-Schreibweise kann manchmal Programmvereinfachung bringen.
Speziell String-Funktionen profitieren oft davon.
Es bringt aber auch nichts, wenn man auf Biegen und Brechen eine
Array-Schreibweise gegen eine Pointer-Schreibweise austauscht. Denn: das
erste was der Compiler macht, machen muss(!), ist genau das: Die
Array-Indizierung gemäss Äquivalenz in eine Pointer-Formulierung
umwandeln.
D.h. ob du das machst und dabei Fehler einbaust, oder ob der Compiler
das mechanisch fehlerfrei macht, unterscheidet sich nur darin, dass du
Fehler eingebaut hast :-) WEnn du es richtig machst, dann ist das
Ergebnis exakt identisch.
Nur eben manchmal mit dem Unterschied, dass man Algorithmen in
Pointer-Schreibweise ein wenig besser vereinfachen kann. Aber das geht
nicht immer. Und dann bringt einem die Pointer-Schreibweise nichts.
Um das ganze noch ein bisschen genauer zu verstehen, im oberen Beispiel
habe ich mld+6 geschrieben und nicht *(mld+6) weil "puts" einen Zeiger
erwartet hat! Woher weiß ich in dem Fall das puts einen Zeiger erwartet
und nicht etwas einen char oder sonst was?
warum müsste ich in dem zweiten Beispiel: *(tracks+i) schreiben anstatt
tracks+i?!
michael schrieb:> Um das ganze noch ein bisschen genauer zu verstehen, im oberen Beispiel> habe ich mld+6 geschrieben und nicht *(mld+6) weil "puts" einen Zeiger> erwartet hat! Woher weiß ich in dem Fall das puts einen Zeiger erwartet> und nicht etwas einen char oder sonst was?
Indem du wahlweise
* in deinem C-Buch
* oder in der Header-Datei (stdio.h)
* oder in einer Online-Referenz
nachschlägst.
zb hier
http://www.cplusplus.com/reference/cstdio/puts/
und da steht explizit
int puts ( const char * str );
> warum müsste ich in dem zweiten Beispiel: *(tracks+i) schreiben anstatt> tracks+i?!
weil tracks den Datentyp char[][] hat
Da hat die Faulheit wohl zugeschlagen.. weil ihr so schnell und
ausfühlich alles erklärt habt! Ajaj - jetzt ist wieder selbststudium und
nicht alles vorkauen lassen angesagt!
Danke aufjedenfall für die zahlreichen Antworten!
lg Michael