Zum Inhalt springen

C -Programmierung/ Weitere Grundelemente/ Zusammenfassung

Aus Wikibooks


Funktionen

[Bearbeiten]

Deklaration

[Bearbeiten]

Eine Funktion kann in C beliebig oft (übereinstimmend) deklariert werden. Zur Deklaration gehören der Funktionsname, der Rückgabetyp und die Parametertypen. Es ist gestattet den Parametern in der Deklaration Namen zu geben, diese werden vom Compiler jedoch vollständig ignoriert, sie dienen ausschließlich als Information für den Programmierer.

Syntax:
«Rückgabetyp» «Funktionsname»(«Parametertypenliste»);

Parametertypenliste: «Parametertyp» »Parametername«, «Parametertypenliste»
Parametertypenliste: «Leere Liste»
«Nicht-C -Code», »optional«

Soll eine Funktion keinen Wert zurückgeben, lautet der Rückgabetyp formal void.

Definition

[Bearbeiten]

Eine Definition ist immer auch eine Deklaration. Eine Definition darf nur einmal im gesamten Programm gemacht werden. Später wird mit dem Schlüsselwort inline die einzige Ausnahme von dieser Regel eingeführt, solche Funktionen dürfen mehrfach übereinstimmend definiert werden.

Syntax:
«Rückgabetyp» «Funktionsname»(«Parametertypenliste»){
    «Anweisungen»
}
«Nicht-C -Code», »optional«

Aufruf

[Bearbeiten]
Syntax:
«Funktionsname»(«Parameterliste»)

Parameterliste: «Ausdruck», «Parameterliste»
Parameterliste: «Leere Liste»
«Nicht-C -Code», »optional«

Beachten Sie für parameterlose Funktionen unbedingt, dass ein Weglassen der Klammern die Funktionsadresse liefert anstatt die Funktion aufruft.

Funktionsname() // Ruft eine Funktion auf
Funktionsname   // Liefert eine Funktionsadresse
&Funktionsname  // Liefert eine Funktionsadresse (wie bei Variable)

C erlaubt Rekursion, wobei die Rekursionstiefe typischerweise vom Compiler beschränkt wird.

Überladen

[Bearbeiten]

Es ist möglich mehrere Funktionen mit gleichem Namen aber unterschiedlicher Parameterliste (hinsichtlich der Länge und/oder der Typen) zu definieren. Der Compiler ermittelt dann beim Aufruf der Funktion aus dem Namen und den übergebenen Parametern welche Funktion konkret gemeint ist. Dies bezeichnet man als Überladung einer Funktion.

Standardparameter

[Bearbeiten]

Für jeden Parameter einer Funktion kann in der Funktionsdeklaration ein Standardwert angegeben werden. Jeder Parameter rechts von einem Parameter mit Standardwert muss ebenfalls einen Standardwert besitzen. Standardparameter dürfen nur in genau einer Deklaration angegeben werden.

Beliebige Parameteranzahl

[Bearbeiten]

In C verwendete man gelegentlich Funktionen für die als letzter Parameter ... angegeben war. Solchen Funktionen kann man beliebig viele Parameter übergeben. Nach dem Einbinden der Headerdatei cstdarg kann innerhalb der Funktion mit Hilfe der va_...-Makros auf die übergebenen Parameter zugegriffen werden. Diese Technik ist typunsicher und veraltet, sie wird mit dem kommenden C Standard (derzeit als C 11 bekannt) überflüssig, da dieser variable Parameterlisten mittels „variadic templates“ erlaubt.

Aufzählungen

[Bearbeiten]

Enumerationen werden mit enum eingeleitet und definieren einen Typ, der mehrere ganzzahlige Konstanten (Enumeratoren) enthält. Die Werte der Konstanten können explizit vergeben werden. Ist kein Wert angegeben zählt der Compiler von der letzten vergebenen Konstante aus hoch, im Falle der ersten Konstante nimmt der Compiler 0 als Wert. Negative Werte sind zulässig. Der Wertebereich einer Enumeration ist so festgelegt, dass die kleinste mögliche Zweierpotenz größer dem größten vergebene Wert ist. Falls negative Indizes vergeben wurden, reichen die Werte von , andernfalls ist er . Enumeratoren können implizit in Ganzzahlen umgewandelt werden. Ganzzahlen können explizit in einen Wert vom Typ einer Enumeration umgewandelt werden. Liegt der Ganzzahlwert hierbei außerhalb des Wertebereichs der Enumeration ist das Verhalten undefiniert!

Syntax:
enum «Name»{
    «Enumerator» »= Compile-Time-konstante-Ganzzahl«,
    ...
};
// explizite Umwandlung aus einer Ganzzahl
«Name»(«Ganzzahl»)
«Nicht-C -Code», »optional«

Zeiger

[Bearbeiten]

Zeiger werden mit einem Stern * im Typ angegeben, wobei der Typ, auf den der Zeiger zeigt, immer links vom Stern steht. Bei der gleichzeitigen Deklaration mehrerer Variablen muss der Stern vor jedem Variablennamen angegeben werden, der ein Zeiger sein soll. Gleichzeitige Deklaration mehrerer Variablen sollte jedoch aus Gründen der Übersicht allgemein vermieden werden.

Syntax:
«Typ»* «Variablenname»;
«Typ» *«Variablenname», *«Variablenname»; // Möglich aber unübersichtlich
«Nicht-C -Code», »optional«

Auf die Daten, auf die der Zeiger zeigt, wird mit Hilfe des Dereferenzierungsoperators * zugegriffen. Die Adresse einer Funktion wird über den Adressoperator & ermittelt. Beide Operatoren müssen links von ihrem Operanden stehen.

Syntax:
*«Zeiger»            // Wert zugreifen
&«Variable/Funktion» // Adresse zugreifen
«Nicht-C -Code», »optional«

Schlüsselwort const

[Bearbeiten]

Das Schlüsselwort const kann für Zeiger sowohl auf den dereferenzierten Wert angewendet werden, als auch auf die Zeigervariable selbst. const bezieht sich wie immer auf das, was direkt links davon steht.

Zeigerarithmetik

[Bearbeiten]

Für Zeiger sind die Addition und die Subtraktion definiert. Die Größe des Datentyps auf den gezeigt wird entscheidet über die Schrittweite. Wird also zu einem Zeiger auf long 5 addiert, so erhöht sich die Adresse um 5*sizeof(long) Byte.

Zeiger auf void

[Bearbeiten]

Zeiger auf void gelten allgemein als veraltet (C), werden aber in begründeten Einzelfällen noch eingesetzt. Ihre Schrittweite in der Zeigerarithmetik beträgt 1 Byte und sie lassen sich implizit aus allen Zeigertypen und in alle Zeigertypen umwandeln. Es ist nicht möglich einen void-Zeiger zu dereferenzieren.

Funktionszeiger

[Bearbeiten]

Die Adresse einer Funktion lässt sich über den Funktionsnamen ermitteln, optional kann diesem auch noch der Adressoperator (&) vorangestellt werden (empfohlen). Ein Funktionszeiger wird äquivalent zum Prototypen des gewünschten Funktionstypes definiert, wobei an Stelle des Funktionsnamens der Variablenname in runden Klammern und hinter einem Stern angegeben wird.

Referenzen

[Bearbeiten]

Referenzen sind Aliasnamen für andere Variablen. Sie müssen als solche immer mit einer anderen Variable initialisiert werden. Ihre Deklaration erfolgt durch ein & rechts vom Typ, äquivalent zum * bei Zeigern. Es ist nicht möglich und auch nicht sinnvoll Referenzen auf Referenzen zu deklarieren. Intern sind Referenzen meist über Zeiger realisiert.

Felder

[Bearbeiten]

Felder oder Arrays sind mehrere Variablen des gleichen Typs, die direkt hintereinander im Speicher stehen. Folglich lassen sich die einzelnen Elemente mithilfe der Zeigerarithmetik ansprechen, es gibt jedoch auch einen Indexoperator, welcher einen intuitiven Zugriff erlaubt. Die Syntax für Felder lautet:

Syntax:
«Typ» «Variablenname»[«positiver ganzzahliger compilezeitkonstanter Wert»]; // Deklaration
*(«Variablenname»   «ganzzahliger Wert») // Elementzugriff mittels Zeigerarithmetik
«Variablenname»[«ganzzahliger Wert»]     // Elementzugriff mittels Indexoperator
«Nicht-C -Code», »optional«

Zeiger und Felder

[Bearbeiten]

Analog zu Funktionen ist der Zugriff auf die Adresse des Arrays über den Variablennamen möglich, wobei die Adresse der Adresse des ersten Array-Elements entspricht. Entsprechend ist es nicht möglich, Arrays an Funktionen zu übergeben, stattdessen kann nur die Array-Adresse übergeben werden. Der Compiler hat innerhalb der Funktion aber nicht mehr die Möglichkeit, zwischen einem Zeiger auf eine gewöhnliche Variable und einer Array-Variable zu unterscheiden. Der Indexoperator ist eine alternative Schreibweise für die Zeiger-Arithmetik, er funktioniert somit für alle Adressen.

Mehrdimensionale Felder

[Bearbeiten]

Mehrdimensionale Arrays werden als Arrays von Arrays deklariert und benutzt.

Zeichenketten

[Bearbeiten]

Strings sind in C kein Basisdatentyp sondern eine Klasse aus der Standardbibliothek. Die Klasse kapselt die aus C stammenden nullterminierten Strings, welche kaum intuitiv zu benutzen waren. Klassen bestehen aus Variablen und Methoden, wobei Methoden nur ein Wort für Funktionen in Klassen ist. Methoden werden aufgerufen, indem der Variablenname ihrer Stringvariable gefolgt von einem Punkt und dem Methodenaufruf (= Funktionsaufruf) geschrieben wird. Außerdem sind einige nützliche Operatoren für die Stringklasse überladen. Im folgenden werden die wichtigsten Methoden und Operatoren kurz vorgestellt. Um Strings benutzen zu können müssen Sie die Headerdatei string einbinden. Der Datentyp (= Klassenname) heißt std::string, wobei das std natürlich der Standardnamensraum ist.

C-Strings sind übrigens char-Arrays, wobei das Stringende durch ein Nullzeichen ('\0') definiert wird. Ein Array aus N char-Elementen kann also N-1 Zeichen speichern. Um Strings auf einfache Weise im Programmcode schreiben zu können, wurde in C ein Literal für derartige char-Arrays eingeführt. Der eigentliche String wird dabei in doppelte Anführungszeichen gesetzt. Diese Literale werden in C natürlich auch noch verwendet, jedoch werden sie hier eben an std::string-Variablen übergeben, daher ist es nicht mehr nötig direkt mit den Arrays zu arbeiten.

Ein-/Ausgabe

[Bearbeiten]

Die Ein-/Ausgabe von Strings funktioniert genau wie bei den Basisdatentypen. Bei der Eingabe ist zu beachten, dass der Eingabeoperator (wie bei den Basisdatentypen) Leerraumzeichen überliest und anschließend alle Zeichen bis zum nächsten Leerraumzeichen einliest. Entsprechend ist es auf diese Weise nicht möglich einen String einzulesen, der Leerzeichen enthält. Hierfür gibt es die Funktion getline, die bis zum nächsten Newline-Zeichen einliest.

Syntax:
#include <string>
std::string var;
std::cin >> var;             // Eingabe eines Strings ohne Leerzeichen
std::getline(std::cin, var); // Eingabe einer kompletten Zeile
std::cout << var;            // Ausgabe
«Nicht-C -Code», »optional«

Zuweisen

[Bearbeiten]

Es ist möglich Stringliterale und andere Stringvariablen per Zuweisungsoperator (=) in eine Stringvariable zu schreiben.

Verketten

[Bearbeiten]

Strings lassen sich mithilfe des Plusoperator verketten, jedoch beachten Sie, dass mindestens ein Operand vom Typ std::string sein muss. Die Verkettung zweier Stringliterale ist somit nicht möglich, für gewöhnlich aber auch nicht nötig. Natürlich ist auch der =-Operator für das Hinzufügen eines Strings zu einem anderen definiert.

Zeichenzugriff

[Bearbeiten]

Mithilfe des Zugriffsoperators ([]) ist, analog zu Arrays, der Zugriff auf die einzelnen Zeichen des Strings möglich. Die Anzahl der aktuell enthaltenen Zeichen liefern die Methoden length() und size(). Welche Funktion Sie verwenden ist reine Geschmackssache. length() drückt gut aus, was abgefragt wird, size() ist dafür in allen Containerklassen definiert.

Vergleiche

[Bearbeiten]

Die sechs Vergleichsoperatoren sind für Strings definiert, wobei auch hier mindestens ein Operand vom Typ std::string sein muss. Der Vergleich wird elementweise auf Basis der Zeichen ausgeführt.

Suchen, Ersetzen, Einfügen, Kopieren, Löschen

[Bearbeiten]

Die folgende nützliche Methoden sind in der Stringklasse enthalten:

  • find(std::string search, std::size_t pos = 0) sucht nach search ab der Position pos.
  • replace(std::size_t from, std::size_t to, std::string replacement) ersetzt den Text zwischen (inklusive) from bis (exklusive) to durch replacement.
  • insert(std::size_t before, std::string insertion) fügt den String insertion vor der Position before ein.
  • substr(std::size_t pos = 0, std::size_t n = std::string::npos) extrahiert ab der Position pos n Zeichen und gibt diese als neuen String zurück.
  • erase(std::size_t pos = 0, std::size_t n = std::string::npos) löscht ab der Position pos n Zeichen.

Umwandlung: Zahl – String

[Bearbeiten]

Definieren Sie zuerst zwei Templates. Wie Templates funktionieren erfahren Sie in einem späteren Kapitel.

#include <sstream>  // String-Ein-/Ausgabe

template <typename Typ>
std::string toString(Typ wert) {
    std::ostringstream strout; // Unser Ausgabe-Stream
    std::string str;           // Ein String-Objekt

    strout << wert;            // Zahl auf Ausgabe-Stream ausgeben
    str = strout.str();        // Streaminhalt an String-Variable zuweisen

    return str;                // String zurückgeben
}

template <typename Typ>
Typ stringTo(std::string str) {
    std::istringstream strin; // Unser Eingabe-Stream
    Typ wert;

    strin.str(str);           // Streaminhalt mit String-Variable füllen
    strin >> wert;            // Variable von Eingabe-Stream einlesen

    return wert;
}

// Anwendung:
std::string var = toString(5.4);
double      var = stringTo<double>("6.6666");

Präprozessor

[Bearbeiten]

Der Präprozessor läuft vor dem Compiler und verarbeitet alle Zeilen die mit einem Doppelkreuz (#) beginnen. Ein solcher Befehl kann auf mehrere Zeilen ausgedehnt werden, indem das Zeilenende mit einem vorangestellten Backslash (\) maskiert wird.

  • #include fügt die angegebene Datei an dieser Stelle ein. Wird die Datei in spitzen Klammern angegeben, so wird in den Standardpfaden der Umgebung nach ihr gesucht. Wird die Datei in doppelten Anführungszeichen angegeben, so wird zusätzlich zunächst im aktuellen Verzeichnis gesucht.
  • #define definiert eine Präprozessorvariable bzw. ein Makro.
  • #undef löscht eine definierte Präprozessorvariable bzw. ein Makro.
  • #pragma führt eine Compilerspezifische Anweisung aus, kennt der Compiler die Anweisung nicht werden solche Zeilen ignoriert.
  • #if, #ifdef, #ifndef, #else, #elif und #endif erlauben bedingten Code. Dies wird insbesondere zum Schutz vor Mehrfachinkludierung von Headerdateien verwendet.
  • #error bzw. #warning lassen den Compiler einen Fehler bzw. eine Warnung ausgeben.

Vordefinierte Präprozessor-Variablen

[Bearbeiten]
  • __LINE__: Zeilennummer
  • __FILE__: Dateiname
  • __DATE__: Datum des Präprozessoraufrufs im Format Monat/Tag/Jahr
  • __TIME__: Zeit des Präprozessoraufrufs im Format Stunden:Minuten:Sekunden
  • __cplusplus: Ist nur definiert, wenn ein C -Programm verarbeitet wird

Headerdateien

[Bearbeiten]

Deklarationen werden üblicherweise in Headerdateien ausgelagert, welche dann mittels #include eingebunden werden. Auf diese Weise können mehrere Quelldateien die gleichen Deklarationen nutzen, während die Definition vom Compiler nur in einer Objektdatei erzeugt wird.