next up previous contents index
Next: Liste vs Array Up: Memoria dinamica. Strutture. Liste Previous: Strutture   Indice   Indice analitico

Puntatori a strutture

Abbiamo detto che i nuovi tipi introdotti con le struct possono essere utilizzati come i tipi primitivi, e a loro volta essere derivati; ad esempio possiamo creare array di Rettangolo o puntatori o riferimenti ad essi. Vediamo un esempio contenente un puntatore ad un oggetto di tipo struttura:

// ex7_5_1
#include <iostream.h>
#include <string.h>

struct Figura {
  int x;  // posizione, ascissa
  int y;  // posizione, ordinata
  double scala;   // fattore di scala
  char nome[20];  // nome della figura
};

void main() {
  Figura f;
  f.x = 150;
  f.y = 70;
  f.scala = 1.5;
  strcpy (f.nome, "quadrato");

  Figura* p = &f;
  (*p).x = 300;
  cout << f.x << "\n";
}

output:
300

Si noti che nello statement


(*p).x = 300;
si è dovuto usare le parentesi tonde affinché il compilatore intepreti il comando come ``il campo x dell'oggetto puntato da p'' piuttosto che ``l'oggetto puntato dal campo x di p''; tale sintassi è piuttosto scomoda, in quanto poco intuitiva e facile da confondere; è stato allora introdotto in C++ un nuovo operatore, che funge da sinonimo sintattico all'operatore ``campo di'' se l'oggetto è un puntatore: l'operatore ``freccia'' (->). Avremmo ad esempio potuto scrivere, al posto di (*p).x = 300;

p->x = 300;
il quale rende bene l'idea di cosa si intenda fare. Vediamo un esempio:

// ex7_5_2
#include <iostream.h>
#include <string.h>

struct Figura {
  int x;  // posizione, ascissa
  int y;  // posizione, ordinata
  double scala;   // fattore di scala
  char nome[20];  // nome della figura
};

// passaggio per indirizzo
// con puntatore costante
void stampa (const Figura* p) {
  cout << "figura: " << p->nome << "\n" <<
    "posizione (" << p->x << ", " << p->y << ")\n" <<
    "scala 1:" << p->scala << "\n";
}

// passaggio per indirizzo
// con puntatore NON costante:
// l'argomento viene modificato
void sposta (Figura* p, int x, int y) {
  p->x = x;
  p->y = y;
}

void main() {
  Figura f;
  f.x = 150; f.y = 70;
  f.scala = 1.5;
  strcpy (f.nome, "quadrato");
  stampa (&f);
  sposta (&f, 80, 25);
  stampa (&f);
}

output:
figura: quadrato
posizione (150, 70)
scala 1:1.5
figura: quadrato
posizione (80, 25)
scala 1:1.5

L'operatore `->' è molto usato; infatti è piuttosto comune utilizzare puntatori a oggetti complessi, come le strutture, spesso perché essi vengono allocati dinamicamente. Comunque è importante capire che l'operatore `->' non è altro che un sinonimo dell'operatore `.', nel caso l'oggetto sia un puntatore.

Poniamoci a questo punto una domanda: è possibile inizializzare oggetti di tipo struttura? La risposta è affermativa; tuttavia inizializzare un oggetto costituito da tanti oggetti di diverso tipo corrisponde ad inizializzare ogni campo della struttura; per compiere tale operazione in un solo statement abbiamo bisogno di un nuovo strumento messo a disposizione dal C++: il costruttore. Quando tratteremo le classi, vedremo meglio l'importanza che ha il costruttore nella creazione di nuovi tipi; per ora impareremo soltanto ad utilizzarlo nelle sue funzionalità base. Un costruttore è una funzione interna ad una struttura, la quale specifica come inizializzare i campi di una istanza (cioè un oggetto) di tale struttura; esso viene chiamato automaticamente una ed una sola volta al momento della creazione dell'oggetto; il nome della funzione costruttore è obbligatoriamente lo stesso nome della struttura. Vediamo un esempio:


// ex7_5_3
#include <iostream.h>

struct Rettangolo {
  int larg;
  int alt;
  Rettangolo () {  // costruttore
    larg = 50;
    alt = 20;
  }
};

void main() {
  Rettangolo r;
  cout << "larg = " << r.larg << "\n" <<
    "alt = " << r.alt << "\n";
}

output:
larg = 50
alt = 20

La funzione costruttore all'intero della struttura Rettangolo ha come compito quello di impostare dei valori iniziali ai campi larg e alt; nulla vieta che solo alcuni dei campi dati della struttura vengano inizializzati, ma si sconsiglia tale pratica perché fuorviante: per quale motivo un utilizzatore del nostro codice dovrebbe aspettarsi che il costruttore inizializzi solo alcuni campi lasciando gli altri fluttuanti?

Il costruttore può avere anche degli argomenti, spesso corrispondenti a ciascun campo da inizializzare:


// ex7_5_4
#include <iostream.h>
#include <string.h>

struct Libro {
  char* titolo;
  int costo;
  bool disponibile;
  Libro (char* Titolo, int Costo, bool Disponibile) {
    titolo = new char[strlen(Titolo) + 1];
    strcpy (titolo, Titolo);
    costo = Costo;
    disponibile = Disponibile;
  }
};

void stampa (const Libro& a) {
  cout << a.titolo << "\n" <<
    "costo: " << a.costo << "\n";
  if (a.disponibile == true)
    cout << "disponibile" << "\n";
  else
    cout << "non disponibile" << "\n";
}

void main() {
  // Libro a;  // NO
  Libro a ("2000: Space odissey", 32000, true);
  Libro* b = new Libro ("Shining", 25000, false);
  stampa (a);
  stampa (*b);
}

output:
2001: Space odissey
costo: 32000
disponibile
Shining
costo: 25000
non disponibile

Come si vede l'inizializzazione di un oggetto struttura è semplice e immediata; si noti bene che se esiste un costruttore in una struttura, non è possibile creare un oggetto di tale struttura senza fornire i parametri opportuni al costruttore. Tuttavia è possibile avere più di un costruttore per ciascuna struttura, sfruttando la nota tecnica di overloading delle funzioni; ad esempio


// ex7_5_5
#include <iostream.h>

enum Colore { ROSSO, VERDE, BLU,
              CIANO, MAGENTA, GIALLO,
              BIANCO, NERO };

struct Tazza {
  Colore col;
  double diametro;
  double capacita;
  Tazza (Colore Col, double Diametro, double Capacita) {
    col = Col;
    diametro = Diametro;
    capacita = Capacita;
  }
  Tazza (double Diametro, double Capacita) {
    col = ROSSO;
    diametro = Diametro;
    capacita = Capacita;
  }
  Tazza () {
    col = ROSSO;
    diametro = 8;
    capacita = 200;
  }
};

void stampa (const Tazza& t) {
  char c[10];
  switch (t.col) {
  case ROSSO: strcpy (c, "rosso"); break;
  case VERDE: strcpy (c, "verde"); break;
  case BLU: strcpy (c, "blu"); break;
  case CIANO: strcpy (c, "ciano"); break;
  case MAGENTA: strcpy (c, "magenta"); break;
  case GIALLO: strcpy (c, "giallo"); break;
  case BIANCO: strcpy (c, "bianco"); break;
  case NERO: strcpy (c, "nero"); break;
  }
  cout << c << "\t" << t.diametro << 
    "\t" << t.capacita << "\n";
}

void main() {
  Tazza t1 (VERDE, 10, 250);
  Tazza t2 (6, 140);
  Tazza t3;
  stampa (t1);
  stampa (t2);
  stampa (t3);
}

output:
 verde 10 250
 rosso 6 140
 rosso 8 200

Comunque l'argomento riguardante i costruttori verrà approfondito ampiamente durante la trattazione delle classi; abbiamo introdotto tale costrutto a proposito delle struttura solo perché potremo farne uso nei prossimi paragrafi, nei quali presenteremo le liste; in realtà utilizzare costruttori nelle strutture non è comune, se non appunto in particolari casi.

ex-2
si modifichi ciascuno degli esempi presentati in questa sezione, aggiungendo funzioni le quali effettuano operazioni sui campi delle strutture utilizzate;


next up previous contents index
Next: Liste vs Array Up: Memoria dinamica. Strutture. Liste Previous: Strutture   Indice   Indice analitico
Claudio Cicconetti
2000-09-06