next up previous contents index
Next: Strutture Up: Memoria dinamica. Strutture. Liste Previous: Gli operatori new e   Indice   Indice analitico

Array dinamici

Allocare in maniera dinamica variabili primitive è una operazione sconsigliata per due motivi, che abbiamo già riferito: è più efficiente utilizzate variabili automatiche, inoltre è piuttosto semplice commettere errori di programmazione. Conviene dunque utilizzare l'allocazione automatica per variabili semplici come interi o reali o booleani; al contrario, è spesso inevitabile il ricorso alla memoria dinamica per strutture ben più complicate delle semplici variabili di tipo primitivo, ad esempio per gli array. In un problema è difficile sapere, in fase di stesura del programma, il numero di celle di un vettore (o di una matrice o di una stringa, che possono entrambe essere ricondotte a vettori); vediamo allora come allocare dinamicamente un array. La sintassi è la seguente:
tipo* nome_array = new tipo[numero_elementi]
ove, diversamente dagli array non dinamici, numero_elementi non è necessariamente constante. Vediamo un esempio:

// ex7_3_1
#include <iostream.h>
void main() {
  int n, i;
  cout << "numero elementi? "; cin >> n;
  // double v[n];             // NO! n non e` costante
  double* v = new double[n];  // ok
  
  //chiede all'utente di immettere gli elementi di v
  for (i = 0; i < n; i++) {
    cout << "? ";
    cin >> v[i];
  }
  // stampa il vettore v
  for (i = 0; i < n; i++)
    cout << "v[" << i << "]: " << v[i] << "\n";
}

esempio di output:
numero elementi? 4
? 5
? -3
? 7
? 19
v[0]: 5
v[1]: -3
v[2]: 7
v[3]: 19

Questo esempio mostra come si può decidere la dimensione di un array in fase di esecuzione; lo statement commentato double v[n] non è corretto in quanto la creazione di array non dinamici suppone che la dimensione di essi sia nota a priori, in fase di scrittura del programma; il motivo dipende, come già detto, dal fatto che il compilatore deve sapere in fase di compilazione quanta memoria occupare nello stack.

Per quanto riguarda le matrici, il metodo di allocazione è leggermente diverso: non possiamo allocare ``con un solo colpo'' una matrice dinamicamente, per cui dobbiamo utilizzare uno stratagemma. Una matrice può essere vista come un vettore costruito mettendo l'una di seguito all'altra tutte le righe di essa; in questa maniera basta allocare dinamicamente un vettore, ricordandoci di stare trattando con una matrice. Per semplificarci la vita, possiamo scrivere una funzione che accetta gli indici dell'elemento di una matrice e il numero delle sue colonne, e restituisca l'indice del vettore corrispondente:


// ex7_3_2
#include <iostream.h>

int indice (int i, int j, int colonne) {
  return i * colonne + j;
}

void main() {
  int i, j;  // contatori per i for
  int righe, colonne;
  cout << "#righe? "; cin >> righe;
  cout << "#colonne? "; cin >> colonne;
  double* m = new double[righe * colonne];
  cout << "scrivi PER RIGHE la matrice:\n";

  // ingresso elementi della matrice
  for (i = 0; i < righe; i++) {
    cout << "? ";
    for (j = 0; j < colonne; j++)
      cin >> m[indice(i, j, colonne)];
  }

  // stampa matrice
  for (i = 0; i < righe; i++) {
    for (j = 0; j < colonne; j++)
      cout << m[indice(i, j, colonne)] << "\t";
    cout << "\n";
  }
}

esempio di output:
#righe? 3
#colonne? 4
scrivi PER RIGHE la matrice:
? 5 -1 7 0
? 3 0 9 13
? 4 -7 2 1
 5 -1 7 0
 3 0 9 13
 4 -7 2 1

La funzione indice ha come unico compito quello di elaborare le coordinate che riceve, unitamente al numero delle colonne della matrice, e tornare l'indice del corrispondente vettore; in questa maniera possiamo gestire le matrici allocate dinamicamente come se fossero automatiche, avendo l'accortezza di usare sempre la funzione indice per ottenere il giusto indice dell'elemento cercato. Si risponda alla seguente domanda: perché non è necessario passare anche il numero di righe alla funzione indice?

Abbiamo visto come allocare nello heap vettori (e matrici), tramite l'operatore new; deallocare un array dinamico è altrettanto semplice che deallocare una semplice variabile: basta utilizzare l'operatore delete[], la cui funzione è unicamente quella di segnare tutta la memoria occupata dall'array come libera. Vediamo un esempio:


// ex7_3_3
#include <iostream.h>
#include <stdlib.h>
#include <time.h>

const int MAX = 90;

void casuali (int* v, int n) {
  for (int i = 0; i < n; i++)
    v[i] = rand() % MAX + 1;
}

void stampa (int* v, int n) {
  for (int i = 0; i < n; i++)
    cout << v[i] << " ";
}

void main() {
  srand( time(0) );
  int* n = new int;
  cout << "? "; cin >> *n;
  int* v = new int[*n];
  casuali (v, *n);
  stampa (v, *n);
  delete n;
  // delete v;  // NO! v e` un array
  delete[] v;   // ok
}

esempio di output:
? 6
12  40  2  23  59  32

Si faccia bene attenzione: se avessimo utilizzato lo statement delete, esso non avrebbe tornato nessun messaggio di errore; infatti il programma avrebbe eseguito l'operazione di deallocazione solo sulla prima cella del vettore v, creando una montagna di garbage: tutti gli elementi tranne il primo sarebbero stati inaccessibili e non deallocabili; si cerchi dunque di ricordare sempre di utilizzare l'operatore delete[] per gli array dinamici.

La possibilità di creare array dinamici risolve anche il problema delle stringhe: è possibile ora creare stringhe dimensionate correttamente per il dato che esse devono contenenere, semplicemente allocandole dinamicamente.


// ex7_3_4
#include <iostream.h>
#include <string.h>
void main() {
  char buffer[50];
  char *nome, *cognome, *residenza;
  cout << "nome? "; cin >> buffer;
  nome = new char[strlen(buffer) + 1];
  // nota: il +1 e` per il carattere di fine stringa
  strcpy (nome, buffer);
  cout << "cognome? "; cin >> buffer;
  cognome = new char[strlen(buffer) + 1];
  strcpy (cognome, buffer);
  cout << "residenza? "; cin >> buffer;
  residenza = new char[strlen(buffer) + 1];
  strcpy (residenza, buffer);
}

esempio di output:
nome? Lewis
cognome? Carrol
residenza? Wonderland

In questo esempio abbiamo mostrato una possibile strada per creare stringhe della opportuna dimensione: si crea una stringa che è ragionevolmente adatta a contenere una qualunque delle successive (buffer nel nostro esempio) e la si utilizza come ingresso del cin; volta per volta poi si alloca un array di caratteri che possa contenere la stringa attualmente in buffer, dandole come dimensione la lunghezza di buffer più una unità per il carattere di terminazione stringa ('\0'); si copia buffer nella stringa appena creata.

ex-2
si scriva un programma che accetti un array di n variabili intere e lo scriva al contrario; n sia dato dall'utente ad inizio programma;
ex-3
si scriva un programma che calcoli la traccia di una matrice n $\times$ n, con n immesso dall'utente;
ex-4
si scriva una funzione che accetti un puntatore a array dinamico di reali, il numero degli elementi di tale array e un numero intero; se tale numero è negativo si raddoppino tutti gli elementi dell'array, se positivo si dimezzino gli stessi, se nullo si lasci l'array invariato;
ex-5
si scriva un programma che costruisca un array dinamico di n Colori, ove sia

enum Colori {
  ROSSO, VERDE, BLU,
  CIANO, MAGENTA, GIALLO,
  BIANCO, NERO }
e n immesso dall'utente; successivamente si riempia l'array con colori scelti a caso (utilizzando le funzioni di libreria time, srand e rand) e lo si stampi;


next up previous contents index
Next: Strutture Up: Memoria dinamica. Strutture. Liste Previous: Gli operatori new e   Indice   Indice analitico
Claudio Cicconetti
2000-09-06