2016-02-26 27 views
18

Ho un problema con la comprensione di alcune sintassi C++ in combinazione con i puntatori a funzione e le dichiarazioni di funzione, che è:Spiegazione dei puntatori a funzione

Di solito quando si vuole dichiarare un tipo di funzione che facciamo qualcosa di simile:

typedef void(*functionPtr)(int); 

e questo va bene per me. D'ora in poi, functionPtr è un tipo, che rappresenta il puntatore alla funzione, che restituisce void e prende int con un valore come argomento.

Possiamo usarlo come segue:

typedef void(*functionPtr)(int); 

void function(int a){ 
    std::cout << a << std::endl; 
} 

int main() { 

    functionPtr fun = function; 
    fun(5); 

    return 0; 
} 

e otteniamo 5 stampata su uno schermo.

abbiamo il puntatore alla funzione fun, assegniamo un puntatore alla funzione esistente - function e eseguiamo questa funzione con un puntatore. Freddo.

Ora come ho letto in alcuni libri, la funzione e il puntatore alla funzione sono trattati in qualche modo uguali, quindi in effetti dopo la dichiarazione della funzione function() ogni volta che diciamo funzione intendiamo funzione reale e puntatore per funzionare allo stesso tipo, quindi compila e ogni istruzione dà lo stesso risultato (5 stampate su uno schermo):

int main() { 

    functionPtr fun = function; 
    fun(5); 
    (*fun)(5); 
    (*function)(5); 
    function(5); 

    return 0; 
} 

Così ora il tempo che posso immaginare, che puntatori a funzioni e le funzioni sono praticamente la stessa, allora è in qualche modo bene per me .

Poi, però, se puntatore a funzione e la funzione reale è lo stesso, quindi perché non posso facciamo seguire:

typedef void(functionPtr)(int); //removed * 

void function(int a){ 
    std::cout << a << std::endl; 
} 

int main() { 

    functionPtr fun = function; 
    fun(5); 

    return 0; 
} 

Questo mi dà seguente errore:

prog.cpp:12:14: warning: declaration of 'void fun(int)' has 'extern' and is initialized functionPtr fun = function;

quindi ho capito, che per qualche motivo il compilatore ora capisce, che il divertimento è già esistente nella funzione. Poi ho provato a seguire:

E ho avuto errore di collegamento. In qualche modo capisco, che come il compilatore ora tratta il divertimento come funzione già esistente, quindi a causa del fatto che il divertimento non è definito da nessuna parte, avrò un errore di collegamento. Quindi ho cambiato il nome della variabile:

typedef void(functionPtr)(int); 

void function(int a){ 
    std::cout << a << std::endl; 
} 

int main() { 

    functionPtr function; 
    function(5); 

    return 0; 
} 

Così ora la funzione nelle ombre principali funzioni nome globale, in modo da function(5) è utilizzato dalle dichiarazione functionPtr function; funziona benissimo e stampe 5 sullo schermo.

Quindi ora sono scioccato. Perché è successo?Inoltre cosa ingannevole è, che quando puntatore a funzione è dichiarata in questo modo:

typedef void(*functionPtr)(int); 

posso creare funzione del tipo functionPtr in modo seguente:

functionPtr function(int a){ 
    std::cout << a << std::endl; 
} 

mentre, quando si dichiara qualcosa come:

typedef void(functionPtr)(int); 

rende questo:

functionPtr function(int a){ 
    std::cout << a << std::endl; 
} 

interpretato da un compilatore come funzione che restituisce la funzione. Se è così, perché la dichiarazione precedente (typedef void(functionPtr)(int);) sapeva che questa è una funzione che restituisce il vuoto e non la funzione che restituisce functionPtr?

Qualcuno potrebbe spiegare cosa sta veramente accadendo per me?

Sto utilizzando il compilatore g ++ C++ con l'opzione C++ 14 abilitata.

risposta

6

Beh, è ​​un po 'confuso.

Il tipo di funzione e il puntatore al tipo di funzione sono in effetti due tipi diversi (non più simili a int e puntatore a int). Tuttavia, esiste una regola per cui una funzione tipo decompone per puntare al tipo di funzione in quasi tutti i contesti. Qui decadente significa approssimativamente convertito (c'è una differenza tra la conversione del tipo e il decadimento, ma probabilmente non ti interessa in questo momento).

Ciò che è importante, è che quasi ogni volta che si utilizza un tipo di funzione, si finisce con il puntatore al tipo di funzione. Si noti il ​​quasi, tuttavia - quasi ogni volta non è sempre!

E in alcuni casi si verificano casi in cui non è così.

typedef void(functionPtr)(int); 
functionPtr fun = function; 

Questo codice tenta di copiare una funzione (non il puntatore! La funzione!) In un'altra. Ma ovviamente, questo non è possibile - non è possibile copiare le funzioni in C++. Il compilatore non lo consente, e non riesco a credere che tu ce l'ha compilato (che stai dicendo che hai errori del linker?)

Ora, questo codice:

typedef void(functionPtr)(int); 
functionPtr function; 
function(5); 

function non shadow nulla. Il compilatore sa che non è un puntatore a funzione che può essere chiamato e chiama semplicemente l'originale function.

+0

questo codice non è stato compilato. Il pezzo di codice, cioè: 'functionPtr fun; fun (5); '(senza assegnazione) Qui il divertimento è visto come una funzione, che esiste da qualche parte, ma collegato non può vederlo, quindi ricevo errori di collegamento – DawidPi

+0

Quindi, perché' typedef void (functionPtr) (int); 'è così e ho: functionPtr fun; divertimento(); Ottengo errori di collegamento? Vedi qui: https://ideone.com/e20FmU – DawidPi

+0

@DawidPi A causa di, come SergeyA ha ben spiegato, quel codice non è corretto. Potrebbe funzionare solo nel caso pubblicato in questa risposta dove è implementata la funzione 'function'. Nel tuo caso il divertimento non lo è. – LPs

3

Vediamo i vostri esempi uno per uno e cosa significano realmente.

typedef void(functionPtr)(int); 

void function(int a){ 
    std::cout << a << std::endl; 
} 

int main() { 

    functionPtr fun = function; 
    fun(5); 

    return 0; 
} 

Qui si sta creando un typedef functionPtr per le funzioni che prendono e int, e non restituiscono valori. functionPtr non è in realtà un typedef per un puntatore a funzione, ma di una funzione effettiva.

Quindi si sta tentando di dichiarare una nuova funzione fun e assegnarvi function. Purtroppo non è possibile assegnare alle funzioni, quindi non funziona.

int main() { 

    functionPtr fun; 
    fun(5); 

    return 0; 
} 

Anche in questo caso, si dichiara una funzione fun con la firma specificata. Ma non lo definisci, quindi giustamente fallisci la fase di collegamento.

typedef void(functionPtr)(int); 

void function(int a){ 
    std::cout << a << std::endl; 
} 

int main() { 

    functionPtr function; 
    function(5); 

    return 0; 
} 

Cosa succede qui? Si definisce il typedef e nel principale si scrive functionPtr function;. Questo in pratica è semplicemente un prototipo della funzione che hai già scritto, function. Riporta che questa funzione esiste, ma altrimenti non fa nulla. Infatti, puoi scrivere:

typedef void(functionPtr)(int); 

void function(int a){ 
    std::cout << a << std::endl; 
} 

int main() { 

    functionPtr function; 
    functionPtr function; 
    functionPtr function; 
    void function(int); 

    function(5); 

    return 0; 
} 

Quante volte vuoi, non cambierà nulla. Lo function(5) che si sta chiamando è sempre la stessa cosa.

Una cosa che puoi fare in C++ è dichiarare il prototipo di una funzione usando un typedef simile, ma non puoi definirlo in quel modo.

typedef void(*functionPtr)(int); 

functionPtr function(int a){ 
    std::cout << a << std::endl; 
} 

Qui si sta definendo una funzione che restituisce un puntatore a funzione, ma poi non restituirlo. Il compilatore, a seconda delle impostazioni, può o non può lamentarsi. Ma ancora function è completamente separato da functionPtr. Hai scritto essenzialmente

void (*)(int) function(int a) { 
    ... 
} 

L'ultimo esempio, quello in cui si dispone di una funzione che restituisce una funzione, è semplicemente non ammessi, perché sarebbe privo di significato.

4

Il più interessante dei tuoi esempi è questo, qui riprodotta senza l'uso di typedef:

void function(int a) { // declaration and definition of 'function' 
    std::cout << a << std::endl; 
} 

int main() { 
    void function(int); // declaration of 'function' 
    function(5); 
} 

Nella maggior parte dei contesti in C++, la ri-dichiarazione di function in ambito locale avrebbe ombra globale ::function. Quindi aspettarsi un errore di linker ha senso - main()::function non ha una definizione giusta?

Tranne le funzioni sono speciali in questo senso. Da una nota in [basic.scope.pdel]:

Function declarations at block scope and variable declarations with the extern specifier at block scope refer to declarations that are members of an enclosing namespace, but they do not introduce new names into that scope.

Affinché esempio di codice è esattamente equivalente a:

void function(int a) { /* ... */ } 
void function(int); // just redeclaring it again, which is ok 

int main() { 
    function(5); 
} 

È inoltre possibile verificare questo mettendo globale function in qualche namespace, N . A questo punto, la dichiarazione dell'ambito locale aggiungerebbe un nome a ::, ma non avrebbe una definizione - quindi si ottiene un errore di linker.


L'altra cosa interessante toccato è la nozione di funzione-to-pointer conversione, [conv.func]:

An lvalue of function type T can be converted to a prvalue of type “pointer to T”. The result is a pointer to the function.

Quando si ha un'espressione funzione di chiamata - se la cosa si sta chiamando è un tipo di funzione, è prima convertito in una funzione di puntatore a. Ecco perché questi sono equivalenti:

fun(5);   // OK, call function pointed to by 'fun' 
(*fun)(5);  // OK, first convert *fun back to 'fun' 
function(5); // OK, first convert to pointer to 'function' 
(*function)(5); // OK, unary* makes function get converted to a pointer 
       // which then gets dereferenced back to function-type 
       // which then gets converted back to a pointer 
-1
typedef void functionPtr (int); 

void function (int a){ 
    std::cout << a << std::endl; 
} 

int main() { 

    functionPtr *func; 
    func = function; 
    func(5); 
    return 0; 
} 

È possibile utilizzare in questo modo, avevo provato. C'è davvero una lettiera ingannevole su questo problema.

+0

Questo non risponde alla domanda * perché * questo problema si verifica. Si prega di aggiungere solo risposte diverse da quelle precedenti o aggiungere alcuni punti rilevanti. – izlin