Vorlesung 9 Flashcards
Arrays
- Arrays (Felder, Vektoren) dienen zum Speichern einer festen Anzahl von
Werten gleichen Typs. - Anwendungsbeispiele:
- Vektoren, Matrizen
- Liste von Namen
- Syntax (Definition eines Arrays):
Datentyp Bezeichner[Elementanzahl]; - Beispiel:
float vektor[3];
short int a, b[10], c; - Die Elementanzahl muss positiv sein.
- Ungeeignet, wenn die Anzahl der Daten unbekannt ist, dann muss
dynamische Speicherverwaltung verwendet werden. - Durch die Definition eines Arrays werden so viele Variablen erzeugt, wie das
Array Elemente hat. - int b[10] erzeugt:
- Variablenname b für Zugriff auf das gesamte Array
- Variablennamen für Zugriff auf die einzelnen Elemente:
b[0], b[1], …, b[9] - Die Indizierung beginnt immer bei 0.
Arrays im Speicher
Elemente werden geordnet, direkt hintereinander im Speicher abgelegt.
Speicherbedarf von Arrays
- Speicherbedarf: Elementanzahl · Größe des Datentyps
- Beispiele:
- int wert[250]; braucht z.B. 4 · 250 = 1000 Byte
- double wert[1000]; braucht z.B. 8 · 1000 = 8000 Byte
- Vorsicht:
- Der gesamte Speicher für die Arrays wird erst zur Laufzeit angefordert.
- Bei zu großen Arrays oder lokal definierte Arrays kann es
Probleme geben (siehe Stack, später).
Zeichenketten als char-Arrays
- Eine der wichtigsten Anwendungen von Arrays ist die Darstellung und
Verarbeitung von Zeichenfolgen (Strings / Text). - Zeichenfolgen treten meist auf bei
- Ein- und Ausgabe (Benutzerschnittstellen)
- Verarbeitung von Text-Dateien
- In C gibt es keinen Datentyp String, stattdessen werden
char-Arrays verwendet. - Die Standardbibliotheken von C enthalten eine Menge Funktionen für
Zeichenketten (später mehr dazu).
Darstellung von Zeichenfolgen
Eine String-Literal wird intern als char-Array dargestellt.
● Die Länge ist um 1 größer als die Anzahl der Zeichen in der
Stringkonstanten.
● Zusatzfeld für die Speicherung von des Null-Zeichens ‘\0’, welches das
Ende des Strings anzeigt
- Wenn man eine Zeichenfolge bearbeitet, muss man immer prüfen, ob das
aktuelle Zeichen das ‘\0’-Zeichen ist. - Vorsicht: Fehler, wenn das Null-Zeichen mitten in einem String vorkommt
(z.B. bei der Konkatenation zweier Strings).
Initialisierung von Arrays
- Automatische Initialisierung wie bei Variablen:
- globale Arrays werden mit 0 initialisiert
- lokale Arrays werden nicht automatisch initialisiert
- Manuelle Initialisierung erfolgt durch Anhängen einer Liste
von Werten
int vector1[5] = {6, -4, 6, 2, -1};
int vector2[5] = {6, -6, 2};
int vector3[] = {3, -5, 7, 2, -1};
char hello[6] = {‘h’, ‘e’, ‘l’, ‘l’, ‘o’, ‘\0’};
char str[] = “hello world\n”;
- Listenwerte dürfen Literale und Variablen sein.
int vector1[5] = {6, -4, 6, 2, -1}; - Es dürfen auch weniger Elemente in der Liste stehen als die angegebene
Elementenanzahl, die übrigen Elemente bleiben uninitialisiert, bzw. werden
bei globalen Variablen mit 0 initialisiert.
int vector2[5] = {6, -6, 2}; - Implizite Längenbestimmung:
- Die Größe des Feldes wird der Anzahl der Initialisierungswerte gleichgesetzt.
- Häufigste Verwendung ist das Initialisieren von Stringkonstanten
wie im Beispiel str.
int vector3[] = {3, -5, 7, 2, -1};
Zugriff auf Array-Elemente
- Syntax: name [arrayindex];
- arrayindex kann ein beliebiger nichtnegativer Ausdruck sein, der zur Laufzeit
ausgewertet wird, z.B. Laufvariablen einer Schleife - Die Indizierung beginnt immer bei 0.
- Beispiel:
int a, v[] = {1,2,3};
a = v[1]; «_space;a wird zu 2
Prüfung von Bereichsgrenzen
- Keine Prüfung, ob arrayindex in den zugelassenen Grenzen liegt! Zugriffe auf
beliebige Speicherinhalte möglich (Speicherorganisation) - Zugriff auf Werte außerhalb der Grenzen:
- lesend: Programm arbeitet mit falschen Werten. Die Fehlerquelle schwer
lokalisierbar, evtl. Programmabsturz durch Speicherschutz des Betriebssystems. - schreibend: Wenn die Abweichung groß ist, wird ggfs. Programmcode
überschrieben. Bei Betriebssystemen ohne Speicherschutz führt dies zum
Crash, bei UNIX-Rechnern wird der Absturz verhindert (Segmentation fault,
eigenes Programm wird beendet, andere laufen weiter), ebenso auf
Windows-Systemen
Arrays backstage
- Die Variable für den gesamten Array ist intern nichts anderes als ein Zeiger.
- Der Zugriff auf ein Element des Arrays wird über die
Adressarithmetik gesteuert. - folgende Ausdrücke sind gleichbedeutend:
s = vektor[3];
s = *(vektor+3);
Arrays als Funktionsparameter
● Bei der Parameterübergabe eines Arrays wird lediglich die Startadresse des
Feldes übergeben (Zeigerübergabe).
● Änderungen der Elemente des Arrays sind daher auch in der aufrufenden
Umgebung sichtbar.
● Beispiel: Bei scanf wird nicht die Adresse des char-Arrays übergeben,
sondern die Array-Variable selbst:
char str[100];
scanf(“%s”, str);
int i;
scanf(“%d”, &i);
● Häufig soll eine Funktion mit Feldern unterschiedlicher Größe
korrekt arbeiten.
● Daher: Bei Funktionsdefinition braucht die Größe nicht angegeben werden
(für mehrdimensionale Felder stimmt dies nicht, folgt später):
● Vorsicht: Gefahr ungültiger Speicherzugriffe!
* Größe des Feldes unter Umständen in der Funktion nicht bekannt, muss dann
als separater Parameter übergeben werden
Funktion zum Vergleich von Arrays
include <string.h></string.h>
- Vergleich mittels der Funktion memcmp aus string.h
(schneller als manuell) - Parameter: die zu vergleichenden Arrays, sowie die Anzahl von Bytes, die
verglichen werden sollen - Ergebnis: 0, wenn Arrays gleich sind
…
int v1[3] = {1,2,3}, v2[3] = {4,5,6};
if(0 == memcmp(v1, v2, 3 * sizeof(int)))
printf(“v1 gleich v2\n”);
else
printf(“v1 nicht gleich v2\n”);
Funktion zum Kopieren von Arrays
- Zuweisung mittels der Funktion memcpy aus string.h
(schneller als manuell) - Parameter: Ziel-Array, Quell-Array, sowie die Anzahl von Bytes,
die zugewiesen werden sollen
beispiel:
#include <string.h>
...
int v1[3] = {1,2,3}, v2[3];
memcpy(v2, v1, 3 * sizeof(int));</string.h>
Mehrdimensionale Arrays
- Mehrdimensionale Arrays dienen zum Aufbau von Tabellen, Matrizen und
ähnlichen Strukturen. - Syntax der Definition:
- Datentyp Bezeichner[Anzahl 1][Anzahl 2]…;
- Beispiel (Matrix mit 2 Zeilen und 3 Spalten):
short int matrix[2][3]; - Syntax des Zugriffs:
Name[Array-Index 1][Array-Index 2]…; - Beispiel (Zugriff):
x = matrix[1][2];
Initialisierung von mehrdimensionalen Arrays
- Initialisierung analog zu eindimensionalen Arrays.
- Sowohl Zeilen als auch Spalten dürfen unvollständig sein.
- Arraygröße muss nicht angegeben werden.
int matrix[3][5] = { { 6,-4, 6, 2,-1},
{ 2, 3,-1, 9,-3},
{-1, 4,-8, 0, 3} };
Mehrdimensionale Arrays als Funktionsparameter
● Bei Funktionsdefinition braucht die erste Dimension nicht angegeben werden,
die anderen müssen angegeben werden.
void drucke_namen(char namen[][256], int count)
● Mit Zeigern kann aber auch dies umgangen werden.
Deklaration von Arrays
- C90 Standard
- Breite Unterstützung, nahezu alle C Compiler
- Größe eines Arrays muss zum Zeitpunkt der Übersetzung feststehen.
- Folgendes Beispiel geht nach diesem Standard nicht, da die Größe erst zur
Laufzeit bestimmt wird:
int size;
scanf(“%d”, &size);
double a[size]; - C99 Standard
- Lokale Arrays dürfen mit Variable deklariert werden, d.h. obiges Beispiel
wäre gültig.
sizeof Operator bei Arrays
● sizeof Operator liefert auch bei Arrays den belegten Platz in Byte, dividiert
durch die Größe eines Elements erhält man so die Elementanzahl
double b[1000];
printf(“b hat %d Elemente\n”, sizeof(b) / sizeof(double));
memcpy und memmove
● Funktionsdefinitionen in string.h
● Kopierfunktionen (1):
* void *memcpy(void *dest, const void *src, size_t n);
* void *memmove(void *dest, const void *src, size_t n);
* kopieren n Bytes, Speicherbereiche dürfen sich bei memcpy nicht überlappen
● Auch für andere Arrays als Strings anwendbar
● Achtung: das Stringende-Zeichen \0 wird nicht beachtet!
● Beispiel:
char text1[] = “Hamburg ist eine tolle Stadt!”;
char text2[] = “Bottrop”;
memcpy(text1, text2, 7);
printf(“%s\n”, text1);
#include <string.h></string.h>
strcpy und strncpy Funktionen für Zeichenketten
● Kopierfunktionen (2):
* char *strcpy(char *dest, const char *src);
* kopiert Zeichenkette inklusive abschließendem ‘\0’
* char *strncpy(char *dest, const char *src, size_t n);
* kopiert maximal n Zeichen, ggf. wird mit Nullen aufgefüllt
* Vorsicht: hat src keine Null in den ersten n Zeichen, ist dest nicht
Nullterminiert!
char text1[100] = “C-Programmieren macht Spaß!”;
char text2[100] = “Bottrop hat eine tolle Hochschule!”;
strcpy(text2, text1);
printf(“%s\n”, text2);
strcat und strncat Funktionen für Zeichenketten
● Aneinanderhängen von Zeichenketten:
* char *strcat(char *dest, const char *src);
* hängt die Zeichenkette src hinten an dest an
* char *strncat(char *dest, const char *src, size_t n);
* hängt maximal n Zeichen der Zeichenkette src hinten an dest an, fügt
\0 hinzu
* Achtung: Die Zielzeichenkette muss genügend Platz für beide Zeichenketten
und das abschließende \0 haben!
char text1[100] = “Bottrop ist eine tolle Stadt”;
char text2[] = “ und hat eine tolle Hochschule!”;
strcat(text1, text2);
printf(“%s\n”, text1);
strcmp, strncmp, memcp Funktionen für Zeichenketten
Vergleichsfunktionen:
* int strcmp(const char *s1, const char *s2);
* Vergleicht zwei mit \0 terminierte Zeichenketten
* int strncmp(const char *s1, const char *s2, size_t n);
* Vergleicht zwei mit \0 terminierte Zeichenketten, maximal n Zeichen
* int memcmp(const void *s1, const void *s2, size_t n);
* Vergleicht die ersten n Zeichen von zwei beliebigen Arrays
* Rückgabewert für alle drei Funktionen:
* < 0, wenn s1 kleiner als s2 (d.h. alphabetisch s1 vor s2 kommt, wenn nur
Buchstaben A-Z verwendet werden)
* > 0, wenn s1 größer als s2 (d.h. alphabetisch s1 nach s2 kommt, wenn nur
Buchstaben A-Z verwendet werden)
* 0, wenn s1 gleicher Inhalt wie s2
char str1[101];
char str2[101];
printf(“Text 1: “);
scanf(“%100s”, str1);
printf(“Text 2: “);
scanf(“%100s”, str2);
int c = strcmp(str1, str2);
if(c > 0) {
printf(“%s kommt nach %s\n”, str1, str2);
} else if(c < 0) {
printf(“%s kommt vor %s\n”, str1, str2);
} else {
printf(“%s gleich %s\n”, str1, str2);
}
*strchr und *strrchr und *strpbrk und *strstr Funktionen für Zeichenketten
● Suchfunktionen:
* char *strchr(const char *s, int c);
char *strrchr(const char *s, int c);
Rückgabewert: Zeiger auf erstes/letztes Auftreten von c in s
* char *strpbrk(const char *s, const char *accept);
Rückgabewert: Zeiger auf erstes Auftreten eines der Zeichen aus accept in s
* char *strstr(const char *haystack,
const char *needle);
Rückgabewert: Zeiger auf erstes Auftreten von needle in haystack
char str1[] = “Heute ist ein toller Tag!”;
char *str2;
str2 = strstr(str1, “ein”);
printf(str2);
strlen und memset Funktionen für Zeichenketten
● Länge einer Zeichenkette:
size_t strlen(const char *s);
Rückgabewert: Länge der Zeichenkette s
● Auffüllen eines Feldes:
void *memset(void *s, int c, size_t n);
Füllt die ersten n Bytes von s mit c.
● Weitere Funktionen in string.h (siehe Literatur):
– strspn strcspn
strerror strtok
memchr
puts gets fgets Funktionen für Zeichenketten
● Funktionsdefinitionen in stdio.h
● Ein-/Ausgabe von Zeichenketten:
* int puts(char *s);
* schreibt die Zeichenkette s und einen nachfolgenden Zeilenumbruch auf den Bildschirm
* char *gets(char *s);
* liest eine Zeichenkette und speichert sie in s. Das Return der Eingabe wird in \0
umgewandelt.
* Vorsicht: Wenn eingegebener Text länger ist als der Speicherplatz von s, wird über das
Ende von s hinaus in den Speicher geschrieben => Sicherheitsrisiko. Daher besser
folgende Funktion verwenden:
* char *fgets(char *s, int size, FILE *stream);
* Für stream verwende stdin, Details im Kapitel Dateioperationen
* Liest bis zum Zeilenumbruch, aber maximal size-1 Zeichen
* \n bleibt erhalten, fügt \0 am Ende an
Formatiert in Strings schreiben, sprintf
- Syntax: int sprintf(char* str, const char * format, …);
- sprintf schreibt formatiert in den String str.
- Syntax analog zu printf.
- Achtung: Buffer overflow vermeiden!
int n = 12;
float f = 3.1234f;
char s[64];
sprintf(s, “%03d, %.2f”, n, f);
printf(“%s”, s);
return 0;
Zeichenketten in Zahlen wandeln
Funktionsdefinitionen in stdlib.h
● Zeichenkette in Ganzzahl umwandeln
int atoi(const char *s)
long atol(const char *s)
long strtol(const char *s, char **endp, int base)
unsigned long strtoul(const char *s, char **endp, int base)
● Zeichenkette in Gleitkommazahl umwandeln
double atof(const char *s)
double strtod(const char *s, char **endp)
● Beschreibung
* wandelt Zeichenkette bis zum ersten nichtnumerischen Zeichen
* liefert 0, wenn kein numerisches Zeichen
* s: Zeichenkette
* endp: zeigt auf erstes Zeichen, das nicht konvertiert wurde
* base: Basis des Zahlsystems (2-36, 0 automatische Erkennung)
Eigene Datentypen
- C erlaubt die Definition von eigenen Datentypen.
- Ziel: Bessere Lesbarkeit von Programmen
- In C
- Aufzählungstypen (enum)
- Typdefinition (typedef)
Aufzählungstypen
- Aufzählungstypen können für Variablen definiert werden, denen nur wenige
verschiedene Werte zugewiesen werden sollen. - Syntax:
- enum TypName {Bez1, Bez2, …} [Var1, Var2, …];
- Die Variablennamen sind optional.
enum tier {hund, katze, maus} t;
t = hund;
enum farbe {rot=5, blau};
…
enum farbe f;
f = blau;
- Interne Umsetzung:
- Aufzählungstyp ist int
- Wenn nicht anders festgelegt, werden Bezeichner durch Werte 0, 1, …
repräsentiert (Reihenfolge in der Definition ist wichtig!) - Vorteil:
- Aussagekräftige Bezeichner machen die Programme lesbarer
und übersichtlicher. - Nachteil:
- Typfremde Zuweisungen an Aufzählungstypen werden meist vom Compiler
nicht erkannt. - Nur für kleine Wertebereiche anwendbar.
Typdefinitionen
- Ein bestehender Datentyp bekommt einen neuen,
alternativen Namen. - Syntax:
- typedef Typ TypName;
enum e_farbe {rot, blau, gruen, gelb};
typedef enum e_farbe farbe;
farbe f;
f = gelb;
Wichtige Anwendungsbereiche:
* Bessere Lesbarkeit großer Programme
* Bessere Portierbarkeit eines Programms, da nur die abstrakten Datentypen an die
neue Umgebung angepasst werden müssen
Datenstrukturen
Bisher: elementare Datentypen
● Jetzt: Datenstrukturen zur Speicherung logisch zusammenhängender Daten.
● Beispiel:
* Szenario: Die Verwaltung einer Firma speichert Daten wie Name, Nachname,
Geburtsdatum, Adresse usw., eines jeden Angestellten.
* Wie kann man diese Daten übersichtlich speichern?
* Für jeden Angestellten wird z.B. eine Karteikarte angelegt.
● Datenstrukturen sind Abbildungen solcher Karteikarten auf den Speicher.
Strukturierte Datentypen in C
Arrays (Felder) geordnete Folge von Werten des gleichen Datentyps
Strukturen fassen Daten unterschiedlichen Typs zusammen
Bitfelder Ähnlich zu Strukturen, jedoch mit der Möglichkeit,
Speicherbedarf zu reduzieren
Unions enthalten eines von mehreren Datenelementen
Strukturen: Ziel und Definition
● Beispiel: ein Kunde wird durch Namen und Adresse sowie Kundennummer
definiert.
● Ziel: Zusammenfassung der Attribute mit unterschiedlichem Datentypen in
einem neuen Datentyp.
● Syntax:
struct [Typname] {
Komponente1;
[Komponente2; …]
} [VarName1, …];
Beispiel (Typdefinition und eine Variable):
struct s_kunde {
char vorname[50];
char nachname[50];
int kundennummer;
} k1;
Beispiel (spätere Variablendeklaration):
struct s_kunde k2;
● Beispiel (struct und typedef)
typedef struct s_kunde kunde;
…
kunde k3;
Operationen mit Strukturen
- Zulässige Operationen
- Übergabe an Funktionen als Parameter (call by value!)
- Rückgabe einer Struktur aus einer Funktion
- Zuweisungsoperator für gesamte Struktur (Kopie wird erzeugt)
- Adress- und sizeof-Operator
- Elementzugriffsoperator: Punkt (.)
- Beispiel (Elementzugriff):
kunde1.kundennummer = 1001;
printf(“Name: %s %s\n”,
kunde1.vorname,
kunde1.nachname);
Die Attribute einer Struktur können direkt bei der Deklaration gesetzt werden
struct Spieler {
char name[50];
int alter;
long punktzahl;
};
struct Spieler bob = {“Bob”, 12, 20000};
Strukturen: Ein größeres Beispiel
typedef struct s_datum {
int tag, monat, jahr;
} datum;
typedef struct s_kunde {
char vorname[50];
char nachname[50];
datum erster_einkauf;
int kundennummer;
} kunde;
…
kunde meine_kunden[100];
…
meine_kunden[42].erster_einkauf.jahr = 2002;
strcpy(meine_kunden[42].vorname, “Peter”);
strcpy(meine_kunden[42].nachname, “Kopitzke”);
…
anfangsbuchstabe = meine_kunden[42].nachname[0];
Zeiger auf Strukturen / Operator ->
- Der Operator -> erlaubt den Zugriff auf Inhalte von Strukturen
über Zeiger.
typedef struct s_kunde {
char name[64];
int alter;
} kunde;
kunde k1;
kunde *zeiger = &k1;
- Normalerweise: (*zeiger).alter = 20;
- Besser: zeiger->alter = 20;
Bitfelder
- Bitfelder sind eine Spezialform von Strukturen.
- Ziel: Verringerung des Speicherbedarfs und leichterer Zugriff auf
einzelne Bits. - Syntax analog zu struct, hinter jedes Element wird die Anzahl der
benötigten Bits geschrieben. - Beispiel für Bitfeld (1 Byte groß):
struct zeichen {
unsigned char data:7;
unsigned char parity:1;
};
- Achtung: Das Bitfeld belegt mindestens so viel Speicher wie der größte
enthaltene Datentyp.
Unions: Definition
- Syntax analog zu Strukturen, Schlüsselwort union statt struct
- Es wird jedoch nur einer der Werte gespeichert.
- Speicherbedarf der Union richtet sich nach dem größten Element.
- Beispiel:
union typen {
int i;
double d;
} u;
u.i = 5;
u.d = 2.3; «u.i ist nun undefiniert
Unions: Anwendungsbereiche
include <stdio.h></stdio.h>
● Einsparen von Speicherplatz: wenn sichergestellt ist, dass manche
Informationen nicht gleichzeitig gespeichert werden müssen.
● Maschinennahe Manipulation: Interpretation eines Speicherbereichs durch
verschiedene Datentypen (z.B. um auf Gleitkommazahlen auch
Bitmanipulationen durchführen zu können).
Achtung: Solche Programme sind i.d.R. nicht portierbar!
● Bessere Lesbarkeit
beispiel:
union floatAndInt
{
float f;
int i;
} zahl;
printf(“sizeof float: %d, int: %d\n”, sizeof(float), sizeof(int));
printf(“Zahl: “);
scanf(“%f”, &zahl.f);
int j;
int l = 8*sizeof(int);
printf(“Die interne binäre Darstellung ist: “);
for(j = l-1; j >= 0; j–) {
printf(“%d”, (zahl.i»_space; j)&1);
}
printf(“\n”);
beispiel:
union vec3d {
struct { float x, y, z; };
struct { float alpha, beta, gamma; };
float value[3];
};
int main()
{
union vec3d v;
v.x = 2;
v.y = 3;
v.z = 4;
printf(“%f %f %f\n”, v.x, v.y, v.z);
// Alternative 1
v.alpha = 4;
v.beta = 5;
v.gamma = 6;
printf(“%f %f %f\n”, v.alpha, v.beta, v.gamma);
// Alternative 2
v.value[0] = 7;
v.value[1] = 8;
v.value[2] = 9;
printf(“%f %f %f\n”, v.value[0], v.value[1], v.value[2]);
return 0;
}