20.1 -- Wskaźniki funkcji

W lekcji 12.7 -- Wprowadzenie do wskaźników, dowiedziałeś się, że wskaźnik jest zmienną przechowującą adres innej zmiennej. Wskaźniki funkcji działają podobnie, z tą różnicą, że zamiast wskazywać na zmienne, wskazują na funkcje!

Rozważ następującą funkcję:

int foo()
{
    return 5;
}

Identyfikator foo() to nazwa funkcji. Ale jakiego typu jest ta funkcja? Funkcje mają swój własny typ funkcji — w tym przypadku typ funkcji, który zwraca liczbę całkowitą i nie przyjmuje żadnych parametrów. Podobnie jak zmienne, funkcje żyją pod przypisanym adresem w pamięci (co czyni je lwartościami).

Gdy funkcja jest wywoływana (przez operator()), wykonanie przeskakuje pod adres wywoływanej funkcji:

int foo() // kod dla foo zaczyna się od adresu pamięci 0x002717f0
{
    return 5;
}

int main()
{
    foo(); // skok do adresu 0x002717f0

    return 0;
}

W pewnym momencie swojej kariery programistycznej (jeśli jeszcze tego nie zrobiłeś) prawdopodobnie popełnisz prosty błąd:

#include <iostream>

int foo() // kod zaczyna się od adresu pamięci 0x002717f0
{
    return 5;
}

int main()
{
    std::cout << foo << '\n'; // chcieliśmy wywołać foo(), ale zamiast tego wypisujemy samo foo!

    return 0;
}

Zamiast wywoływania funkcji foo() i drukowania wartość zwracaną, niechcący wysłaliśmy funkcję foo bezpośrednio do std::cout. Co się dzieje w tym przypadku?

Kiedy odwołanie się do funkcji następuje po nazwie (bez nawiasów), C++ konwertuje tę funkcję na wskaźnik funkcji (przechowujący adres funkcji). Następnie operator<< próbuje wydrukować wskaźnik funkcji, ale kończy się to niepowodzeniem, ponieważ operator<< nie wie, jak wydrukować wskaźniki funkcji. Norma mówi, że w tym przypadku foo należy zamienić na bool (który operator<< wie jak wydrukować). A ponieważ wskaźnik funkcji dla foo nie jest wskaźnikiem o wartości null, zawsze powinien mieć wartość Boolean true. Powinno to zatem wyświetlić:

1

Wskazówka

Niektóre kompilatory (np. Visual Studio) mają rozszerzenie kompilatora, które zamiast tego wypisuje adres funkcji:

0x002717f0

Jeśli Twoja platforma nie drukuje adresu funkcji, a chcesz, możesz ją wymusić, konwertując funkcję na wskaźnik pustej przestrzeni i wyświetlając następujący komunikat:

#include <iostream>

int foo() // kod zaczyna się od adresu pamięci 0x002717f0
{
    return 5;
}

int main()
{
    std::cout << reinterpret_cast<void*>(foo) << '\n'; // Powiedz C++, aby interpretował funkcję foo jako wskaźnik pustki (zachowanie zdefiniowane w implementacji)

    return 0;
}

To jest zachowanie zdefiniowane w implementacji, więc może nie działać na wszystkich platformy.

Tak jak możliwe jest zadeklarowanie niestałego wskaźnika do normalnej zmiennej, możliwe jest również zadeklarowanie niestałego wskaźnika do funkcji. W dalszej części tej lekcji przyjrzymy się wskaźnikom funkcji i ich zastosowaniu. Wskaźniki funkcji to dość zaawansowany temat i resztę tej lekcji mogą bezpiecznie pominąć lub prześledzić ci, którzy szukają jedynie podstaw C++.

Wskaźniki do funkcji

Składnia tworzenia niestałego wskaźnika funkcji jest jedną z najbrzydszych rzeczy, jakie kiedykolwiek zobaczysz w C++:

// fcnPtr jest wskaźnikiem do funkcji, która nie przyjmuje argumentów i zwraca liczbę całkowitą
int (*fcnPtr)();

W powyższym fragmencie fcnPtr jest wskaźnikiem do funkcji, która ma nie ma parametrów i zwraca liczbę całkowitą. fcnPtr może wskazywać dowolną funkcję pasującą do tego typu.

Nawiasy wokół *fcnPtr są konieczne ze względu na pierwszeństwo, ponieważ int* fcnPtr() byłyby interpretowane jako deklaracja forward dla funkcji o nazwie fcnPtr, która nie przyjmuje parametrów i zwraca wskaźnik do liczby całkowitej.

Aby utworzyć wskaźnik funkcji const, const znajduje się po gwiazdka:

int (*const fcnPtr)();

Jeśli umieścisz const przed int, oznacza to, że wskazywana funkcja zwróci const int.

Wskazówka

Składnia wskaźnika funkcji może być trudna do zrozumienia. W poniższych artykułach przedstawiono metodę analizowania takich deklaracji:

Przypisywanie funkcji do wskaźnika funkcji

Wskaźniki funkcji można inicjować funkcją (a wskaźniki funkcji niebędące stałymi można przypisać funkcję). Podobnie jak w przypadku wskaźników do zmiennych, możemy również użyć &foo, aby uzyskać wskaźnik funkcji do foo.

int foo()
{
    return 5;
}

int goo()
{
    return 6;
}

int main()
{
    int (*fcnPtr)(){ &foo }; // fcnPtr wskazuje na funkcję foo
    fcnPtr = &goo; // fcnPtr wskazuje teraz na funkcję goo

    return 0;
}

Jednym z częstych błędów jest wykonanie następującej czynności:

fcnPtr = goo();

To próbuje przypisać wartość zwracaną z wywołania funkcji goo() (która ma typ int) do fcnPtr (który oczekuje wartości typu int(*)()), czego nie chcemy. Chcemy, aby fcnPtr miał przypisany adres funkcji goo, a nie wartość zwracaną przez funkcję goo(). Zatem nie są potrzebne żadne nawiasy.

Zauważ, że typ (parametry i typ zwracany) wskaźnika funkcji musi odpowiadać typowi funkcji. Oto kilka przykładów:

// prototypy funkcji
int foo();
double goo();
int hoo(int x);

// funkcja inicjatory wskaźników
int (*fcnPtr1)(){ &foo };    // okay
int (*fcnPtr2)(){ &goo };    // źle - typy zwracane nie pasują!
double (*fcnPtr4)(){ &goo }; // okay
fcnPtr1 = &hoo;              // źle - fcnPtr1 nie ma parametrów, ale hoo() ma
int (*fcnPtr3)(int){ &hoo }; // okay

W przeciwieństwie do typów podstawowych, C++ będziesz w razie potrzeby niejawnie konwertuje funkcję na wskaźnik funkcji (więc nie musisz używać operatora adresu (&), aby uzyskać adres funkcji). Jednakże wskaźniki funkcji nie zostaną skonwertowane na wskaźniki puste i odwrotnie (chociaż niektóre kompilatory, takie jak Visual Studio, i tak mogą na to pozwolić).

	// prototypy funkcji
	int foo();

	// inicjalizacje funkcji
	int (*fcnPtr5)() { foo }; // OK, foo niejawnie konwertuje wskaźnik funkcji na foo
	void* vPtr { foo };       // nie w porządku, chociaż niektóre kompilatory mogą zezwolenie

Wskaźniki funkcji można również inicjować lub przypisywać im wartość nullptr:

int (*fcnptr)() { nullptr }; // okay

Wywoływanie funkcji przy użyciu wskaźnika funkcji

Inną podstawową rzeczą, którą można zrobić ze wskaźnikiem funkcji, jest użycie go do faktycznego wywołania funkcji. Można to zrobić na dwa sposoby. Pierwszy polega na jawnym dereferencji:

int foo(int x)
{
    return x;
}

int main()
{
    int (*fcnPtr)(int){ &foo }; // Zainicjuj fcnPtr funkcją foo
    (*fcnPtr)(5); // wywołaj funkcję foo(5) poprzez fcnPtr.

    return 0;
}

Drugi sposób polega na niejawnym dereferencji:

int foo(int x)
{
    return x;
}

int main()
{
    int (*fcnPtr)(int){ &foo }; // Zainicjuj fcnPtr funkcją foo
    fcnPtr(5); // wywołaj funkcję foo(5) poprzez fcnPtr.

    return 0;
}

Jak widzisz, metoda ukrytego dereferencji wygląda jak zwykłe wywołanie funkcji - czego można się spodziewać, ponieważ normalne nazwy funkcji i tak są wskaźnikami do funkcji! Jednak niektóre starsze kompilatory nie obsługują metody ukrytego dereferencji, ale wszystkie nowoczesne kompilatory powinny.

Pamiętaj również, że ponieważ wskaźniki funkcji można ustawić na wartość nullptr, dobrym pomysłem jest sprawdzenie lub warunkowe sprawdzenie, czy wskaźnik funkcji jest wskaźnikiem zerowym przed jego wywołaniem. Podobnie jak w przypadku zwykłych wskaźników, dereferencja wskaźnika funkcji zerowej prowadzi do niezdefiniowanego zachowania.

int foo(int x)
{
    return x;
}

int main()
{
    int (*fcnPtr)(int){ &foo }; // Zainicjuj fcnPtr funkcją foo
    if (fcnPtr) // upewnij się, że fcnPtr nie jest wskaźnikiem zerowym    
        fcnPtr(5); // w przeciwnym razie doprowadziło to do niezdefiniowanego zachowania

    return 0;
}

Domyślne argumenty nie działają w przypadku funkcji wywoływanych poprzez wskaźniki funkcji Zaawansowane

Kiedy kompilator napotyka normalne wywołanie funkcji z jednym lub większą liczbą domyślnych argumentów, przepisuje wywołanie funkcji, aby uwzględnić argumenty domyślne. Proces ten zachodzi w czasie kompilacji i dlatego można go zastosować tylko do funkcji, które można rozwiązać w czasie kompilacji.

Jednak gdy funkcja jest wywoływana poprzez wskaźnik funkcji, jest ona rozpoznawana w czasie wykonywania. W tym przypadku nie ma potrzeby przepisywania wywołania funkcji w celu uwzględnienia argumentów domyślnych.

Kluczowa informacja

Ponieważ rozpoznawanie odbywa się w czasie wykonywania, argumenty domyślne nie są rozpoznawane, gdy funkcja jest wywoływana przez wskaźnik funkcji.

Oznacza to, że możemy użyć wskaźnika funkcji do ujednoznacznienia wywołania funkcji, które w przeciwnym razie byłoby niejednoznaczne ze względu na domyślne argumenty. W poniższym przykładzie pokazujemy dwa sposoby, aby to zrobić:

#include <iostream>

void print(int x)
{
    std::cout << "print(int)\n";
}

void print(int x, int y = 10)
{
    std::cout << "print(int, int)\n";
}

int main()
{
//    drukuj(1); // niejednoznaczne wywołanie funkcji

    // Deconstructed method
    using vnptr = void(*)(int); // zdefiniuj alias typu dla wskaźnika funkcji do funkcji void(int)
    vnptr pi { print }; // zainicjuj nasz wskaźnik funkcji za pomocą funkcji print
    pi(1); // wywołaj funkcję print(int) poprzez wskaźnik funkcji

    // Concise method
    static_cast<void(*)(int)>(print)(1); // wywołaj void(int) wersję print z argumentem 1
    
    return 0;
}

Przekazywanie funkcji jako argumentów do innych funkcji

Jedną z najbardziej przydatnych rzeczy związanych ze wskaźnikami funkcji jest przekazywanie funkcji jako argumentu do innej funkcji. Funkcje używane jako argumenty innej funkcji są czasami nazywane funkcjami wywołania zwrotnego.

Rozważmy przypadek, w którym piszesz funkcję w celu wykonania zadania (takiego jak sortowanie tablicy), ale chcesz, aby użytkownik mógł określić, w jaki sposób będzie wykonywana konkretna część tego zadania (np. czy tablica będzie sortowana w kolejności rosnącej, czy malejącej). Przyjrzyjmy się bliżej temu problemowi w zastosowaniu konkretnie do sortowania, jako przykładowi, który można uogólnić na inne podobne problemy.

Wiele algorytmów sortowania opartego na porównaniach działa na podobnej koncepcji: algorytm sortowania iteruje po liście liczb, dokonuje porównań par liczb i zmienia kolejność liczb na podstawie wyników tych porównań. W rezultacie, zmieniając porównanie, możemy zmienić sposób sortowania algorytmu bez wpływu na resztę kodu sortującego.

Oto nasza procedura sortowania przez wybór z poprzedniej lekcji:

#include <utility> // dla std::swap

void SelectionSort(int* array, int size)
{
    if (!array)
        return;

    // Przejdź przez każdy element tablicy
    for (int startIndex{ 0 }; startIndex < (size - 1); ++startIndex)
    {
        // smallestIndex to indeks najmniejszego elementu, jaki do tej pory napotkaliśmy.
        int smallestIndex{ startIndex };
 
        // Poszukaj najmniejszego elementu pozostałego w tablicy (zaczynając od startIndex+1)
        for (int currentIndex{ startIndex + 1 }; currentIndex < size; ++currentIndex)
        {
            // Jeśli bieżący element jest mniejszy niż poprzednio znaleziony najmniejszy
            if (array[smallestIndex] > array[currentIndex]) // PORÓWNANIE ZROBIONE TUTAJ
            {
                // Jest to nowa najmniejsza liczba w tej iteracji
                smallestIndex = currentIndex;
            }
        }
 
        // Zamień nasz element początkowy z naszym najmniejszym elementem
        std::swap(array[startIndex], array[smallestIndex]);
    }
}

Zastąpmy to porównanie funkcją umożliwiającą dokonanie porównania. Ponieważ nasza funkcja porównania porówna dwie liczby całkowite i zwróci wartość logiczną wskazującą, czy elementy powinny zostać zamienione, będzie to wyglądać mniej więcej tak:

bool ascending(int x, int y)
{
    return x > y; // zamiana, jeśli pierwszy element jest większy niż drugi
}

A oto nasza procedura sortowania przez wybór wykorzystująca funkcję rosnąco() do wykonania porównania:

#include <utility> // dla std::swap

void SelectionSort(int* array, int size)
{
    if (!array)
        return;

    // Przejdź przez każdy element tablicy
    for (int startIndex{ 0 }; startIndex < (size - 1); ++startIndex)
    {
        // smallestIndex to indeks najmniejszego elementu, jaki do tej pory napotkaliśmy.
        int smallestIndex{ startIndex };
 
        // Poszukaj najmniejszego elementu pozostałego w tablicy (zaczynając od startIndex+1)
        for (int currentIndex{ startIndex + 1 }; currentIndex < size; ++currentIndex)
        {
            // Jeśli bieżący element jest mniejszy niż poprzednio znaleziony najmniejszy
            if (ascending(array[smallestIndex], array[currentIndex])) // PORÓWNANIE ZROBIONE TUTAJ
            {
                // Jest to nowa najmniejsza liczba w tej iteracji
                smallestIndex = currentIndex;
            }
        }
 
        // Zamień nasz element początkowy z naszym najmniejszym elementem
        std::swap(array[startIndex], array[smallestIndex]);
    }
}

Teraz, aby pozwolić wywołującemu zdecydować, w jaki sposób sortowanie zostanie wykonane, zamiast korzystać z naszej własnej, zakodowanej na stałe funkcji porównania, pozwolimy wywołującemu zapewnić własne sortowanie funkcja! Odbywa się to poprzez wskaźnik funkcji.

Ponieważ funkcja porównawcza wywołującego porówna dwie liczby całkowite i zwróci wartość logiczną, wskaźnik do takiej funkcji będzie wyglądał mniej więcej tak:

bool (*comparisonFcn)(int, int);

Pozwolimy więc wywołującemu przekazać naszej procedurze sortującej wskaźnik do żądanej funkcji porównania jako trzeci parametr, a następnie użyjemy funkcji wywołującej do wykonania porównanie.

Oto pełny przykład sortowania przez zaznaczenie, które wykorzystuje parametr wskaźnika funkcji do wykonania porównania zdefiniowanego przez użytkownika, wraz z przykładem, jak to wywołać:

#include <utility> // dla std::swap
#include <iostream>

// Pamiętaj, że naszym porównaniem zdefiniowanym przez użytkownika jest trzeci parametr
void selectionSort(int* array, int size, bool (*comparisonFcn)(int, int))
{
    if (!array || !comparisonFcn)
        return;

    // Przejdź przez każdy element tablicy
    for (int startIndex{ 0 }; startIndex < (size - 1); ++startIndex)
    {
        // bestIndex to indeks najmniejszego/największego elementu, jaki napotkaliśmy do tej pory.
        int bestIndex{ startIndex };
 
        // Poszukaj najmniejszego/największego elementu pozostałego w tablicy (zaczynając od startIndex+1)
        for (int currentIndex{ startIndex + 1 }; currentIndex < size; ++currentIndex)
        {
            // Jeśli bieżący element jest mniejszy/większy niż poprzednio znaleziony najmniejszy
            if (comparisonFcn(array[bestIndex], array[currentIndex])) // PORÓWNANIE ZROBIONE TUTAJ
            {
                // To jest nowa najmniejsza/największa liczba w tej iteracji
                bestIndex = currentIndex;
            }
        }
 
        // Zamień nasz element początkowy z naszym najmniejszym/największym elementem
        std::swap(array[startIndex], array[bestIndex]);
    }
}

// Oto funkcja porównawcza sortująca w porządku rosnącym
// (Uwaga: to dokładnie to samo, co poprzednia funkcja ascending())
bool ascending(int x, int y)
{
    return x > y; // zamiana, jeśli pierwszy element jest większy niż drugi
}

// Oto funkcja porównawcza sortująca w porządku malejącym
bool descending(int x, int y)
{
    return x < y; // zamiana, jeśli drugi element jest większy niż pierwszy
}

// Ta funkcja wypisuje wartości z tablicy
void printArray(int* array, int size)
{
    if (!array)
        return;

    for (int index{ 0 }; index < size; ++index)
    {
        std::cout << array[index] << ' ';
    }
    
    std::cout << '\n';
}

int main()
{
    int array[9]{ 3, 7, 9, 5, 6, 1, 8, 2, 4 };

    // Sortuj tablicę w porządku malejącym za pomocą funkcji malejącej()
    selectionSort(array, 9, descending);
    printArray(array, 9);

    // Sortuj tablicę w porządku rosnącym za pomocą funkcji ascending()
    selectionSort(array, 9, ascending);
    printArray(array, 9);

    return 0;
}

Ten program generuje wynik:

9 8 7 6 5 4 3 2 1
1 2 3 4 5 6 7 8 9

Czy to fajne, czy co? Daliśmy wywołującemu możliwość kontrolowania sposobu, w jaki sortowanie przez wybór wykonuje swoją pracę.

Wywołujący może nawet zdefiniować własne „dziwne” funkcje porównawcze:

bool evensFirst(int x, int y)
{
	// jeśli x jest parzyste, a y jest nieparzyste, x jest pierwsze (bez konieczności zamiany)
	if ((x % 2 == 0) && !(y % 2 == 0))
		return false;
 
	// jeśli x jest nieparzyste, a y jest parzyste, y jest pierwsze (wymagana zamiana)
	if (!(x % 2 == 0) && (y % 2 == 0))
		return true;

        // w przeciwnym razie posortuj rosnąco
	return ascending(x, y);
}

int main()
{
    int array[9]{ 3, 7, 9, 5, 6, 1, 8, 2, 4 };

    selectionSort(array, 9, evensFirst);
    printArray(array, 9);

    return 0;
}

Powyższy fragment daje następujący wynik:

2 4 6 8 1 3 5 7 9

Jak widzisz, użycie wskaźnika funkcji w tym kontekście to dobry sposób na umożliwienie wywołującemu „podczepienia” własnej funkcjonalności do czegoś, co wcześniej napisałeś i przetestowałeś, co ułatwia ponowne użycie kodu! Poprzednio, jeśli chcieliśmy posortować jedną tablicę w porządku malejącym, a drugą w porządku rosnącym, potrzebowaliśmy wielu wersji procedury sortowania. Teraz możesz mieć jedną wersję, która może sortować w dowolny sposób!

Uwaga: Jeśli parametr funkcji jest typu funkcji, zostanie on przekonwertowany na wskaźnik do typu funkcji. Oznacza to, że:

void selectionSort(int* array, int size, bool (*comparisonFcn)(int, int))

można równoważnie zapisać jako:

void selectionSort(int* array, int size, bool comparisonFcn(int, int))

Działa to tylko w przypadku parametrów funkcji, a zatem ma nieco ograniczone zastosowanie. W przypadku parametru niefunkcyjnego ten ostatni jest interpretowany jako deklaracja forward:

    bool (*ptr)(int, int); // definicja wskaźnika funkcji ptr
    bool fcn(int, int);    // deklaracja funkcji forward fcn

Udostępnianie funkcji domyślnych

Jeśli chcesz pozwolić wywołującemu przekazywać funkcję jako parametr, często przydatne może być udostępnienie wywołującemu pewnych standardowych funkcji, z których będzie mógł korzystać dla własnej wygody. Na przykład w powyższym przykładzie sortowania przez wybór udostępnienie funkcji rosnąco() i malejąco() wraz z funkcją choiceSort() ułatwiłoby życie wywołującemu, ponieważ nie musiałby on przepisywać funkcji rosnąco() lub malejąco() za każdym razem, gdy chce ich użyć.

Możesz nawet ustawić jeden z nich jako parametr domyślny:

// Domyślnie sortuj rosnąco
void selectionSort(int* array, int size, bool (*comparisonFcn)(int, int) = ascending);

W tym przypadku, o ile użytkownik normalnie wywoła wybórSort (nie przez wskaźnik funkcji), parametr porównaniaFcn będzie domyślnie ustawiony na wartość rosnącą. Będziesz musiał upewnić się, że ascending funkcja została zadeklarowana przed tym punktem, w przeciwnym razie kompilator będzie narzekał, że nie wie co ascending jest.

Ulepszanie wskaźników funkcji za pomocą aliasów typów

Spójrzmy prawdzie w oczy — składnia wskaźników do funkcji jest brzydka. Jednakże aliasów typów można używać, aby wskaźniki do funkcji wyglądały bardziej jak zwykłe zmienne:

using ValidateFunction = bool(*)(int, int);

Definiuje to alias typu o nazwie „ValidateFunction”, który jest wskaźnikiem do funkcji, która pobiera dwie wartości typu int i zwraca wartość bool.

Teraz zamiast to robić:

bool validate(int x, int y, bool (*fcnPtr)(int, int)); // ugly

Możesz to zrobić:

bool validate(int x, int y, ValidateFunction pfcn) // clean

Używając std::function

Alternatywną metodą definiowania i przechowywania wskaźników funkcji jest użycie std::function, która jest częścią nagłówka <funkcjonalnej> biblioteki standardowej. Aby zdefiniować wskaźnik funkcji przy użyciu tej metody, zadeklaruj obiekt std::function w następujący sposób:

#include <functional>
bool validate(int x, int y, std::function<bool(int, int)> fcn); // std::function Metoda, która zwraca wartość bool i przyjmuje dwa parametry int

Jak widać, zarówno typ zwracany, jak i parametry są podawane w nawiasach kątowych, a parametry w nawiasach. Jeśli nie ma parametrów, nawiasy można pozostawić puste.

Aktualizacja naszego wcześniejszego przykładu za pomocą std::function:

#include <functional>
#include <iostream>

int foo()
{
    return 5;
}

int goo()
{
    return 6;
}

int main()
{
    std::function<int()> fcnPtr{ &foo }; // deklaruj wskaźnik funkcji, który zwraca wartość typu int i nie przyjmuje żadnych parametrów
    fcnPtr = &goo; // fcnPtr wskazuje teraz na funkcję goo
    std::cout << fcnPtr() << '\n'; // wywołaj funkcję tak jak zwykle

    std::function fcnPtr2{ &foo }; // można również użyć CTAD do wywnioskowania argumentów szablonu

    return 0;
}

Aliasing typu std::function może być pomocny dla czytelności:

using ValidateFunctionRaw = bool(*)(int, int); // wpisz alias do surowego wskaźnika funkcji
using ValidateFunction = std::function<bool(int, int)>; // wpisz alias do std::function

Pamiętaj również, że std::function pozwala na wywołanie funkcji tylko poprzez niejawne wyłuskanie (np. fcnPtr()), a nie jawne wyłuskanie (np. (*fcnPtr)()).

Podczas definiowania aliasu typu musimy jawnie określić argumenty szablonu. Nie możemy w tym przypadku użyć CTAD, ponieważ nie ma inicjatora, z którego można by wydedukować argumenty szablonu.

Wnioskowanie o typie dla wskaźników funkcji

Podobnie jak automatyczny słowo kluczowe może być użyte do wywnioskowania typu normalnych zmiennych, automatyczny słowo kluczowe może również wnioskować o typie wskaźnika funkcji.

#include <iostream>

int foo(int x)
{
	return x;
}

int main()
{
	auto fcnPtr{ &foo };
	std::cout << fcnPtr(5) << '\n';

	return 0;
}

Działa to dokładnie tak, jak można się spodziewać, a składnia jest bardzo przejrzysta. Minusem jest oczywiście to, że wszystkie szczegóły dotyczące typów parametrów funkcji i typu zwracanych wartości są ukryte, więc łatwiej jest popełnić błąd podczas wywoływania funkcji lub używania jej zwracanej wartości.

Wnioski

Wskaźniki funkcji są przydatne przede wszystkim wtedy, gdy chcesz do przechowywania funkcji w tablicy (lub innej strukturze) lub gdy musisz przekazać funkcję do innej funkcji, ponieważ natywna składnia deklarowania wskaźników funkcji jest brzydka i podatna na błędy, zalecamy użycie std::function. W miejscach, gdzie typ wskaźnika funkcji jest używany tylko raz (np. pojedynczy parametr lub zwracana wartość), std::function można użyć bezpośrednio w miejscach, gdzie typ wskaźnika funkcji jest używany wielokrotnie, lepszym wyborem jest alias typu do std::function (aby zapobiec powtarzaniu). siebie).

Czas quizu

  1. W tym quizie napiszemy wersję naszego podstawowego kalkulatora wykorzystującego wskaźniki funkcji.

1a) Stwórz krótki program proszący użytkownika o podanie dwóch liczb całkowitych i wykonanie operacji matematycznej („+”, „-”, „*”, „/”). Upewnij się, że użytkownik wprowadził poprawną operację.

Pokaż rozwiązanie

1b) Napisz funkcje o nazwach add(), subtract(), multiply() i dziel(). Powinny one przyjmować dwa parametry całkowite i zwracać liczbę całkowitą.

Pokaż rozwiązanie

1c) Utwórz alias typu o nazwie ArithmeticFunction dla wskaźnika do funkcji, która przyjmuje dwa parametry całkowite i zwraca liczbę całkowitą. Użyj std::function i dołącz odpowiedni nagłówek.

Pokaż rozwiązanie

1d) Napisz funkcję o nazwie getArithmeticFunction(), która przyjmuje znak operatora i zwraca odpowiednią funkcję jako wskaźnik funkcji.

Pokaż rozwiązanie

1e) Zmodyfikuj funkcję main(), aby wywoływała getArithmeticFunction(). Wywołaj wartość zwracaną przez tę funkcję za pomocą swoich danych wejściowych i wydrukuj wynik.

Pokaż rozwiązanie

Oto pełny program:

Pokaż rozwiązanie

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
813 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze