Inhalt 4.5 Arbeit mit Zeigern (engl. Pointer)
Transcription
Inhalt 4.5 Arbeit mit Zeigern (engl. Pointer)
Inhalt Inhalt: 4. Programmiersprache C 4.1 Programmaufbau in C 4.2 Basisdatentypen und einfache Anweisungen 4.3 Steuerfluss-Konstrukte 4.4 Arbeit mit indizierten Größen (Felder) 4.5 Arbeit mit Zeigern (Pointer) 4.6 Zeichen und Zeichenketten 4.7 Funktionen … Peter Sobe 72 4.5 Arbeit mit Zeigern (engl. Pointer) Zeiger Ein Zeiger (engl. Pointer) speichert eine Adresse, unter der ein Wert im Speicher des Computers gespeichert werden kann. Eine Variable im Gegensatz speichert einen Wert. Der Name eines Zeigers ist mit einer Adresse verbunden, ein Variablenname dagegen mit einem bestimmten Wert. Zeiger werden z.B. für folgende Zwecke benutzt: Dynamische Speicherverwaltung Parameterübergabe in Funktionen Zeichenkettenverarbeitung Peter Sobe 73 Definition von Zeigervariablen Bei der Definition eines Zeigers wird ein Stern ("*") vor den Bezeichner geschrieben. Werden die Bezeichner mit Kommas getrennt in einer Definitionsliste angegeben, so muss der Stern vor jeden einzelnen Bezeichner geschrieben werden, der als Zeiger definiert werden soll. long *LZ, LZ2; // LZ ist ein Zeiger, LZ2 eine Variable float summe, * psumme; // summe ist eine Variable, // psumme ein Zeiger Für eine gute Übersichtlichkeit, jede Variable in einer eigenen Zeile definieren! Der Stern sollte direkt (ohne Blank) hinter dem Typ stehen: float summe; float* psumme; Peter Sobe 74 Veranschaulichung einer Zeigervariablen Der Speicher eines Rechners wird über Adressen verwaltet, die die Speicherstellen (Bytes) linear durchnummerieren. Die Nummer des angesprochenen Bytes ist die Adresse. Hier ein Beispiel (stark vereinfacht) : float summe; … float* psumme = &summe; // Zeiger mit Adresse von summe 4 0 1 4 summe 12 psumme … Inhalt Adresse Zugriff auf summe wird durch Compiler mit Zugriff auf Adresse 4 ersetzt Peter Sobe 75 Der &-Operator Zeiger - Adressoperator • Um die Adresse einer Variablen (einen sogenannten L-Wert) zu erhalten, muss ein spezieller Operator verwendet werden. Lvalue = location for a value Dieser Operator wird als Adressoperator bezeichnet. Er wird mit dem Zeichen "&" (Ampersand) symbolisiert. Zum Beispiel: int Io = 1024; int* IZ = &Io; // IZ erhält die Adresse von Io // und nicht den Wert 1024 Peter Sobe 76 Zeiger Ein Zeiger kann auch mit einem anderen Zeiger desselben Typs initialisiert werden. // jetzt enthält auch IZ2 die Adresse von Io int* IZ2 = IZ; int ** IZ4 = &IZ; // Zeiger auf Zeiger Peter Sobe 77 Ein Beispiel zur Benutzung von Zeigern void swap(float* x1, float* x2) { // zwei Werte vertauschen float temp = *x1; *x1 = *x2; *x2 = temp; } void swap2(float** x1, float** x2) { // zwei Zeiger vertauschen float* temp = *x1; *x1 = *x2; *x2 = temp; } int main(void) { float a, b, *pa, *pb; a = 1.0, b = 2.0, pa = &a, pb = &b; swap(&a, &b); /* Tauscht die Werte a und b wirklich* / … swap2(&pa, &pb); /* tauscht nur die Pointer */ … } Peter Sobe 78 Der * - Operator „*“ ist der Inhaltsoperator. Damit kann ein Zeiger dereferenziert werden, d.h. auf den Wert der Variable (auf die gezeigt wird) zugegriffen werden. Die Vereinbarung „int* pi;“ oder auch „int *pi;“ kann gelesen werden als „der Inhalt von pi ist vom Typ int, also ist pi vom Typ Zeiger auf int“: int i = 1234; int* pi; // pi ist also vom Typ „Zeiger auf int“ pi = &i; // pi zeigt jetzt auf i *pi = 5678; // i hat jetzt den Wert 5678, // der Zugriff auf i erfolgt hier durch Dereferenzieren // des Zeigers pi. Dereferenzierung: Typ* ptr; // hier wird eine Zeigervariable definiert *ptr = ...; ... = *ptr; // hier wird ptr dereferenziert // und damit auf den Inhalt zugegriffen Peter Sobe 79 Zeiger und Typen Jeder Zeiger ist mit einer Variablen eines bestimmten Typs verbunden Der Datentyp gibt den Typ des Datenobjekts an, das mit Hilfe dieses Zeigers adressiert wird. Ein Zeiger des Typs int zeigt zum Beispiel auf ein Objekt des Typs int. int intvar; int* intZeiger = &intvar; Um auf ein Objekt des Typs double zu zeigen, muss der Zeiger mit dem Datentyp double definiert werden. double dvar; double* dZeiger = &dvar; Peter Sobe 80 Zeiger - Speicherbedarf von Zeigern Der Speicherplatz, der für einen Zeiger reserviert wird, muss eine Speicheradresse aufnehmen können. Das bedeutet, dass ein Zeiger des Typs int und ein Zeiger auf einen Datentyp double normalerweise gleich groß sind. Der Typ, der einem Zeiger zugeordnet ist, gibt den Inhalt und damit auch die Größe des adressierten Speicherbereichs an. int* pint ; // pint belegt 4 Byte double* pdouble; // pdouble belegt auch 4 Byte Der Speicherplatz der für Zeiger verwendte wird ist implementierungsabhängig und typischerweise 32 oder 64 Bit (d.h. 4 oder 8 Byte lang). Peter Sobe 81 Zugriff auf Felder durch Zeiger (1) In C sind Variablen für Felder und Zeiger zuweisungskompatibel. Beispiel: int zahlenfeld[20]; int einzelzahl; int *iptr; iptr = zahlenfeld; // dem Zeiger kann der Name eines Feldes // zugewiesen werden einzelzahl = iptr[5]; // Ein Zeiger kann wie ein Feld benutzt werden Ein Feld mit Elementen eines bestimmten Typs wird als Variable wie ein Zeiger auf ein Element des Typs behandelt. Gleichzeitig kann ein Zeiger auch wie ein Feldbezeichner mit Indexklammern versehen werden. Peter Sobe 82 Zugriff auf Felder durch Zeiger (2) Hintergrund: int feld[10]; int* ptr = feld; // entspricht: int* ptr = &feld[0] Der Feldname entspricht damit der Adresse des ersten Feldelementes, d.h. dem mit dem Index 0. Der Zusammenhang zwischen Feldern und Zeigern wird oft bei der Übergabe von Feldern in Funktionsparametern ausgenutzt: void auswahlfunktion( int *feld, int index, int *wert ) { *wert = feld [index]; } Details zu Funktionen und deren Parametern folgen später! Peter Sobe 83 Addition und Subtraktion bei Zeigern Bei der Addition (oder Subtraktion) eines ganzzahligen Wertes (z.B. 2), wird der Wert der entsprechenden Adresse um die Größe von z.B. zwei Objekten dieses Datentyps (hier int) erhöht (oder erniedrigt). Wenn z.B. ein char 1 Byte, ein int 4 Byte und ein double 8 Byte belegt, dann erhöht sich bei einer Addition um 2 zu einem Zeiger, der Wert der im Zeiger gespeicherten Adresse um 2, 8 bzw. 16 Byte. Beispiele: int i; // z.B. Adresse 0x0100 int j; // z.B. Adresse 0x0104 int k; // z.B. Adresse 0x0108 int feld[10]; // z.B. Adresse 0x010C, 0x0110, 0x0114 ... int* ptr = & i; // ptr enthält Adresse von i, d.h 0x0100 ptr = ptr + 2; // ptr enthält nun 0x0108, d.h. Adresse von k ptr++; // ptr enthält nun 0x010C, d.h. Adresse feld[0] Peter Sobe 84 Operationen mit Zeigern (1) Einem Zeiger kann man einen Zeiger des gleichen Typs zuweisen. int* ptr1 = &i; int* prt2 = ptr1; Ein Zeiger kann auch inkrementiert oder dekrementiert werden. So bedeutet z.B. „+5“ hier eine Inkrementierung um 5 Schritte mit der Schrittweite „sizeof(int)“: int* ptr3 = ptr2 + 5; Eine Zuweisung eines anderen Typs ist nur über Typecast möglich: char* pchar = (char*) ptr2; // typecast Peter Sobe 85 Operationen mit Zeigern (2) Es kann die Differenz zweier Zeiger gebildet werden: int feld[10]; int* ptr1 = &feld[1]; int* ptr8 = &feld[8]; int anzahl = ptr8 – ptr1; // ergibt den Wert 7 // Besser: ptrdiff_t anzahl = ptr8 – ptr1; ( ptrdiff_t ist in cstddef bzw. stddef.h vereinbart ) Einem Zeiger darf der Wert 0 (NULL) zugewiesen werden. Dadurch wird der Zeiger als nicht initialisierter Zeiger gekennzeichnet: int* ptr = 0; // oder: int* ptr = NULL; Die Vergleichsoperationen == und != sind zulässig, um zwei Zeiger auf Gleichheit zu testen, d.h. ob sie auf die gleiche Adresse verweisen. Dieses hat nichts mit den Werten, die die Adressen enthalten, zu tun. Peter Sobe 86 NULL - Zeiger Es gibt einen besonderen Zeigerwert, der als Konstante NULL definiert ist. Die interne Darstellung der NULL ist implementierungsabhängig, aber kompatibel zur ganzzahligen Null (0). NULL kann jeder Zeigervariablen zugewiesen werden, und jede Zeigervariable kann auf NULL getestet werden; p = NULL; /* Beide Anweisungen sind */ p = 0; /* korrekt und bedeutungsgleich! */ ... if (p==NULL) . . . /* Alle drei */ if (!p) . . . /* Abfragen sind korrekt */ if (p==0) . . . /* und bedeutungsgleich! */ Diese Operationen können auch dazu verwendet werden, um zu ermitteln, ob ein Zeiger bereits initialisiert wurde: int *ptr = 0, *ptr2; ... if (ptr == 0) { … // initialisiere jetzt ptr } Peter Sobe 87 Weitere Bedeutung von Zeigern Zeiger zur Übergabe von Parametern an Funktionen “Call by Reference” – Parameter sind Zeiger void function(int x, int *y) { *y = 2*x*x – 3*x +5; // der Wert auf den y zeigt wird verändert } Zeiger für dynamische Speicherstrukturen int *p; int anzahl_werte; // konkreter Wert ergibt sich zur Laufzeit … p = malloc(anzahl_werte*sizeof(int)); … Peter Sobe 88 Inhalt Inhalt: 4. Programmiersprache C 4.1 Programmaufbau in C 4.2 Basisdatentypen und einfache Anweisungen 4.3 Steuerfluss-Konstrukte 4.4 Arbeit mit indizierten Größen (Felder) 4.5 Arbeit mit Pointern 4.6 Zeichen und Zeichenketten 4.7 Funktionen … Peter Sobe 89 Zeichen und Zeichenketten (engl. Strings) • Motivation - Textoperationen werden immer wichtiger Das menschliche Leben besteht zum großen Teil aus Sprache und Schrift und nur wenigen Zahlen. Informationsverarbeitung geht zunehmend auf Klartextdaten über. Internet/HTML/XML ist eine reine Textgenerierung • Typische Aufgaben bei der Zeichenkettenverarbeitung Numerische Daten in Texte einfügen Textdaten analysieren und Teile extrahieren Datenbankinformationen als HTML-Text kodieren Befehlsanweisungen an Server zusammenbauen (SQL) Peter Sobe 90 Erweiterung des C-Typkonzepts um Zeichen Kodierung von Zeichen Die Darstellung von Zeichen ist standardisiert, z.B. durch American Standards Council (ASC) Allgemein verwendet: ASCII 1-Byte Darstellung in C (American Standard Code for Information Interchange) Zukünftig: Unicode 2-Byte Darstellung zur Erfassung von Sonder-zeichen, virtuelle Tasten (z.B. Funktionstasten), Tastenkombinationen oder allgemein fremdsprachige Zeichen (diakritische Zeichen, kyrilisch, arabisch, asiatische Sprachen) •Veraltet, aber noch in Benutzung: •EBCDIC 1-Byte Darstellung (Großrechner) Peter Sobe 91 Zeichenkodierung am Beispiel ASCII ASCII: 1Byte kodiert ein Zeichen (bei Unicode 1 Zeichen= 2bytes) genau binär, z.B. 0100 0000 Wertigkeit: 2726 ... 2120 d.h. 26=64 = @ Jedes Zeichen (auch nichtdruckbare) hat eine eindeutige Binärdarstellung in ASCII. Infolge der Binärdarstellung ordnet man deshalb einem Zeichen auch einen Zahenlwert zu. Für nichtdruckbare Zeichen unterscheiden sich die Zahlwerte in den 16Bit- und 32Bit-Versionen! Grobe Einteilung (Details siehe Codetabelle ASCII): 0-31 nichtdruckbare Zeichen (8=TAB 10+13 neue Zeile) 48-57 Ziffern (48 = 30H = entspricht Ziffer 0) (39H=Ziffer 9) 65-90 Großbuchstaben A = 65 97-122 Kleinbuchstaben a = 97 Peter Sobe 92 Definition von Zeichen in C (1) In C gibt es 2 unterschiedliche Zahlzuordnungen je nach Typdeklaration. Die Unterschiede wirken sich aber nur für spezielle Sonderzeichen, wie z.B. ö,ü,ß usw. aus. a) Deklaration mit char (signed char) Hier wird die Binärdarstellung als vorzeichenbehaftet gedeutet. Das werthöchste Bit (links) wird als Vorzeichen gedeutet (1 ist negativ). Der Darstellungsbereich ist damit –128 bis +127 Bsp. ö 1111 0110 -negative Zahl wird als 2er-Komplement gedeutet: 1111 1111 -1111 0110 0000 1001 +1 0000 1010 Peter Sobe = -10 als Zahl oder ö als Buchstabe 93 Definition von Zeichen in C (2) b) Deklaration mit unsigned char Hier wird die Binärdarstellung als vorzeichenlos, d.h. als positiv gedeutet. Der Darstellungsbereich ist damit 0 bis 255 Bsp. ö 1111 0110 27 20 Wert ist deshalb 27+26+25+24+22+21=246 Bei Benutzung von Vergleichsoperatoren in C in Zusammenhang mit Zeichen ist die Deklaration mit char oder unsigned char für das Ergebnis entscheidend. Peter Sobe 94 Angabe von Zeichenkonstanten Zeichenkonstanten werden in einfache Apostrophzeichen eingeschlossen. Beispiele: ‘a‘ ‘1‘ ‘%‘ ‘/‘ ‘#‘ Bestimmte Zeichen können als sogenannte Escape-Sequenzen dargestellt werden, z.B. ‘\n‘ Zeilenvorschub ‘\\‘ Backslash ‘\‘‘ Apostroph Nichtdruckbare Zeichen gibt man als Hexadezimalzahl ihrer Binärdarstellung ein: Bsp. 1111 0110 f 6 ‘\xf6‘ Peter Sobe 95 Deklarationsmöglichkeiten von Zeichen in C Deklarationen können in allen Gültigkeitsbereichen, d.h. auch auf globalem Niveau unter Angabe des Typs char oder unsigned char erfolgen. char a,z; bzw. unsigned char f,u2,z0; Eine Initialisierug mit einem Wert ist bei der Deklaration möglich: char a,z= ‘%‘; bzw. unsigned char f= ‘\\‘,k1,z0; Namen für Zeichenkonstanten können durch eine Präprozessor define-Anweisung festgelegt werden: #define kreuz ‘#‘ Peter Sobe 96 Ausgabe von Zeichen mit printf() oder putchar Bei Benutzung der Ein- u. Ausgabe muss immer #include <stdio.h> eingebunden werden Standardausgabe mit printf-Funktion: char c,glob; printf(“\nc=%5c glob=%2c“,c,glob); % leitet Formatangabe ein 5 ist die Feldbreite für die auszugebenden Zeichen c ist das char-Format Zeichenausgabe mit putchar putchar(glob); Peter Sobe 97 Eingabe von Zeichen mit scanf oder getchar Standardeingabe scanf–Funktion, auch stdio.h einzubinden char glob; … scanf(“%c“, &glob); // & bedeutet Adresse von glob % leitet Formatangabe ein c ist das char-Format Einzugebende Zeichen werden mit ENTER abgeschlossen. Weitere scanf-Anweisungen sollten mit \n beginnen. Zeicheneingabe mit getchar glob = getchar(); Peter Sobe 98 Arbeit mit Zeichen Wie bei jedem Typ ist die Wertezuweisung definiert. char x, y; .... x = y; Es können auch Vergleiche mit char-Größen ausgeführt werden. Hierbei wird auf den Zahlenwert des Zeichens zurückgegriffen. Dies kann je nach char oder unsigned char unterschiedlich sein: if (x<y) ... Es existieren Umwandlungsfunktionen: z.B. i=atoi(&c); (char c; int i) Die Funktion atoi bietet eine ascii-to-int Konvertierung. Peter Sobe 99 Zeichenketten Zeichenketten existieren in C nicht als eigener Typ. Es können nur Zeichenkettenkonstanten z.B. zur Ausgabe benutzt werden. Eine Zeichenkette kann aber in C über ein Feld von Zeichen ( char zk[20]; ) simuliert werden. Für Zeichenketten stehen eine Reihe von Funktionen, wie z.B. strcpy zur Verfügung. Diese befinden sich in string.h . Konvention: Werden diese Funktionen auf char-Felder angewandt, muss das letzte Zeichen immer ein Nullzeichen \0 sein! Peter Sobe 100 Zeichenkettenkonstante Zeichenketten – Konstanten werden stets in DoppelApostroph eingeschlossen: “Das ist eine nette Zeichenkette!“ Leerzeichen sind signifikant! Wird eine solche Zeichenkettenkonstante z.B. einem charFeld zugewiesen, wird automatisch das Abschlusszeichen '\0' angehängt. char zk[40] = “Das ist eine nette Zeichenkette!“ ; // ergibt: zk={'D', 'a', 's', ' ', 'i', 's', 't', …, 'k', 'e', 't', 't', 'e', '!','\0'} Eine solche Zuweisung ist nur so möglich! Sonst muss sie über eine Funktion strcpy erfolgen. Peter Sobe 101 Felder von Zeichen Zeichenketten können in char-Feldern gespeichert werden! Die Zuweisung erfolgt über den Copykonstruktor: char zk[20] = “Das ist eine Kette!“ ; oder z.B.: char w[5]={‘a‘,‘b‘,‘c‘,‘d‘,‘\0‘}; Ausgabe von Zeichenketten erfolgt über printf: printf(“Ausgabe: %s“, zk); s (string) ist das Format für Zeichenkette; diese muss durch \0 abgeschlossen sein! Peter Sobe 102 Eingabe von Zeichenketten erfolgt über scanf: scanf(“%10s“, zk); • s (string) ist das Format-Umwandlungszeichen für eine Zeichenkette; • 10 ist die max.Länge der zu übernehmenden Zeichen • Eingabe wird durch ENTER abgeschlossen! Achtung: Ein eingegebenes Leerzeichen beendet bei %s die zu übernehmenden Zeichen ! scanf(“%[^\n]“, zk); liest die Zeichen bis zum Zeilenumbruch in die Zeichenkette ein Peter Sobe 103 Standardfunktionen für Zeichenketten Voraussetzung: #include <string.h> Kopieren / Zuweisen strcpy() strcpy(ziel, quelle); Kopiert die Zeichenkette des char-Feldes quelle auf das char-Feld ziel und erzeugt ein Endezeichen ‘\0‘. Der Rückkehrwert von strcpy ist vom Typ char *, d.h. ein Zeiger auf die Kette „ziel“. Verketten von Zeichenketten strcat() strcat(ziel, quelle); // ziel=ziel°quelle Kettet die Zeichenkette des char-Feldes quelle an das char-Feld ziel. Der Rückkehrwert von strcat ist vom Typ char *, d.h. ein Zeiger auf die Kette „ziel“. Damit läßt sich auch ein Funktionsaufruf so gestalten: strcpy(text1, strcat(“Das ist “,“ eine Verkettung“)); Peter Sobe 104 Standardfunktionen für Zeichenketten Vergleichen von Zeichenketten strcmp() strcmp(text1, text2); Vergleicht die Zeichenkette des char-Feldes text1 mit der Zeichenkette des char-Feldes text2 lexikographisch. Der Rückkehrwert von strcmp ist vom Typ int. Ergebnis : < 0 : text1 less than text2 ==0 : text1 identical to text2 > 0 : text1 greater than text2 Merke: Verglichen wird anhand des Zahlencodes der Zeichen !! Peter Sobe 105 Standardfunktionen für Zeichenketten Länge einer Zeichenkette strlen() int Laenge; Laenge=strlen(kette); Der Rückkehrwert ist die Länge der Zeichenkette, d.h. die Anzahl der Zeichen ohne das Abschlusszeichen ‘\0‘. In der Standardbibliothek string.h gibt es noch zahlreiche weitere Funktionen für die Zeichenketten-Arbeit. Peter Sobe 106