Come spiegare i puntatori C (dichiarazione contro operatori unari) a un principiante?


141

Ho avuto il recente piacere di spiegare i suggerimenti a un principiante della programmazione in C e mi sono imbattuto nella seguente difficoltà. Potrebbe non sembrare affatto un problema se sai già come utilizzare i puntatori, ma prova a guardare il seguente esempio con una mente chiara:

int foo = 1;
int *bar = &foo;
printf("%p\n", (void *)&foo);
printf("%i\n", *bar);

Per il principiante assoluto l'output potrebbe essere sorprendente. Nella riga 2 aveva appena dichiarato * bar essere & foo, ma nella riga 4 risulta che * bar è effettivamente foo invece di & foo!

La confusione, si potrebbe dire, deriva dall'ambiguità del simbolo *: nella riga 2 viene utilizzato per dichiarare un puntatore. Nella riga 4 viene utilizzato come operatore unario che recupera il valore a cui punta il puntatore. Due cose diverse, giusto?

Tuttavia, questa "spiegazione" non aiuta affatto un principiante. Introduce un nuovo concetto sottolineando una sottile discrepanza. Questo non può essere il modo giusto per insegnarlo.

Quindi, come hanno spiegato Kernighan e Ritchie?

L'operatore unario * è l'operatore di riferimento indiretto o di dereferenziazione; quando applicato a un puntatore, accede all'oggetto a cui punta il puntatore. [...]

La dichiarazione del puntatore ip, int *ipè intesa come un mnemonico; dice che l'espressione *ipè un int. La sintassi della dichiarazione per una variabile imita la sintassi delle espressioni in cui potrebbe apparire la variabile .

int *ipdovrebbe essere letto come " *iprestituirà un int"? Ma allora perché l'assegnazione dopo la dichiarazione non segue questo schema? Cosa succede se un principiante vuole inizializzare la variabile? int *ip = 1(leggi: *iprestituirà an inte intis 1) non funzionerà come previsto. Il modello concettuale non sembra coerente. Mi sto perdendo qualcosa qui?


Modifica: ha provato a sintetizzare le risposte qui .


15
La migliore spiegazione è disegnare le cose su un foglio e collegarle con le frecce;)
Maroun,

16
Quando ho dovuto spiegare la sintassi dei puntatori, ho sempre insistito sul fatto che *in una dichiarazione è un token che significa "dichiarare un puntatore", nelle espressioni è l'operatore di dereference e che questi due rappresentano cose diverse che hanno lo stesso simbolo (uguale all'operatore di moltiplicazione - stesso simbolo, significato diverso). È confuso, ma qualsiasi cosa diversa dall'attuale situazione sarà ancora peggio.
Matteo Italia,

40
forse scriverlo come int* barrende più ovvio che la stella è effettivamente parte del tipo, non parte dell'identificatore. Naturalmente questo ti fa incontrare diversi problemi con cose non intuitive come int* a, b.
Niklas B.

9
Ho sempre pensato che la spiegazione di K&R sia sciocca e inutile. Il linguaggio usa lo stesso simbolo per due cose diverse e dobbiamo solo affrontarlo. *può avere due significati diversi a seconda del contesto. Proprio come la stessa lettera può essere pronunciata in modo diverso a seconda della parola in cui è difficile imparare a parlare molte lingue. Se ogni singolo concetto / operazione avesse il suo simbolo avremmo bisogno di tastiere molto più grandi, quindi i simboli vengono riciclati quando ha senso farlo.
Art

8
Ho incontrato lo stesso problema molte volte durante l'insegnamento del C ad altri e, nella mia esperienza, può essere risolto come la maggior parte delle persone qui ha suggerito. Innanzitutto, spiega il concetto di puntatore senza la sintassi C. Quindi, insegna la sintassi e sottolinea l'asterisco come parte del tipo ( int* p), mentre avverti lo studente di non utilizzare più dichiarazioni nella stessa riga quando sono coinvolti i puntatori. Quando lo studente ha compreso completamente il concetto di puntatore, spiega allo studente che la int *psintassi is è equivalente e quindi spiega il problema con più dichiarazioni.
Theodoros Chatzigiannakis,

Risposte:


43

Affinché lo studente capisca il significato del *simbolo in contesti diversi, deve prima capire che i contesti sono effettivamente diversi. Una volta capito che i contesti sono diversi (ovvero la differenza tra il lato sinistro di un compito e un'espressione generale) non è un gran salto cognitivo per capire quali sono le differenze.

Spiegare innanzitutto che la dichiarazione di una variabile non può contenere operatori (dimostrarlo dimostrando che inserire un simbolo -o +in una dichiarazione di variabile causa semplicemente un errore). Quindi vai avanti per mostrare che un'espressione (cioè sul lato destro di un compito) può contenere operatori. Assicurati che lo studente capisca che un'espressione e una dichiarazione variabile sono due contesti completamente diversi.

Quando comprendono che i contesti sono diversi, puoi continuare spiegando che quando il *simbolo si trova in una dichiarazione di variabile davanti all'identificatore di variabile, significa "dichiarare questa variabile come un puntatore". Quindi puoi spiegare che quando usato in un'espressione (come operatore unario) il *simbolo è "operatore di dereferenza" e significa "il valore all'indirizzo di" piuttosto che il suo significato precedente.

Per convincere veramente il tuo studente, spiega che i creatori di C avrebbero potuto usare qualsiasi simbolo per indicare l'operatore di dereference (cioè avrebbero potuto usare @invece) ma per qualsiasi motivo abbiano preso la decisione di progettazione da usare *.

Tutto sommato, non c'è modo di spiegare che i contesti sono diversi. Se lo studente non capisce che i contesti sono diversi, non può capire perché il *simbolo può significare cose diverse.


80

Il motivo per cui la stenografia:

int *bar = &foo;

nel tuo esempio può essere fonte di confusione è che è facile interpretarlo erroneamente come equivalente a:

int *bar;
*bar = &foo;    // error: use of uninitialized pointer bar!

quando in realtà significa:

int *bar;
bar = &foo;

Scritto in questo modo, con la dichiarazione e l'assegnazione variabili separate, non esiste un tale potenziale di confusione e l'uso del parallelismo delle dichiarazioni ↔ descritto nella citazione di K&R funziona perfettamente:

  • La prima riga dichiara una variabile bar, tale che *barè un int.

  • La seconda riga assegna l'indirizzo di fooa bar, creando *bar(un int) un alias per foo(anche un int).

Quando si introduce la sintassi del puntatore C per i principianti, può essere utile attenersi inizialmente a questo stile di separazione delle dichiarazioni del puntatore dalle assegnazioni e introdurre la sintassi abbreviata combinata (con avvertenze appropriate sul suo potenziale di confusione) una volta che i concetti di base del puntatore vengono utilizzati in C sono stati adeguatamente interiorizzati.


4
Sarei tentato di farlo typedef. typedef int *p_int;significa che una variabile di tipo p_intha la proprietà che *p_intè un int. Quindi abbiamo p_int bar = &foo;. Incoraggiare chiunque a creare dati non inizializzati e assegnarli successivamente come abitudine predefinita sembra ... una cattiva idea.
Yakk - Adam Nevraumont,

6
Questo è solo lo stile cerebrale danneggiato delle dichiarazioni C; non è specifico per i puntatori. considera int a[2] = {47,11};che questa non è a[2]un'inizializzazione dell'elemento (inesistente) eiher.
Marc van Leeuwen,

5
@MarcvanLeeuwen D'accordo con il danno cerebrale. Idealmente, *dovrebbe essere parte del tipo, non associato alla variabile, e quindi si sarebbe in grado di scrivere int* foo_ptr, bar_ptrper dichiarare due puntatori. Ma in realtà dichiara un puntatore e un numero intero.
Barmar,

1
Non si tratta solo di dichiarazioni / assegnazioni "stenografiche". L'intero problema si ripresenta nel momento in cui si desidera utilizzare i puntatori come argomenti di funzione.
Armin,

30

Dichiarazioni brevi

È bello conoscere la differenza tra dichiarazione e inizializzazione. Dichiariamo le variabili come tipi e le inizializziamo con valori. Se facciamo entrambi allo stesso tempo, spesso lo chiamiamo una definizione.

1. int a; a = 42;

int a;
a = 42;

Noi dichiariamo un intnome un . Quindi lo inizializziamo dandogli un valore 42.

2. int a = 42;

Noi dichiariamo e intchiamato un e dare il valore 42. E 'inizializzato con 42. Una definizione.

3. a = 43;

Quando usiamo le variabili diciamo di operare su di esse. a = 43è un'operazione di assegnazione. Assegniamo il numero 43 alla variabile a.

Dicendo

int *bar;

dichiariamo che la barra è un puntatore a un int. Dicendo

int *bar = &foo;

dichiariamo bar e inizializziamo con l'indirizzo di foo .

Dopo aver inizializzato la barra , possiamo usare lo stesso operatore, l'asterisco, per accedere e operare sul valore di pippo . Senza l'operatore accediamo e operiamo sull'indirizzo a cui punta il puntatore.

Oltre a ciò ho lasciato parlare l'immagine.

Che cosa

Un'ASCIIMAZIONE semplificata su ciò che sta succedendo. (E qui una versione del lettore se si desidera mettere in pausa ecc.)

          ASCIIMATION


22

La seconda affermazione int *bar = &foo;può essere vista in modo pittorico in memoria come

   bar           foo
  +-----+      +-----+
  |0x100| ---> |  1  |
  +-----+      +-----+ 
   0x200        0x100

Ora barè un puntatore di tipo intcontenente l'indirizzo &di foo. Usando l'operatore unario *deferiamo per recuperare il valore contenuto in "pippo" usando il puntatore bar.

EDIT : Il mio approccio con i principianti è quello di spiegare la memory addressvariabile ie

Memory Address:A ogni variabile è associato un indirizzo fornito dal sistema operativo. In int a;, &aè l'indirizzo della variabile a.

Continua a spiegare i tipi base di variabili in Cas,

Types of variables: Le variabili possono contenere valori dei rispettivi tipi ma non indirizzi.

int a = 10; float b = 10.8; char ch = 'c'; `a, b, c` are variables. 

Introducing pointers: Come detto sopra variabili, per esempio

 int a = 10; // a contains value 10
 int b; 
 b = &a;      // ERROR

È possibile assegnare b = ama non b = &a, poiché la variabile bpuò contenere valore ma non indirizzo, quindi abbiamo bisogno di puntatori .

Pointer or Pointer variables :Se una variabile contiene un indirizzo, è nota come variabile puntatore. Utilizzare *nella dichiarazione per informare che si tratta di un puntatore.

 Pointer can hold address but not value
 Pointer contains the address of an existing variable.
 Pointer points to an existing variable

3
Il problema è che leggendo int *ipcome "ip è un puntatore (*) di tipo int" ci si mette nei guai quando si legge qualcosa di simile x = (int) *ip.
Armin,

2
@abw È qualcosa di completamente diverso, quindi le parentesi. Non credo che le persone avranno difficoltà a comprendere la differenza tra dichiarazioni e casting.
bzeaman,

@abw In x = (int) *ip;, ottieni il valore dereferenziando il puntatore ipe lancia il valore intda qualunque tipo ipsia.
Sunil Bojanapally,

1
@BennoZeeman Hai ragione: casting e dichiarazioni sono due cose diverse. Ho cercato di accennare al diverso ruolo dell'asterisco: 1st "questo non è un int, ma un puntatore a int" 2nd "questo ti darà l'int, ma non il puntatore a int".
Armin,

2
@abw: Quale è il motivo per cui l'insegnamento int* bar = &foo;rende carichi più senso. Sì, lo so che provoca problemi quando si dichiarano più puntatori in una singola dichiarazione. No, non penso che sia importante.
Razze di leggerezza in orbita,

17

Guardando le risposte e i commenti qui, sembra esserci un accordo generale sul fatto che la sintassi in questione possa creare confusione per un principiante. Molti di loro propongono qualcosa di simile:

  • Prima di mostrare qualsiasi codice, usa diagrammi, schizzi o animazioni per illustrare come funzionano i puntatori.
  • Quando si presenta la sintassi, spiegare i due diversi ruoli del simbolo dell'asterisco . Molti tutorial mancano o eludono quella parte. Segue la confusione ("Quando si interrompe una dichiarazione del puntatore inizializzata in una dichiarazione e in un compito successivo, è necessario ricordare di rimuovere le *" - FAQ comp.lang.c ) Speravo di trovare un approccio alternativo, ma immagino che sia la strada da percorrere.

Puoi scrivere int* barinvece di int *barevidenziare la differenza. Ciò significa che non seguirai l'approccio K&R "dichiarazione imita l'uso", ma l' approccio Stroustrup C ++ :

Non dichiariamo *bardi essere un numero intero. Dichiariamo bardi essere un int*. Se vogliamo inizializzare una variabile appena creata nella stessa riga, è chiaro che stiamo trattando bar, non *bar.int* bar = &foo;

Gli svantaggi:

  • Devi avvisare il tuo studente del problema relativo alla dichiarazione con più puntatori ( int* foo, barvs int *foo, *bar).
  • Devi prepararli per un mondo di dolore . Molti programmatori vogliono vedere l'asterisco accanto al nome della variabile e impiegheranno molto per giustificare il loro stile. E molte guide di stile applicano esplicitamente questa notazione (stile di codifica del kernel Linux, Guida allo stile della NASA C, ecc.).

Modifica: un approccio diverso che è stato suggerito è quello di seguire il modo "mimico" di K&R, ma senza la sintassi "abbreviata" (vedi qui ). Non appena ometti di fare una dichiarazione e un incarico nella stessa riga , tutto sembrerà molto più coerente.

Tuttavia, prima o poi lo studente dovrà trattare i puntatori come argomenti di funzione. E puntatori come tipi di ritorno. E puntatori a funzioni. Dovrai spiegare la differenza tra int *func();e int (*func)();. Penso che prima o poi le cose andranno in pezzi. E forse prima è meglio che dopo.


16

C'è un motivo per cui i favori di stile K&R int *pe quelli di Stroustrup int* p; entrambi sono validi (e significano la stessa cosa) in ogni lingua, ma come ha detto Stroustrup:

La scelta tra "int * p;" e "int * p;" non si tratta di giusto e sbagliato, ma di stile ed enfasi. C espressioni enfatizzate; le dichiarazioni erano spesso considerate poco più che un male necessario. Il C ++, d'altra parte, ha una forte enfasi sui tipi.

Ora, dal momento che stai cercando di insegnare C qui, ciò suggerirebbe che dovresti enfatizzare le espressioni più di quei tipi, ma alcune persone possono più facilmente individuare un'enfasi più rapidamente dell'altra, e riguarda loro piuttosto che la lingua.

Pertanto alcune persone troveranno più facile iniziare con l'idea che una int*sia una cosa diversa da una inte che vada da lì.

Se qualcuno cerca rapidamente il modo di vederlo che int* bardeve avere baruna cosa che non è un int, ma un puntatore a int, allora vedranno rapidamente che *barsta facendo qualcosa per bar, e il resto seguirà. Una volta fatto ciò, puoi in seguito spiegare perché i programmatori C tendono a preferire int *bar.

O no. Se ci fosse un modo in cui tutti hanno capito per prima cosa il concetto non avresti avuto problemi in primo luogo, e il modo migliore per spiegarlo a una persona non sarà necessariamente il modo migliore per spiegarlo a un altro.


1
Mi piace l'argomentazione di Stroustrup, ma mi chiedo perché abbia scelto il simbolo & per indicare riferimenti - un'altra possibile trappola.
Armin,

1
@abw Penso che abbia visto la simmetria se possiamo fare, int* p = &aallora possiamo fare int* r = *p. Sono abbastanza sicuro che l'abbia trattato in The Design and Evolution of C ++ , ma è da molto tempo che non lo leggo e ho stupidamente appoggiato la mia copia a qualcuno.
Jon Hanna,

3
Immagino tu intenda int& r = *p. E scommetto che il mutuatario sta ancora cercando di digerire il libro.
Armin,

@abw, sì, era esattamente quello che volevo dire. Purtroppo i refusi nei commenti non generano errori di compilazione. Il libro è in realtà una lettura piuttosto vivace.
Jon Hanna,

4
Uno dei motivi per cui prediligo la sintassi di Pascal (come popolarmente estesa) su C è che Var A, B: ^Integer;chiarisce che il tipo "puntatore a numero intero" si applica sia a Ache a B. L'uso di uno K&Rstile int *a, *bè anche praticabile; ma una dichiarazione del genere int* a,b;, tuttavia, sembra come se ae bsono entrambi dichiarati come int*, ma in realtà si dichiara acome int*e bcome int.
Supercat,

9

tl; dr:

D: Come spiegare i puntatori C (dichiarazione contro operatori unari) a un principiante?

A: non farlo. Spiega i puntatori al principiante e mostra loro come rappresentare i loro concetti di puntatore nella sintassi C dopo.


Ho avuto il recente piacere di spiegare i suggerimenti a un principiante della programmazione in C e mi sono imbattuto nella seguente difficoltà.

La sintassi C di IMO non è terribile, ma non è nemmeno meravigliosa: non è né un grande ostacolo se hai già capito i puntatori, né alcun aiuto nell'apprenderli.

Pertanto: inizia spiegando i puntatori e assicurati che li comprendano davvero:

  • Spiegali con i diagrammi di casella e freccia. Puoi farlo senza indirizzi esadecimali, se non sono rilevanti, mostra solo le frecce che puntano verso un'altra casella o verso un simbolo nullo.

  • Spiega con pseudocodice: basta scrivere l' indirizzo di foo e il valore memorizzato nella barra .

  • Quindi, quando il tuo principiante capisce quali sono i puntatori, e perché e come usarli; quindi mostra la mappatura sulla sintassi C.

Ho il sospetto che il motivo per cui il testo di K&R non fornisce un modello concettuale è che hanno già compreso i suggerimenti e probabilmente presumevano che anche tutti gli altri programmatori competenti all'epoca lo facessero. Il mnemonico è solo un promemoria della mappatura dal concetto ben compreso, alla sintassi.


Infatti; Inizia prima con la teoria, la sintassi viene dopo (e non è importante). Si noti che la teoria dell'uso della memoria non dipende dalla lingua. Questo modello di casella e frecce ti aiuterà con le attività in qualsiasi linguaggio di programmazione.
o

Vedi qui per alcuni esempi (anche se Google aiuterà anche) eskimo.com/~scs/cclass/notes/sx10a.html
oɔɯǝɹ

7

Questo problema è alquanto confuso quando si inizia a studiare C.

Ecco i principi di base che potrebbero aiutarti a iniziare:

  1. Esistono solo alcuni tipi di base in C:

    • char: un valore intero con dimensione di 1 byte.

    • short: un valore intero con dimensione di 2 byte.

    • long: un valore intero con dimensione di 4 byte.

    • long long: un valore intero con dimensione di 8 byte.

    • float: un valore non intero con dimensione di 4 byte.

    • double: un valore non intero con dimensione di 8 byte.

    Si noti che la dimensione di ciascun tipo è generalmente definita dal compilatore e non dallo standard.

    I tipi interi short, longe long longdi solito sono seguiti da int.

    Non è un must, tuttavia, e puoi usarli senza il int.

    In alternativa, puoi semplicemente affermare int, ma ciò potrebbe essere interpretato in modo diverso da diversi compilatori.

    Quindi per riassumere questo:

    • shortè uguale short intma non necessariamente uguale a int.

    • longè uguale long intma non necessariamente uguale a int.

    • long longè uguale long long intma non necessariamente uguale a int.

    • Su un determinato compilatore, intè short into long into long long int.

  2. Se dichiari una variabile di qualche tipo, puoi anche dichiarare un'altra variabile che la punta.

    Per esempio:

    int a;

    int* b = &a;

    Quindi, in sostanza, per ogni tipo di base, abbiamo anche un tipo di puntatore corrispondente.

    Ad esempio: shorte short*.

    Esistono due modi per "guardare" la variabile b (questo è ciò che probabilmente confonde la maggior parte dei principianti) :

    • Puoi considerare buna variabile di tipo int*.

    • Puoi considerare *buna variabile di tipo int.

    Quindi, alcune persone dichiarerebbero int* b, mentre altri dichiarerebbero int *b.

    Ma il fatto è che queste due dichiarazioni sono identiche (gli spazi sono insignificanti).

    È possibile utilizzare bcome puntatore a un valore intero o *bcome valore intero puntato effettivo.

    È possibile ottenere (lettura) il valore punta: int c = *b.

    Ed è possibile impostare (scrittura) il valore di punta: *b = 5.

  3. Un puntatore può puntare a qualsiasi indirizzo di memoria e non solo all'indirizzo di alcune variabili precedentemente dichiarate. Tuttavia, è necessario fare attenzione quando si utilizzano i puntatori per ottenere o impostare il valore situato all'indirizzo di memoria puntato.

    Per esempio:

    int* a = (int*)0x8000000;

    Qui, abbiamo variabile che apunta all'indirizzo di memoria 0x8000000.

    Se questo indirizzo di memoria non è mappato nello spazio di memoria del programma, qualsiasi operazione di lettura o scrittura che *averrà probabilmente causerà l'arresto anomalo del programma, a causa di una violazione dell'accesso alla memoria.

    Puoi tranquillamente cambiare il valore di a, ma dovresti stare molto attento a cambiare il valore di *a.

  4. Il tipo void*è eccezionale nel fatto che non ha un "tipo di valore" corrispondente che può essere utilizzato (ovvero, non è possibile dichiarare void a). Questo tipo viene utilizzato solo come puntatore generale a un indirizzo di memoria, senza specificare il tipo di dati che risiede in quell'indirizzo.


7

Forse passarci attraverso un po 'di più rende più semplice:

#include <stdio.h>

int main()
{
    int foo = 1;
    int *bar = &foo;
    printf("%i\n", foo);
    printf("%p\n", &foo);
    printf("%p\n", (void *)&foo);
    printf("%p\n", &bar);
    printf("%p\n", bar);
    printf("%i\n", *bar);
    return 0;
}

Invitali a dire cosa si aspettano che l'output sia su ogni riga, quindi far loro eseguire il programma e vedere cosa succede. Spiega le loro domande (la versione nuda lì dentro ne farà sicuramente alcune domande, ma in seguito potrai preoccuparti di stile, rigore e portabilità). Quindi, prima che la loro mente diventi pignola dal pensare troppo o diventino uno zombi dopo pranzo, scrivi una funzione che assume un valore e la stessa che prende un puntatore.

Nella mia esperienza sta superando quel "perché questo stampa in quel modo?" gobba, e quindi mostrare immediatamente perché questo è utile nei parametri di funzione giocando prontamente (come preludio ad alcuni materiali K&R di base come l'analisi delle stringhe / elaborazione dell'array) che rende la lezione non solo sensata, ma continua.

Il passo successivo è quello di ottenere loro di spiegare a voi come i[0]si riferisce a &i. Se riescono a farlo, non lo dimenticheranno e puoi iniziare a parlare di strutture, anche un po 'in anticipo sui tempi, solo così sprofonda.

Anche i consigli sopra riportati su scatole e frecce sono buoni, ma possono anche finire a divagare in una vera e propria discussione su come funziona la memoria - che è un discorso che deve accadere ad un certo punto, ma può distrarre dal punto immediatamente a portata di mano : come interpretare la notazione del puntatore in C.


Questo è un buon esercizio. Ma la questione che volevo sollevare è una sintattica specifica che potrebbe avere un impatto sul modello mentale che gli studenti costruiscono. Considerate questo: int foo = 1;. Ora, questo è OK: int *bar; *bar = foo;. Non va bene:int *bar = foo;
armin,

1
@abw L'unica cosa che ha senso è ciò che gli studenti finiscono per dirsi. Ciò significa "vedere uno, fare uno, insegnare uno". Non puoi proteggere o prevedere quale sintassi o stile vedranno là fuori nella giungla (anche i tuoi vecchi repository!), Quindi devi mostrare abbastanza permutazioni che i concetti di base siano compresi indipendentemente dallo stile - e quindi inizia a insegnare loro perché determinati stili sono stati decisi. Come insegnare l'inglese: espressione di base, modi di dire, stili, stili particolari in un determinato contesto. Purtroppo non è facile. In ogni caso, buona fortuna!
zxq9,

6

Il tipo di espressione *bar è int; quindi, il tipo di variabile (ed espressione) barè int *. Poiché la variabile ha un tipo di puntatore, anche il suo inizializzatore deve avere un tipo di puntatore.

C'è un'incoerenza tra l'inizializzazione della variabile puntatore e l'assegnazione; è solo qualcosa che deve essere appreso nel modo più duro.


3
Guardando le risposte qui ho la sensazione che molti programmatori esperti non riescano più a vedere il problema . Immagino che sia un sottoprodotto di "imparare a vivere con incoerenze".
Armin,

3
@abw: le regole per l'inizializzazione sono diverse dalle regole per l'assegnazione; per i tipi aritmetici scalari le differenze sono trascurabili, ma contano per i tipi puntatore e aggregato. È qualcosa che dovrai spiegare insieme a tutto il resto.
John Bode,

5

Preferirei leggerlo come il primo *vale per intpiù di bar.

int  foo = 1;           // foo is an integer (int) with the value 1
int* bar = &foo;        // bar is a pointer on an integer (int*). it points on foo. 
                        // bar value is foo address
                        // *bar value is foo value = 1

printf("%p\n", &foo);   // print the address of foo
printf("%p\n", bar);    // print the address of foo
printf("%i\n", foo);    // print foo value
printf("%i\n", *bar);   // print foo value

2
Quindi devi spiegare perché int* a, bnon fa quello che pensano che faccia.
Pharap,

4
Vero, ma non credo che int* a,bdovrebbe essere usato affatto. Per una migliore flessibilità, aggiornamento, ecc ... dovrebbe esserci una sola dichiarazione variabile per riga e mai più. È qualcosa da spiegare anche ai principianti, anche se il compilatore può gestirlo.
Grorel,

Questa è l'opinione di un uomo però. Ci sono milioni di programmatori là fuori che sono completamente d'accordo nel dichiarare più di una variabile per riga e lo fanno ogni giorno come parte del loro lavoro. Non puoi nascondere agli studenti modi alternativi di fare le cose, è meglio mostrare loro tutte le alternative e lasciare che decidano in che modo vogliono fare le cose perché se si assumono mai si aspettano che seguano un certo stile che possono o meno sentirsi a proprio agio con. Per un programmatore, la versatilità è una caratteristica molto buona da avere.
Pharap,

1
Sono d'accordo con @grorel. È più facile pensare *come parte del tipo e semplicemente scoraggiarlo int* a, b. A meno che non preferiate dire che *aè di tipo intpiuttosto che aun puntatore a int...
Kevin Ushey,

@grorel ha ragione: int *a, b;non dovrebbe essere usato. Dichiarare due variabili con tipi diversi nella stessa affermazione è una pratica piuttosto scadente e un valido candidato per i problemi di manutenzione. Forse è diverso per quelli di noi che lavorano nel campo incorporato dove an int*e an intsono spesso di dimensioni diverse e talvolta archiviati in posizioni di memoria completamente diverse. È uno dei tanti aspetti del linguaggio C che sarebbe meglio insegnare come "è permesso, ma non farlo".
Evil Dog Pie,

5
int *bar = &foo;

Question 1: Che cos'è bar?

Ans: È una variabile puntatore (da digitare int). Un puntatore dovrebbe puntare a una posizione di memoria valida e in seguito dovrebbe essere dereferenziato (* barra) utilizzando un operatore unario *per leggere il valore memorizzato in quella posizione.

Question 2: Che cos'è &foo?

Ans: foo è una variabile di tipo. che intè memorizzata in una posizione di memoria valida e quella posizione la prendiamo dall'operatore, &quindi ora ciò che abbiamo è una posizione di memoria valida &foo.

Quindi entrambi hanno messo insieme cioè ciò che il puntatore necessario era una posizione di memoria valida e che viene ottenuta &fooquindi l'inizializzazione è buona.

Ora il puntatore barpunta a una posizione di memoria valida e il valore memorizzato in essa può essere ottenuto dereferenziandolo, cioè*bar


5

Dovresti sottolineare a un principiante che * ha un significato diverso nella dichiarazione e nell'espressione. Come sapete, * nell'espressione è un operatore unario e * Nella dichiarazione non è un operatore e solo una specie di sintassi che si combina con il tipo per far sapere al compilatore che è un tipo di puntatore. è meglio dire un principiante, "* ha un significato diverso. Per capire il significato di *, dovresti trovare dove * è usato"


4

Penso che il diavolo sia nello spazio.

Scriverei (non solo per il principiante, ma anche per me stesso): int * bar = & foo; invece di int * bar = & foo;

questo dovrebbe rendere evidente qual è la relazione tra sintassi e semantica


4

È già stato notato che * ha più ruoli.

C'è un'altra semplice idea che può aiutare un principiante ad afferrare le cose:

Pensa che "=" abbia anche più ruoli.

Quando l'assegnazione viene utilizzata sulla stessa riga con la dichiarazione, pensala come una chiamata del costruttore, non come un'assegnazione arbitraria.

Quando vedi:

int *bar = &foo;

Pensa che sia quasi equivalente a:

int *bar(&foo);

Le parentesi hanno la precedenza sull'asterisco, quindi "& foo" è molto più facilmente attribuito intuitivamente a "bar" piuttosto che a "* bar".


4

Ho visto questa domanda qualche giorno fa, e poi mi è capitato di leggere la spiegazione della dichiarazione del tipo di Go sul blog di Go . Inizia dando un resoconto delle dichiarazioni di tipo C, che sembra una risorsa utile da aggiungere a questo thread, anche se penso che ci siano già risposte più complete fornite.

C ha adottato un approccio insolito e intelligente alla sintassi delle dichiarazioni. Invece di descrivere i tipi con sintassi speciale, si scrive un'espressione che coinvolge l'elemento che viene dichiarato e si indica quale tipo avrà quell'espressione. così

int x;

dichiara x essere un int: l'espressione 'x' avrà tipo int. In generale, per capire come scrivere il tipo di una nuova variabile, scrivere un'espressione che coinvolge quella variabile che valuta un tipo di base, quindi posizionare il tipo di base a sinistra e l'espressione a destra.

Quindi, le dichiarazioni

int *p;
int a[3];

indica che p è un puntatore a int perché '* p' ha tipo int e che a è un array di ints perché a [3] (ignorando il valore dell'indice particolare, che è punito come dimensione dell'array) ha tipo Int.

(Continua descrivendo come estendere questa comprensione ai puntatori a funzioni ecc.)

Questo è un modo a cui non ci avevo mai pensato prima, ma sembra un modo piuttosto semplice di spiegare il sovraccarico della sintassi.


3

Se il problema è la sintassi, può essere utile mostrare un codice equivalente con template / using.

template<typename T>
using ptr = T*;

Questo può quindi essere usato come

ptr<int> bar = &foo;

Successivamente, confronta la sintassi normale / C con questo solo approccio C ++. Questo è utile anche per spiegare i puntatori const.


2
Per i principianti sarà molto più confuso.
Karsten,

Il mio pensiero era che non avresti mostrato la definizione di ptr. Usalo solo per le dichiarazioni dei puntatori.
MI3 Acquista

3

La fonte di confusione deriva dal fatto che il *simbolo può avere significati diversi in C, a seconda del fatto in cui viene usato. Per spiegare il puntatore a un principiante, *dovrebbe essere spiegato il significato del simbolo in diversi contesti.

Nella dichiarazione

int *bar = &foo;  

il *simbolo non è l'operatore di riferimento indiretto . Invece, aiuta a specificare il tipo di barinformazione del compilatore che barè un puntatore a unint . D'altra parte, quando appare in un'istruzione, il *simbolo (se usato come operatore unario ) esegue una indiretta. Pertanto, la dichiarazione

*bar = &foo;

sarebbe sbagliato in quanto assegna l'indirizzo foodell'oggetto a cui barpunta, non a barse stesso.


3

"forse scriverlo come int * bar rende più ovvio che la stella è effettivamente parte del tipo, non parte dell'identificatore." Faccio così. E dico che è qualcosa di simile a Tipo, ma solo per un nome di puntatore.

"Naturalmente questo ti fa incontrare diversi problemi con cose non intuitive come int * a, b."


2

Qui devi usare, comprendere e spiegare la logica del compilatore, non la logica umana (lo so, sei un essere umano, ma qui devi imitare il computer ...).

Quando scrivi

int *bar = &foo;

il compilatore raggruppa che come

{ int * } bar = &foo;

Cioè: ecco una nuova variabile, il suo nome è bar, il suo tipo è puntatore a int e il suo valore iniziale è &foo.

E si deve aggiungere: i =denota sopra un non un vezzo di inizializzazione, mentre in seguito le espressioni *bar = 2;che è un vezzo

Modifica per commento:

Attenzione: in caso di dichiarazione multipla *è correlato solo alla seguente variabile:

int *bar = &foo, b = 2;

bar è un puntatore a int inizializzato dall'indirizzo di foo, b è un int inizializzato a 2 e in

int *bar=&foo, **p = &bar;

barra nel puntatore fermo a int e p è un puntatore a un puntatore a un int inizializzato all'indirizzo o alla barra.


2
In realtà il compilatore non lo raggruppa in questo modo: int* a, b;dichiara a di essere un puntatore a un int, ma b di essere un int. Il *simbolo ha solo due significati distinti: in una dichiarazione, indica un tipo di puntatore e in un'espressione è l'operatore di dereference unario.
luglio

@tmlen: Quello che volevo dire è che nell'inizializzazione, è rattaccato *al tipo, in modo che il puntatore sia inizializzato mentre in un'affettività il valore puntato è influenzato. Ma almeno mi hai dato un bel cappello :-)
Serge Ballesta il

0

Fondamentalmente il puntatore non è un'indicazione di array. Il principiante pensa facilmente che il puntatore assomigli ad un array. la maggior parte degli esempi di stringhe usando il

"char * pstr" è simile

"char str [80]"

Ma, cose importanti, il puntatore viene trattato come intero nel livello inferiore del compilatore.

Diamo un'occhiata agli esempi ::

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv, char **env)
{
    char str[] = "This is Pointer examples!"; // if we assume str[] is located in 0x80001000 address

    char *pstr0 = str;   // or this will be using with
    // or
    char *pstr1 = &str[0];

    unsigned int straddr = (unsigned int)pstr0;

    printf("Pointer examples: pstr0 = %08x\n", pstr0);
    printf("Pointer examples: &str[0] = %08x\n", &str[0]);
    printf("Pointer examples: str = %08x\n", str);
    printf("Pointer examples: straddr = %08x\n", straddr);
    printf("Pointer examples: str[0] = %c\n", str[0]);

    return 0;
}

I risultati apprezzeranno questo 0x2a6b7ed0 è l'indirizzo di str []

~/work/test_c_code$ ./testptr
Pointer examples: pstr0 = 2a6b7ed0
Pointer examples: &str[0] = 2a6b7ed0
Pointer examples: str = 2a6b7ed0
Pointer examples: straddr = 2a6b7ed0
Pointer examples: str[0] = T

Quindi, in sostanza, tieni presente che il puntatore è una specie di intero. presentando l'indirizzo.


-1

Spiegherei che gli ints sono oggetti, così come i float, ecc. Un puntatore è un tipo di oggetto il cui valore rappresenta un indirizzo in memoria (quindi perché un puntatore viene impostato automaticamente su NULL).

Quando si dichiara per la prima volta un puntatore, si utilizza la sintassi type-pointer-name. Viene letto come un "puntatore intero chiamato nome che può puntare all'indirizzo di qualsiasi oggetto intero". Usiamo questa sintassi solo durante la declerazione, in modo simile a come dichiariamo un int come 'int num1' ma usiamo 'num1' solo quando vogliamo usare quella variabile, non 'int num1'.

int x = 5; // un oggetto intero con un valore di 5

int * ptr; // un numero intero con un valore NULL per impostazione predefinita

Per fare in modo che un puntatore punti a un indirizzo di un oggetto, utilizziamo il simbolo '&' che può essere letto come "l'indirizzo di".

ptr = & x; // ora value è l'indirizzo di 'x'

Dato che il puntatore è solo l'indirizzo dell'oggetto, per ottenere il valore effettivo trattenuto a quell'indirizzo dobbiamo usare il simbolo '*' che quando usato prima di un puntatore significa "il valore all'indirizzo indicato da".

std :: cout << * ptr; // stampa il valore all'indirizzo

Puoi spiegare brevemente che " " è un "operatore" che restituisce risultati diversi con diversi tipi di oggetti. Se utilizzato con un puntatore, l' operatore ' ' non significa più "moltiplicato per".

Aiuta a disegnare un diagramma che mostra come una variabile ha un nome e un valore e un puntatore ha un indirizzo (il nome) e un valore e mostra che il valore del puntatore sarà l'indirizzo dell'int.


-1

Un puntatore è solo una variabile utilizzata per memorizzare gli indirizzi.

La memoria di un computer è composta da byte (un byte è composto da 8 bit) disposti in modo sequenziale. A ogni byte è associato un numero proprio come l'indice o il pedice in un array, che viene chiamato l'indirizzo del byte. L'indirizzo del byte inizia da 0 a uno in meno della dimensione della memoria. Ad esempio, diciamo in un 64 MB di RAM, ci sono 64 * 2 ^ 20 = 67108864 byte. Pertanto l'indirizzo di questi byte inizierà da 0 a 67108863.

inserisci qui la descrizione dell'immagine

Vediamo cosa succede quando dichiari una variabile.

segni di int;

Come sappiamo un int occupa 4 byte di dati (supponendo che stiamo usando un compilatore a 32 bit), quindi il compilatore riserva 4 byte consecutivi dalla memoria per memorizzare un valore intero. L'indirizzo del primo byte dei 4 byte allocati è noto come indirizzo dei segni variabili. Supponiamo che l'indirizzo di 4 byte consecutivi sia 5004, 5005, 5006 e 5007, quindi l'indirizzo dei segni variabili sarà 5004. inserisci qui la descrizione dell'immagine

Dichiarazione delle variabili del puntatore

Come già detto, un puntatore è una variabile che memorizza un indirizzo di memoria. Proprio come qualsiasi altra variabile, devi prima dichiarare una variabile puntatore prima di poterla usare. Ecco come è possibile dichiarare una variabile puntatore.

Sintassi: data_type *pointer_name;

data_type è il tipo di puntatore (noto anche come tipo di base del puntatore). pointer_name è il nome della variabile, che può essere qualsiasi identificatore C valido.

Facciamo alcuni esempi:

int *ip;

float *fp;

int * ip significa che ip è una variabile puntatore in grado di puntare a variabili di tipo int. In altre parole, un ip variabile del puntatore può memorizzare solo l'indirizzo di variabili di tipo int. Allo stesso modo, la variabile puntatore fp può solo memorizzare l'indirizzo di una variabile di tipo float. Il tipo di variabile (noto anche come tipo base) ip è un puntatore a int e il tipo di fp è un puntatore a float. Una variabile puntatore di tipo puntatore a int può essere rappresentata simbolicamente come (int *). Allo stesso modo, una variabile puntatore di tipo pointer to float può essere rappresentata come (float *)

Dopo aver dichiarato una variabile puntatore, il passo successivo è assegnare ad esso un indirizzo di memoria valido. Non dovresti mai usare una variabile puntatore senza assegnargli un indirizzo di memoria valido, perché subito dopo la dichiarazione contiene un valore di immondizia e potrebbe puntare verso qualsiasi punto della memoria. L'uso di un puntatore non assegnato può dare un risultato imprevedibile. Potrebbe anche causare l'arresto anomalo del programma.

int *ip, i = 10;
float *fp, f = 12.2;

ip = &i;
fp = &f;

Fonte: thecguru è di gran lunga la spiegazione più semplice ma dettagliata che abbia mai trovato.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.