C-Programmierung - Häufige Fragen (und Antworten)

Dies FAQ zur C-Programmierung fasst häufig gestellte Fragen der C-Programmierung in einem übersichtlichen Frage-Antwort-Format zusammen.

Fragen zur Programmstruktur

Q1. Wie ist eine C-Quelldatei aufgebaut?

Eine C-Quelldatei besteht aus:
1. #include-Direktiven - um Headerdateien zu inkludieren
2. #define-Direktiven - um Makros / Konstanten zu deklarieren
3. Funktionsprototypen selbstdefinierter Funktionen (optional)
4. main-Funktion - Einstiegspunkt des Programms.
5. Funktionsdefinitionen selbstdefinierter Funktionen (optional)
Größere C-Programme verwenden zusätzlich Quelldateien, die nur Funktionsdefinitionen enthalten, mit dazugehörigen Headerdateien.


Q2. Was bewirkt eine #include-Direktive?

Eine #include-Direktive bewirkt, dass der Präprozessor diese Zeile durch den Inhalt der angegebenen Datei ersetzt. Mit #include<stdio.h> wird die Header-Datei für Ein- und Ausgabefunktionen in das eigene Programm eingefügt, damit können Funktionen wie printf und scanf verwendet werden.


Q3. Was sind #define-Direktiven?

#define-Direktiven werden verwendet, um Konstante und sogenannte Makros zu definieren z.B. #define PI 3.14159.


Q4. Was ist die Rolle der Funktion main() in einem C-Programm?

Jedes C-Programm muss genau eine Funktion main() enthalten, in der die Programmausführung beginnt und endet.


Q5. Wie / wo werden Anweisungen in C geschrieben?

Anweisungen sind Ausdrücke, die mit einem Semikolon beendet werden, z.B. eine Variablendeklaration, eine Zuweisung oder ein Funktionsaufruf.
- Anweisungen können einzeilig oder mehrzeilig sein.
- Anweisungen werden in eine Funktion geschrieben, entweder direkt in die main-Funktion, oder in eine andere Funktion.

int jahr = 0; // Einzeilige Anweisung
// Mehrzeilig: Schleife umfasst 3 Zeilen
for(jahr=2020;jahr<2024;jahr++){
    printf("Jahr: %d\n", jahr);
}


Q6. Wie schreibt man Kommentare in C?

Mehrzeilige Kommentare beginnen mit /* und enden mit */. Einzeilige Kommentare beginnen mit //.




Fragen zu Variablen und Datentypen

Q1. Was sind Variablen und wie werden sie deklariert?

Variable sind benannte Speicherplätze, in denen die Daten des Programms gespeichert werden. Die Deklaration einer Variablen erfolgt durch Angabe eines Datentyps und des Variablennamens, z.B.
int m, n; double x;


Q2. Was ist eine Konstante und wie wird sie deklariert?

Eine Konstante wird zur Speicherung von Werte verwendet, die nicht änderbar sind. Deklaration durch Voranstellung des Schlüsselwertes const vor den Datentyp, z.B.
const double PI = 3.14159;

Alternativ können #define-Direktiven verwendet werden, z.B.
#define PI 3.14159


Q3. Was ist die Rolle des Datentyps bei der Variablendeklaration?

Der Datentyp bestimmt, welche Art von Werten in dieser Variablen gespeichert werden können und wie viel Speicher verwendet wird.


Q4. Welche C-Datentypen werden verwendet, um mit ganzen Zahlen zu arbeiten?

Datentypen für ganze Zahlen in C sind: byte, short, int, long long.


Q5. Was bedeutet die Angabe des Schlüsselworts unsigned bei der Variablendeklaration, z.B.
unsigned int n;?

Durch das Schlüsselwort unsigned wird auf die Speicherung des Vorzeichens verzichtet, man kann in der Variablen dann nur nichtnegative Werte speichern. Dafür verdoppelt sich der darstellbare Wertebereich.


Q6. Wann und wie werden Typumwandlungen / Typecasts in C durchgeführt?

Typumwandlungen werden dann durchgeführt, wenn man den Datentyp eines Variablenwertes in einer Zuweisung oder Berechnung ändern muss.
Typumwandlungen können implizit vom Compiler durchgeführt oder explizit angegeben werden.
Bei der expliziten Typumwandlung wird der gewünschte Datentyp vor den Namen der Variablen geschrieben.

// Implizite Typumwandlung: 20 wird als 20.0 gespeichert
double x = 20; 

// Explizite Typumwandlung
int z = 1, n = 100;
double quot = (double)z / (double)n;




Fragen zu Ein- und Ausgabe-Funktionen

Q1. Wie funktioniert die Ein- und Ausgabe von Daten in C-Programmen?

Zunächst muss die C-Standardbibliothek stdio.h inkludiert werden, diese bündelt alle Funktionen, die man für Ein- und Ausgabe von Daten verwenden kann.

C definiert das abstrakte Konzept eines Datenstroms, dieser repräsentiert gemeinsame Eigenschaften realer Ausgabemedien wie Konsole Datei, Zeichenkette. Ein Datenstrom kann geöffnet und geschlossen werden, die Daten können als Text-Zeilen oder Binärdaten behandelt werden, etc. Dementsprechend gibt es für jede Art von Datenstrom eigene Funktionen, die jedoch ähnlich aufgebaut sind, z.B.
printf - für Konsole, fprintf - für Dateien, sprintf - für Strings. Siehe: https://cplusplus.com/reference/cstdio/


Q2. Welche Möglichkeiten gibt es, Daten (Zahlen, Zeichen, Zeichenketten) in ein C-Programm einzugeben?

1. Eingabe über die Konsole.
scanf() - formatierte Eingabe, getchar() - einzelnes Zeichen einlesen
2. Daten aus einer Datei einlesen.
fscanf() - formatierte Eingabe, fread() - binäre blockweise Eingabe
3. Daten aus einem Stream lesen.
fgets - Zeile lesen


Q3. Welche Möglichkeiten gibt es, Daten (Zahlen, Zeichen, Zeichenketten) aus einem C-Programm auszugeben?

1. Ausgabe über die Konsole.
printf() - formatierte Ausgabe, putchar() - einzelnes Zeichen ausgeben
2. Daten in eine Datei ausgeben.
fopen() - Datei öffnen, fclose() - Datei schließen
fprintf() - formatierte Ausgabe
3. Daten in einen Stream ausgeben
fputs - Zeile ausgeben


Q4. Wie funktioniert die formatierte Ausgabe mittels printf-Funktion? Wie werden die auszugebenden Werte übergeben?

Die printf-Funktion erhält als ersten Parameter eine formatierende Zeichenkette, die Text und Formatzeichen enthält z.B. "%10s|%10d|%10.2lf".
Danach folgt eine Liste von Variablen oder Werten, die in Anzahl, Datentyp und Reihenfolge mit den Formatzeichen übereinstimmen müssen.

int n = 100; double x = 1.2345;
// Ausgabe:
// Zeile 1|    100|    1.23|
printf("%7s|%7d|%7.2lf", "Zeile 1", x, y);


Q5. Welches ist die Rolle der Formatzeichen %d, %f etc. bei der Verwendung der printf-Funktion?

Die Formatzeichen werden als Platzhalter für Variablenwerte verwendet, wobei es für jeden Datentyp das dazu passende Formatzeichen gibt: %d für int, %lf für double usw.

int n = 10; double x = 1.23;
printf("n hat den Wert %d, x hat den Wert %lf\n", n, x);


Q6. Wie funktioniert die formatierte Eingabe mittels scanf-Funktion? Wie werden die einzugebenden Werte übergeben?

Die scanf-Funktion erhält als ersten Parameter eine formatierende Zeichenkette, die Formatzeichen und ggf. andere Zeichen enthält z.B. "%d %lf".
Danach folgt eine Liste von Variablen-Referenzen, die in Anzahl, Datentyp und Reihenfolge mit den Formatzeichen übereinstimmen müssen.

Die Variablen-Referenzen erkennt man am Adress-Operator (&), der jedem Variablennamen vorangestellt wird.

int n = 0; double x = 0.0;
scanf("%d %lf", &n, &x);




Fragen zu Verzweigungen

Q1. Welche C-Anweisungen gibt es, um Verzweigungen im Programmablauf umzusetzen?

C-Anweisungen für Verzweigungen sind:
1. if-else - für einfache Verzweigungen
2. if - else if - else - für mehrfache "kaskadierende" Verzweigungen.
3. switch-case - für viele Fallunterscheidungen.


Q2. Wie wird eine einfache if-else-Anweisung geschrieben?

Falls die Bedingung wahr ist, werden die Anweisungen im if-Teil ausgeführt, sonst die Anweisungen im else-Teil.

if (bedingung){
  anweisungen1
}
else {
  anweisungen2
}


Q3. Wie werden Bedingungen in C formuliert?

Bedingungen werden formuliert, indem Variablen und Werte mit Hilfe von Vergleichs- und logischen Operatoren zu Ausdrücken verknüpft werden, die als wahr oder falsch ausgewertet werden.
Um z.B. zu überprüfen, ob ein Jahr ein Schaltjahr ist, lautet die Bedingung
((jahr % 4 == 0) && !(jahr % 100 == 0)) || (jahr % 400 == 0).


Q4. Welche Vergleichsoperatoren gibt es in C?

Um zwei Operanden / Ausdrücke zu vergleichen, werden die Operatoren ==, >, >=, <, <= verwendet.
Gleich: a == b
Größer: a > b, Größer oder gleich: a >= b
Kleiner: a < b, Kleiner oder gleich: a <= b


Q5. Welche logischen Operatoren gibt es in C?

Um zwei Ausdrücke logisch zu verknüpfen, werden die logischen Operatoren &&, ||, ! verwendet.
Logisches UND: a && b ist wahr genau dann, wenn beide Operanden wahr sind.
Logisches ODER: a || b ist wahr genau dann, wenn mindestens einer der beiden Operanden wahr ist.
Logische Verneinung: !a ist wahr genau dann, wenn a falsch ist.


Q6. Weshalb ist es besser, mehrfach verzweigte if - else if- else Anweisungen zu verwenden, anstatt mehrere einfache if-else-Anweisungen nacheinander zu schreiben?

Bei der Verwendung mehrerer einfacher if-else-Anweisungen kann es passieren, dass die Reihenfolge logisch falsch ist, dass ein Fall vergessen wird, oder der Default-Fall nicht abgebildet wird. Eine mehrfache Verzweigung ermöglicht hingegen eine logisch präzisere Formulierung.


Q7. Wann wird eine switch-case-Anweisung eingesetzt, wann eine mehrfach verzweigte if - else-Anweisung?

Die switch-case-Anweisung wird dann eingesetzt, wenn anhand der Werte, die eine Variable annehmen kann, viele unterschiedliche Fallunterscheidungen abgebildet werden. Beispiel: Wenn "A": Hilfe anzeigen, wenn "B": Tabelle ausgeben, wen "C": [...], wenn "Z": Programm beenden.

Die mehrfach verzweigte if - else-Anweisung wird dann eingesetzt, wenn Bedingungen "kaskadierend" überprüft werden.
Beispiel: Berechne Zinssatz abhängig von Betrag.
Wenn der Betrag größer ist als 50000, beträgt der Zinssatz 3.0
Sonst, wenn der Betrag größer ist als 10000, beträgt der Zinssatz 2.0
Sonst, wenn der Betrag größer ist als 0, beträgt der Zinssatz 1.0
Sonst beträgt der Zinssatz 0.0




Fragen zu Schleifen

Q1. Welche Arten von Schleifen gibt es in C?

In C gibt es drei Arten von Schleifen:
while-Schleife: kopfgesteuert, Bedingung wird am Anfang geprüft. Die Anweisungen werden so oft ausgeführt, wie die Bedingung wahr ist, das kann auch kein einziges Mal sein.
do-while-Schleife: fußgesteuert, Bedingung wird am Ende geprüft. Die Anweisungen in der Schleife werden unabhängig von der Bedingung mindestens einmal ausgeführt.
for-Schleife: zählergesteuert. Für eine Zählvariable werden Anfangswert, Endwert und Schrittweite festgelegt.


Q2. Wann wird in C welche Schleifen-Art bevorzugt eingesetzt? Wann besser while, wann besser do-while, wann besser for-Schleife?

Viele Problemstellungen können gleichwertig mit while, do-while oder for-Schleife abgebildet werden.
while-Schleifen werden verwendet, wenn Anweisungen nur dann ausgeführt werden sollen, wenn eine Bedingung zutrifft. Beispiel: Solange das Ende einer Datei nicht erreicht ist, lese die Daten aus der Datei aus.
do-while-Schleifen werden verwendet, wenn Anweisungen mindestens einmal ausgeführt werden sollen, und nach jeder Ausführung überprüft wird, ob die Schleife fortgesetzt werden soll. Beispiel: die Ausgabe eines Hilfe-Menüs.
for-Schleifen werden verwendet, wenn die genaue Anzahl der auszuführenden Iterationen bekannt ist und gezählt werden kann. Beispiel: Durchlaufen von Arrays, dort wird die Zählvariable durch den Index eines Elementes gegeben und die kompakte Schreibweise ist vorteilhaft.


Q3. Endlosschleifen treten in der Programmierung meist wegen Fehlern auf und führen zum Absturz des Programms. Wann werden in C Endlos-Schleifen mit Sinn und Absicht eingesetzt?

Endlos-Schleifen werden eingesetzt, wenn ein Programm fortlaufend auf eine Benutzereingabe oder auf ein Event warten muss, wie z.B. in der Mikrocontroller-Programmierung. Oder wie im folgenden Beispiel: In einer Endlos-Schleife wird eine Funktion berechne_volumen() immer wieder ausgeführt. Die Schleife wird abgebrochen, wenn der Benutzer das Abbrechen mit J bestätigt.

char wahl = 'N';
while(1){
  berechne_volumen();
  printf("Abbrechen? J = Ja\n"); 
  scanf("%c", &wahl);
  if(wahl == 'J') break;
}


Q4. Wie wird die break-Anweisung in einer Schleife eingesetzt?

Die break-Anweisung bewirkt, dass die innerste umgebende Schleife sofort verlassen wird. Man kann damit abhängig von einer Bedingung den Abbruch einer Schleife bewirken.


Q5. Wie wird die continue-Anweisung in einer Schleife eingesetzt?

Die continue-Anweisung ermöglicht es, abhängig von einer Bedingung Schleifenschritte zu überspringen und die Ausführung beim jeweils nächsten zutreffenden Schleifenschritt fortzusetzen. Die continue-Anweisung wird z.B. benutzt, wenn die Anweisungen der Schleife für ganz bestimmte Werte nicht ausgeführt werden sollen.




Fragen zu Funktionen

Q1. Wie werden eigene Funktionen in C deklariert und verwendet?

Funktionen werden einmal deklariert mittels Funktionsdefinition und Funktionsprototyp:
1. Funktionsdefinition: diese enthält den Rückgabetyp, den Namen der Funktion, eine Parameterliste (in runden Klammern) und die Anweisungen (in geschweiften Klammern). Man schreibt Funktionsdefinitionen im Anschluss an die main-Funktion oder in eine eigene Quelldatei, z.B. mylib.c.
2. Funktionsprototyp: dieser enthält den Rückgabetyp, den Namen der Funktion, die Parameterliste (in runde Klammern) und wird mit Semikolon beendet. Man schreibt Funktionsprototypen im Anschluss an die #include-Direktiven oder in eine eigene Header-Datei mylib.h.

Funktionen können beliebig oft verwendet werden, indem man sie in einer anderen Funktion aufruft, durch Angabe ihres Namens und passender Argumente (=Parameter-Werte).


Q2. Wie werden Funktionen der C-Standardbibliothek verwendet?

1. Finde heraus, in welcher Header-Datei die gewünschte Funktion enthalten ist. String-Funktionen befinden sich z.B. in der string.h, Ein- und Ausgabe in der stdio.h etc.
2. Inkludiere die Header-Dateien.
3. Achte auf die korrekte Parameterübergabe. Anzahl, Reihenfolge und Datentyp der übergebenen Argumente müssen mit den Parametern übereinstimmen.

#include <stdio.h>  // wegen printf, fgets
#include <stdlib.h> // wegen malloc, sizeof
#include <string.h> // wegen strlen
int main(void) {
  char *txt = (char*)malloc(20*sizeof(char));
  printf("Text eingeben: ");
  fgets(txt, 20, stdin);
  printf("Eingabe-Text: %s\n", txt);
  printf("Laenge: %d\n", strlen(txt));
}


Q3. Was genau sind die Parameter einer Funktion und wie werden sie verwendet?

Die Parameter einer Funktion sind eine kommagetrennte Liste von Datentypen, jeder gefolgt von einem Parameternamen. Die Parameter einer Funktion werden verwendet, um Daten an eine Funktion zu übergeben. Die Daten werden als Argumente übergeben, diese können Werte sein, oder Variablen mit passendem Datentyp.
Beispiel: Die Funktion myfunc berechnet die Potenz ihrer Argumente und hat die Parameterliste float x, int n.

#include<stdio.h>
float myfunc(float x, int n); // Funktionsprototyp
int main(void) {
    float arg1 = 2.0; int arg2 = 4;
    // Verwendung mit Argumenten arg1 und arg2, Ausgabe: 16.0
    printf("%.1lf\n", myfunc(arg1, arg2));
    // Verwendung mit Argumenten 10.0 und 3, Ausgabe: 1000.0
    printf("%.1lf\n", myfunc(10.0, 3));
}
float myfunc(float x, int n) { // Funktionsdefinition
  float p = 1.0;
  for (int i = 1; i <= n; i++)
      p = p * x;
   return p; // p = x^n
}


Q4. Was versteht man unter Parameterübergabe als Wert und wie funktioniert sie?

Parameterübergabe als Wert bedeutet, dass die Parameter als Werte in die Funktion einfließen, selbst wenn im Funktionsaufruf Variablennamen als Argumente angegeben sind. Beispiel: Die Funktion mittelwert() berechnet den Mittelwert ihrer Parameter. Bei dem Funktionsaufruf m = mittelwert(x1, x2); werden die Variablen x1 und x2 als Argumente eingetragen, tatsächlich fließen in die Funktion jedoch Kopien der Variablenwerte ein, 10.0 für x1 und 4.0 für x2, und daraus wird der Mittelwert 7.0 berechnet und zurückgegeben.

double mittelwert(double x, double y){
   return (x + y) / 2;
}
int main(void){
   double x1 = 10.0, x2 = 4.0;
   double m = mittelwert(x1, x2); 
   printf("Mittel = .1lf\n", m); // Mittel = 7.0
}


Q5. Was versteht man unter Parameterübergabe als Referenz und wie funktioniert sie?

Parameterübergabe als Referenz bedeutet, dass die Parameter als Adressen in die Funktion einfließen. Infolgedessen kann der Wert der übergebenen Variablen in der aufrufenden Funktion verändert werden. Beispiel: Die Funktion mittelwert() berechnet den Mittelwert ihrer ersten beiden Parameter und speichert das Ergebnis in dem Referenzparameter m. Bei dem Funktionsaufruf mittelwert(x1, x2, &m); werden die ersten beiden Parameter x1 und x2 als Wert übergeben, der Parameter m wird als Adresse übergeben, daher &m im Funktionsaufruf.

void mittelwert(double x, double y, double *m){
   *m = (x + y)/2;
}
int main(void){
   double x1 = 10.0, x2 = 4.0, m = 0.0;
   mittelwert(x1, x2, &m); 
   printf("Mittel = .1lf\n", m); // Mittel = 7.0
}


Q6. Wann wird in einer Funktion Parameterübergabe als Wert verwendet, und wann Parameterübergabe als Referenz? Was sind die Vorteile / Nachteile?

Parameterübergabe als Wert wird verwendet, wenn die Daten nur als Eingabewerte in die Funktion einfließen.
Vorteile: Einfache Verwendung.
Nachteile: 1. Mehr Speicherverbrauch, da der Wert der übergebenen Variablen in die Funktion kopiert wird. 2. Wert der Variablen in der aufrufenden Funktion kann nicht verändert werden.
Beispiel: Mathematische Funktionen verwenden Wert-Parameter, z.B. y = sin(x);

Parameterübergabe als Referenz wird verwendet, wenn der Wert der übergebenen Variablen in der aufrufenden Funktion verändert werden soll.
Vorteile: 1. Weniger Speicherverbrauch, da direkt der Wert in der übergebenen Variablen geändert wird. 2. Es wird der Wert der übergebenen Variablen in der aufrufenden Funktion geändert.
Nachteile: Verwendung erfolgt über Adressen und Zeigervariablen, für Einsteiger etwas schwieriger.
Beispiel: Funktionen für Ein- und Ausgabe, z.B. scanf("%d", &x);




Fragen zu Arrays

Q1. Was genau ist ein Array?

Ein Array ist eine Menge von Datenobjekten desselben Datentyps, die im Arbeitsspeicher direkt hintereinander gespeichert sind. Ein Array ist eine statische Datenstruktur, für die ein Speicherbereich fester Größe reserviert wird.


Q2. Was ist ein Element eines Arrays?

Ein Element eines Arrays ist ein einzelnes Datenobjekt, dessen Position im Array über sogenannte Indizes festgelegt wird, die bei 0 anfangen.


Q3. Was ist die Dimension eines Arrays?

Die Dimension eines Arrays ist die Anzahl der Indizes, die benötigt werden, um ein Element eines Arrays auszuwählen. Ein eindimensionales Array entspricht einer Liste und benötigt nur einen Index, ein zweidimensionales Array entspricht einer Matrix oder Tabelle und benötigt zwei Indizes.


Q4. Wie werden eindimensionale Arrays in C deklariert?

Ein 1D-Array wird deklariert, indem 1. der Datentyp, 2. der Name des Arrays, und 3. die konstante Größe des Arrays in eckigen Klammern angegeben werden. Z.B.
int a[10]; // ganzzahliges Array mit max. 10 Elementen
float x[100]; // Fließkomma-Array mit max. 100 Elementen
char text[10]; // Zeichenkette mit max. 10 Zeichen


Q5. Wie weist man den Elementen eines eindimensionalen Arrays Werte zu?

1. Explizite Aufzählung
Sinnvoll nur bei wenig Elementen.

float x[] = {0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0};

2. Mittels Schleifen
Sinnvoll in den meisten Fällen

float x[11] = {0};
for(int i=0;i<11;i++)
  x[i] = 0.1*i;

3. Initialisierung aller Elemente

int a[10] = {0}; // Alle Elemente werden mit 0 initialisiert


Q6. Wie wird die Größe eines Arrays in C üblicherweise festgelegt?

1. Größe wird direkt als Zahl angegeben

int main(void){
  int a[100]; float x[100];
}

2. Größe wird mit define festgelegt

#define N 100
int main(void){
  int a[N]; float x[N];
}

3. Größe wird mit const int festgelegt

int main(void){
  const int N = 100;
  int a[N]; float x[N];
}


Q7. Wie werden zweidimensionale Arrays in C deklariert?

Ein 2D-Array wird deklariert, indem 1. der Datentyp, 2. der Name des Arrays, und 3. die konstante Anzahl der Zeilen und Spalten des Arrays in eckigen Klammern angegeben werden. Z.B.
int a[3][5] = {0.0}; // ganzzahliges Array mit 3 Zeilen und 5 Spalten
float x[4][2]; // Fließkomma-Array mit 4 Zeilen und 2 Spalten


Q8. Wie weist man den Elementen eines zweidimensionales Arrays in C Werte zu?

1. Explizite Aufzählung
Beispiel: Tabelle mit 3 Zeilen und 2 Spalten.
int tab[][2] = {{0, 1}, {1, 2}, {2, 3}};

2. Mittels Schleifen
Beispiel: hier wird dieselbe Tabelle erzeugt wie mit der expliziten Aufzählung.

int tab[3][2] = {0};
for (int i=0;i<3;i++){ // Schleife über die Zeilen mit Index i
  for (int j=0;j<2;j++){ // Schleife über die Spalten mit Index j
    tab[i][j] = i + j;
   }
}


Q9. Wann werden ein- oder zweidimensionale Arrays verwendet?

Arrays werden verwendet, um Listen oder Tabellen / Matrizen im Speicher abzulegen.
Beispiel 1: Mittelwert von Messwerten berechnen
Wenn man z.B. eine Liste von Messwerten hat und deren Mittelwert berechnen will, muss für die Liste ein eindimensionales Array deklariert werden.
Beispiel 2: Matrix-Multiplikation
Für die Berechnung des Produktes zweier Matrizen benötigt man drei zweidimensionale Arrays:
a[M][N] - Matrix a mit M Zeilen und N Spalten,
b[N][P] - Matrix b mit N Zeilen und P Spalten,
c[M][P] - Matrix c für das Produkt, mit M Zeilen und P Spalten.




Fragen zu Zeigervariablen

Q1. Welcher Ausdruck ermittelt die Adresse einer Variablen x? Wie wird die Adresse einer Variablen x korrekt ausgegeben?

Die Adresse einer Variablen wird ermittelt, indem man dem Namen der Variablen ein kaufmännisches Und (&) voranstellt, z.B. &x. Bei der Ausgabe mit printf muss %p verwendet werden, ein spezielles Formatzeichen für Adressen.

double x = 10.5;
printf("Adresse von %lf = %p\n", x, &x);


Q2. Was genau sind Pointer / Zeigervariablen und wodurch unterscheiden sie sich von "normalen" Variablen?

Pointer / Zeigervariablen sind Variablen, in denen Adressen gespeichert werden.
 ★ Variablen mit Datentyp int, float oder double werden als Datenspeicher verwendet und in Berechnungen eingesetzt.
 ★ Pointer haben den Datentyp int*, float*, double* etc. und werden verwendet, um andere Variablen oder Speicherbereiche über Adressen zu referenzieren.
 ★ Arithmetische Operationen wie +, -, *, / sind somit auf Zeigervariablen nicht anwendbar.
Beispiel: Variable vs. Pointer

int a = 10;
int* ptr = &a; // ptr = Zeiger auf a, enthält Adresse von a
*ptr = 20;     // Wert von a wird via ptr geändert
printf("a = %d\n", a); // a ist jetzt 20!


Q3. Wie werden Pointer in C deklariert, wie referenziert man mit ihrer Hilfe andere Variablen?

Pointer deklarieren
Um eine Zeigervariable zu deklarieren, wird dem Namen der Variablen ein Stern * vorangestellt, z.B. int *ptr; für einen Zeiger, der auf int-Variablen zeigen soll.
 ★ Alternativ wird auch die Schreibweise int* ptr; verwendet, diese sagt aus, dass ptr den Datentyp (int*) hat, also Zeiger auf int.
 ★ Zeiger müssen denselben Datentyp haben wie die Variablen, auf die sie zeigen.

Pointer referenzieren
Um einem Pointer die Adresse einer anderen Variablen zuzuweisen, wird der Adress-Operator & verwendet.
Um über den Zeiger auf den Inhalt der referenzierten Variablen zuzugreifen, wird der Inhalts-Operator * verwendet, wie in Zeile 3: *ptr = 20; bedeutet, dass indirekt über den Zeiger der Wert von a geändert wird.

int a = 10;
int* ptr = &a; // ptr = Zeiger auf a, enthält Adresse von a
*ptr = 20;     // Wert von a wird via ptr geändert
printf("a = %d\n", a); // a ist jetzt 20!


Q4. Welches sind die Anwendungsbereiche von Zeigervariablen?

Pointer haben drei wichtige Anwendungsbereiche:
(1) Arrays und Zeiger können in vielen Ausdrücken austauschbar verwendet werden, dies wird vor allem bei Zeichenketten verwendet.
(2) Zeiger als Funktionsparameter ermöglichen Parameterübergabe als Referenz.
(3) Zeiger ermöglichen dynamische Speicherverwaltung. Mit Hilfe der Funktionen malloc, calloc und free kann Speicher im Heap-Bereich allokiert und freigegeben werden.


Q5. Was versteht man unter Zeigerarithmetik bzw. Zeigerinkrementierung?

Unter Zeigerarithmetik versteht man ein paar wenige Operationen, die man mit Zeigervariablen durchführen kann. Dazu gehören: Addition / Subtraktion von ganzen Zahlen, Inkrementierung, Dekrementierung.
 ★ Bei der Zeigerinkrementierung ptr++ ändert sich die Adresse, auf die ptr zeigt, um die Größe des Datentyps.
 ★ Mit Hilfe der Zeigerinkrementierung kann man Zeichenketten oder Zahlenfelder durchlaufen und den Wert an einer bestimmten Stelle verändern.


Tools, Quellen und weiterführende Links

  1. Visual Studio Community Edition: Für die Entwicklung von C-Programmen. Visual Studio unterstützt C-Programmierung indirekt über C++ und die Vollversion wird in vielen Unternehmen als Entwicklungsumgebung eingesetzt.
  2. yED Graph Editor: Für die Entwicklung von Fluss­diagrammen bzw. Programm­ablauf­plänen.
  3. Jürgen Wolf, C von A bis Z: openbook.rheinwerk-verlag.de/c_von_a_bis_z/, 2020.
  4. Thomas Theis: Einstieg in C. Für Programmiereinsteiger geeignet, Galileo Press, 2014.
  5. Manfred Daussman, C als erste Programmiersprache: Vom Einsteiger zum Fortgeschrittenen. Vieweg, 2010.
  6. Axel Böttcher, Franz Kneißl. Informatik für Ingenieure: Grundlagen und Programmierung in C. Oldenbourg Verlag, 1999.
  7. Brian Kernighan, Dennis Ritchie, The C programming language. Prentice-Hall, 2010.
  8. Visual Studio C Language Reference, https://docs.microsoft.com/en-us/cpp/c-language