Passando una serie di strutture per funzionare in c ++

Scusa per la domanda di Noob, sono solo un po ‘confusa.
Se ho una matrice di strutture in main che voglio passare ad una funzione:

struct MyStruct{ int a; int b; char c; mayarray[5]; }; MyStruct StructArray[10]; myFunction(StructArray[]) 

Passa a questo a una funzione:

 void myFunction(struct MyStruct PassedStruct[]) { PassedStruct[0].a = 1; PassedStruct[0].b = 2; // ... etc } 

La mia domanda è, chiamerà la funzione in questo modo modificare i dati in StructArray ? Ne ho bisogno Sarebbe chiamata per riferimento? Sono un po ‘confuso. Come dovrei cambiarlo in modo che quando passo la matrice di strutture alla funzione, la funzione modificherà l’array StructArray ? Mi piace lo studio visivo btw.
Grazie.

 struct MyStruct PassedStruct[] 

è principalmente una syntax alternativa per:

 struct MyStruct * PassedStruct 

Quindi sì, potrai accedere e modificare la struttura originale.

Solo un dettaglio da modificare, la chiamata corretta alla funzione non lo è

 myFunction(StructArray[]); 

ma:

 myFunction(StructArray); 

Ora proverò a spiegare perché ho usato la parola principalmente nella frase sopra:

Darò qualche accenno alla differenza tra array e puntatori, perché non dovresti confonderli (anche se non direi che non sono correlati, al contrario), e il problema con il parametro MyStruct PassedStruct[] che passa syntax.

Questo non è per i principianti, e gli esperti standard del C ++ dovrebbero anche evitare di leggerlo (perché non voglio entrare in qualche guerra ISO standard quando inserisco un territorio di comportamento non definito ISO – ovvero territorio proibito).

Iniziamo con gli array:

Immagina una struttura semplice:

 struct MyStruct{ int a; int b; char c; }; 

MyStruct a1[3]; è la dichiarazione di una matrice i cui elementi sono del tipo di struttura precedente. La cosa più importante che fa il compilatore quando definisce un array è di allocare lo spazio per esso. Nel nostro esempio ha riservato spazio per 3 strutture. Questo spazio riservato può essere in pila o da risorse di memoria globale a seconda di dove si trova l’istruzione di dichiarazione.

Puoi anche inizializzare la struct quando la dichiari come in:

 struct MyStruct a1[3] = {{1, 2}, {3, 4}, {5, 6}}; 

Si noti che in questo esempio, non ho inizializzato il campo c ma solo b . Questo è permesso. Potrei anche usare la syntax del designatore se il mio compilatore lo supporta come in:

 struct MyStruct a1[3] = {{a:1, b:2}, {a:3, b:4}, {a:5, b:6}}; 

Ora, c’è un’altra syntax per la definizione di un array usando i backset quadrati vuoti come in:

 struct MyStruct a2[] = {{1, 2}, {3, 4}, {5, 6}}; 

Il punto qui è che a2 è un array perfettamente normale, proprio come a1 . La dimensione dell’array non è implicita, è data dall’inizializzatore: ho tre inizializzatori quindi ottengo una matrice di tre structs.

Potrei definire una matrice non inizializzata di dimensioni note con questa syntax. Per una matrice non inizializzata di dimensione 3 avrei:

 struct MyStruct a2[] = {{},{},{}}; 

Lo spazio è allocato, esattamente come con la syntax precedente, nessun puntatore coinvolto qui.

Introduciamo un puntatore:

 MyStruct * p1; 

Questo è un semplice puntatore a una struttura di tipo MyStruct. Posso accedere ai campi attraverso la solita syntax del puntatore p1->a o (*p1).a . C’è anche una syntax dell’array per fare lo stesso di p1[0].a . Ancora come sopra. Devi solo ricordare che p1 [0] è una scorciatoia per (*(p1+0)) .

Ricorda anche la regola dell’aritmetica del puntatore: aggiungere 1 a un puntatore significa aggiungere la dimensione dell’object appuntito all’indirizzo di memoria sottostante (ciò che ottieni quando usi il parametro di formato% p printf). L’aritmetica del puntatore consente l’accesso a strutture identiche successive. Ciò significa che puoi accedere alle strutture per indice con p1[0] , p1[2] , ecc.

I confini non sono controllati. Ciò che è indicato nella memoria è la responsabilità del programmatore. Sì, so che ISO dice in modo diverso, ma è quello che fanno tutti i compilatori che ho mai provato, quindi se ne conosci uno che non funziona, ti prego dimmelo.

Per fare qualcosa di utile con p1, devi fare in modo che punti ad alcune strutture di tipo MyStruct . Se disponi di una matrice di tali strutture disponibile come la nostra a1 , puoi semplicemente fare p1=a1 e p1 punta all’inizio della matrice. In altre parole, avresti potuto fare anche p1=&a1[0] . È naturale disporre di una syntax semplice in quanto è esattamente ciò che l’aritmetica del puntatore è progettata per: accedere a matrici di oggetti simili.

La bellezza di questa convenzione è che consente di unificare completamente la syntax dell’accesso puntatore e array. La differenza è vista solo dal compilatore:

  • quando vede p1[0] , sa che deve recuperare il contenuto di una variabile il cui nome è p1 e che conterrà l’indirizzo di qualche struttura di memoria.

  • quando vede a1[0] , sa che a1 è una costante che dovrebbe essere intesa come un indirizzo (non qualcosa da recuperare in memoria).

Ma una volta che l’indirizzo da p1 o a1 è disponibile, il trattamento è identico.

Un errore comune è scrivere p1 = &a1 . Se lo fai, il compilatore ti darà alcune parole di quattro lettere. Ok, &a1 è anche un puntatore, ma quello che ottieni quando prendi l’indirizzo di a1 è un puntatore all’intero array. Ciò significa che se aggiungi 1 a un puntatore di questo tipo, l’indirizzo effettivo si sposterà di passi di 3 strutture contemporaneamente.

Il tipo effettivo di un puntatore di quel tipo (chiamiamolo p2 ) sarebbe MyStruct (*p2)[3]; . Ora puoi scrivere p2 = &a1 . Se vuoi accedere alla prima struct MyStruct all’inizio del blocco di memoria puntato da p2 dovrai scrivere qualcosa come p2[0][0].a o (*p2)[0].a o (*(*p2)).a o (*p2)->a o p2[0]->a .

Grazie al sistema di tipo e all’aritmetica del puntatore, tutti questi stanno facendo esattamente la stessa cosa: recupera l’indirizzo contenuto in p2, usa quell’indirizzo come una matrice (un indirizzo costante noto) come spiegato sopra.

Ora puoi capire perché i puntatori e gli array sono tipi completamente diversi che non dovrebbero essere confusi come alcuni potrebbero dire. In parole semplici i puntatori sono variabili che contengono un indirizzo, gli array sono indirizzi costanti. Per favore non spararmi Guru C ++, sì, so che non è la storia completa e che i compilatori mantengono molte altre informazioni insieme all’indirizzo, la dimensione dell’object appuntito (indirizzato?) Per esempio.

Ora potreste chiedervi perché nel contesto del parametro passare è ansible utilizzare parentesi quadre vuote e significa davvero puntatore . ? Nessuna idea. Qualcuno probabilmente ha pensato che fosse bello.

A proposito, almeno con gcc, puoi anche inserire un valore tra parentesi invece di tenerle vuote. Non farà la differenza, otterrete comunque un puntatore, non un array, e non vengono eseguiti i limiti o il controllo del tipo. Non ho controllato lo standard ISO, dovrebbe essere fatto e se è richiesto dallo standard o se si tratta di un comportamento specifico.

Se vuoi controllare i limiti dei caratteri, usa solo un riferimento. Può essere sorprendente, ma questa è un’area in cui se si utilizza un riferimento, il tipo effettivo del parametro viene modificato da puntatore a matrice (e non dal puntatore al riferimento al puntatore come ci si può aspettare).

 MyStruct StructArray[10]; 
  • header: void myFunction(struct MyStruct * PassedStruct)
  • chiamante: myFunction(StructArray)
  • stato: funziona, si lavora con un puntatore in PassedStruct
  • header: void myFunction(struct MyStruct PassedStruct[])
  • chiamante: myFunction(StructArray)
  • stato: funziona, si lavora con un puntatore in PassedStruct
  • header: void myFunction(struct MyStruct (& PassedStruct)[10])
  • chiamante: myFunction(StructArray)
  • stato: funziona, si lavora con un riferimento ad una matrice di dimensione 10
  • header: void myFunction(struct MyStruct (& PassedStruct)[11])
  • chiamante: myFunction(StructArray)
  • stato: non compilato, tipo di mismatch dell’array tra prototipo e parametro attuale
  • header: void myFunction(struct MyStruct PassedStruct[10])
  • chiamante: myFunction(StructArray)
  • stato: funziona, PassedStruct è un puntatore, la dimensione fornita viene ignorata
  • header: void myFunction(struct MyStruct PassedStruct[11])
  • chiamante: myFunction(StructArray)
  • stato: funziona, PassedStruct è un puntatore, la dimensione fornita viene ignorata

Sebbene gli array e i puntatori siano concettualmente diversi, le acque sono molto confuse dai parametri di funzione. Non è ansible passare direttamente una matrice a una funzione; puoi solo passare un puntatore. Di conseguenza, la lingua “utile” converte un prototipo come questo:

 void foo (char arr[]) 

in questo:

 void foo (char *arr) 

E quando si chiama la funzione, non si passa una matrice completa, si passa un puntatore al primo elemento. Di conseguenza, all’interno della funzione foo , avrà un puntatore alla matrice originale , e assegnando agli elementi di arr cambierà anche la matrice nel chiamante.

In tutte le altre situazioni al di fuori dei parametri di funzione, la syntax dell’array e del puntatore nelle dichiarazioni non sono equivalenti e si riferiscono a cose concettualmente diverse. Ma all’interno degli elenchi dei parametri delle funzioni, la syntax dell’array crea i tipi di puntatore.

Credo che x [] significhi la stessa cosa di x * che significa “puntatore al tipo”. Poiché questo è il caso, modificherete l’object che passate (dovrete chiamarlo usando l’operatore &, o ‘address of’), e potete pensarlo come riferimento.

Quando si passa un array come argomento a una funzione, l’array decade in un puntatore al primo elemento dell’array.

Quindi, quando all’interno della tua funzione usi [] per accedere agli elementi dell’array, stai solo facendo aritmetica puntatore con il puntatore iniziale per ottenere gli elementi dell’array ORIGINAL.

Quindi, sì, stai modificando la matrice originale. E questa risposta è praticamente indipendente da quale compilatore stai usando (anche se è una buona pratica, IMHO, dichiarare il compilatore nella domanda come hai fatto tu)

Potresti usare invece un vettore std ::. Gli array sono molto costrutti C, il C ++ ha classi wrapper per prevenire esattamente questo tipo di ambiguità