Forum: PC-Programmierung C, Zeiger und Array


von michael (Gast)


Lesenswert?

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
int drinks[] = {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
void anrufen(char *mld)
2
{
3
  puts(SELBST AUSZUFÜLLEN!!!!!);
4
}
5
6
int main()
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!!

von Karl H. (kbuchegg)


Lesenswert?

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
> int drinks[] = {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?

von Karl H. (kbuchegg)


Lesenswert?

> 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

von Rene H. (Gast)


Lesenswert?

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.

von Rene H. (Gast)


Lesenswert?

Zu spät :-)

von michael (Gast)


Lesenswert?

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!

von Marwin (Gast)


Lesenswert?

Blaettere in dem Buch nochmal zurueck an die Stelle, wo der *-Operator 
erklaert wird.

von Sebastian (Gast)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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.

von michael (Gast)


Lesenswert?

1
void anrufen(char *mld)
2
{
3
  puts(SELBST AUSZUFÜLLEN!!!!!);
4
}
5
6
int main()
7
{
8
char *mld_von_marie = "Nicht anrufen";  // Da ändere ich doch den Inhalt der Adresse auf "Nicht anrufen" Oder?
9
anrufen(mld_von_marie);   // Da übergebe ich doch den Wert von mld_von_marie an die Zeigervariable char *mld in der Funktion oder?
10
return;
11
}


Das beudetet doch das *mld nicht nur 1. Wert hat!?

Ich stell mich grad sehr sehr blöd an, verstehe nicht warum ich so auf 
der Leitung stehe!

von Karl H. (kbuchegg)


Lesenswert?

michael schrieb:


> Das beudetet doch das *mld nicht nur 1. Wert hat!?

*mld wäre in deinem Beispiel das 'N' aus "Nicht anrufen"

von Simon B. (nomis)


Lesenswert?

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
int drinks[] = {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

von Udo S. (urschmitt)


Lesenswert?

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.

von Marwin (Gast)


Lesenswert?

Udo Schmitt schrieb:
> char *mld
> dann ist mld vom Typ 'char *'

Weswegen es sinnvoller ist, char* zu schreiben.

von michael (Gast)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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
void anrufen(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!
1
  mld_von_marie
2
  +---+
3
  | o-----+
4
  +---+   |
5
          |
6
          v
7
     +--->+---+---+---+---+---+---+---+---+---+---+---+ ...
8
     |    | N | i | c | h | t |   | a | n | r | u | f | ...
9
     |    +---+---+---+---+---+---+---+---+---+---+---+ ...
10
     |
11
     +----------+
12
                |
13
                |
14
    mld         |
15
    +---+       |
16
    | o---------+
17
    +---+


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 ....
1
  mld_von_marie
2
  +---+
3
  | o-----+
4
  +---+   |
5
          |
6
          v
7
     +--->+---+---+---+---+---+---+---+---+---+---+---+ ...
8
     |    | N | i | c | h | t |   | a | n | r | u | f | ...
9
     |    +---+---+---+---+---+---+---+---+---+---+---+ ...
10
     |                              ^
11
     +----------+                   |
12
                |                  mld+6
13
                |
14
    mld         |
15
    +---+       |
16
    | o---------+
17
    +---+

.... damit man die Adresse hat, ab der puts dann ausgeben soll.

von Simon B. (nomis)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

Simon Budig schrieb:
> 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;
2
>
> 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
  const char* a = "Hallo";
2
  const char* b = "world";

und nicht
1
  const char *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
  int i,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.

1
  const char *a, c, *const b;

von michael (Gast)


Lesenswert?

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
char tracks[][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
void track_suchen(char gesucht[])
13
{
14
  int i;
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
int main()
22
{
23
  char gesucht[80];
24
  printf("Suchen nach: ");
25
  scanf("%79s", gesucht);
26
  track_suchen(gesucht);
27
  return 0;
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

von DirkB (Gast)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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
void track_suchen(char gesucht[])

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
void track_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 :-)

von Karl H. (kbuchegg)


Lesenswert?

Ü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.

von michael (Gast)


Lesenswert?

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?!

von Karl H. (kbuchegg)


Lesenswert?

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

von Michael (Gast)


Lesenswert?

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

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.