Warum Funktionen
Alles bisher lebte innerhalb von main. Das funktioniert, bis ein Programm mehr als eine Sache tut, und dann kommen drei Probleme auf einmal: Derselbe Codeblock wird an drei Stellen kopiert, main wächst über das hinaus, was jemand im Kopf behalten kann, und es gibt keine Möglichkeit zu benennen, wofür ein Stück Code da ist.
Eine Funktion behebt alle drei. Sie gibt einem Codeblock einen Namen, eigene lokale Variablen, eine Liste von Eingaben und einen einzelnen Wert, den sie zurückreicht.
square ist einmal geschrieben und wird dreimal aufgerufen, einmal davon auf seinem eigenen Ergebnis. Du hast bereits Funktionen aufgerufen - printf ist eine, und main ebenfalls.
Der Aufbau einer Funktion
returnType name(parameterList) {
// Rumpf
return value;
}
Vier Teile:
- Rückgabetyp - der Typ des Werts, den die Funktion zurückreicht.
int,double,char, ein Zeigertyp odervoidfür „nichts". - Name - folgt denselben Regeln wie ein Variablenname und sollte sagen, was die Funktion tut oder erzeugt.
- Parameterliste - die Eingaben, jede mit eigenem Typ, durch Kommas getrennt.
(void)bedeutet, dass sie keine nimmt. - Rumpf - die Anweisungen in geschweiften Klammern, die mit einem
returnenden, sofern der Rückgabetyp nichtvoidist.
Beachte, dass pi() an der Aufrufstelle weiterhin die leeren Klammern braucht - sie machen es zu einem Aufruf statt zu einer bloßen Nennung des Namens. Und printBanner(20); ist eine Anweisung für sich, weil es keinen Wert erzeugt, den man benutzen könnte.
Schreibe (void), nicht (), für eine Funktion ohne Parameter. In C bedeuten diese beiden Verschiedenes, und () ist die lockere Form - gleich mehr dazu.
Aufrufen und Zurückkehren
Ein Aufruf übergibt die Steuerung an die Funktion, führt ihren Rumpf aus und kommt mit einem Wert zurück, der den Aufruf im umgebenden Ausdruck ersetzt.
return tut zwei Dinge auf einmal: Es liefert den Wert und es beendet die Funktion auf der Stelle. Alles nach einem return auf demselben Pfad läuft nie, und das lässt den Stil der frühen Rückkehr in larger funktionieren - kein else ist nötig, denn die letzte Zeile zu erreichen bedeutet bereits, dass a > b falsch war.
Eine Funktion kann mehrere return-Anweisungen haben. Sie kann pro Aufruf immer nur eine davon ausführen.
void-Funktionen
Eine Funktion, die um ihrer Wirkung willen existiert und nicht um ihres Werts willen, hat den Rückgabetyp void: ausgeben, über einen Zeiger etwas aktualisieren, zeichnen.
In einer void-Funktion steigt return; ohne Wert früh aus. return someValue; ist dort ein Übersetzungsfehler, und den Aufruf in einem Ausdruck zu benutzen ebenfalls - int x = printTable(7, 5); kompiliert nicht, und das ist das Typsystem bei der Arbeit.
Zerlegung: der eigentliche Punkt
Bei Funktionen geht es nicht hauptsächlich darum, Wiederholung zu vermeiden. Es geht darum, eine lange Prozedur in ein paar benannte Schritte zu verwandeln, sodass sich main wie eine Beschreibung des Programms liest statt wie dessen Implementierung.
main besteht nun aus vier Ausgabezeilen und einer Berechnung. Jeder Helfer tut eine Sache, hat einen Namen, der sagt, welche das ist, und lässt sich für sich testen. Beachte, dass die Länge des Arrays als zweiter Parameter mitreist: Sobald ein Array an eine Funktion übergeben wurde, funktioniert der sizeof-Trick dort nicht mehr, weil das Array zu einem Zeiger zerfällt.
Drei Faustregeln, um Arbeit in Funktionen aufzuteilen:
- Eine Aufgabe pro Funktion. Wenn der Name ein „und" braucht, sind es wahrscheinlich zwei Funktionen.
- Nimm entgegen, was du brauchst, gib zurück, was du erzeugst. Eine Funktion, die globalen Zustand liest, ist schwerer zu durchdenken und für sich nicht testbar.
- Kurz genug, um sie auf einmal zu sehen. Es gibt keine magische Zeilenzahl, aber eine Funktion, die nicht auf einen Bildschirm passt, versteckt meist eine kleinere Funktion in sich.
Funktionen, die Funktionen aufrufen
Eine Funktion kann jede Funktion aufrufen, die bereits deklariert wurde, auch selbstgeschriebene:
main ruft report auf, das sumEvens aufruft, das in einer Schleife isEven aufruft. Jeder Aufruf bekommt seinen eigenen frischen Satz lokaler Variablen, gestapelt auf denen des Aufrufers, und sie werden beim Zurückkehren verworfen.
Die Definitionen oben stehen in Abhängigkeitsreihenfolge - isEven vor sumEvens vor report -, denn C verlangt, dass eine Funktion vor ihrem Aufruf deklariert ist. Rufe eine auf, die weiter unten in der Datei steht, und der Compiler beschwert sich über eine implizite Deklaration. Die allgemeine Lösung ist ein Prototyp oben in der Datei, mit dem du die Definitionen beliebig anordnen kannst. Eine Funktion, die sich selbst aufruft, ist ebenfalls erlaubt und nützlich; das ist Rekursion.
main ist eine Funktion
main ist eine gewöhnliche Funktion, die die Laufzeit für dich aufruft. Ihr int-Rückgabetyp ist der Exit-Status des Programms: 0 bedeutet Erfolg, ungleich null bedeutet Fehlschlag, und Shell-Skripte und Build-Werkzeuge lesen ihn.
int main(void) {
/* ... */
return 0;
}
Seit C99 liefert das Herausfallen aus main ohne return implizit 0 - eine Sonderregel, die nur für main gilt. Jede andere Nicht-void-Funktion, die ohne Rückgabewert endet, erzeugt undefiniertes Verhalten, sobald der Aufrufer das Ergebnis benutzt.
Die andere Standardform ist int main(int argc, char *argv[]), die Kommandozeilenargumente entgegennimmt. Beide sind korrekt; void main() ist es nicht, was alte Anleitungen auch behaupten.
Häufige Fehler
- Das
returnin einer Nicht-void-Funktion vergessen. Der Aufrufer liest dann einen Müllwert.gcc -Wallwarnt. - Im Rumpf eine Variable mit dem Namen eines Parameters deklarieren. Sie verdeckt den Parameter, und die Zuweisung, die du an die Eingabe machen wolltest, geht ins Leere.
- Erwarten, dass eine Funktion ihre Argumente ändert. C übergibt alles als Wert, eine Funktion erhält also Kopien.
void reset(int x) { x = 0; }ändert an der Aufrufstelle nichts - das Thema der nächsten Seite. - Die Klammern in einem Aufruf weglassen.
printBanner;ist ein erlaubter Ausdruck, der die Adresse der Funktion auswertet und verwirft. Er kompiliert, tut nichts, und-Wallbeanstandet ihn. ()statt(void)schreiben. Erlaubt, schaltet aber in älteren C-Dialekten die Argumentprüfung ab.
Häufig gestellte Fragen
Wie deklariert man eine Funktion in C?
Schreibe den Rückgabetyp, den Namen und eine geklammerte Parameterliste, dann den Rumpf in geschweiften Klammern: int add(int a, int b) { return a + b; }. Eine Funktion, die nichts entgegennimmt, benutzt (void), und eine, die nichts liefert, hat den Rückgabetyp void.
Was macht return in einer C-Funktion?
Es beendet die Funktion sofort und reicht einen Wert an den Aufrufer zurück. Der Typ des Werts muss zum deklarierten Rückgabetyp passen. In einer void-Funktion kannst du ein nacktes return; schreiben, um früh auszusteigen, oder es weglassen und die Funktion an der schließenden Klammer enden lassen.
Warum liefert main in C ein int?
Der Rückgabewert von main ist der Exit-Status des Programms, den das Betriebssystem und Shell-Skripte lesen können. return 0; bedeutet Erfolg und jeder Wert ungleich null bedeutet Fehlschlag. Seit C99 liefert das Erreichen des Endes von main ohne return-Anweisung implizit 0.