Quali problemi di programmazione vengono risolti meglio utilizzando i puntatori? [chiuso]


58

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?


Chiedi quali problemi di programmazione vengono risolti meglio utilizzando i puntatori, non quali progetti / programmi. Puoi scrivere un programma usando i puntatori oppure no - loro (progetti) sono semplicemente un costrutto troppo grande per una risposta significativa.
Oded,

37
I problemi di programmazione vengono creati utilizzando i puntatori, non risolti. :)
xpda,

4
In realtà, mi chiedo quali problemi possano essere risolti senza puntatore. Pochissimi di sicuro.
deadalnix,

4
@deadalnix, dipende se intendi puntatori espliciti o puntatori impliciti. Se intendi i puntatori impliciti (ovvero, ci sono dei puntatori usati da qualche parte), sarebbe impossibile scrivere qualsiasi programma che usi lo stack o l'heap. Se intendi puntatori espliciti, allora sei libero, puoi fare qualsiasi dimensione di allocazione creando nuovi frame di stack (fondamentalmente usando una chiamata di funzione) e deallocazione usando le chiamate di coda, supponendo che siano ottimizzati dal compilatore.
dan_waterworth,

3
alcune di queste risposte sono orribili.

Risposte:


68

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.


12
Questa è un'ottima risposta e aggiungerei che non hai bisogno di oggetti di grandi dimensioni per manipolare grandi quantità di dati; occasionalmente la manipolazione di oggetti di grandi dimensioni lo farà, ma un problema ancora più comune è manipolare un mucchio di piccoli oggetti davvero molto frequentemente. Che, se ci pensate, riguarda solo tutti i programmi per computer.
jhocking

44

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:

  1. 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.

  2. Elenchi collegati
    È possibile memorizzare la posizione dell'articolo successiva e / o precedente e non tutti i dati associati al record.

  3. 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:


14
Non direi che C # non incoraggi l'uso dei puntatori; invece, non incoraggia l'uso di puntatori non gestiti - ci sono molte cose in C # che vengono passate per riferimento invece che per valore.
Blakomen,

Bello, buona spiegazione
Sabby,

5
Ho dovuto votare per il commento C #. I "riferimenti" in C # sono equivalenti ai puntatori, è solo che C # ti allontana dal sapere che in realtà hai a che fare con un puntatore a un oggetto nell'heap gestito.
MattDavey,

1
@MattDavey: i riferimenti C # non sono puntatori. Non è possibile aggiungere da essi, usarli per indicizzare un array, utilizzare più riferimenti indiretti, ecc. Non è possibile avere riferimenti a riferimenti, ad esempio.
Billy ONeal,

5
@Billy ONeal l'idea che "è solo un puntatore se puoi farci l'aritmetica" è francamente ridicola, ma non ho la pazienza di discutere con te.
MattDavey,

29

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 .


Stai dimenticando l'elenco collegato XOR [=
dan_waterworth,

@dan_waterworth: No. Conosco quella magia nera fin troppo bene.
Jon Purdy,

8

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:

  1. Trova alcuni progetti che giocano con le tradizionali stringhe a C e array.
  2. Trova alcuni progetti che utilizzano l'ereditarietà.

  3. 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.


2
"Il polimorfismo funziona solo con i puntatori." Non accurato: il polimorfismo funziona anche con riferimenti. Quali sono, in definitiva, indicazioni, ma penso che la distinzione sia importante, specialmente per i neofiti.
Laurent Pireyn,

il tuo esempio per copiare un array in una riga è davvero per copiare una stringa con terminazione zero in una riga, non un array generale.
Tcrosley,

8

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 fooesce 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 mallocalloca 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 ++ newe new[]svolgono la stessa funzione malloc. Gli operatori deletee delete[]sono analoghi a free. Mentre newe deletedovrebbe essere usato esclusivamente per allocare e liberare oggetti C ++, l'uso di malloced freeè perfettamente legale nel codice C ++.


1
(+1) noto anche come "variabili allocate statiche" e "variabili allocate dinamiche" ;-)
umlcat,

4

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:

  • Un web server piccolo e semplice
  • Strumenti da riga di comando Unix (less, cat, sort etc.)
  • Qualche progetto reale che vuoi fare per se stesso e non solo per l'apprendimento

Raccomando l'ultimo.


Quindi vado solo per un progetto che voglio fare (come un gioco 2D) e si suppone che avrò bisogno e apprendere i puntatori?
dysoco,

3
Nella mia esperienza, realizzare progetti reali che sono leggermente fuori dalla tua portata in termini di abilità è il modo migliore per imparare. I concetti possono essere appresi leggendo e scrivendo piccoli programmi "giocattolo". Tuttavia, per imparare davvero come usare questi concetti per scrivere programmi migliori, devi semplicemente scrivere programmi non giocattolo.
Viktor Dahl,

3

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:

  1. La copia dei valori di oggetti di grandi dimensioni è lenta e renderà l'oggetto memorizzato nella RAM due volte (potenzialmente molto costoso), ma la copia per riferimento è quasi istantanea utilizzando solo pochi byte di RAM (per l'indirizzo). Ad esempio, supponiamo di avere ~ 1000 oggetti di grandi dimensioni (ognuno dei quali contiene circa 1 MB di RAM) in memoria e l'utente deve essere in grado di selezionare l'oggetto corrente (su cui verrà applicato l'utente). Avere una variabile selected_objectche è un riferimento a uno degli oggetti è molto più efficiente della copia del valore dell'oggetto corrente in una nuova variabile.
  2. Avere strutture di dati complicate che fanno riferimento ad altri oggetti come elenchi collegati o alberi, in cui ogni elemento nella struttura di dati si riferisce ad altri elementi nella struttura di dati. Il vantaggio di fare riferimento ad altri elementi nella struttura dei dati significa che non è necessario spostare in memoria tutti gli elementi dell'elenco solo perché è stato inserito un nuovo elemento al centro dell'elenco (è possibile che si verifichino inserimenti temporali costanti).

2

La manipolazione delle immagini a livello di pixel è quasi sempre più facile e veloce utilizzando i puntatori. A volte è possibile solo usando i puntatori.


1
Non credo che l'affermazione "a volte è possibile solo usare i puntatori" sia vera. Esistono lingue che non espongono i puntatori allo sviluppatore, ma possono essere utilizzate per risolvere problemi nello stesso spazio.
Thomas Owens

Forse ... Non ho conoscenza di tutte le lingue disponibili, ma certamente non vorrei scrivere una routine per applicare un filtro di convoluzione a un'immagine usando una lingua che non ha puntatori.
Dave Nay,

@Thomas, mentre hai ragione, la domanda riguarda c ++ che espone i puntatori allo sviluppatore.
Jonathan Henson,

@Jonathan Anche così, se riesci a risolvere un problema in una lingua che non espone i puntatori, è anche possibile risolvere quel problema in una lingua che espone i puntatori senza usare i puntatori. Ciò rende vera la prima frase, ma la seconda frase è falsa: non ci sono (per quanto ne sappia) nessun problema che non possa essere risolto senza l'uso di puntatori.
Thomas Owens

1
Al contrario, l'elaborazione delle immagini spesso comporta "coordinate aritmetiche" - diciamo, prendendo il seno e il coseno di un angolo e moltiplicandolo con le coordinate xe y. In altri casi è necessaria la replica pixel avvolgente o fuori bordo. In questo senso, l'aritmetica del puntatore è piuttosto inadeguata.
rwong,

1

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)


1

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?).


+1 per menzionare le strutture di dati, l'uso negli algoritmi è una conseguenza naturale.
Matthieu M.,

0

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.


0

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?"


0

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:

  1. È 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.

  2. È 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).


0

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.

La tua domanda potrebbe ricevere risposta in questa parte

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


0

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.


0

Vedo i puntatori come il mio dito indice, usiamo per fare un paio di cose:

  1. quando qualcuno ti chiede qualcosa che non puoi portare, come dov'è la strada così e così, vorrei "indicare" questa strada, ed è quello che facciamo in caso di argomenti per riferimento
  2. quando contiamo o attraversiamo qualcosa, dovremmo usare lo stesso dito, ed è quello che facciamo negli array

per favore perdona questa povera risposta


0

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.

1. Polimorfismo

Un puntatore della classe base consente di chiamare un metodo virtuale che dipende dal tipo di oggetto a cui punta il puntatore.

2. Creazione di oggetti persistenti

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.


Forse qualcuno preferirebbe tagliare l'oggetto piuttosto che usare il puntatore. Buon debug: D
deadalnix,
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.