Wskaźnik funkcji Typedef?

458

Uczę się, jak dynamicznie ładować biblioteki DLL, ale nie rozumiem tej linii

typedef void (*FunctionFunc)();

Mam parę pytań. Gdyby ktoś był w stanie na nie odpowiedzieć, byłbym wdzięczny.

  1. Dlaczego jest typedefużywany?
  2. Składnia wygląda dziwnie; po voidczy nie powinna być nazwa funkcji czy coś? Wygląda jak funkcja anonimowa.
  3. Czy wskaźnik funkcji został utworzony w celu przechowywania adresu pamięci funkcji?

W tej chwili jestem zdezorientowany; czy możesz mi wyjaśnić?

Jack Harvin
źródło
5
Spójrz na link (ostatnia sekcja) learncpp.com/cpp-tutorial/78-function-pointers
entuzjastycznie
9
Należy zauważyć, że ponieważ using FunctionFunc = void (*)();zamiast tego można użyć c ++ 11 . Jest nieco bardziej jasne, że właśnie
ogłaszasz
aby dodać do @ user362515, dla mnie nieco bardziej przejrzysta forma to:using FunctionFunc = void(void);
topspin
@topspin IIRC te dwa nie są takie same. Jeden to typ wskaźnika funkcji, drugi to typ funkcji. Istnieje niejawna konwersja, dlatego działa, IANA (C ++) L, więc można mnie wkroczyć i poprawić. W każdym razie, jeśli zamierzamy zdefiniować typ wskaźnika, myślę, że składnia z *jest nieco bardziej wyraźna.
user362515

Odpowiedzi:

463

typedefto konstrukcja języka, która wiąże nazwę z typem.
Używasz go na przykład w taki sam sposób, jak oryginalnego typu

  typedef int myinteger;
  typedef char *mystring;
  typedef void (*myfunc)();

używając ich jak

  myinteger i;   // is equivalent to    int i;
  mystring s;    // is the same as      char *s;
  myfunc f;      // compile equally as  void (*f)();

Jak widać, możesz po prostu zastąpić nazwę pisma maszynowego jej definicją podaną powyżej.

Trudność polega na wskaźniku składni funkcji i czytelności w C i C ++, a także typedefmoże poprawić czytelność takich deklaracji. Jednak składnia jest odpowiednia, ponieważ funkcje - w przeciwieństwie do innych prostszych typów - mogą mieć zwracaną wartość i parametry, a zatem czasem długą i złożoną deklarację wskaźnika do funkcji.

Czytelność może być naprawdę trudna w przypadku wskaźników do tablic funkcji i niektórych innych, nawet bardziej pośrednich smaków.

Aby odpowiedzieć na trzy pytania

  • Dlaczego używany jest typedef? Aby ułatwić odczyt kodu - szczególnie w przypadku wskaźników do funkcji lub nazw struktur.

  • Składnia wygląda dziwnie (we wskaźniku do deklaracji funkcji) Ta składnia nie jest oczywista do odczytania, przynajmniej na początku. Użycie typedefdeklaracji zamiast tego ułatwia odczyt

  • Czy wskaźnik funkcji został utworzony w celu przechowywania adresu pamięci funkcji? Tak, wskaźnik funkcji przechowuje adres funkcji. Nie ma to nic wspólnego z typedefkonstrukcją, która jedynie ułatwia pisanie / czytanie programu; kompilator po prostu rozszerza definicję typedef przed skompilowaniem rzeczywistego kodu.

Przykład:

typedef int (*t_somefunc)(int,int);

int product(int u, int v) {
  return u*v;
}

t_somefunc afunc = &product;
...
int x2 = (*afunc)(123, 456); // call product() to calculate 123*456
e2-e4
źródło
6
w ostatnim przykładzie, „kwadrat” nie odnosi się do tej samej rzeczy, tj. wskaźnika do funkcji zamiast używania & square.
pranavk
2
Pytanie, w pierwszym przykładzie typedef masz formy typedef type alias, ale z wskazówki funkcjonują tylko wydaje się być 2 argumenty typedef type. Czy domyślny alias ma nazwę podaną w argumencie type?
dchhetri
2
@pranavk: Tak squarei &square(i rzeczywiście *squarei **square) odnoszą się do tego samego wskaźnika funkcji.
Jonathan Leffler
6
@ user814628: Nie jest jasne, o co pytasz. Z typedef int newnametworzysz newnamealias dla int. Za pomocą typedef int (*func)(int)przekształcasz funcsię w alias int (*)(int)- wskaźnik do funkcji, który przyjmuje intargument i zwraca intwartość.
Jonathan Leffler
10
Chyba po prostu mylę się co do zamawiania. W typedef int (*func)(int)rozumiem, że func jest aliasem, tylko trochę pomieszanym, ponieważ alias jest splątany z typem. Przechodząc typedef int INTjako przykład, byłbym bardziej swobodny, gdyby wskaźnik funkcji typedef miał formę typedef int(*function)(int) FUNC_1. W ten sposób widzę typ i alias w dwóch osobnych tokenach zamiast w siatce.
dchhetri
188
  1. typedefjest używany do typów aliasów; w tym przypadku jesteś alias FunctionFuncdo void(*)().

  2. Rzeczywiście składnia wygląda dziwnie, spójrz na to:

    typedef   void      (*FunctionFunc)  ( );
    //         ^                ^         ^
    //     return type      type name  arguments
  3. Nie, to po prostu mówi kompilatorowi, że FunctionFunctyp będzie wskaźnikiem funkcji, nie definiuje go, jak poniżej:

    FunctionFunc x;
    void doSomething() { printf("Hello there\n"); }
    x = &doSomething;
    
    x(); //prints "Hello there"
Jacob Relkin
źródło
27
typedefczy nie zadeklarować nowy typ. możesz mieć wiele typedefzdefiniowanych nazw tego samego typu i nie są one różne (np. wrt. przeciążenie funkcji). istnieją pewne okoliczności, w których pod względem sposobu używania nazwy typedef-definowana nazwa nie jest dokładnie równoważna z definicją, ale wielokrotne typedefzdefiniowane dla niej nazwy są równoważne.
Pozdrawiam i hth. - Alf
Ach, rozumiem teraz. Dzięki.
Jack Harvin
4
+1 za odpowiedź na pytanie 2 - bardzo jasne. Pozostałe odpowiedzi też były jasne, ale ta wyróżnia się dla mnie :-)
Michael van der Westhuizen,
33

Bez typedefsłowa, w C ++ deklaracja deklarowałaby zmienną FunctionFunctypu wskaźnik do funkcji bez argumentów, zwracając void.

Z typedefto zamiast definiuje FunctionFuncjako nazwa dla tego typu.

Pozdrawiam i hth. - Alf
źródło
5
To naprawdę dobry sposób, aby o tym pomyśleć. Jeśli uważnie przeczytasz inne odpowiedzi, tak naprawdę nie rozwiązują one pomyłki PO dotyczącej splątania aliasu i nazwy. Nauczyłem się czegoś nowego, czytając ten post.
Szalony fizyk
9

Jeśli możesz używać C ++ 11, możesz chcieć użyć std::functioni usingsłowa kluczowego.

using FunctionFunc = std::function<void(int arg1, std::string arg2)>;
Halil Kaskavalci
źródło
1
bez usingsłowa kluczowego C ++ 11 byłoby typedef std::function<void(int, std::string)> FunctionFunc;na wypadek, gdyby ktoś chciał innego opakowania funkcji bez C ++ 11
Top-Master
2
#include <stdio.h>
#include <math.h>

/*
To define a new type name with typedef, follow these steps:
1. Write the statement as if a variable of the desired type were being declared.
2. Where the name of the declared variable would normally appear, substitute the new type name.
3. In front of everything, place the keyword typedef.
*/

// typedef a primitive data type
typedef double distance;

// typedef struct 
typedef struct{
    int x;
    int y;
} point;

//typedef an array 
typedef point points[100]; 

points ps = {0}; // ps is an array of 100 point 

// typedef a function
typedef distance (*distanceFun_p)(point,point) ; // TYPE_DEF distanceFun_p TO BE int (*distanceFun_p)(point,point)

// prototype a function     
distance findDistance(point, point);

int main(int argc, char const *argv[])
{
    // delcare a function pointer 
    distanceFun_p func_p;

    // initialize the function pointer with a function address
    func_p = findDistance;

    // initialize two point variables 
    point p1 = {0,0} , p2 = {1,1};

    // call the function through the pointer
    distance d = func_p(p1,p2);

    printf("the distance is %f\n", d );

    return 0;
}

distance findDistance(point p1, point p2)
{
distance xdiff =  p1.x - p2.x;
distance ydiff =  p1.y - p2.y;

return sqrt( (xdiff * xdiff) + (ydiff * ydiff) );
}
Amjad
źródło
1
W jakich okolicznościach potrzebne jest „&”? Na przykład, czy „func_p = & findDistance” i „func_p = findDistance” działają równie dobrze?
Donna
1
Nazwa funkcji jest wskaźnikiem, więc nie musisz używać z nią „&”. Innymi słowy, „&” jest opcjonalne, gdy uwzględniane są wskaźniki funkcji. Jednak w przypadku prymitywnego typu danych należy użyć znaku „&”, aby uzyskać jego adres.
Amjad
1
Po prostu zawsze wydawało się dziwne, że „i” może być opcjonalne. Jeśli do utworzenia wskaźnika do typedef (*double) poperacji podstawowej użyty zostanie typedef (tj. Czy opcja „&” jest opcjonalna?
Donna,
Myślę, że chciałeś wpisać, typedef double* paby zdefiniować wskaźnik do podwójnego. Jeśli chcesz wypełnić pwskaźnik pierwotną zmienną, musisz użyć „&”. Uwaga dodatkowa: my * <nazwa wskaźnika>, aby wyrejestrować wskaźnik. *Jest używana w funkcji wskaźnika do nieprawidłowego wskaźnika (nazwy funkcji) i przejdź do bloku pamięci, gdzie znajdują się instrukcje funkcyjne.
Amjad
2

Ogólny przypadek składni można znaleźć w załączniku A normy ANSI C. .

Stamtąd w formularzu Backus-Naur można zobaczyć, że typedefma on typ storage-class-specifier.

W typie declaration-specifierswidać, że można mieszać wiele typów specyfikatorów, których kolejność nie ma znaczenia.

Na przykład poprawne jest powiedzenie

long typedef long a;

zdefiniować typ ajako alias dla long long. Tak więc, aby zrozumieć typedef wyczerpującego użycia, musisz skonsultować się z pewną formą backus-naur, która definiuje składnię (istnieje wiele poprawnych gramatyk dla ANSI C, nie tylko ISO).

Kiedy używasz typedef do zdefiniowania aliasu dla typu funkcji, musisz umieścić alias w tym samym miejscu, w którym umieścisz identyfikator funkcji. W twoim przypadku definiujesz typ FunctionFuncjako alias wskaźnika, który działa, którego sprawdzanie typu jest wyłączone podczas połączenia i nic nie zwraca.

Alinsoar
źródło