Einführung in die Programmiersprache C und in C-Zeiger
Inhalt und Lernziele des Kapitels Einführung in die Programmiersprache C und in C-Zeiger.
Einordnung des Artikels
- Einführung in die Informatik
- Einführung in die Programmiersprache C und in C-Zeiger
Ein Vergleich zwischen C und C++, insbesondere Felder und Zeiger betreffend
C++ ist eine Weiterentwicklung von C.
Wenn man diesen Satz liest, fragt man sich sofort, warum man überhaupt C lernen soll oder sich speziell in Konzepte einarbeiten soll, die in C++ nicht mehr benötigt werden. Versucht man den Unterschied zwischen C und C++ knapp zu beschreiben, kann man vielleicht sagen:
Mit C++ erreicht man einen höheren Abstaktionsgrad, C erlaubt maschinennahe Programmierung.
Fragt man nach den Konzepte, die in C++ neu enthalten sind, so sollte man vor allem nennen:
- Referenzen,
- Funktions-Templates,
- Klassen und alle mit objekt-orientierter Programmierung verbundenen Konzepte (wie etwa Vererbung oder Polymorphismus).
Wer mit diesen Konzepten arbeiten möchte, kann sich bei der Wahl zwischen C und C++ nur für C++ entscheiden.
Wer dagegen nur im Sinne der prozeduralen Programmierung arbeiten möchte, kann sowohl auf C als auch auf C++ zurückgreifen. Wobei man aber die Vorteile beider Sprachen sofort nennen sollte:
- C hat einen geringeren Sprachumfang und ist somit leichter zu erlernen.
- Der größere Sprachumfang von C++ erlaubt Erweiterungen, die mit C nicht zu realisieren sind.
- Zudem werden in C++ deutlich mehr Bibliotheken standardmäßig eingebunden; der Blick in sie erspart oftmals eigene Programmierarbeit.
Da C++ eine Weiterentwicklung von C ist, könnte man argumentieren, dass es für maschinennahe Programmierung unerheblich ist, für welche Sprache man sich entscheidet. Stehen aber nur begrenzte Ressourcen zur Verfügung (Speicherplatz, Prozessor), ist meist C die bessere Wahl.
Als weiteres Kriterium für die Unterscheidung zwischen C und C++ müssen Felder (array) und Zeiger (pointer) genannt werden. Auch hier kann man versuchen, den Unterschied knapp zu formulieren:
- C ist ohne Felder und Zeiger undenkbar; wer sich nicht darin einarbeitet, wird nur Anfänger-Programme schreiben können.
- Es darf aber nicht unerwähnt bleiben, dass der Umgang mit Feldern und Zeigern nicht ganz leicht ist: die Syntax enthält hier einige Spitzfindigkeiten und die Programme sind sehr fehleranfällig.
- In C++ können Felder und Zeiger wie in C eingesetzt werden.
- Seit dem Standard C11 sind Felder und Zeiger in C++ aber nahezu überflüssig und durch andere – weniger fehleranfällige – Konzepte ersetzt worden.
Dieser Kurs sollte eine Einführung in C++ bieten; dabei wurden die Konzepte so präsentiert, dass man schnell zum höheren Abstraktionsgrad gelangt. Wenn man jetzt wieder einen Schritt zurückgeht und – die gerade als nahezu überflüssig bezeichneten – Zeiger erläutert, soll auch erklärt werden, welchen Vorteil die Beschäftigung mit Feldern und Zeigern bieten kann:
- Wie gesagt: wer eine echte C-Anwendung schreiben möchte, wird Feldern und an Zeigern nicht vorbeikommen.
- Sie machen viel besser verständlich, wie Prozessor und Speicher arbeiten und geben damit einen Einblick, wie ein in höherer Programmiersprache geschriebenes Programm in die Maschinensprache übersetzt und dann ausgeführt wird.
- Für ressourcenschonende Programmierung ist dies unerlässlich; im Gegensatz dazu fällt es sehr schwer sich vorzustellen, wie Konzepte mit höherem Abstraktionsgrad in Maschinensprache übersetzt wird. Was passiert etwa bei einem Konstruktoraufruf?
Inhalte der Einführung in die Programmiersprache C und C-Zeiger
Der Teil Einführung in die Programmiersprache C und in C-Zeiger des Kurses Einführung in die Informatik soll eine Lücke schließen, die die Teile über C++ hinterlassen haben. Letztere wurden – wie oben ausführlich geschildert – so konzipiert, dass Felder nicht mehr eingesetzt werden, sondern durch die Klassen array und vector ersetzt werden. Diese Klassen beinhalten eine Vielzahl an Methoden, die immer wiederkehrende Funktionalitäten beim Umgang mit Feldern bereitstellen.
Wer dagegen mit C arbeiten möchte, kann auf diese Klassen und Methoden nicht zurückgreifen und ist gezwungen sich in Felder und Zeiger einzuarbeiten, die in diesem Teil vorgestellt werden. Dabei werden aber nicht alle mit Feldern und Zeigern zusammenhängenden Konzepte vorgestellt; die Auswahl bezieht sich auf diejenigen, die zum Einstieg unerlässlich sind.
Bevor Felder und Zeiger besprochen werden, sollen die wichtigsten Sprach-Konzepte von C kurz dargestellt werden. Dabei handelt es sich um:
- HelloWorld in C
- Konsolen-Eingaben und -Ausgaben
- Datentypen
- arithmetische und logische Operationen
- Schleifen.
Diese Konzepte werden nicht so ausführlich dargestellt wie bei C++, sie sollen nur exemplarisch an einfachen Programmen aufgezeigt und kurz erläutert werden. Dabei wird aber darauf geachtet, dass keine Sprachelemente von C++ verwendet werden, die in C nicht verfügbar sind.
Kurze Inhaltsangabe
- Einführung in die Programmiersprache C: HelloWorld und erste Programme
- Einführung in die Programmiersprache C: Schleifen mit for und while
- Einführung in die Programmiersprache C: Funktionen
- Definition und einfache Anwendungen von C-Zeigern
- Felder (arrays) in C
- Anwendung von C-Zeigern: Der Zusammenhang zwischen Feldern und Zeigern
- Weitere Anwendungen von C-Zeigern: Der Zusammenhang von Feldern, Zeigern und Funktionen
- Programmier-Aufgaben in C: erste Schritte mit Feldern und Zeigern
- Programmier-Aufgaben in C: Anwendungen von Feldern, Zeigern und Funktionen
Fehlende Teile
Es soll ausdrücklich betont werden, dass hier nur eine Einführung in die Programmiersprache C und in Felder und Zeiger geboten wird und daher nicht alle Aspekte dieser und verwandter Konzepte behandelt werden. Um einige Konzepte zu nennen, die hier nicht behandelt werden:
- Die Syntax von C wird nur exemplarisch besprochen: die wichtigsten Konzepte werden an Beispielen erläutert, aber nicht in allen Details.
- Zeichenketten werden in C++ als string abgespeichert, wozu es eine Vielzahl von Hilfs-Methoden gibt. In C muss man Zeichenketten als char-Felder definieren und es gibt in den C-Bibliotheken nur wenig Unterstützung zum Verarbeiten von Zeichenketten. Wer in seinen Programmen umfangreiche und komplizierte Operationen mit Zeichenketten vornehmen möchte, sollte sich überlegen, ob nicht C++ die geeignetere Sprache ist. Wer in C-Programmen mit Zeichenketten arbeiten muss, wird sich auch in die Feinheiten der char-Felder einarbeiten müssen.
- Es wird hier zwar vorgestellt, wie man zweidimensionale Felder (Matrizen) initialisiert, aber es werden damit keine komplizierten Operationen vorgenommen. Hier ist ein Blick in die weiterführende Literatur nötig – die Grundlage zu deren Verständnis wird aber hier gelegt.
Lernziele
Gerade für Programmier-Anfänger, die wenig mit der Arbeitsweise und Architektur eines Computers vertraut sind, steht mit Feldern und C-Zeiger eine längere Durststrecke bevor. (Im ersten Teil dieses Informatik-Kurses über Rechnerarchitektur können die Kenntnisse über Computer unabhängig von einer Programmiersprache und von Programmiertechniken nachgearbeitet werden.)
Eine "Durststrecke" steht bevor, da einige spezielle Eigenschaften und Spitzfindigkeiten von Feldern und Zeigern diskutiert werden müssen, ehe sie in relevanten Anwendungen eingesetzt werden können. Es ist daher dem Leser dringend empfohlen, sich zunächst – zumindest oberflächlich – klarzumachen, wozu Felder und Zeiger eigentlich eingeführt wurden. Hier ein kurzer Überblick:
- Felder verallgemeinern fundamentale Datentypen in dem Sinn, dass mehrere Komponenten zu einem Objekt zusammengefasst werden können. In der Mathematik nennt man ein derartiges Objekt einen Vektor.
- Felder können auch in höheren Dimensionen betrachtet werden; zweidimensionale Felder würde man als Matrizen bezeichnen.
- Zeiger realisieren die interne Organisation eines Feldes und ermöglichen so zum Beispiel den Zugriff auf die Komponenten.
- Das andere große Anwendungsgebiet von Zeigern sind Funktionen:
- Mit Zeigern kann der Mechanismus call by reference realisiert werden. Damit ist gemeint, dass bei einem Funktionsaufruf keine Kopien angelegt werden, um die aufrufenden Argumente an die Funktion weiterzugeben. Stattdessen werden lediglich Adressen weitergereicht, mit denen die Argumente angesprochen werden können. Damit kann man umfangreiche Kopiervorgänge vermeiden.
- Der Mechanismus call by reference kann auch genutzt werden, um ein Objekt außerhalb einer Funktion zu verändern – man muss dazu nur dessen Adresse kennen. Dadurch ist es nicht nötig, in einer Funktion einen Rückgabewert zu berechnen (den man dann wieder an die aufrufende Instanz kopieren müsste).
- Letzteres kann man auch verwenden, um Funktionen mit mehreren Rückgabewerten zu simulieren, die es in C nicht geben kann (genauer: man müsste die Rückgabewerte erst zu einem Objekt verpacken).
1. Einführung in die Programmiersprache C: HelloWorld und erste Programme:
Wie immer wird zuerst eine HelloWorld-Anwendung gezeigt, um zu testen, ob die Arbeitsumgebung fehlerfrei funktioniert. Anschließend werden einige einfache Programme diskutiert, die im Sinne der prozeduralen Programmierung geschrieben sind.
Wer mit C++ vertraut ist, wird feststellen, dass sich auf diesem einfachen Niveau die Unterschiede zwischen C und C++ eigentlich nur auf zwei Elemente beziehen:
- Die inkludierten Bibliotheken werden anders angesprochen.
- Konsolen-Ausgaben erfolgen mit den Funktion puts() und printf().
2. Einführung in die Programmiersprache C: Schleifen mit for und while:
Die Schleifen werden nur exemplarisch dargestellt, hier gibt es keinen Unterschied zwischen C und C++.
3. Einführung in die Programmiersprache C: Funktionen:
Mit Funktionen lassen sich Quelltexte besser strukturieren und man vermeidet Wiederholungen des Quelltextes, indem man öfters auftretende Anweisungen zu einer Funktion zusammenfasst, die dann an beliebiger Stelle aufgerufen werden kann.
Um nicht nur bestehende Funktionen aufzurufen (etwa aus den Bibliotheken), sondern selbst Funktionen zu schreiben, muss man die Syntax beherrschen:
Wie werden Funktionen deklariert, aufgerufen und implementiert?
Die Syntax wird an mehreren Beispielen demonstriert, dabei wird auch aufgezeigt, welche "Design-Entscheidungen" man beim Entwurf von Funktionen treffen muss.
4. Definition und einfache Anwendungen von C-Zeigern:
Zeiger sind Variable, die eine Adresse speichern können, insbesondere die Adresse, unter der eine andere Variable abgespeichert ist. In diesem Kapitel sollen Sie:
- Die Begriffe kennenlernen, die im Zusammenhang mit Zeigern verwendet werden.
- Die Syntax kennen und einsetzen können, wie Zeiger deklariert und initialisiert werden und wie man mit ihnen auf den Wert einer Variable zugreifen kann.
- Am Beispiel der swap()-Funktion verstehen, was man unter call by reference versteht und wie der Mechanismus eingesetzt wird, um Kopiervorgänge zu vermeiden oder Funktionen mit mehreren Rückgabewerten zu simulieren.
5. Felder (arrays) in C:
Felder werden eingesetzt, wenn man mehrere gleichartige Variablen zu einem Objekt zusammenfassen möchte; "gleichartig" heißt hier, dass sie gleichen Datentyp besitzen. Um Felder geeignet einzusetzen, lernen Sie hier:
- Die verschiedenen Möglichkeiten, wie ein Feld deklariert und initialisiert wird.
- Den Zugriff auf die Komponenten eines Feldes.
- Die Tatsache, dass der Compiler die Zugriffe auf die Komponenten nicht überprüft, führt leicht dazu, dass man auf "nicht vorhandene" Komponenten zugreift. Ihnen muss bewußt sein, dass allein Sie als Programmierer die Verantwortung dafür tragen, dass dies nicht geschieht – die Reaktion des Programmes ist schwer vorhersehbar (von unbestimmten Werten bis Programm-Absturz ist alles möglich).
- Die Deklaration und Initialisierung von zweidimensionalen Feldern (Matrizen).
- Da Initialisierungslisten eine rekursive Struktur besitzen, kann man leicht herleiten, wie Felder von beliebiger Dimension initialisiert werden.
6. Anwendung von C-Zeigern: Der Zusammenhang zwischen Feldern und Zeigern:
Dieser Abschnitt soll die oben angekündigte "Durststrecke" beenden: jetzt werden die Kenntnisse über Felder und Zeiger zusammengeführt.
Intern werden nämlich Felder durch Zeiger organisiert und beispielsweise alle Zugriffe auf die Komponenten eines Feldes werden vom Compiler in Anweisungen mit Zeigern übersetzt. Und C ist eine der wenigen Sprachen, in denen man alle Anweisungen auf dieser niedrigeren Abstraktionsstufe selbst implementieren kann. Für einen souveränen Umgang mit Feldern ist es daher nötig, diese Organisation von Feldern, insbesondere die Zeigerarithmetik, zu kennen.
Der Abschnitt wird:
- einige Fallen im Umgang mit Feldern aufzeigen,
- einige praktische Probleme diskutieren (welchen Speicherplatz belegt ein Feld, wie greift man auf die Komponenten zu, wie gibt man ein Feld aus, wie vergleicht man zwei Felder) und dabei
- die Bedeutung der Zeiger für Felder klarmachen.
Es werden dabei viele Spitzfindigkeiten erneut auftreten, die in Definition und einfache Anwendungen von C-Zeigern gezeigt wurden, aber es wird besser verständlich werden, warum es nötig war, sie zu diskutieren.
7. Weitere Anwendungen von C-Zeigern: Der Zusammenhang von Feldern, Zeigern und Funktionen:
Wenn eine Funktion als Eingabewert einen primitiven Datentyp erwartet, sollte man sich vorstellen, dass beim Funktionsaufruf tatsächlich eine Kopie des Objektes an die Funktion übergeben wird. Dieser Mechanismus wird bei Feldern nicht verwendet, da man eventuell riesige Speicherbereiche kopieren müsste. Stattdessen wird die Übergabe eines Feldes an eine Funktion mit einem Zeiger realisiert.
Sie lernen in diesem Abschnitt, wie man den Funktionsaufruf realisiert und welche Besonderheiten sich daraus ergeben, dass nicht mehr ein Objekt kopiert wird.
8. Programmier-Aufgaben in C: erste Schritte mit Feldern und Zeigern
Die Aufgaben behandeln die typischen Schwierigkeiten, mit denen man konfrontiert wird, wenn man zum ersten Mal selbständig mit Feldern und Zeigern umgehen muss. Es wird dringend empfohlen, die Aufgaben selber zu lösen, bei Problemen erst nochmal in der Theorie nachzulesen und die Lösungen erst anzusehen, wenn man eine eigene Lösung entwickelt hat.
9. Programmier-Aufgaben in C: Anwendungen von Feldern, Zeigern und Funktionen:
Gerade für Programmier-Anfänger kann die Syntax von Variablen, Feldern, Zeigern eher verwirrend sein. Wichtig ist es dann nicht bei den einführenden Beispielen stehen zu bleiben, die lediglich die Spitzfindigkeiten erklären. Viel wichtiger ist, diese Konzepte in relevanten Anwendungen selber zu testen. Die hier vorgestellten Aufgaben bieten dazu ausreichend Gelegenheit. Sie sind alle konzipiert, dass Felder, Zeiger, Funktionen und der Mechanismus call by reference eingesetzt werden müssen.
Lösungen werden zu den Aufgaben nicht gegeben, da diese nur dazu verführen, deren Quelltexte anzusehen. Sie werden beim Umgang mit den genannten Konzepten aber schnell bemerken, wie wichtig es ist, eigene Versuche zu unternehmen und so auf die versteckten Fehlerquellen aufmerksam zu werden – Vorführen von Lösungen kann diesen Lernprozess niemals ersetzen.
C-Projekte in Code::Blocks
Wie man C++ Projekte mit Code::Blocks anlegt, wurde in Kennenlernen der Entwicklungsumgebung Code::Blocks ausführlich erklärt. Hier wird nur auf die kleinen Unterschiede eingegangen, wenn mit C anstelle von C++ gearbeitet wird. Dabei wird aber die Syntax von C nicht ausführlich erklärt.
Anlegen eines neuen Projektes
Um ein neues C-Projekt anzulegen, wählt man in Code::Blocks:
File -> New -> Project -> Console Application -> C Project
Anschließend muss man den Titel des Projektes und den Ordner festlegen. Ansonsten empfiehlt es sich, die vorgeschlagenen default-Einstellungen zu wählen.
Das erste Projekt HelloWorld
Das neu angelegte Projekt zeigt im Projekt-Explorer einen Ordner Sources für die Quelltexte, in dem die übliche HelloWorld-Anwendung als main.c enthalten ist:
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("Hello world!\n");
return 0;
}
Die Syntax für die inkludierten Bibliotheken ist in C etwas anders als in C++; auch für die Ausgabe werden jetzt andere Funktionen verwendet – hier zum Beispiel die Funktion printf().
In Kennenlernen der Entwicklungsumgebung Code::Blocks wurde empfohlen, die offline-Version der Dokumentation für C++ herunterzuladen; diese enthält auch die Dokumentation für C, in der sie die Beschreibungen der hier verwendeten Funktionen finden können.
Löst man den Build-Prozess aus, wird die Objekt-Datei erzeugt und man kann auf der Code::Blocks-Konsole sehen, welcher Compiler-Aufruf stattfindet.
Führt man das Programm anschließend aus, öffnet sich eine Konsole, in der Hello world!
steht.
Möchte man weitere Quelltext-Dateien erzeugen, kann man mit
File -> New -> File ...
entweder Header-Dateien oder Quelltext-Dateien anlegen; letzere müssen jetzt die Datei-Endung .c besitzen.
In anderen Entwicklungsumgebungen sind die Schritte zum Anlegen eines neuen C-Projektes ähnlich.
Wenn in den weiteren Kapiteln Listings für C-Programme gezeigt werden, so sind diese – sofern nicht ausdrücklich eine main()-Funktion enthalten ist – folgendermaßen zu verstehen:
- Die nötigen include-Befehle werden gezeigt (wie in Zeile 1 und 2 der HelloWorld-Anwendung).
- Die weiteren Befehle muss man sich in eine main()-Funktion eingebettet denken.
Man kann daher die Programme leicht testen, indem man sich von der Entwicklungsumgebung eine HelloWorld-Anwendung erzeugen lässt, die typischen Anweisungen für "HelloWorld" löscht und die Anweisungen aus dem Listing einfügt.