Perché devo usare il tipo ** per indicare il tipo *?

Ho letto Learn C The Hard Way per alcuni giorni, ma ecco qualcosa che voglio veramente capire. Zed, l’autore, ha scritto che char ** è per un “puntatore a (un puntatore al char)”, e dicendo che questo è necessario perché sto cercando di indicare qualcosa di bidimensionale.

Ecco cosa è esattamente scritto nella pagina web

Un char * è già un “puntatore al char”, quindi è solo una stringa. Hai comunque bisogno di 2 livelli, poiché i nomi sono bidimensionali, il che significa che hai bisogno di char ** per un tipo “puntatore a (puntatore al carattere)”.

Questo significa che devo usare una variabile che può puntare a qualcosa di bidimensionale, motivo per cui ho bisogno di due ** ?

Solo un piccolo follow-up, questo vale anche per la dimensione n?

Ecco il codice pertinente

 char *names[] = { "Alan", "Frank", "Mary", "John", "Lisa" }; char **cur_name = names; 

No, quel tutorial è di qualità discutibile. Non consiglierei di continuare a leggerlo.

Un char** è un puntatore al puntatore. Non è un array 2D. Non è un puntatore a un array. Non è un puntatore a un array 2D.

L’autore del tutorial è probabilmente confuso perché c’è una pratica scorretta e scorretta diffusa che dice che dovresti allocare array dinamici 2D come questo:

 // BAD! Do not do like this! int** heap_fiasco; heap_fiasco = malloc(X * sizeof(int*)); for(int x=0; x 

Questo non è tuttavia un array 2D, è una tabella di ricerca lenta e frammentata allocata su tutto l'heap. La syntax di accesso a un elemento nella tabella di ricerca, heap_fiasco[x][y] , è simile alla syntax di indicizzazione degli array, quindi molte persone credono che questo sia il modo in cui allocate gli array 2D.

Il modo corretto per allocare dynamicmente un array 2D è:

 // correct int (*array2d)[Y] = malloc(sizeof(int[X][Y])); 

Puoi dire che il primo non è un array perché se fai memcpy(heap_fiasco, heap_fiasco2, sizeof(int[X][Y])) il codice andrà in crash e brucerà. Gli articoli non sono allocati nella memoria adiacente.

Allo stesso modo anche memcpy(heap_fiasco, heap_fiasco2, sizeof(*heap_fiasco)) andrà in crash e masterizzerà, ma per altri motivi: si ottiene la dimensione di un puntatore non di un array.

Mentre memcpy(array2d, array2d_2, sizeof(*array2d)) funzionerà, perché è un array 2D.

I puntatori mi hanno messo un po ‘a capire. Consiglio vivamente di disegnare diagrammi.

Si prega di leggere e comprendere questa parte del tutorial del C ++ (almeno per quanto riguarda i puntatori che i diagrammi mi hanno veramente aiutato).

Dicendoti che hai bisogno di un puntatore a un puntatore per disegnare un array bidimensionale è una bugia. Non ne hai bisogno ma è un modo per farlo.

La memoria è sequenziale Se vuoi mettere 5 caratteri (lettere) in fila come nella parola ciao puoi definire 5 variabili e ricordare sempre in quale ordine usarle, ma cosa succede quando vuoi salvare una parola con 6 lettere? Definisci più variabili? Non sarebbe più facile se li hai memorizzati in una sequenza?

Quindi quello che fai è chiedere al sistema operativo di 5 caratteri (e ogni char è solo un byte) e il sistema ti restituisce un indirizzo di memoria in cui inizia la sequenza di 5 caratteri. Prendi questo indirizzo e lo memorizzi in una variabile che chiamiamo puntatore, perché punta alla tua memoria.

Il problema con i puntatori è che sono solo indirizzi. Come fai a sapere cosa viene memorizzato a quell’indirizzo? È 5 caratteri o è un grande numero binario che è 8 byte? O è parte di un file che hai caricato? Come lo sai?

È qui che il linguaggio di programmazione come C cerca di aiutare dandoti dei tipi. Un tipo ti dice cosa sta memorizzando la variabile e anche i puntatori hanno tipi ma i loro tipi ti dicono a cosa punta il puntatore. Quindi, char * è un puntatore a una posizione di memoria che contiene un singolo char o una sequenza di chars . Purtroppo, la parte relativa a quanti char ci sono è che dovrai ricordarti di te stesso. Di solito memorizzi tali informazioni in una variabile che tieni in giro per ricordarti quanti caratteri ci sono.

Quindi quando vuoi avere una struttura dati bidimensionale come la rappresenti?

Questo è meglio spiegato con un esempio. Facciamo una matrice:

 1 2 3 4 5 6 7 8 9 10 11 12 

Ha 4 colonne e 3 file. Come lo conserviamo?

Bene, possiamo fare 3 sequenze di 4 numeri ciascuna. La prima sequenza è 1 2 3 4 , la seconda è 5 6 7 8 e la terza e ultima sequenza è 9 10 11 12 . Quindi se vogliamo memorizzare 4 numeri chiediamo al sistema di riservare 4 numeri per noi e darci un puntatore a loro. Questi saranno puntatori ai numeri . Tuttavia, dal momento che abbiamo bisogno di averne 3, chiederemo al sistema di darci 3 indicatori per i numeri dei puntatori .

Ed è così che si finisce con la soluzione proposta …

L’altro modo per farlo sarebbe capire che hai bisogno di 4 volte 3 numeri e solo chiedere al sistema di memorizzare 12 numeri in una sequenza. Ma come accedere al numero nella riga 2 e nella colonna 3? È qui che entra in gioco la matematica, ma proviamo con il nostro esempio:

 1 2 3 4 5 6 7 8 9 10 11 12 

Se li immagazziniamo l’uno accanto all’altro sembrerebbe questo:

 offset from start: 0 1 2 3 4 5 6 7 8 9 10 11 numbers in memory: [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12] 

Quindi la nostra mapping è così:

 row | column | offset | value 1 | 1 | 0 | 1 1 | 2 | 1 | 2 1 | 3 | 2 | 3 1 | 4 | 3 | 4 2 | 1 | 4 | 5 2 | 2 | 5 | 6 2 | 3 | 6 | 7 2 | 4 | 7 | 8 3 | 1 | 8 | 9 3 | 2 | 9 | 10 3 | 3 | 10 | 11 3 | 4 | 11 | 12 

E ora dobbiamo elaborare una formula semplice e facile per convertire una riga e una colonna in un offset … Tornerò su di esso quando avrò più tempo … In questo momento ho bisogno di tornare a casa (mi dispiace). ..

Edit: Sono un po ‘in ritardo, ma lasciami continuare. Per trovare l’offset di ciascuno dei numeri da una riga e una colonna è ansible utilizzare la seguente formula:

 offset = (row - 1) * 4 + (column - 1) 

Se notate i due -1 qui e pensateci, capirete che è perché le nostre numerazioni di riga e colonna iniziano con 1 che dobbiamo fare questo ed è per questo che gli scienziati informatici preferiscono gli offset basati su zero (a causa di questa formula). Tuttavia con i puntatori in C il linguaggio stesso applica questa formula per te quando usi un array multidimensionale. E quindi questo è l’altro modo di farlo.

Dalla tua domanda, quello che ho capito è che stai chiedendo perché hai bisogno di char ** per la variabile che è dichiarata come * nomi []. Quindi la risposta è quando scrivi semplicemente nomi [], piuttosto che la syntax dell’array e dell’array è fondamentalmente un puntatore.

Quindi quando scrivi * nomi [] di quello significa che stai puntando a un array. E come array è fondamentalmente un puntatore quindi significa che hai un puntatore a un puntatore e questo è il motivo per cui il compilatore non si lamenterà se scrivi

char ** cur_name = nomi;

Nella riga precedente stai dichiarando un puntatore a un puntatore di carattere e quindi inizializzalo con il puntatore a un array (ricorda che anche l’array è un puntatore).