Funkcje (C++)

Funkcja to blok kodu, który wykonuje jakąś operację. Funkcja może opcjonalnie definiować parametry wejściowe, które umożliwiają obiektom wywołującym przekazywanie argumentów do funkcji. Funkcja może opcjonalnie zwracać wartość jako dane wyjściowe. Funkcje są przydatne do hermetyzacji typowych operacji w jednym bloku wielokrotnego użytku, najlepiej z nazwą, która wyraźnie opisuje działanie funkcji. Poniższa funkcja akceptuje dwie liczby całkowite z obiektu wywołującego i zwraca ich sumę; a i bparametrami typu int.

int sum(int a, int b)
{
    return a + b;
}

Funkcję można wywołać lub wywołać z dowolnej liczby miejsc w programie. Wartości przekazywane do funkcji to argumenty, których typy muszą być zgodne z typami parametrów w definicji funkcji.

int main()
{
    int i = sum(10, 32);
    int j = sum(i, 66);
    cout << "The value of j is" << j << endl; // 108
}

Nie ma praktycznego limitu długości funkcji, ale dobry projekt ma na celu funkcje, które wykonują jedno dobrze zdefiniowane zadanie. Złożone algorytmy powinny być podzielone na łatwiejsze do zrozumienia prostsze funkcje, gdy tylko jest to możliwe.

Funkcje zdefiniowane w zakresie klas są nazywane funkcjami składowych. W języku C++, w przeciwieństwie do innych języków, funkcję można również zdefiniować w zakresie przestrzeni nazw (w tym niejawnej globalnej przestrzeni nazw). Takie funkcje są nazywane funkcjami wolnymi lub funkcjami niebędącymi członkami; są one szeroko używane w bibliotece standardowej.

Funkcje mogą być przeciążone, co oznacza, że różne wersje funkcji mogą mieć taką samą nazwę, jeśli różnią się liczbą i/lub typem parametrów formalnych. Aby uzyskać więcej informacji, zobacz Przeciążenie funkcji.

Części deklaracji funkcji

Minimalna deklaracja funkcji składa się z zwracanego typu, nazwy funkcji i listy parametrów (które mogą być puste) wraz z opcjonalnymi słowami kluczowymi, które zawierają więcej instrukcji dla kompilatora. Poniższy przykład to deklaracja funkcji:

int sum(int a, int b);

Definicja funkcji składa się z deklaracji oraz treści, która jest całym kodem między nawiasami klamrowymi:

int sum(int a, int b)
{
    return a + b;
}

Deklaracja funkcji, po której następuje średnik, może pojawić się w wielu miejscach w programie. Musi się pojawić przed wywołaniami tej funkcji w każdej jednostce tłumaczenia. Definicja funkcji musi być wyświetlana tylko raz w programie zgodnie z regułą jednej definicji (ODR).

Wymagane części deklaracji funkcji to:

  1. Zwracany typ, który określa typ wartości zwracanej przez funkcję lub void jeśli żadna wartość nie jest zwracana. W języku C++11 jest prawidłowym typem zwracanym, auto który nakazuje kompilatorowi wnioskowanie typu z instrukcji return. W języku C++14 decltype(auto) jest również dozwolone. Aby uzyskać więcej informacji, zobacz Typ potrącenia w zwracanych typach poniżej.

  2. Nazwa funkcji, która musi zaczynać się literą lub podkreśleniami i nie może zawierać spacji. Ogólnie rzecz biorąc, wiodące podkreślenia w nazwach funkcji biblioteki standardowej wskazują prywatne funkcje składowe lub funkcje inne niż składowe, które nie są przeznaczone do użycia przez kod.

  3. Lista parametrów, rozdzielany nawiasem klamrowym, rozdzielany przecinkami zestaw zer lub więcej parametrów, które określają typ i opcjonalnie nazwę lokalną, za pomocą której wartości mogą być dostępne wewnątrz treści funkcji.

Opcjonalne części deklaracji funkcji to:

  1. constexpr, co wskazuje, że wartość zwracana funkcji jest wartością stałą można obliczyć w czasie kompilacji.

    constexpr float exp(float x, int n)
    {
        return n == 0 ? 1 :
            n % 2 == 0 ? exp(x * x, n / 2) :
            exp(x * x, (n - 1) / 2) * x;
    };
    
  2. Jego specyfikacja extern powiązania lub static.

    //Declare printf with C linkage.
    extern "C" int printf( const char *fmt, ... );
    
    

    Aby uzyskać więcej informacji, zobacz Jednostki tłumaczenia i połączenie.

  3. inline, który nakazuje kompilatorowi zastąpienie każdego wywołania funkcji samym kodem funkcji. Inlining może pomóc w wydajności w scenariuszach, w których funkcja jest wykonywana szybko i jest wywoływana wielokrotnie w sekcji kodu krytycznego dla wydajności.

    inline double Account::GetBalance()
    {
        return balance;
    }
    

    Aby uzyskać więcej informacji, zobacz Funkcje wbudowane.

  4. Wyrażenie noexcept określające, czy funkcja może zgłosić wyjątek. W poniższym przykładzie funkcja nie zgłasza wyjątku, jeśli is_pod wyrażenie zwróci wartość true.

    #include <type_traits>
    
    template <typename T>
    T copy_object(T& obj) noexcept(std::is_pod<T>) {...}
    

    W celu uzyskania więcej informacji, zobacz następujący temat: noexcept.

  5. (Tylko funkcje składowe) Kwalifikatory cv, które określają, czy funkcja ma wartość const , czy volatile.

  6. (Tylko funkcje składowe) virtual, overridelub final. virtual określa, że funkcję można zastąpić w klasie pochodnej. override oznacza, że funkcja w klasie pochodnej zastępuje funkcję wirtualną. final oznacza, że funkcja nie może zostać zastąpiona w żadnej dalszej klasie pochodnej. Aby uzyskać więcej informacji, zobacz Funkcje wirtualne.

  7. (tylko funkcje składowe) static zastosowana do funkcji składowej oznacza, że funkcja nie jest skojarzona z żadnymi wystąpieniami obiektów klasy.

  8. (Tylko funkcje niestatyczne składowych) Kwalifikator ref, który określa kompilator, który przeciążenie funkcji do wybrania, gdy niejawny parametr obiektu (*this) jest odwołaniem rvalue a odwołaniem lvalue. Aby uzyskać więcej informacji, zobacz Przeciążenie funkcji.

Definicje funkcji

Definicja funkcji składa się z deklaracji i treści funkcji, ujętej w nawiasy klamrowe, które zawierają deklaracje zmiennych, instrukcje i wyrażenia. W poniższym przykładzie przedstawiono kompletną definicję funkcji:

    int foo(int i, std::string s)
    {
       int value {i};
       MyClass mc;
       if(strcmp(s, "default") != 0)
       {
            value = mc.do_something(i);
       }
       return value;
    }

Zmienne zadeklarowane wewnątrz treści są nazywane zmiennymi lokalnymi lub lokalnymi. Wyjdą poza zakres, gdy funkcja zakończy działanie; dlatego funkcja nigdy nie powinna zwracać odwołania do lokalnego!

    MyClass& boom(int i, std::string s)
    {
       int value {i};
       MyClass mc;
       mc.Initialize(i,s);
       return mc;
    }

const i constexpr, funkcje

Można zadeklarować funkcję składową, const aby określić, że funkcja nie może zmienić wartości żadnych składowych danych w klasie. Deklarując funkcję składową jako const, możesz pomóc kompilatorowi wymuszenie poprawności const-correctness. Jeśli ktoś błędnie próbuje zmodyfikować obiekt przy użyciu funkcji zadeklarowanej jako const, zostanie zgłoszony błąd kompilatora. Aby uzyskać więcej informacji, zobacz const.

Zadeklaruj funkcję jako constexpr , gdy wartość, która generuje, może być określona w czasie kompilacji. Funkcja constexpr zwykle wykonuje się szybciej niż zwykła funkcja. W celu uzyskania więcej informacji, zobacz następujący temat: constexpr.

Szablony funkcji

Szablon funkcji jest podobny do szablonu klasy; generuje konkretne funkcje na podstawie argumentów szablonu. W wielu przypadkach szablon może wywnioskować argumenty typu i dlatego nie jest konieczne jawne ich określenie.

template<typename Lhs, typename Rhs>
auto Add2(const Lhs& lhs, const Rhs& rhs)
{
    return lhs + rhs;
}

auto a = Add2(3.13, 2.895); // a is a double
auto b = Add2(string{ "Hello" }, string{ " World" }); // b is a std::string

Aby uzyskać więcej informacji, zobacz Szablony funkcji

Parametry i argumenty funkcji

Funkcja ma rozdzielaną przecinkami listę parametrów zero lub więcej typów, z których każda ma nazwę, do której można uzyskać dostęp wewnątrz treści funkcji. Szablon funkcji może określać więcej parametrów typu lub wartości. Obiekt wywołujący przekazuje argumenty, które są konkretnymi wartościami, których typy są zgodne z listą parametrów.

Domyślnie argumenty są przekazywane do funkcji według wartości, co oznacza, że funkcja odbiera kopię przekazanego obiektu. W przypadku dużych obiektów tworzenie kopii może być kosztowne i nie zawsze jest konieczne. Aby spowodować przekazanie argumentów przez odwołanie (w szczególności odwołanie lvalue), dodaj kwantyfikator odwołania do parametru:

void DoSomething(std::string& input){...}

Gdy funkcja modyfikuje argument przekazywany przez odwołanie, modyfikuje oryginalny obiekt, a nie kopię lokalną. Aby zapobiec modyfikowaniu takiego argumentu przez funkcję, należy zakwalifikować parametr jako const&:

void DoSomething(const std::string& input){...}

C++11: Aby jawnie obsługiwać argumenty przekazywane przez rvalue-reference lub lvalue-reference, użyj podwójnej ampersand na parametrze, aby wskazać uniwersalne odwołanie:

void DoSomething(const std::string&& input){...}

Funkcja zadeklarowana za pomocą pojedynczego słowa kluczowego void na liście deklaracji parametrów nie przyjmuje żadnych argumentów, o ile słowo kluczowe void jest pierwszym i jedynym elementem członkowskim listy deklaracji argumentów. Argumenty typu void gdzie indziej na liście generują błędy. Na przykład:

// OK same as GetTickCount()
long GetTickCount( void );

Chociaż nie można określić argumentu void , z wyjątkiem opisanych tutaj, typy pochodzące z typu void (takie jak wskaźniki do void i tablice ) mogą być wyświetlane w dowolnym miejscu listy deklaracji argumentów void.

Argumenty domyślne

Ostatni parametr lub parametry w podpisie funkcji mogą mieć przypisany argument domyślny, co oznacza, że obiekt wywołujący może pominąć argument podczas wywoływania funkcji, chyba że chce określić inną wartość.

int DoSomething(int num,
    string str,
    Allocator& alloc = defaultAllocator)
{ ... }

// OK both parameters are at end
int DoSomethingElse(int num,
    string str = string{ "Working" },
    Allocator& alloc = defaultAllocator)
{ ... }

// C2548: 'DoMore': missing default parameter for parameter 2
int DoMore(int num = 5, // Not a trailing parameter!
    string str,
    Allocator& = defaultAllocator)
{...}

Aby uzyskać więcej informacji, zobacz Argumenty domyślne.

Typy zwracane przez funkcję

Funkcja może nie zwracać innej funkcji ani wbudowanej tablicy; może jednak zwracać wskaźniki do tych typów lub lambda, która generuje obiekt funkcji. Z wyjątkiem tych przypadków funkcja może zwracać wartość dowolnego typu, który znajduje się w zakresie, lub może nie zwracać żadnej wartości, w takim przypadku zwracany typ to void.

Końcowe typy zwracane

Typ zwracany "zwykły" znajduje się po lewej stronie podpisu funkcji. Lewy typ zwracany znajduje się po prawej stronie podpisu i jest poprzedzony operatorem -> . Końcowe typy zwracane są szczególnie przydatne w szablonach funkcji, gdy typ wartości zwracanej zależy od parametrów szablonu.

template<typename Lhs, typename Rhs>
auto Add(const Lhs& lhs, const Rhs& rhs) -> decltype(lhs + rhs)
{
    return lhs + rhs;
}

Gdy auto jest używany w połączeniu z typem zwracanym na skutek końcowy, służy on tylko jako symbol zastępczy dla tego, co tworzy wyrażenie decltype, i nie wykonuje samego odliczenia typu.

Zmienne lokalne funkcji

Zmienna zadeklarowana wewnątrz treści funkcji jest nazywana zmienną lokalną lub po prostu lokalną. Niestatyczne ustawienia lokalne są widoczne tylko wewnątrz treści funkcji, a jeśli są deklarowane na stosie, wyjdą poza zakres, gdy funkcja zakończy działanie. Podczas konstruowania zmiennej lokalnej i zwracania jej według wartości kompilator zwykle może wykonać optymalizację nazwanej wartości zwracanej, aby uniknąć niepotrzebnych operacji kopiowania. Jeśli zwracasz zmienną lokalną przez odwołanie, kompilator wyświetli ostrzeżenie, ponieważ każda próba użycia tego odwołania przez obiekt wywołujący zostanie wykonana po zniszczeniu lokalnego.

W języku C++ zmienna lokalna może być zadeklarowana jako statyczna. Zmienna jest widoczna tylko wewnątrz treści funkcji, ale istnieje pojedyncza kopia zmiennej dla wszystkich wystąpień funkcji. Lokalne obiekty statyczne są niszczone podczas kończenia działania określonego przez atexit. Jeśli obiekt statyczny nie został skonstruowany, ponieważ przepływ sterowania programu pominął jego deklarację, nie podjęto próby zniszczenia tego obiektu.

Potrącenie typu w typach zwracanych (C++14)

W języku C++14 można użyć auto polecenia kompilatora, aby wywnioskował typ zwracany z treści funkcji bez konieczności podawania końcowego typu zwracanego. Należy pamiętać, że auto zawsze wyjmuje wartość zwracaną przez wartość. Użyj auto&& polecenia , aby kompilator wyłudzał odwołanie.

W tym przykładzie auto zostanie wywołana kopia wartości innej niż const sumy lhs i rhs.

template<typename Lhs, typename Rhs>
auto Add2(const Lhs& lhs, const Rhs& rhs)
{
    return lhs + rhs; //returns a non-const object by value
}

Należy pamiętać, że auto nie zachowuje const-ness typu, który wyłuduje. W przypadku funkcji przekazywania, których wartość zwracana musi zachować const-ness lub ref-ness argumentów, można użyć słowa kluczowego , które używa decltype(auto)decltype reguł wnioskowania typu i zachowuje wszystkie informacje o typie. decltype(auto) może być używana jako zwykła wartość zwracana po lewej stronie lub jako końcowa wartość zwracana.

Poniższy przykład (oparty na kodzie z N3493) pokazuje decltype(auto) , że jest używany do umożliwienia doskonałego przekazywania argumentów funkcji w typie zwrotnym, który nie jest znany, dopóki szablon nie zostanie utworzone.

template<typename F, typename Tuple = tuple<T...>, int... I>
decltype(auto) apply_(F&& f, Tuple&& args, index_sequence<I...>)
{
    return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(args))...);
}

template<typename F, typename Tuple = tuple<T...>,
    typename Indices = make_index_sequence<tuple_size<Tuple>::value >>
   decltype( auto)
    apply(F&& f, Tuple&& args)
{
    return apply_(std::forward<F>(f), std::forward<Tuple>(args), Indices());
}

Zwracanie wielu wartości z funkcji

Istnieją różne sposoby zwracania więcej niż jednej wartości z funkcji:

  1. Hermetyzowanie wartości w nazwanym obiekcie klasy lub struktury. Wymaga, aby definicja klasy lub struktury był widoczny dla obiektu wywołującego:

    #include <string>
    #include <iostream>
    
    using namespace std;
    
    struct S
    {
        string name;
        int num;
    };
    
    S g()
    {
        string t{ "hello" };
        int u{ 42 };
        return { t, u };
    }
    
    int main()
    {
        S s = g();
        cout << s.name << " " << s.num << endl;
        return 0;
    }
    
  2. Zwróć obiekt std::tuple lub std::p air:

    #include <tuple>
    #include <string>
    #include <iostream>
    
    using namespace std;
    
    tuple<int, string, double> f()
    {
        int i{ 108 };
        string s{ "Some text" };
        double d{ .01 };
        return { i,s,d };
    }
    
    int main()
    {
        auto t = f();
        cout << get<0>(t) << " " << get<1>(t) << " " << get<2>(t) << endl;
    
        // --or--
    
        int myval;
        string myname;
        double mydecimal;
        tie(myval, myname, mydecimal) = f();
        cout << myval << " " << myname << " " << mydecimal << endl;
    
        return 0;
    }
    
  3. Program Visual Studio 2017 w wersji 15.3 lub nowszej (dostępny w /std:c++17 trybie i nowszym): użyj powiązań strukturalnych. Zaletą powiązań strukturalnych jest to, że zmienne, które przechowują wartości zwracane, są inicjowane w tym samym czasie, gdy są deklarowane, co w niektórych przypadkach może być znacznie bardziej wydajne. W instrukcji auto[x, y, z] = f(); nawiasy wprowadzają i inicjują nazwy, które są w zakresie całego bloku funkcji.

    #include <tuple>
    #include <string>
    #include <iostream>
    
    using namespace std;
    
    tuple<int, string, double> f()
    {
        int i{ 108 };
        string s{ "Some text" };
        double d{ .01 };
        return { i,s,d };
    }
    struct S
    {
        string name;
        int num;
    };
    
    S g()
    {
        string t{ "hello" };
        int u{ 42 };
        return { t, u };
    }
    
    int main()
    {
        auto[x, y, z] = f(); // init from tuple
        cout << x << " " << y << " " << z << endl;
    
        auto[a, b] = g(); // init from POD struct
        cout << a << " " << b << endl;
        return 0;
    }
    
  4. Oprócz używania samej wartości zwracanej można "zwrócić" wartości, definiując dowolną liczbę parametrów do użycia odwołania przekazywanego, aby funkcja mogła modyfikować lub inicjować wartości obiektów, które udostępnia obiekt wywołujący. Aby uzyskać więcej informacji, zobacz Argumenty funkcji typu odwołania.

Wskaźniki funkcji

Język C++ obsługuje wskaźniki funkcji w taki sam sposób jak język C. Jednak bardziej bezpieczną alternatywą jest zwykle użycie obiektu funkcji.

Zaleca typedef się zadeklarowanie aliasu dla typu wskaźnika funkcji w przypadku deklarowania funkcji zwracającej typ wskaźnika funkcji. Na przykład

typedef int (*fp)(int);
fp myFunction(char* s); // function returning function pointer

Jeśli tak się nie stanie, prawidłowa składnia deklaracji funkcji może zostać wyprowadzona ze składni deklaratora wskaźnika funkcji, zastępując identyfikator (fp w powyższym przykładzie) nazwą funkcji i listą argumentów w następujący sposób:

int (*myFunction(char* s))(int);

Poprzednia deklaracja jest równoważna deklaracji przy użyciu typedef wcześniejszej.

Zobacz też

Przeładowywanie funkcji
Funkcje ze zmiennymi listami argumentów
Jawnie domyślne i usunięte funkcje
Odnośnik do nazwy zależnej od argumentu (Koenig) funkcji
Argumenty domyślne
Funkcje śródwierszowe