Le strutture

Continuiamo a parlare della gestione dei dati nel C++.
Dopo aver visto i tipi disponibili nel C++, le variabili e gli array, vediamo le strutture.

Mettiamo di dover continuare il nostro ipotetico videogioco, oltre ai tesori da ritrovare vogliamo aggiungere la presenza di nemici da combattere, per rendere il tutto più interessante.
Decidiamo di inserire vari tipi di nemici, con caratteristiche diverse l'uno dall'altro.
Ogni tipologia di nemico avrà il suo nome, un potere specifico, una sua resistenza ai nostri attacchi, un valore che indica la sua forza ed il numero di punti che si guadagnano se li si uccide.

Abbiamo a che fare con tipi diversi di dato, stringhe, interi, numeri con la virgola.
Volendo potremmo procedere nel modo seguente:

string nomeDrago = "drago";
string armaDrago = "fuoco";
float armaturaDrago = 12.5;
float forzaDrago = 37.2;
int puntiDrago = 500;

E così per ogni nemico. Sembra piuttosto scomodo e soprattutto disordinato, poiché i dati non hanno un vero legame tra loro, tranne il nome delle variabili che termina in tutti i casi con "Drago".

Potremmo usare un array di 5 elementi:

string Drago[5]; //un array può essere di un solo tipo

Drago[0] = "drago"; //nome
Drago[1] = "fuoco"; //arma
Drago[2] = "12.5"; //ERRATO!

Ma come sappiamo in un array tutti gli elementi devono essere dello stesso tipo indicato durante la dichiarazione, in questo caso string.
Il terzo valore (12,5) deve indicare la resistenza dell'armatura, e dev'essere trattato come un numero, non come una stringa.
Mettiamo ad esempio che, man mano che il nostro personaggio colpisce il drago, la sua armatura si indebolisce. Abbiamo quindi bisogno di manipolare quel dato, di decrementarlo man mano che il drago viene menato. Tuttavia se memorizziamo 12,5 in un elemento di un array di tipo string, questo verrà trattato come una semplice stringa letterale, anche se composta da cifre, e non ci sarà possibile eseguire delle operazioni aritmetiche su di esso. A noi serve che sia di tipo float.

Ed è qui che entrano in gioco le strutture. Una struttura è un tipo di dato definito dall'utente, ed è un tipo composto.
Una struttura si definisce tramite l'uso della parola chiave struct:
struct Nemico {
string nome;
string arma;
float armatura;
float forza;
int punti;
};
Abbiamo appena creato un nuovo tipo composto, chiamato Nemico.
Prima di tutto notate come la parentesi graffa di chiusura è seguita da un punto e virgola, a differenza di quanto avviene nelle funzioni e nelle strutture di controllo.
Ricordatevi che nella definizione di una nuova struttura va sempre messo il punto e virgola ; alla fine.

E' importante capire che la struttura da noi definita è solo un tipo, e non può contenere dati allo stato attuale.
Per fare un parallelo con la vita reale, cane è una struttura, ma non è qualcosa di concreto, è solo un modello utilizzato per identificare i vari cani.
Una struttura di tipo cane ha un nome, una razza, un sesso ecc.. ma non è un cane nel senso fisico del termine, è solo una categoria.
Per poterla utilizzare, dobbiamo prendere un animale in carne ed ossa appartenente alla categoria cane.
Una volta che abbiamo il nostro cane concreto, possiamo assegnargli quei valori in accordo al suo tipo.
Ad esempio Brian, il mio cane, ha un nome, una razza ecc.. che lo distinguono da un altro esemplare dello stesso tipo (e cioè da un altro cane).

Lo stesso avviene con le strutture: per essere utilizzata, una struttura definita dall'utente, dev'essere prima istanziata.
Un po' come avviene nei tipi base forniti dal C++ dove, prima di poter utilizzare ad esempio il tipo int, dobbiamo creare un'istanza concreta di quel tipo, e cioé una variabile.
int è quindi un tipo astratto, un modello che sta ad indicare che tutte le istanze appartenenti a quel tipo potranno contenere solo interi. Perciò int non può contenere in sé nessun valore concreto.
Allo stesso modo, nel nostro esempio, una struttura di tipo Nemico si limita a dire "tutte le mie istanze, e cioé tutte le variabili di tipo Nemico, potranno contenere un nome, un'arma ecc..". Tuttavia la struttura Nemico in partenza non contiene nessun nome o arma particolare.

Nel nostro esempio la struttura Nemico è solo un modello che definisce le caratteristiche comuni a tutti i nemici. Ma per poterla utilizzare concretamente, dobbiamo creare delle istanze, e cioé degli oggetti reali, di tale struttura:

Nemico drago;
Nemico orco;
Nemico ninja;

E' anche possibile utilizzare una forma condensata, dove le istanze della struttura sono definite subito dopo la struttura stessa:
struct Nemico {
string nome;
string arma;
float armatura;
float forza;
int punti;
}
drago, orco, ninja;
Come abbiamo appena visto, una volta creata, una struttura si utilizza allo stesso modo di un qualsiasi altro tipo.
Nel nostro esempio abbiamo definito tre variabili di tipo Nemico, e siamo ora pronti ad utilizzarle. Il loro utilizzo è molto simile a quello degli altri tipi, int, float, string ecc..
ma dal momento che il tipo che abbiamo creato, la struttura Nemico, è un tipo composto, la sintassi è leggermente diversa da quella utilizzata dalle comuni variabili.

Vediamo prima di tutto come inizializzare una variabile appartenente a un tipo definito con una struttura (ricordiamo che inizializzare significa assegnare un valore ad una variabile al momento della sua definizione):
Nemico zombie = {
"Zombie", //nome
"energia oscura", //arma
12.3, //armatura
44.1, //arma
300 //punti
}; //notare il punto e virgola
In questo modo abbiamo appena definito una variabile zombie, di tipo Nemico, e l'abbiamo inizializzata con un nome, un'arma ecc..
Notate come anche durante l'inizializzazione sia necessario il punto e virgola. I valori devono essere separati da una virgola, e devono seguire l'ordine dettato dalla struttura Nemico.
In altre parole non possiamo assegnare i valori in modo casuale, prima dobbiamo inserire il nome, poi l'arma e così via. Questo perché, quando abbiamo creato la struttura Nemico, abbiamo messo prima la variabile string nome, poi la variabile string arma ecc..

Per accedere ai singoli membri di una struttura, dobbiamo utilizzare l'operatore punto:

Nemico arpia; //definiamo due variabili di tipo nemico
Nemico demone;

arpia.nome = "Arpia";
arpia.arma = "piume avvelenate";

demone.armatura = 92.418; //è un nemico molto resistente
demone.arma = "maledizioni";

Come avete visto, per accedere ad un membro specifico di una struttura, basta far seguire al nome della variabile di quel tipo il nome del membro stesso preceduto da un punto.
E' anche possibile assegnare ad una variabile di un tipo definito da una struttura, il valore di un'altra variabile dello stesso tipo:

Nemico drago;

drago.nome = "Drago";
drago.arma = "alito di fuoco";
drago.punti = 6000;
drago.forza = 57.3;
drago.armatura = 77.8;

Nemico draghetto;

draghetto = drago; //assegniamo a draghetto i valori di drago

//tuttavia il draghetto è meno potente del drago, meglio modificare la sua forza

draghetto.forza = 48.9; //così va meglio :)

Ovviamente la linea di codice draghetto = drago è possibile perché sia draghetto e drago sono entrambe variabili dello stesso tipo, e cioé Nemico.

Naturalmente è possibile, come con tutte le variabili, utilizzare le istanze di una struttura con le istruzioni cout e cin.
Ad esempio mettiamo che una volta ucciso, un nemico debba mostrare a schermo il suo punteggio:

cout << drago.punti; //stampa i punti di drago

Appare superfluo aggiungere che è altrettanto possibile utilizzare degli array di struttura. Così come creiamo un array di int, possiamo benissimo creare un array di Nemici.
Ad esempio immaginiamo di dover creare un livello del nostro videogioco, ambientato in un castello abbandonato, abitato da temibili vampiri. Naturalmente non ci basta creare un solo vampiro, perciò ricorriamo agli array:

Nemici vampiro[56];

vampiro[6].arma = "canini appuntiti";

Infine anche la struttura stessa può contenere un array. Chiariamo meglio con un esempio:
ogni livello del nostro videogioco è diviso in stanze, dobbiamo quindi definire una struttura di tipo stanza, che utilizzaremo poi per creare le varie stanze nei nostri livelli;
ogni stanza ha un tipo particolare di parete, ad esempio in mattoni o in legno;
ogni stanza è formata da quattro pareti, e ogni parete può avere un passaggio segreto:
struct Stanza {
string materialeParete;
bool passaggioParete[4];
};
Abbiamo creato la nostra struttura dal nome Stanza, e cioé il "template" che utilizzeremo per creare delle stanze concrete all'interno nel livello del nostro videogioco.
Il primo membro della classe è di tipo string, e contiene il materiale di cui sono fatte le pareti di una particolare stanza.
Il secondo membro è di tipo bool. Ricordate cos'è un tipo boleano?
Una variabile di tipo bool è una variabile che potrà assumere solo due valori, true o false.
Nel nostro caso true starà ad indicare che la parete ha un passaggio segreto, mentre false indicherà il contrario.
Infine notiamo che la variabile di tipo bool è un array di 4 elementi. Questo perché ogni parete ha 4 stanze, ed ogni elemento rappresenta una parete.
Andiamo a creare una Stanza per il livello dei vampiri, la stanza di ingresso del castello stregato:

Stanza ingressoCastello;

La stanza ha quattro pareti in pietra apparentemente chiuse, sta al nostro giocatore trovare la via d'uscita.
Decidiamo di seguire una convenzione dove le pareti sono così disposte:
   [2]
[3] [1]
[0]
Dove ogni parete è rappresentata da un elemento del nostro array.
Decidiamo di inserire un passaggio nascosto nella parete [2]. Ora però andiamo a settare i valori dell'ingresso del nostro castello:

ingressoCastello.materialeParete = "pietra";

ingressoCastello.passaggioParete[0] = false;
ingressoCastello.passaggioParete[1] = false;
ingressoCastello.passaggioParete[2] = true; //la parete [2] ha un passaggio segreto
ingressoCastello.passaggioParete[3] = false;

Abbiamo settato prima di tutto il materiale con cui sono costruite le pareti della nostra stanza di ingresso.
Abbiamo poi settato il passaggio segreto nella parete numero due, infatti come potrete notare è l'unica ad assumere il valore true.

Come vedete è molto intuitivo utilizzare gli array, l'importante è non confondere gli array di struttura:

Stanza cameraDaLetto[10]; //array di struttura

cameraDaLetto[5].materialeParete = "granito verde";

Con gli array dei membri di una struttura:
struct Mano {
string coloreSmalto;
float lunghezzaUnghia[5]; //array di un membro della struttura
};

Mano manoSinistra;

manoSinistra.lunghezzaUnghia[2] = 7.3;
Se avete capito bene le strutture siete già a metà della strada che porta alla programmazione ad oggetti.

Nessun commento:

Posta un commento