DOMANDA Quando utilizzare i puntatori?

Pubblicità

CShappire

Nuovo Utente
Messaggi
21
Reazioni
0
Punteggio
25
Salve, avrei una domanda che mi faccio da tempo.
Quando utilizzare i puntatori? So come definirli e so anche come assegnare ad un puntatore l'indirizzo di una variabile.
Ora, le domande che mi pongo sono:
- I puntatori si utilizzando solo per l'allocamento di memoria dinamica?
- Quando utilizzarli?
 
Ultima modifica:
Salve, avrei una domanda che mi faccio da tempo.
Quando utilizzare i puntatori? So come definirli e so anche come assegnare ad un puntatore l'indirizzo di una variabile.
Ora, le domande che mi pongo sono:
- I puntatori si utilizzando solo per l'allocamento di memoria dinamica?
- Quando utilizzarli?
I puntatori hanno modi d'uso molto vari.
Per esempio quando passi un valore per riferimento a una funzione, dentro quella funzione di fatto hai un puntatore al valore.
Quando crei un array, di fatto la variabile array è un puntatore alla prima locazione di memoria.
Analogamente quando allochi memoria, se hai dato un tipo diverso da void* al valore ritrnato dal malloc, puoi usare la memoria come un vettore del tipo che hai imposto.
I puntatori li usi molto per costruire strutture dati come liste, alberi, grafi...
 
I puntatori hanno modi d'uso molto vari.
Per esempio quando passi un valore per riferimento a una funzione, dentro quella funzione di fatto hai un puntatore al valore.
Quando crei un array, di fatto la variabile array è un puntatore alla prima locazione di memoria.
Analogamente quando allochi memoria, se hai dato un tipo diverso da void* al valore ritrnato dal malloc, puoi usare la memoria come un vettore del tipo che hai imposto.
I puntatori li usi molto per costruire strutture dati come liste, alberi, grafi...
Il malloc non riguarda solo il C?
Forse non ho specificato bene (errore mio) ma mi riferisco al C++.
Potresti farmi qualche esempio con commenti? Ho seguito le spiegazioni di HTML.IT ed ho letto pure un libro online ma è troppo generico, non ti spiega quando è utile utilizzare i puntatori e no.
 
Il malloc non riguarda solo il C?
Forse non ho specificato bene (errore mio) ma mi riferisco al C++.
Potresti farmi qualche esempio con commenti? Ho seguito le spiegazioni di HTML.IT ed ho letto pure un libro online ma è troppo generico, non ti spiega quando è utile utilizzare i puntatori e no.

Perché non c'è una regola che ti dice qui è meglio usare un puntatore e di là invece no. Ci sono dei casi in cui il linguaggio te lo chiede (esempio: passare un array o una cella di un array come argomento a una funzione), altri in cui può essere comodo ma mediamente dipende da te. :) Come diceva ind33d sono utili quando ad esempio definisci strutture dati come una lista circolare, in cui ogni elemento ha un puntatore al precedente e successivo per esempio, oppure in grafi rappresentati da liste (heap) and so on. :)
 
Il malloc non riguarda solo il C?
Forse non ho specificato bene (errore mio) ma mi riferisco al C++.
Anche in C++ puoi usare malloc e altre funzioni "ereditate" dal C, anche se ciò porta, a detta di molti, a un codice poco "coerente" diventando un miscuglio tra C e C++, pratica tipica di chi inizia dal C e passa al C++ pensando che "il C++ è solo C con le classi".
In C++ tipicamente si usa la keyword new anzichè la funzione malloc, che però ha delle restrizioni in più rispetto al malloc (per esempio non ha un corrispettivo realloc).
Potresti farmi qualche esempio con commenti? Ho seguito le spiegazioni di HTML.IT ed ho letto pure un libro online ma è troppo generico, non ti spiega quando è utile utilizzare i puntatori e no.
Come ti ha risposto anche lock3r, la scelta di usare un puntatore non è soggetta a vincoli particolari, in alcuni casi è implicita, in altri è una comodità per il programmatore. Sta a te fare delle prove e poi confrontarti con altri codici (possibilmente scritti da programmatori esperti).
Prova intanto a costruire una lista concatenata o un albero con delle strutture, ti troverai di fronte a una classica situazione che fa largo uso dei puntatori.
 
Anche in C++ puoi usare malloc e altre funzioni "ereditate" dal C, anche se ciò porta, a detta di molti, a un codice poco "coerente" diventando un miscuglio tra C e C++, pratica tipica di chi inizia dal C e passa al C++ pensando che "il C++ è solo C con le classi".
In C++ tipicamente si usa la keyword new anzichè la funzione malloc, che però ha delle restrizioni in più rispetto al malloc (per esempio non ha un corrispettivo realloc).

Come ti ha risposto anche lock3r, la scelta di usare un puntatore non è soggetta a vincoli particolari, in alcuni casi è implicita, in altri è una comodità per il programmatore. Sta a te fare delle prove e poi confrontarti con altri codici (possibilmente scritti da programmatori esperti).
Prova intanto a costruire una lista concatenata o un albero con delle strutture, ti troverai di fronte a una classica situazione che fa largo uso dei puntatori.
Adesso sto leggendo una guida (Guida C - Puntatori) che spiega con più dettaglio i puntatori.
Grazie a tutti delle risposte!
 
Ho una domanda da farvi:
Se io creo un array ed un puntatore di tipo int:
int Array[10];
int *P;

Quando eseguo questo codice:
P=Array; //Che equivale a P=&Array[0];
il puntatore punta SOLO all'indirizzo di Array[0] giusto?
Se invece io scrivessi:
P=&Array[2];
oppure
P= Array+2;
Punterebbe SOLO all'indirizzo di Array[2] giusto?

Un'altra cosa: che cosa potrei programmare per capire meglio l'uso dei puntatori?
 
Ultima modifica:
Ho una domanda da farvi:
Se io creo un array ed un puntatore di tipo int:
int Array[10];
int *P;

Quando eseguo questo codice:
P=Array; //Che equivale a P=&Array[0];
il puntatore punta SOLO all'indirizzo di Array[0] giusto?
Se invece io scrivessi:
P=&Array[2];
oppure
P= Array+2;
Punterebbe SOLO all'indirizzo di Array[2] giusto?
Si, ma non conforderti.
Tu hai specificato "SOLO", in realtà non ha senso dire "SOLO", in quanto un puntatore per definizione punta sempre a una locazione di memoria (e solo una ovviamente).
Il valore contenuto in una variabile di tipo puntatore è un indirizzo di memoria (indirizzo del valore detto "puntato"), mentre il tipo di puntatore, per esempio int*, indica che quando uso quel puntatore, vado a leggere non solo l'indirizzo di memoria puntato ma tutti quelli successivi necessari a definire un dato di tipo int, tipicamente 4 byte.
Se poi effettui dell'aritmetica sui puntatori, per esempio P++, avendo fissato il tipo int*, il compilatore sa che quella istruzione sommerà 4 (il numero di byte di un int) all'indirizzo puntato, così da puntare il valore int successivo.

Alcuni esempi di aritmetica sui puntatori:
Creo un array di unsigned int (unsigned per rendere le cose più semplici)
Codice:
unsigned int interi[10];
    for (int i = 0; i < 10; i++)
        interi[i] = i;
Creo un puntatore di tipo unsigned int e lo uso per leggere l'array:
Codice:
unsigned int *n = interi;
for (int i = 0; i < 10; i++)
    cout << n+i << "\t" << *(n+i) << endl;

while (n < &interi[10]){
    cout << n << "\t" << *n << endl;
    n++;
}
Entrambi i cicli produrranno lo stesso output, che nel mio pc è stato:
Codice:
0022FC28        0
0022FC2C        1
0022FC30        2
0022FC34        3
0022FC38        4
0022FC3C        5
0022FC40        6
0022FC44        7
0022FC48        8
0022FC4C        9
Per ogni riga, il primo valore è l'indirizzo puntato dal puntatore, il secondo è il valore puntato.
Puoi notare che gli indirizzi sono spaziati di 4 uno dall'altro, infatti, sulla mia macchina, gli interi sono di 4 byte.
E se cambiassi il tipo di puntatore?
Proviamo con un unsigned short, che ha dimensione 2 byte, quindi l'aritmetica sul puntatore sarà diversa:
Codice:
unsigned short *ns = (unsigned short*) interi;
while (ns < (unsigned short*) &interi[10]){
    cout << ns << "\t" << *ns << endl;
    ns++;
}

//Output:
0022FC28        0
0022FC2A        0
0022FC2C        1
0022FC2E        0
0022FC30        2
0022FC32        0
0022FC34        3
0022FC36        0
0022FC38        4
0022FC3A        0
0022FC3C        5
0022FC3E        0
0022FC40        6
0022FC42        0
0022FC44        7
0022FC46        0
0022FC48        8
0022FC4A        0
0022FC4C        9
0022FC4E        0
Come puoi notare, gli indirizzi sono spaziati di 2 byte, perchè il puntatore punta a valori di 2 byte.
Ma il linguaggio C/C++ mi permette ugualmente di leggere i dati in memoria (C e C++ non sono linguaggi type-safe) e ottengo in output il valore intero letto ogni 2 byte. Un unsigned int di 4 byte è scritto in notazione binaria semplice, in formato little endian (i byte meno significativi prima).
Quindi, per esempio il valore 1 sarà rappresentato da 4 byte, di cui il primo vale 00000001 e gli altri 3 00000000.
Leggendo la seconda posizione dell'array, per esempio, otterrò prima 00000001 00000000 e poi 00000000 00000000.
A questo punto tanto vale che facciamo un'altra prova, con i char (1 byte):
Codice:
char* nc = (char *) interi;
while (nc < (char*) &interi[10]){
    cout << (void*)nc << "\t" << (unsigned int)(*nc) << endl;
    nc++;
}

//Output:
0022FC28        0
0022FC29        0
0022FC2A        0
0022FC2B        0
0022FC2C        1
0022FC2D        0
0022FC2E        0
0022FC2F        0
0022FC30        2
0022FC31        0
0022FC32        0
0022FC33        0
0022FC34        3
0022FC35        0
0022FC36        0
0022FC37        0
0022FC38        4
0022FC39        0
0022FC3A        0
0022FC3B        0
0022FC3C        5
0022FC3D        0
0022FC3E        0
0022FC3F        0
0022FC40        6
0022FC41        0
0022FC42        0
0022FC43        0
0022FC44        7
0022FC45        0
0022FC46        0
0022FC47        0
0022FC48        8
0022FC49        0
0022FC4A        0
0022FC4B        0
0022FC4C        9
0022FC4D        0
0022FC4E        0
0022FC4F        0
Stessa storia di prima, ma ora leggiamo ogni singolo byte come se fosse un valore intero di un byte (char).
Ogni valore del nostro array viene diviso in 4 parti, e ognuna viene stampata a parte. Essendo il dato originale scritto in little endian, il byte meno significato è il primo, seguito dai 3 meno significativi, che nel nostro caso sono tutti zero, in quanto abbiamo inserito numeri inferiori a 256.

ps: si mi sono divertito :asd:

pps: ah, e l'aritmetica dei puntatori vale anche quando si usano strutture o altri tipi di dato.
 
Ultima modifica:
Si, ma non conforderti.
Tu hai specificato "SOLO", in realtà non ha senso dire "SOLO", in quanto un puntatore per definizione punta sempre a una locazione di memoria (e solo una ovviamente).
Il valore contenuto in una variabile di tipo puntatore è un indirizzo di memoria (indirizzo del valore detto "puntato"), mentre il tipo di puntatore, per esempio int*, indica che quando uso quel puntatore, vado a leggere non solo l'indirizzo di memoria puntato ma tutti quelli successivi necessari a definire un dato di tipo int, tipicamente 4 byte.
Se poi effettui dell'aritmetica sui puntatori, per esempio P++, avendo fissato il tipo int*, il compilatore sa che quella istruzione sommerà 4 (il numero di byte di un int) all'indirizzo puntato, così da puntare il valore int successivo.

Alcuni esempi di aritmetica sui puntatori:
Creo un array di unsigned int (unsigned per rendere le cose più semplici)
Codice:
unsigned int interi[10];
    for (int i = 0; i < 10; i++)
        interi[i] = i;
Creo un puntatore di tipo unsigned int e lo uso per leggere l'array:
Codice:
unsigned int *n = interi;
for (int i = 0; i < 10; i++)
    cout << n+i << "\t" << *(n+i) << endl;

while (n < &interi[10]){
    cout << n << "\t" << *n << endl;
    n++;
}
Entrambi i cicli produrranno lo stesso output, che nel mio pc è stato:
Codice:
0022FC28        0
0022FC2C        1
0022FC30        2
0022FC34        3
0022FC38        4
0022FC3C        5
0022FC40        6
0022FC44        7
0022FC48        8
0022FC4C        9
Per ogni riga, il primo valore è l'indirizzo puntato dal puntatore, il secondo è il valore puntato.
Puoi notare che gli indirizzi sono spaziati di 4 uno dall'altro, infatti, sulla mia macchina, gli interi sono di 4 byte.
E se cambiassi il tipo di puntatore?
Proviamo con un unsigned short, che ha dimensione 2 byte, quindi l'aritmetica sul puntatore sarà diversa:
Codice:
unsigned short *ns = (unsigned short*) interi;
while (ns < (unsigned short*) &interi[10]){
    cout << ns << "\t" << *ns << endl;
    ns++;
}

//Output:
0022FC28        0
0022FC2A        0
0022FC2C        1
0022FC2E        0
0022FC30        2
0022FC32        0
0022FC34        3
0022FC36        0
0022FC38        4
0022FC3A        0
0022FC3C        5
0022FC3E        0
0022FC40        6
0022FC42        0
0022FC44        7
0022FC46        0
0022FC48        8
0022FC4A        0
0022FC4C        9
0022FC4E        0
Come puoi notare, gli indirizzi sono spaziati di 2 byte, perchè il puntatore punta a valori di 2 byte.
Ma il linguaggio C/C++ mi permette ugualmente di leggere i dati in memoria (C e C++ non sono linguaggi type-safe) e ottengo in output il valore intero letto ogni 2 byte. Un unsigned int di 4 byte è scritto in notazione binaria semplice, in formato big endian (i byte più significativi prima).
Quindi, per esempio il valore 1 sarà rappresentato da 4 byte, di cui il primo vale 00000001 e gli altri 3 00000000.
Leggendo la seconda posizione dell'array, per esempio, otterrò prima 00000001 00000000 e poi 00000000 00000000.
A questo punto tanto vale che facciamo un'altra prova, con i char (1 byte):
Codice:
char* nc = (char *) interi;
while (nc < (char*) &interi[10]){
    cout << (void*)nc << "\t" << (unsigned int)(*nc) << endl;
    nc++;
}

//Output:
0022FC28        0
0022FC29        0
0022FC2A        0
0022FC2B        0
0022FC2C        1
0022FC2D        0
0022FC2E        0
0022FC2F        0
0022FC30        2
0022FC31        0
0022FC32        0
0022FC33        0
0022FC34        3
0022FC35        0
0022FC36        0
0022FC37        0
0022FC38        4
0022FC39        0
0022FC3A        0
0022FC3B        0
0022FC3C        5
0022FC3D        0
0022FC3E        0
0022FC3F        0
0022FC40        6
0022FC41        0
0022FC42        0
0022FC43        0
0022FC44        7
0022FC45        0
0022FC46        0
0022FC47        0
0022FC48        8
0022FC49        0
0022FC4A        0
0022FC4B        0
0022FC4C        9
0022FC4D        0
0022FC4E        0
0022FC4F        0
Stessa storia di prima, ma ora leggiamo ogni singolo byte come se fosse un valore intero di un byte (char).
Ogni valore del nostro array viene diviso in 4 parti, e ognuna viene stampata a parte. Essendo il dato originale scritto in big endian, il byte meno significato è il primo, seguito dai 3 meno significativi, che nel nostro caso sono tutti zero, in quanto abbiamo inserito numeri inferiori a 256.

ps: si mi sono divertito :asd:

pps: ah, e l'aritmetica dei puntatori vale anche quando si usano strutture o altri tipi di dato.
Aspetta un momento, perché si utilizza ((cosa che non capivo neanche nelle guide) (void*), (unsigned int), (char *), ..., ?
 
Sono cast in stile C. In C++ non dovrebbero essere usati, in favore dei quattro cast offerti: static, dynamic, const, reinterpret (in ordine di pericolosità).
 
Aspetta un momento, perché si utilizza ((cosa che non capivo neanche nelle guide) (void*), (unsigned int), (char *), ..., ?
Come detto dal signore del tempo, sono dei cast espliciti, li ho usati per lo più per chiarezza, anche se il (void*) è necessario quando mandi in output l'indirizzo puntato da un puntatore char. Infatti il cout interpreta il puntatore come carattere da stampare, quindi senza cast va a stampare il valore puntato interpretandolo come carattere ASCII.
In C++ i cast sono regolati in modo particolare per lo più per le classi, quando parliamo di puntatori a valori primitivi, in realtà non c'è molta differenza d'uso. I cast espliciti sono utili per applicazioni ad alta efficienza quando si lavora direttamente su dati in memoria, in quel caso tipicamente il C++ regredisce a C. Nel C++ di solito si usano funzioni di cast per i primitivi e si sfrutta il dynamic lookup per le classi, ma sono aspetti un po' più complessi da trattare (all'università ho fatto un corso da 10 crediti nel quale per metà del tempo si è parlato quasi esclusivamente del dynamic lookup e tipizzazione di C++ comparato ad altri linguaggi).

ps: Mi correggo per il post precedente, ho scritto che l'unsigned int viene memorizzato in big endian, ma non è stata una svista (per lo più un mistype). Anche dall'output è evidente che è il contrario: little endian. Infatti vengono prima letti i byte meno significativi.
 
Ultima modifica:
Pubblicità
Pubblicità
Indietro
Top