Bene, in pratica capisco come usare i puntatori, ma non il modo migliore di usarli per fare una migliore programmazione.
Quali sono i buoni progetti o problemi da risolvere che coinvolgono l'uso di puntatori in modo da poterli capire meglio?
Bene, in pratica capisco come usare i puntatori, ma non il modo migliore di usarli per fare una migliore programmazione.
Quali sono i buoni progetti o problemi da risolvere che coinvolgono l'uso di puntatori in modo da poterli capire meglio?
Risposte:
La manipolazione di grandi quantità di dati in memoria è dove i puntatori brillano davvero.
Passare un oggetto di grandi dimensioni per riferimento equivale a passare semplicemente lungo un vecchio numero. È possibile manipolare direttamente le parti necessarie invece di copiare un oggetto, modificarlo, quindi passare indietro la copia per sostituirla 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:
Ordinamento
Quando si spostano i dati in un algoritmo di ordinamento, è possibile spostare il puntatore anziché i dati stessi — pensare di ordinare milioni di righe su una stringa di 100 caratteri; risparmi molti movimenti di dati non necessari.
Elenchi collegati
È possibile memorizzare la posizione dell'articolo successiva e / o precedente e non tutti i dati associati al record.
Passaggio dei parametri
In questo caso, si passa l'indirizzo dei dati anziché i dati stessi. Ancora una volta, pensa a un algoritmo di compressione dei nomi che gira su milioni di righe.
Il concetto può essere esteso a strutture dati come database relazionali in cui un puntatore è simile a una chiave esterna . Alcuni linguaggi non incoraggiano l'uso di puntatori come C # e COBOL.
Esempi possono essere trovati in molti luoghi come:
Il seguente post può 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ù altre in modi complessi.
Elenchi collegati (singolarmente, doppiamente e circolarmente collegati), alberi (rosso-nero, AVL, trie, binario, partizionamento dello spazio ...) e grafici sono tutti esempi di strutture che possono essere costruite in modo più naturale in termini di riferimenti piuttosto che solo valori .
Un modo semplice è nel polimorfismo. Il polimorfismo funziona solo con i puntatori.
Inoltre, si utilizzano i puntatori ogni volta che è necessario l'allocazione dinamica della memoria. In C, questo di solito accade quando è necessario archiviare i dati in un array ma non si conoscono le dimensioni al momento della compilazione. Chiamereste quindi 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 intero array in una riga:
while( (*array1++ = *array2++) != '\0')
In c ++, si utilizza new per allocare memoria per un oggetto e archiviarlo in un puntatore. Puoi farlo ogni volta che devi creare un oggetto durante il runtime anziché durante il tempo di 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 sul quale mi sono tagliato i denti:
Leggi in due matrici nxn da un file ed esegui le operazioni di base dello spazio vettoriale su di esse e stampa il loro risultato sullo schermo.
Per fare ciò, è necessario utilizzare array dinamici e fare riferimento agli array mediante puntatori poiché si avranno due array di array (array dinamici multidimensionali). Dopo aver finito quel progetto, avrai una buona idea di come usare i puntatori.
Per capire veramente perché i puntatori sono importanti, è necessario comprendere la differenza tra allocazione heap e allocazione stack.
Di seguito è riportato un esempio di allocazione di 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 dal campo di applicazione, anche 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 dall'esempio sopra).
Ad esempio, la seguente funzione comporterebbe il cosiddetto "comportamento indefinito".
struct Foo {
int bar, baz
};
struct Foo *foo(void) {
struct Foo f;
return &f;
}
Se vuoi restituire qualcosa di simile struct Foo *
a una funzione, ciò di cui hai veramente bisogno è un'allocazione di 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. Si noti che qui il termine "oggetto" viene usato liberamente, che significa "qualcosa" piuttosto che oggetto nel senso di programmazione orientata agli oggetti.
La durata degli oggetti allocati in heap è controllata dal programmatore. La memoria per questo oggetto sarà riservata fino a quando il programmatore non lo libera, ovvero chiamando free()
o fino alla chiusura del programma.
Modifica : non sono riuscito a notare che questa domanda è contrassegnata come una domanda C ++. Gli operatori C ++ new
e new[]
svolgono la stessa funzione malloc
. Gli operatori delete
e delete[]
sono analoghi a free
. Mentre new
e delete
dovrebbe essere usato esclusivamente per allocare e liberare oggetti C ++, l'uso di malloc
ed free
è perfettamente legale nel codice C ++.
Scrivi qualsiasi progetto non banale in C e dovrai capire come / quando usare i puntatori. In C ++ utilizzerai principalmente oggetti abilitati per RAII che gestiscono i puntatori internamente, ma in C i puntatori grezzi hanno un ruolo molto più prevalente. Per quanto riguarda il tipo di progetto che dovresti fare, può essere qualcosa di non banale:
Raccomando l'ultimo.
Quasi tutti i problemi di programmazione che possono essere risolti con i puntatori possono essere risolti con altri tipi di riferimenti più sicuri (non riferendosi ai riferimenti C ++ , ma il concetto CS generale di avere una variabile si riferisce al valore dei dati memorizzati altrove).
I puntatori essendo un'implementazione specifica di basso livello di riferimenti, in cui è possibile 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 dell'utilizzo diretto dei puntatori è che saranno leggermente più veloci non dovendo eseguire alcun controllo di sicurezza. Lingue come Java che non implementano direttamente i puntatori in stile C subiranno un leggero calo delle prestazioni, ma ridurranno molti tipi di situazioni difficili da eseguire il debug.
Per quanto riguarda il motivo per cui è necessario il riferimento indiretto, l'elenco è piuttosto lungo, ma essenzialmente le due idee chiave sono:
selected_object
che è un riferimento a uno degli oggetti è molto più efficiente della 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 usando i puntatori.
I puntatori sono utilizzati in così tanti linguaggi di programmazione sotto la superficie senza disturbare l'utente al riguardo. C / C ++ ti dà solo accesso ad essi.
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 altro posto in memoria e restituire un puntatore - questa volta che punta a object_2)
I puntatori sono una parte essenziale di qualsiasi implementazione della struttura di dati in C e le strutture di dati sono una parte essenziale di qualsiasi programma non banale.
Se desideri sapere perché i puntatori sono così vitali, ti suggerirei di sapere cos'è un elenco collegato e di provare a scriverne uno senza usare i puntatori. Non ti ho posto una sfida impossibile, (SUGGERIMENTO: i puntatori vengono utilizzati per fare riferimento a posizioni in memoria, come si fa a fare riferimento alle cose negli array?).
Come esempio di vita reale, costruendo un libro degli ordini limite.
il feed ITCH 4.1, ad esempio, ha un concetto di "sostituzione" degli ordini, in cui i prezzi (e quindi la priorità) possono cambiare. Vuoi essere in grado di estrarre gli ordini e spostarli altrove. L'implementazione di una coda doppia con puntatori rende l'operazione molto semplice.
Esaminando i vari siti StackExchange, mi rendo conto che è piuttosto in voga fare una domanda come questa. A rischio di critiche e valutazioni negative, sarò onesto. Questo non significa trollare o infiammare, intendo solo aiutare, dando una valutazione onesta della domanda.
E questa valutazione è la seguente: questa è una domanda molto strana da porre a un programmatore C. Praticamente tutto ciò che dice è "Non conosco C." Se analizzo un po 'più e più cinicamente, c'è un sottotono di un seguito a questa "domanda": "C'è qualche scorciatoia che posso prendere per acquisire rapidamente e improvvisamente la conoscenza di un programmatore C esperto, senza dedicarmi in qualsiasi momento per studio indipendente? " Qualsiasi "risposta" che qualcuno può dare non è un sostituto per andare e fare il lavoro di comprensione del concetto di base e del suo uso.
Sento che è più costruttivo imparare bene C, in prima persona, 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 sono meglio risolti 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 essi.
Il puntatore è assolutamente essenziale per la cosiddetta programmazione dinamica , è quando non sai quanta memoria avrai bisogno prima che il tuo programma venga eseguito. Nella programmazione dinamica puoi richiedere blocchi di memoria durante il runtime e inserire i tuoi dati in quelli - quindi hai bisogno di puntatori o riferimenti (la differenza non è importante qui) per poter lavorare con quei blocchi di dati.
Finché è possibile rivendicare una certa memoria durante il runtime e posizionare i dati nella memoria appena acquisita, è possibile effettuare le seguenti operazioni:
È possibile disporre di strutture dati autoestendenti. Quelle sono strutture che possono estendersi rivendicando memoria aggiuntiva finché la loro capacità si esaurisce. La proprietà chiave di ogni struttura autoestendente è che è costituita da piccoli blocchi di memoria (chiamati 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, elenchi ecc.
È possibile programmare utilizzando il paradigma OOP (Object-Oriented Programming). L'intero OOP si basa sull'utilizzo di variabili non dirette, ma su riferimenti a istanze di classe (oggetti definiti) e sulla loro manipolazione. Nessuna singola istanza può esistere senza puntatori (anche se è possibile utilizzare classi solo statiche anche senza puntatori, è piuttosto un'eccezione).
Divertente, ho appena risposto a una domanda sul C ++ e ho parlato di puntatori.
La versione breve è MAI bisogno di puntatori a meno che 1) la libreria che stai usando ti imponga 2) È necessario 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 anziché un oggetto e copierà i dati solo se ci si scrive. Questo è C ++ normale, nemmeno il nuovo C ++ 11, che renderà più semplice chi scrive una libreria.
Se si utilizza un puntatore, assicurarsi che si trovi in una di queste due condizioni. 1) Stai passando input che potrebbero essere nullable. Un esempio è un nome file opzionale. 2) Se vuoi dare via la proprietà. Come nel caso in cui si passi o si restituisca il puntatore, non è presente NESSUNA copia di esso né si utilizza il puntatore fornito
ptr=blah; func(ptr); //never use ptr again for here on out.
Ma non ho usato puntatori o puntatori intelligenti per molto tempo e ho profilato la mia applicazione. Funziona molto velocemente.
NOTA AGGIUNTIVA: noto che scrivo le mie strutture e le faccio passare. Quindi, come posso fare senza usare i puntatori? Non è un contenitore STL, quindi passare per ref è lento. Carico sempre la mia lista di dati / deques / maps e simili. Non ricordo di aver restituito alcun oggetto a meno che non fosse una specie di elenco / mappa. Neanche una stringa. Ho esaminato il codice per singoli oggetti e noto che faccio qualcosa del genere, { MyStruct v; func(v, someinput); ... } void func(MyStruct&v, const D&someinput) { fillV; }
quindi restituisco praticamente oggetti (multipli) o preallocate / passo un riferimento per riempire (singolo).
Ora se stavi scrivendo sei un tuo deque, una mappa, ecc. Dovrai usare i puntatori. Ma non è necessario. Lasciate che STL e possibilmente aumentiate la preoccupazione al riguardo. Hai solo bisogno di scrivere i dati e le soluzioni. Non contenitori per trattenerli;)
Spero che ora non usi mai i puntatori: D. Buona fortuna a gestire le librerie che ti costringono a farlo
I puntatori sono molto utili per lavorare con dispositivi mappati in memoria. È possibile definire una struttura che riflette (diciamo) un registro di controllo, quindi assegnarlo all'indirizzo del registro di controllo effettivo in memoria e manipolarlo direttamente. È inoltre possibile puntare direttamente a un buffer di trasferimento su una scheda o chip se la MMU lo ha mappato nello spazio di memoria del sistema.
Vedo i puntatori come il mio dito indice, usiamo per fare un paio di cose:
per favore perdona questa povera risposta
Poiché la tua domanda è taggata in C ++, risponderò alla tua domanda per quella lingua.
In C ++ esiste una distinzione tra puntatori e riferimenti, pertanto esistono due scenari in cui i puntatori (o puntatori intelligenti) sono necessari per facilitare determinati comportamenti. Possono essere utilizzati in altre circostanze, tuttavia si chiede come "utilizzarli al meglio" e in tutte le altre circostanze esistono alternative migliori.
Un puntatore della classe base consente di chiamare un metodo virtuale che dipende dal tipo di oggetto a cui punta il puntatore.
I puntatori sono necessari durante la creazione dinamica di un oggetto (sull'heap anziché sullo 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 qui, qualsiasi progetto non banale farà uso di puntatori.