Bene, in pratica ho capito come usare i puntatori, ma non il modo migliore di usarli per fare una programmazione migliore.
Quali sono i buoni progetti o problemi da risolvere che comportano l'uso di puntatori, quindi posso comprenderli meglio?
Manipolando grandi quantità di dati nella memoria è dove i puntatori brillano davvero.
Il passaggio di un oggetto di grandi dimensioni per riferimento equivale a passare semplicemente un vecchio numero semplice. Puoi manipolare le parti necessarie direttamente anziché copiare un oggetto, modificarlo, quindi restituire la copia da mettere al posto dell'originale.
Il concetto di puntatore consente di fare riferimento ai dati per indirizzo senza duplicare la memorizzazione dei dati. Questo approccio consente di scrivere algoritmi efficienti come:
Ricerca
Quando si spostano i dati in un algoritmo di ordinamento, è possibile spostare il puntatore anziché i dati stessi; si pensi di ordinare milioni di righe su una stringa di 100 caratteri; risparmi un sacco di movimenti dati inutili.
elenchi collegati
È possibile memorizzare la posizione dell'elemento successivo e / o precedente e non l'intero dato associato al record.
Passing parameters
In questo caso, si passa l'indirizzo dei dati anziché i dati stessi. Ancora una volta, pensa ad un algoritmo di compressione del nome che gira su milioni di righe.
Il concetto può essere esteso a strutture di dati come i database relazionali in cui un puntatore è simile a una chiave esterna . Alcune lingue non incoraggiano l'uso di puntatori come C # e COBOL.
Gli esempi possono essere trovati in molti posti come:
Il seguente post potrebbe essere rilevante in qualche modo:
Sono sorpreso che nessun'altra risposta abbia menzionato questo: i puntatori consentono di creare strutture di dati non contigue e non lineari, in cui un elemento può essere correlato a più parti in modi complessi.
Gli elenchi collegati (singolarmente, doppiamente e collegati in modo circolare), gli alberi (rosso-nero, AVL, trie, binario, partizionamento dello spazio ...) ei grafici sono tutti esempi di strutture che possono essere costruite più naturalmente in termini di riferimenti piuttosto di soli valori.
Un modo semplice è nel polimorfismo. Il polimorfismo funziona solo con i puntatori.
Inoltre, si usano puntatori ogni volta che si ha bisogno dell'allocazione dinamica della memoria. In C, questo di solito accade quando è necessario memorizzare i dati in un array ma non si conosce la dimensione in fase di compilazione. Dovresti quindi chiamare malloc per allocare la memoria e un puntatore per accedervi. Inoltre, che tu lo sappia o no, quando usi un array stai usando i puntatori.
for(int i = 0; i < size; i++)
std::cout << array[i];
è l'equivalente di
for(int i = 0; i < size; i++)
std::cout << *(array + i);
Questa conoscenza ti consente di fare cose davvero interessanti come copiare un'intera serie in una sola riga:
while( (*array1++ = *array2++) != 'for(int i = 0; i < size; i++)
std::cout << array[i];
')
In c ++, usi new per allocare memoria per un oggetto e memorizzarlo in un puntatore. Puoi farlo ogni volta che devi creare un oggetto durante l'esecuzione anziché durante la compilazione (ad esempio un metodo crea un nuovo oggetto e lo memorizza in un elenco).
Per capire meglio i puntatori:
Trova alcuni progetti che utilizzano l'ereditarietà.
Ecco il progetto su cui mi sono tagliato i denti:
Leggi in due n x n matrici da un file ed esegui le operazioni dello spazio vettoriale di base su di esse e stampa il risultato sullo schermo.
Per fare questo, devi usare gli array dinamici e fare riferimento ai tuoi array da puntatori poiché avrai due array di array (array dinamici multidimensionali). Dopo aver completato il progetto, avrai una buona idea su come usare i puntatori.
Per capire veramente perché i puntatori sono importanti devi capire la differenza tra l'allocazione dell'heap e l'allocazione dello stack.
Quello che segue è un esempio di assegnazione dello stack:
struct Foo {
int bar, baz
};
void foo(void) {
struct Foo f;
}
Gli oggetti allocati nello stack esistono solo per la durata dell'esecuzione della funzione corrente. Quando la chiamata a foo
esce dall'ambito, la variabile f
.
Un caso in cui questo diventa un problema è quando è necessario restituire qualcosa di diverso da un tipo integrale da una funzione (ad esempio la struttura Foo dell'esempio precedente).
Ad esempio, la seguente funzione risulterebbe nel cosiddetto "comportamento non definito".
struct Foo {
int bar, baz
};
struct Foo *foo(void) {
struct Foo f;
return &f;
}
Se vuoi restituire qualcosa come struct Foo *
da una funzione, ciò di cui hai veramente bisogno è un'allocazione dell'heap:
struct Foo {
int bar, baz
};
struct Foo *foo(void) {
return malloc(sizeof(struct Foo));
}
La funzione malloc
alloca un oggetto sull'heap e restituisce un puntatore a quell'oggetto. Nota che il termine "oggetto" è usato in modo approssimativo qui, che significa "qualcosa" piuttosto che oggetto nel senso di programmazione orientata agli oggetti.
La durata degli oggetti allocati su heap è controllata dal programmatore. La memoria per questo oggetto sarà riservata fino a quando il programmatore lo libera, cioè chiamando free()
o finché il programma non si chiude.
Modifica : non ho notato che questa domanda è contrassegnata come una domanda C ++. Gli operatori C ++ new
e new[]
hanno la stessa funzione di malloc
. Gli operatori delete
e delete[]
sono analoghi a free
. Mentre new
e delete
dovrebbero essere usati esclusivamente per allocare e liberare oggetti C ++, l'uso di malloc
e free
è perfettamente legale nel codice C ++.
Scrivi qualsiasi progetto non banale in C e dovrai calcolare come / quando utilizzare i puntatori. In C ++ utilizzerete principalmente oggetti abilitati per RAII che gestiscono internamente i puntatori, ma nei puntatori grezzi C hanno un ruolo molto più prevalente. Per quanto riguarda il tipo di progetto che dovresti fare, può essere qualsiasi cosa non banale:
Consiglio l'ultimo.
Quasi tutti i problemi di programmazione che possono essere risolti con i puntatori possono essere risolti con altri tipi più sicuri di riferimenti (non si riferisce ai riferimenti C ++ , ma il concetto CS generale di avere una variabile si riferisce a il valore dei dati memorizzati altrove).
Puntatori essendo un'implementazione di basso livello specifica dei riferimenti, in cui puoi manipolare direttamente gli indirizzi di memoria sono molto potenti, ma possono essere leggermente pericolosi da usare (ad esempio, puntare a posizioni di memoria esterne al programma).
Il vantaggio di utilizzare i puntatori direttamente è che saranno leggermente più veloci non dovendo eseguire alcun controllo di sicurezza. Linguaggi come Java che non implementano direttamente i puntatori in stile C subiranno un leggero calo di prestazioni, ma ridurranno molti tipi di situazioni difficili da debug.
Per quanto riguarda il motivo per cui è necessario indiretto, la lista è abbastanza lunga, ma essenzialmente le due idee chiave sono:
selected_object
che è un riferimento a uno degli oggetti è molto più efficiente rispetto alla copia del valore dell'oggetto corrente in una nuova variabile. La manipolazione delle immagini a livello di pixel è quasi sempre più facile e veloce utilizzando i puntatori. A volte è possibile solo usare i puntatori.
I puntatori sono usati in così tanti linguaggi di programmazione sotto la superficie senza mettere a rischio l'utente. C / C ++ ti dà solo accesso.
Quando usarli: il più spesso possibile, perché la copia dei dati è inefficiente. Quando non usarli: quando vuoi due copie che possono essere cambiate individualmente. (Ciò che sostanzialmente finirà per copiare il contenuto di object_1 in un'altra posizione in memoria e restituire un puntatore - questa volta puntando a object_2)
I puntatori sono una parte essenziale di qualsiasi implementazione della struttura dati in C e le strutture dati sono una parte essenziale di qualsiasi programma non banale.
Se vuoi sapere perché sono così importanti i puntatori, suggerirei di apprendere cos'è una lista concatenata e provare a scriverne una senza usare i puntatori. Non ti ho impostato una sfida impossibile, (SUGGERIMENTO: i puntatori sono usati per fare riferimento a posizioni nella memoria, come fai a fare riferimento agli elementi negli array?).
Come esempio di vita reale, creando un libro degli ordini limitato.
il feed ITCH 4.1, ad esempio, ha un concetto di ordini "replace", in cui i prezzi (e quindi la priorità) possono cambiare. Vuoi essere in grado di prendere ordini e spostarli altrove. Implementare una coda a doppio attacco con i puntatori rende l'operazione molto semplice.
Esplorando i vari siti di StackExchange, mi rendo conto che è piuttosto in voga fare una domanda come questa. A rischio di critiche e downvotes, sarò onesto. Questo non è inteso come troll o fiamma, sto solo volendo aiutare, dando una valutazione onesta della domanda.
E quella valutazione è la seguente: Questa è una domanda molto strana da chiedere a un programmatore C. Praticamente tutto ciò che dice è "Non so C." Se analizzo un po 'più e cinicamente, c'è un sottotono di follow-up a questa "domanda": "C'è una scorciatoia che posso prendere per acquisire rapidamente e improvvisamente la conoscenza di un programmatore C esperto, senza dedicare alcun tempo per uno studio indipendente? " Qualsiasi "risposta" che qualcuno può dare non è un sostituto per andare e fare il lavoro di comprensione del concetto sottostante e del suo uso.
Penso che sia più costruttivo andare a imparare C bene, di prima mano, piuttosto che andare sul web e chiedere alla gente questo. Quando conosci bene C non ti preoccuperai di fare domande come questa, sarà come chiedere "quali problemi si risolvono meglio usando uno spazzolino da denti?"
Anche se i puntatori brillano davvero mentre si lavora con oggetti di memoria di grandi dimensioni, c'è ancora un modo per fare lo stesso senza di loro.
Il puntatore è assolutamente essenziale per la cosiddetta programmazione dinamica , è quando non sai quanta memoria hai bisogno prima dell'esecuzione del programma. Nella programmazione dinamica è possibile richiedere blocchi di memoria durante il runtime e inserire i propri dati in questi - quindi è necessario disporre di puntatore o riferimenti (la differenza non è importante qui) per poter lavorare con quei blocchi di dati.
Finché puoi rivendicare certa memoria durante il runtime e inserire i tuoi dati nella memoria appena acquisita, puoi fare quanto segue:
È possibile avere strutture di dati autoestenti. Quelle sono strutture che possono estendersi rivendicando memoria aggiuntiva finché la loro capacità si esaurisce. La proprietà chiave di ogni struttura autoestinguente è che consiste in piccoli blocchi di memoria (denominati nodi, voci di elenco ecc. A seconda della struttura) e ogni blocco contiene riferimenti ad altri blocchi. Queste strutture "collegate" costruiscono la maggior parte dei moderni tipi di dati: grafici, alberi, liste ecc.
Puoi programmare usando il paradigma OOP (Object-Oriented Programming). L'intero OOP si basa sull'uso di variabili non dirette, ma su riferimenti a istanze di classe (oggetti definiti) e sulla loro manipolazione. Nessuna istanza singola può esistere senza puntatori (anche se è possibile utilizzare classi solo statiche anche senza puntatori, che è piuttosto un'eccezione).
Divertente, ho appena risposto a una domanda sul C ++ e ho parlato dei puntatori.
La versione abbreviata è che non hai MAI bisogno di puntatori a meno che 1) la libreria che stai usando ti costringa 2) Hai bisogno di un riferimento nullable.
Se hai bisogno di un array, di un elenco, di una stringa, ecc., basta averlo nello stack e usare un oggetto stl. Restituire o passare oggetti stl sono veloci (fatto non controllato) perché hanno un codice interno che copia un puntatore invece di un oggetto e copierà i dati solo se ci scrivi sopra. Questo è C ++ regolare, nemmeno il nuovo C ++ 11, che renderà più semplice la scrittura di librerie.
Se usi un puntatore, assicurati che sia in una di queste due condizioni. 1) Trasmetti input che potrebbero essere nullable. Un esempio è un nome file opzionale. 2) Se vuoi dare la proprietà di distanza. Come in se passi o ritorni il puntatore non hai ANCORA alcuna copia di esso né usi il puntatore che dai via
ptr=blah; func(ptr); //never use ptr again for here on out.
Ma non ho usato puntatori o puntatori intelligenti per un tempo molto lungo e ho profilato la mia domanda. Funziona molto velocemente.
NOTA SUPPLEMENTARE: noto che scrivo le mie proprie strutture e le faccio passare in giro. Quindi, come faccio a farlo senza usare i puntatori? Non è un contenitore STL quindi passare per ref è lento. Carico sempre la mia lista di dati / accessori / mappe e così via. Non ricordo di aver restituito alcun oggetto a meno che non fosse una sorta di elenco / mappa. Neanche una corda. Ho esaminato il codice per singoli oggetti e ho notato che faccio qualcosa di simile a questo { MyStruct v; func(v, someinput); ... } void func(MyStruct&v, const D&someinput) { fillV; }
quindi ho praticamente restituito oggetti (multipli) o preallocato / passaggio in un riferimento a riempimento (singolo).
Ora se stavi scrivendo sei proprio deque, map, ecc dovrai usare i puntatori. Ma non è necessario. Lascia che STL e possibilmente aumenti la tua preoccupazione. Hai solo bisogno di scrivere dati e le soluzioni. Non contenitori per tenerli;)
Spero che ora non usi mai i puntatori: D. Buona fortuna con le librerie che ti costringono a
I puntatori sono molto utili per lavorare con dispositivi mappati in memoria. È possibile definire una struttura che rifletta (ad esempio) un registro di controllo, quindi assegnarlo all'indirizzo del registro di controllo effettivo in memoria e manipolarlo direttamente. Puoi anche puntare direttamente a un buffer di trasferimento su una scheda o un chip se la MMU lo ha mappato nello spazio di memoria del sistema.
Vedo i puntatori come indice, che usiamo per fare un paio di cose:
per favore perdona questa povera risposta
Poiché la tua domanda è codificata in C ++, risponderò alla tua domanda per quella lingua.
In C ++ c'è una distinzione tra puntatori e riferimenti, quindi ci sono due scenari in cui puntatori (o puntatori intelligenti) sono necessari per facilitare certi comportamenti. Possono essere utilizzati in altre circostanze, tuttavia chiedi come "utilizzarli al meglio" e in tutte le altre circostanze ci sono alternative migliori.
Un puntatore di classe base consente di chiamare un metodo virtuale che dipende dal tipo di oggetto a cui punta il puntatore.
I puntatori sono necessari quando si crea un oggetto dinamicamente (nell'heap anziché nello stack). Ciò è necessario quando si desidera che la durata degli oggetti sia più lunga dell'ambito in cui è stata creata.
In termini di "buoni progetti o problemi da risolvere", come altri hanno già detto, qualsiasi progetto non banale utilizzerà i puntatori.
Leggi altre domande sui tag c++ personal-projects pointers learning