C++-Funktionsgrundlagen

C++C++Beginner
Jetzt üben

💡 Dieser Artikel wurde von AI-Assistenten übersetzt. Um die englische Version anzuzeigen, können Sie hier klicken

Einführung

In diesem Lab lernst du die Funktionen in C++. Du wirst lernen, wie Funktionen definiert und aufgerufen werden, und wie Argumente an Funktionen übergeben werden.

Inhaltsvorschau

Manchmal muss ein bestimmter Codeabschnitt mehrfach verwendet werden. Es ist besser, ihn in eine „Unterroutine“ – Funktion – zu packen und diese Funktion mehrfach zu „aufrufen“ – um Wartbarkeit und Verständlichkeit zu gewährleisten.

Beim Verwenden einer Funktion sind zwei Parteien beteiligt: Der Anrufer, der die Funktion aufruft, und die aufgerufene Funktion. Der Anrufer übergibt Argument(e) an die Funktion. Die Funktion empfängt diese Argument(e), führt die im Funktionskörper programmierte Operation durch und gibt ein Ergebnis an den Anrufer zurück.

Verwendung von Funktionen

Angenommen, wir müssen die Fläche eines Kreises vielfach berechnen. Es ist besser, eine Funktion namens getArea() zu schreiben und sie bei Bedarf wiederzuverwenden.

/* Test Function */
#include <iostream>
using namespace std;
const int PI = 3.14159265;

// Funktionsprototyp (Funktionsdeklaration)
double getArea(double radius);

int main() {
   double radius1 = 1.1, area1, area2;
   // rufe die Funktion getArea() auf
   area1 = getArea(radius1);
   cout << "area 1 is " << area1 << endl;
   // rufe die Funktion getArea() auf
   area2 = getArea(2.2);
   cout << "area 2 is " << area2 << endl;
   // rufe die Funktion getArea() auf
   cout << "area 3 is " << getArea(3.3) << endl;
}

// Funktionsdefinition
// Gib die Fläche eines Kreises für einen gegebenen Radius zurück
double getArea(double radius) {
   return radius * radius * PI;
}

Ausgabe:

area 1 is 3.63
area 2 is 14.52
area 3 is 32.67
image desc
image desc

In C++ musst du einen Funktionsprototyp deklarieren (bevor die Funktion verwendet wird) und eine Funktionsdefinition angeben, die einen Körper enthält, der die programmierten Operationen aufweist.

Die Syntax für die Funktionsdefinition lautet wie folgt:

returnValueType functionName ( parameterList ) {
   functionBody ;
}

Ein Funktionsprototyp gibt der Compiler die Schnittstelle der Funktion an, d.h. den Rückgabetyp, den Funktionsnamen und die Liste der Parametertypen (die Anzahl und der Typ der Parameter). Die Funktion kann nun an beliebiger Stelle in der Datei definiert werden. Beispielsweise:

// Funktionsprototyp - platziert vor der Verwendung der Funktion.
double getArea(double);  // ohne Parametername
double getArea(double radius);  // Parameternamen werden ignoriert, dienen aber als Dokumentation

Der Rückgabetyp "void"

Wenn es nicht erforderlich ist, einen Wert an den Anrufer zurückzugeben, kannst du seinen Rückgabetyp als void deklarieren. Im Funktionskörper kannst du eine Anweisung "return;" ohne Rückgabewert verwenden, um die Steuerung an den Anrufer zurückzugeben.

Aktuelle Parameter vs. formale Parameter

Im obigen Beispiel ist die Variable (double radius), die in der Signatur von getArea(double radius) deklariert ist, als formaler Parameter bekannt. Sein Gültigkeitsbereich liegt innerhalb des Funktionskörpers. Wenn die Funktion von einem Anrufer aufgerufen wird, muss der Anrufer sogenannte aktuelle Parameter (oder Argumente) liefern, deren Wert dann für die tatsächliche Berechnung verwendet wird. Beispielsweise ist bei der Funktionsoberfläche "area1 = getArea(radius1)" radius1 der aktuelle Parameter mit einem Wert von 1.1.

Gültigkeitsbereich von lokalen Variablen und Parametern einer Funktion

Alle Variablen, einschließlich der Parameter einer Funktion, die innerhalb einer Funktion deklariert werden, sind nur der Funktion verfügbar. Sie werden erstellt, wenn die Funktion aufgerufen wird, und freigegeben (zerstört) nachdem die Funktion zurückkehrt. Sie werden als lokale Variablen bezeichnet, weil sie der Funktion lokal sind und außerhalb der Funktion nicht verfügbar sind.

Standardargumente

C++ führt sogenannte Standardargumente für Funktionen ein. Diese Standardwerte werden verwendet, wenn der Anrufer das entsprechende aktuelle Argument beim Aufruf der Funktion weglässt. Standardargumente werden im Funktionsprototyp angegeben und können in der Funktionsdefinition nicht wiederholt werden. Die Standardargumente werden anhand ihrer Positionen aufgelöst. Daher können sie nur verwendet werden, um die folgenden Argumente zu ersetzen, um eine Mehrdeutigkeit zu vermeiden. Beispielsweise:

/* Test Function default arguments */
#include <iostream>
using namespace std;

// Funktionsprototyp - Gib hier die Standardargumente an
int fun1(int = 1, int = 2, int = 3);
int fun2(int, int, int = 3);

int main() {
   cout << fun1(4, 5, 6) << endl; // Keine Standardwerte
   cout << fun1(4, 5) << endl;    // 4, 5, 3(Standard)
   cout << fun1(4) << endl;       // 4, 2(Standard), 3(Standard)
   cout << fun1() << endl;        // 1(Standard), 2(Standard), 3(Standard)

   cout << fun2(4, 5, 6) << endl; // Keine Standardwerte
   cout << fun2(4, 5) << endl;    // 4, 5, 3(Standard)
   // cout << fun2(4) << endl;
   // Fehler: Zu wenige Argumente für die Funktion 'int fun2(int, int, int)'
}

int fun1(int n1, int n2, int n3) {
   // Standardargumente können in der Funktionsdefinition nicht wiederholt werden
   return n1 + n2 + n3;
}

int fun2(int n1, int n2, int n3) {
   return n1 + n2 + n3;
}

Ausgabe:

15
12
9
6
15
12
image desc

Funktionsüberladung

C++ führt die Funktionsüberladung (oder Funktionspolymorphismus) ein, die es Ihnen ermöglicht, mehrere Versionen des gleichen Funktionsnamens zu haben, die durch die Parameterliste (Anzahl, Typ oder Reihenfolge der Parameter) unterschieden werden. Überladene Funktionen können nicht anhand des Rückgabetyps unterschieden werden (Compilerfehler). Die Version, die der Argumentliste des Anrufers entspricht, wird ausgewählt, um ausgeführt zu werden. Beispielsweise:

/* Test Function Overloading */
#include <iostream>
using namespace std;

void fun(int, int, int);  // Version 1
void fun(double, int);          // Version 2
void fun(int, double);          // Version 3

int main() {
   fun(1, 2, 3);   // version 1
   fun(1.0, 2);    // version 2
   fun(1, 2.0);    // version 3
   fun(1.1, 2, 3); // version 1 - double 1.1 wird zu int 1 umgewandelt (ohne Warnung)

   // fun(1, 2, 3, 4);
      // Fehler: keine passende Funktion für den Aufruf 'fun(int, int, int, int)'
   // fun(1, 2);
      // Fehler: Der Aufruf der überladenen Funktion 'fun(int, int)' ist mehrdeutig
      // Hinweis: Kandidaten sind:
      //    void fun(double, int)
      //    void fun(int, double)
   // fun(1.0, 2.0);
      // Fehler: Der Aufruf der überladenen Funktion 'fun(double, double)' ist mehrdeutig
}

void fun(int n1, int n2, int n3) {  // version 1
   cout << "version 1" << endl;
}

void fun(double n1, int n2) { // version 2
   cout << "version 2" << endl;
}

void fun(int n1, double n2) { // version 3
   cout << "version 3" << endl;
}

Ausgabe:

version 1
version 2
version 3
version 1
image desc

Funktionen und Arrays

Sie können auch Arrays an Funktionen übergeben. Allerdings müssen Sie auch die Größe des Arrays an die Funktion übergeben. Dies liegt daran, dass es keine Möglichkeit gibt, die Größe des Arrays aus dem Arrayargument innerhalb der aufgerufenen Funktion zu ermitteln. Beispielsweise:

/* Function to compute the sum of an array */
#include <iostream>
using namespace std;

// Funktionsprototyp
int sum(int array[], int size);    // Die Arraygröße muss ebenfalls übergeben werden
void print(int array[], int size);

// Testtreiber
int main() {
   int a1[] = {8, 4, 5, 3, 2};
   print(a1, 5);   // {8,4,5,3,2}
   cout << "sum is " << sum(a1, 5) << endl;  // sum is 22
}

// Funktionsdefinition
// Gib die Summe des gegebenen Arrays zurück
int sum(int array[], int size) {
   int sum = 0;
   for (int i = 0; i < size; ++i) {
      sum += array[i];
   }
   return sum;
}

// Drucke die Inhalte des gegebenen Arrays
void print(int array[], int size) {
   cout << "{";
   for (int i = 0; i < size; ++i) {
      cout << array[i];
      if (i < size - 1) {
         cout << ",";
      }
   }
   cout << "}" << endl;
}

Ausgabe:

{8,4,5,3,2}
sum is 22
image desc

Call-by-Value vs. Call-by-Reference

Es gibt zwei Möglichkeiten, wie ein Parameter an eine Funktion übergeben werden kann: Call-by-Value vs. Call-by-Reference.

Call-by-Value

Beim Call-by-Value wird eine "Kopie" des Arguments erstellt und an die Funktion übergeben. Die aufgerufene Funktion arbeitet auf der "Klonversion" und kann die ursprüngliche Kopie nicht modifizieren. In C/C++ werden grundlegende Datentypen (wie int und double) call-by-value übergeben.

/* Fundamental types are passed by value into Function */
#include <iostream>
using namespace std;

// Funktionsprototypen
int inc(int number);

// Testtreiber
int main() {
   int n = 8;
   cout << "Before calling function, n is " << n << endl; // 8
   int result = inc(n);
   cout << "After calling function, n is " << n << endl;  // 8
   cout << "result is " << result << endl;                // 9
}

// Funktionsdefinitionen
// Gib number+1 zurück
int inc(int number) {
   ++number;  // Modifiziere Parameter, hat keine Auswirkung auf den Aufrufer
   return number;
}

Ausgabe:

Before calling function, n is 8
After calling function, n is 8
result is 9
image desc

Call-by-Reference

Andererseits wird beim Call-by-Reference eine Referenz auf die Variable des Aufrufers an die Funktion übergeben. Mit anderen Worten, die aufgerufene Funktion arbeitet auf den gleichen Daten. Wenn die aufgerufene Funktion den Parameter modifiziert, wird auch die gleiche Kopie des Aufrufers modifiziert. In C/C++ werden Arrays call-by-reference übergeben. C/C++ erlaubt es nicht, dass Funktionen ein Array zurückgeben.

/* Function to increment each element of an array */
#include <iostream>
using namespace std;

// Funktionsprototypen
void inc(int array[], int size);
void print(int array[], int size);

// Testtreiber
int main() {
   int a1[] = {8, 4, 5, 3, 2};

   // Bevor die Inkrementierung
   print(a1, 5);   // {8,4,5,3,2}
   // Inkrementiere
   inc(a1, 5);     // Array wird call-by-reference übergeben (hat Nebenwirkungen)
   // Nach der Inkrementierung
   print(a1, 5);   // {9,5,6,4,3}
}

// Funktionsdefinitionen

// Inkrementiere jedes Element des gegebenen Arrays
void inc(int array[], int size) {  // array[] ist nicht const
   for (int i = 0; i < size; ++i) {
      array[i]++;  // Nebenwirkung
   }
}

// Drucke die Inhalte des gegebenen Arrays
void print(int array[], int size) {
   cout << "{";
   for (int i = 0; i < size; ++i) {
      cout << array[i];
      if (i < size - 1) {
         cout << ",";
      }
   }
   cout << "}" << endl;
}

Ausgabe:

{8,4,5,3,2}
{9,5,6,4,3}
image desc

Konstante Funktionsparameter

Verwenden Sie const, wann immer möglich, wenn Sie Referenzen übergeben, da dies verhindert, dass Sie die Parameter versehentlich modifizieren und Sie vor vielen Programmierfehlern schützt.

Beim linearen Suchen wird der Suchschlüssel linear mit jedem Element des Arrays verglichen. Wenn es eine Übereinstimmung gibt, wird der Index des übereinstimmenden Elements zurückgegeben; andernfalls wird -1 zurückgegeben. Der lineare Suchalgorithmus hat eine Komplexität von O(n).

/* Search an array for the given key using Linear Search */
#include <iostream>
using namespace std;

int linearSearch(const int a[], int size, int key);

int main() {
   const int SIZE = 8;
   int a1[SIZE] = {8, 4, 5, 3, 2, 9, 4, 1};

   cout << linearSearch(a1, SIZE, 8) << endl;  // 0
   cout << linearSearch(a1, SIZE, 4) << endl;  // 1
   cout << linearSearch(a1, SIZE, 99) << endl; // 8 (nicht gefunden)
}

// Search the array for the given key
// If found, return array index [0, size-1]; otherwise, return size
int linearSearch(const int a[], int size, int key) {
   for (int i = 0; i < size; ++i) {
      if (a[i] == key) return i;
   }
   // a[0] = 1;
   // Dies führt zu einem Fehler, weil a[] konstant ist, was bedeutet, dass es nur lesbar ist
   return -1;
}

Ausgabe:

0
1
-1
image desc

Call-by-Reference über "Referenzparameter"

Sie können einen Parameter eines grundlegenden Typs per Referenz übergeben, indem Sie den Referenzparameter verwenden, der durch & gekennzeichnet ist.

/* Test Pass-by-reference for fundamental-type parameter
   via reference declaration */
#include <iostream>
using namespace std;

int squareByValue (int number);        // Call-by-value
void squareByReference (int &number); // Call-by-reference

int main() {
   int n1 = 8;
   cout << "Before call, value is " << n1 << endl;  // 8
   cout << squareByValue(n1) << endl;  // keine Nebenwirkung
   cout << "After call, value is " << n1 << endl;   // 8

   int n2 = 9;
   cout << "Before call, value is " << n2 << endl;  // 9
   squareByReference(n2);  // Nebenwirkung
   cout << "After call, value is " << n2 << endl;   // 81
}

// Übergebe Parameter per Wert - keine Nebenwirkung
int squareByValue (int number) {
   return number * number;
}

// Übergebe Parameter per Referenz, indem Sie als Referenz (&) deklarieren
// - mit Nebenwirkung für den Aufrufer
void squareByReference (int &number) {
   number = number * number;
}

Ausgabe:

Before call, value is 8
64
After call, value is 8
Before call, value is 9
After call, value is 81
image desc

###2.8 Mathematische Funktionen

C++ stellt in der Bibliothek <cmath> viele häufig verwendete mathematische Funktionen zur Verfügung

sin(x), cos(x), tan(x), asin(x), acos(x), atan(x):
   Nehmen Argument- und Rückgabetyp von float, double, long double.
sinh(x), cosh(x), tanh(x):
   Hyperbelfunktionen.
pow(x, y), sqrt(x):
   Potenz und Quadratwurzel.
ceil(x), floor(x):
   Gibt die Decke und den Boden des Gleitkommazahls als Ganzzahl zurück.
fabs(x), fmod(x, y):
   Gleitkomma-Absolutwert und Modulo.
exp(x), log(x), log10(x):
   Exponenten- und Logarithmusfunktionen.

Die cstdlib-Headerdatei (portiert aus C's stdlib.h) liefert eine Funktion rand(), die eine pseudozufällige Ganzzahl zwischen 0 und RAND_MAX (einschließlich) generiert.

/* Test Random Number Generation */
#include <iostream>
#include <cstdlib>  // für rand(), srand()
#include <ctime>    // für time()
using namespace std;

int main() {
   // rand() generiert eine Zufallszahl im Bereich [0, RAND_MAX]
   cout << "RAND_MAX is " << RAND_MAX << endl;   // 32767

   // Generiere 10 pseudozufällige Zahlen zwischen 0 und 99
   //   ohne Initialisierung des Zufallsgenerators.
   // Sie erhalten die gleiche Sequenz jedes Mal, wenn Sie dieses Programm ausführen
   for (int i = 0; i < 10; ++i) {
      cout << rand() % 100 << " ";   // benötigt <cstdlib>-Header
   }
   cout << endl;

   // Initialisiere den Zufallsgenerator mit der aktuellen Zeit
   srand(time(0));   // benötigt <cstdlib> und <ctime>-Header
   // Generiere 10 pseudozufällige Zahlen
   // Sie erhalten eine andere Sequenz bei jedem Ausführungsversuch,
   //   da die aktuelle Zeit unterschiedlich ist
   for (int i = 0; i < 10; ++i) {
      cout << rand() % 100 << " ";   // benötigt <cstdlib>-Header
   }
   cout << endl;
}

Ausgabe:

RAND_MAX is 2147483647
83 86 77 15 93 35 86 92 49 21
29 0 83 60 22 55 97 80 68 87
image desc
- name: check if keyword exist
  script: |
    #!/bin/bash
    grep -i 'rand' /home/labex/Code/test.cpp
  error: Oops! We find that you didn't use "rand()" method in "test.cpp".
  timeout: 3

Zusammenfassung

Die Vorteile der Verwendung von Funktionen sind:

  1. Divide and conquer: Bauen Sie das Programm aus einfachen, kleinen Teilen oder Komponenten. Modularisieren Sie das Programm in selbständige Aufgaben.
  2. Vermeiden von Codewiederholungen: Es ist einfach, zu kopieren und einzufügen, aber schwierig, alle Kopien zu pflegen und zu synchronisieren.
  3. Software-Wiederverwendung: Sie können die Funktionen in anderen Programmen wiederverwenden, indem Sie sie in Bibliothekscode verpacken.