Funkcje

Deklaracja i definicja funkcji

Deklaracja (zwykle umieszczona w pliku nagłówkowym hpp):

int square(int);

Definicja w pliku cpp:

int square(int x)
{
    return x * x;
}

W C++11 możliwa jest definicja funkcji wykorzystująca słowo kluczowe auto. Typ zwracany jest deklarowany po nazwie funkcji i liście parametrów:

auto square(int x) -> int
{
    return x * x;
}

Argumenty funkcji

Argumenty domyślne

Argumentom można przypisać domyślną wartość. Przy wywołaniu funkcji można pominąć argumenty posiadające wartości domyślne. Wartości domyślne mogą mieć tylko argumenty znajdujące się na końcu listy parametrów.

void f(int x, int y = 0, double z = 0.0);

f(1, 2, 3.0);
f(1, 2);  // f(1, 2, 0.0)
f(1);     // f(1, 0, 0.0)

Przekazywanie argumentów przez wartość

Najprostszy sposób przekazania argumentu do funkcji. Argument wywołania funkcji jest kopiowany do zmiennej będącej parametrem funkcji.

int f(int x)
{
   x = x + 1;   // nadanie lokalnej zmiennej x nowej wartości
   return x;
}

int main()
{
   int xx = 0;
   std::cout << f(xx) << std::endl; // wyświetla 1
   std::cout << xx << std::endl;    // wyświetla 0; f() nie zmienia xx
}

Przekazywanie argumentów przez referencję

Aby funkcja mogła modyfikować swoje parametry, muszą one zostać przekazane przez referencję.

void increment(int& x)
{
   x = x + 1; // nadanie zmiennej x nowej wartości
}

int main()
{
   int xx = 0;
   increment(xx);

   std::cout << xx << std::endl; // drukuje 1;
}

Obiekty tymczasowe nie mogą być przekazywane do funkcji oczekującej referencji:

increment(xx + 1);  // błąd kompilacji

Przekazywanie argumentów przez stałą referencję

Przekazywanie przez wartość jest wydajną techniką jeżeli przekazywane są obiekty małych rozmiarów. Jeżeli kopiowanie obiektu jest kosztowne należy przekazać argument przez stałą referencję.

void print(std::vector<double> v)   // kosztowne kopiowanie obiektu
{
   std::cout << "[ ";
   for(int i = 0; i < v.size(); ++i) {
      std::cout << v[i];
      if ( i != v.size()-1 ) std::cout << ", ";
   }
   std::cout << "]\n";
}
std::vector<double> vd1(10);        // mały wektor
std::vector<double> vd2(1000000);   // duży wektor
//..
print(vd1);
print(vd2);

void print(const std::vector<double>& v)    // optymalnie
{
   std::cout << "[ ";
   for(int i = 0; i < v.size(); ++i) {
      std::cout << v[i];
      if ( i != v.size()-1 ) std::cout << ", ";
   }
   std::cout << "]\n";
}

Obiekty tymczasowe mogą być

Funkcje przeciążone

Funkcje przeciążone posiadają taką samą nazwę, ale inne zestawy parametrów. Na podstawie liczby i typu parametrów wywoływana jest konkretna funkcja przeciążona.

Nie jest możliwe rozróżnienie funkcji przeciążonych jedynie po typie zwracanej wartości.

void print(double);
void print(long);
void print(const std::vector<int>&);

void f()
{
   print(1L);  // print(long);
   print(1.0); // print(double);
   print(1);   // błąd! dwuznaczne wywołanie

   std::vector<int> vec = { 1, 2, 3, 4 };
   print(vec); // print(const std::vector<int>&);
}

Kryteria dopasowania przy wywołaniu funkcji przeciążonych

Kryteria dopasowania funkcji na podstawie typów parametrów:

  • Dokładne dopasowanie – dopuszczalna jedynie „trywialna” konwersja typów (np. nazwa tablicy na wskaźnik, T na const T)

  • Dopasowanie dopuszczające „promocję typu” (bool na int, char na int, short na int, float na double, itp.)

  • Dopasowanie z użyciem konwersji standardowej (np. int na double, double na int, Derived* na Base*, T* na void*)

  • Dopasowanie z użyciem konwersji zdefiniowanej przez użytkownika

  • Dopasowanie z użyciem elipsy (…) w deklaracji funkcji

Jeśli po dopasowana jest więcej niż jedna funkcja wywołanie jest odrzucone (błąd dwuznaczności).

Automatyczna dedukcja typu zwracanego z funkcji (C++14)

Dedukcja z auto

W C++14 typ zwracany z funkcji może być automatycznie dedukowany z implementacji funkcji. Mechanizm dedukcji jest taki sam jak mechanizm automatycznej dedukcji typów zmiennych.

auto multiply(int x, int y)
{
    return x * y;
}

Jeśli w funkcji występuje wiele instrukcji return muszą one wszystkie zwracać wartości tego samego typu.

auto get_name(int id)
{
    if (id == 1)
        return "Gadget"s;
    else if (id == 2)
        return "SuperGadget"s;
    return string("Unknown");
}

Rekurencja dla funkcji z auto jest możliwa, o ile rekurencyjne wywołanie następuje po przynajmniej jednym wywołaniu return zwracającego wartość nierekurencyjną.

auto factorial(int n)
{
    if (n == 1)
        return 1;
    return factorial(n-1) * n;
}

Dedukcja z decltype(auto)

Deklaracja decltype(auto) jako typu zwracanego z funkcji powoduje zastosowanie do dedukcji typu mechanizmu decltype (zachowującego referencje i modyfikatory const oraz volatile) zamiast mechanizmu auto.

decltype(auto) get_first(const array<int, 10>& data)
{
    return data[0];
}