** INTERMEZZO **

Fino ad ora abbiamo introdotto alcuni concetti basilari del
linguaggio C.
Con quanto appreso possiamo acquisire dei caratteri,
stamparli a video, eseguire cicli e condizioni, ma vi sono
ben altre cose che si possono fare con un linguaggio di
programmazione quale e' il C.
Introdurremo nei prossimi capitoli argomenti quali gli
array, le strutture, le union, i tipi enum, la gestione dei
file, i puntatori, la memoria, le funzioni ed inizieremo a
prendere in considerazione maggiormente le caratteristiche
del C del QL, le sue peculiarita' e gli eventuali limiti.

** CAPITOLO 6 **

- ARRAY -

Abbiamo visto nel capitolo 3 che e' possibile scrivere sul
terminale dei caratteri acquisendoli da tastiera o
generandoli da programma.
Ma la domanda che ci si pone subito e': e se volessimo
introdurre qualcosa di piu' grande di un carattere come
possiamo fare?
Si potrebbe utilizzare un ciclo for che attenda la
pressione di un tasto per poi visualizzarlo ripetendo il
ciclo fino al limite previsto, ma cio' non sarebbe molto
utile (a parte il fatto che possa essere un buon esercizio
di programmazione) anche perche' alla fine avremmo una
serie di caratteri a video senza alcuna possibilita' di
ulteriore gestione degli stessi.
Il C, cosi' come altri linguaggi, permette di definire una
zona di memoria in cui contenere una serie di dati omogenei
(tutti char, int, long o altro) identificati univocamente
da un valore posizionale o, meglio, un array.
La definizione di un array in C si effettua nel seguente
modo:

funzione()
{
    char un_array_di_char[10];
...

un_array_di_char identifica un array di caratteri ad una
sola dimensione in grado di contenere al massimo 10
caratteri ed e', percio', un vettore o array
monodimensionale di tipo char che occupera' in memoria la
dimensione di 8 bit * 10 = 80 bit = 10 caratteri.
Nel C gli array hanno origine 0 per cui avremo la
possibilita' di accedere ai singoli caratteri del vettore
numerandoli da 0 a n (e non da 1 a n come nella DIM del
Basic).

A questo punto abbiamo dichiarato l'array ma il suo valore
non e' ancora definito per cui proviamo ad inizializzarlo.
Non si puo' fare una cosa di questo tipo:

funzione()
{
   char un_array_di_char[10];

   un_array_di_char = "SincalirQL";

mentre e' lecito:

funzione()
{
   char un_array_di_char[10] = "SinclairQL";
oppure
   char un_array_di_char[] = {"SinclairQL"};

Questo perche' gli array possono essere assegnati con il
simbolo = soltanto al momento della loro dichiarazione.

L'uso della parentesi graffa permette un'ulteriore
finezza: la possibilita' di definire un array e lasciare
al compilatore il compito di dimensionarlo.

Nel C l'array di caratteri deve terminare con il carattere
ASCII 0 (NULL) affinche' venga trattato come stringa di
caratteri correttamente stampabile e gestibile con le
funzioni di libreria.
Nell'esempio precedente abbiamo dichiarato un array di 10
caratteri e vi abbiamo inserito un testo lungo esattamente
10 caratteri.

In questo caso il compilatore C, se esente da bugs interni,
inserisce uno zero binario al termine della stringa ma, in
caso l'assegnazione fosse avvenuta in altro modo (es.: la
copia diretta da un'area di memoria ad un'altra), il
terminatore di stringa potrebbe mancare e, in tal caso, se
provassimo a stampare con la funzione di libreria printf la
nostra stringa potremmo ottenere in output qualcosa tipo:

SinclairQLd oppure SinclairQL?^& o altri simboli spuri al
termine della stringa: questo perche', come gia' detto,
manca il terminatore di stringa e vengono stampati anche i
contenuti della memoria consecutiva alla locazione dove
termina l'array fino ad incontrare il primo valore binario
0. (In caso piu' grave possiamo anche causare un crash di
sistema che obblighera' a resettare la macchina!).
E' percio' utile dichiarare l'array di dimensioni almeno di
un'unita' superiore al massimo numero di elementi che si
prevede di utilizzare e, in ogni caso, inizializzare
l'array con tutti gli elementi a 0.

funzione()
{
    char stringa[11];
    int i;

    for( i = 0; i < 11; i++ )
        stringa[i] = 0;
...

In questo modo ci siamo assicurati che la nostra stringa
sia inizializzata correttamente ad un valore nullo.
Il C prevede alcune utili funzioni di libreria che
consentono di effettuare un'inizializzazione di un'area di
memoria ad un valore predefinito.
Utilizzando la memset il precedente pezzo di codice
diverra':

funzione()
{
    char stringa[11];

    memset( stringa, 0, 11 );
...

memset permette di inizializzare l'area di memoria
referenziata dal primo parametro con il valore del secondo
parametro per il numero di bytes definito dal terzo
parametro ed e' definita nel file di include 

Dopo aver inizializzato a zero la stringa possiamo
assegnare una serie di valori ai suoi elementi.

#include 
#include 

void main( void );

void main( void )
{
    char stringa[15];
    int i;

    memset( stringa, 0, 15 );
    for( i = 0; i < 14; i++ )
        stringa[i] = (char)getchar();

 printf( "stringa = %s\n", stringa );
}

Definiamo una stringa di 15 elementi, la inizializziamo a 0
e tramite un ciclo for introduciamo da tastiera 14
caratteri.
Tramite la funzione printf stampiamo la stringa.

La printf permette si stampare informazioni a video ed ha
la seguente sintassi generale:

int printf( const char *format [, argomenti]... );

Ritorna il numero di caratteri stampati o un valore
negativo in caso di errore.
La const char *format e' la stringa di formattazione
dell'output e si compone di una serie di testo e caratteri
di formattazione (quali %s %c %d %l %x, ecc...) che
indicano al compilatore il modo in cui stampare gli
argomenti che seguono la stringa di formattazione.
Nel nostro esempio la stringa di formattazione e':
"stringa = %s\n" che significa:
stampa la parola stringa seguita da uno spazio (blank), da
un segno di uguale ed un altro spazio, quindi interpreta il
primo parametro come una stringa terminata da 0, il %s, ed
emetti un carattere di newline, \n.

Alcuni altri caratteri di formattazione sono:
%c carattere singolo minuscolo
%C carattere singolo maiuscolo
%d numero decimale intero
%x numero esadecimale con lettere minuscole
%X numero esadecimale con lettere maiuscole
%02X numero esadecimale di 2 cifre maiuscole, se il numero
e' inferiore alle due cifre stampa uno 0 davanti alla cifra
per ottenere il numero di cifre specificate (in questo caso
2 ma se ne possono usare quante necessarie).
%s stringa di caratteri stampabili terminata da 0.
%lf numero long float.
%- allineamento a sinistra del valore stampato
%+ se il valore e' del tipo con segno ed e' negativo il
segno meno precedera' la cifra.
Nota: per una completa definizione di tutti i possibili
caratteri di formattazione fare riferimento alla
documentazione acclusa al compilatore C.

L'esempio ultimo, pur essendo sintatticamente corretto, e'
ancora un sistema abbastanza stupido di inizializzare una
stringa di caratteri in quanto obbliga ad inserire tutti i
caratteri, non permette correzioni in caso di errore di
battitura e necessita di un ciclo di lettura.

Utilizziamo la funzione scanf cosi' definita in stdio.h:

int scanf( const char *format [,argomenti]... );

sostituiamo il ciclo for con la seguente istruzione:

    scanf( "%s", stringa );

La scanf e' simile alla printf ma assegna agli argomenti il
tipo indicato dalla const char *format.
In questo caso assegna all'array stringa i valori letti da
tastiera fino a quando si preme il tasto di INVIO o viene
raggiunta la dimensione massima dell'array destinazione.
Anche per la scanf valgono gli stessi caratteri di
formattazione della printf ma bisogna considerare una cosa
molto importante: se la destinazione (argomento) e' un tipo
differente dall'array (ovvero carattere singolo, intero
ecc... ) il nome dell'argomento deve essere preceduto dal
simbolo & (indirizzo di). Vedremo in seguito il significato
degli operatori * (puntatore o indirezione) e & (indirizzo
di) ed il loro uso.

Naturalmente gli array possono essere di tipi differenti
dal char e si avranno array di qualunque tipo predefinito o
definibile.
Gli array possono avere anche piu' dimensioni per formare
tabelle o strutture piu' complesse; si possono avere array
di array, array di puntatori, array di puntatori a
funzioni, array di puntatori a puntatori di array, ecc...

Un esempio di array tabellare:

    int tabella[10][10];

definisce una tabella di 10 righe per 10 colonne di interi.
Ogni singolo elemento sara' identificato da:

    tabella[x][y];

ed in questo caso non occorre settare a zero l'ultimo elemento
dell'array in quanto non si tratta di una stringa di caratteri

Esercizio.
Definire una tabella 10*10 di interi e creare una tavola
pitagorica.

#include 
#include 

main()
{
  int tabella[10][10];
  int x, y;
  char nome[20] ;

  memset( nome, 0, 20 );
  printf( "Introdurre il vostro nome (max 19 caratteri): " );
  scanf( "%s", nome ) ;

  printf("Tavola Pitagorica programmata da %s\n\n", nome );

  for( x = 0 ; x < 10 ; x++ )
      for( y = 0 ; y < 10 ; y++ )
         tabella[x][y] = (x+1)*(y+1);

  for( x = 0 ; x < 10 ; x++ ) {
      for( y = 0 ; y < 10 ; y++ )
         printf( "%3d\t", tabella[x][y] );
      printf( "\n" );
  }
}

Questo esercizio riassume quanto finora esposto sugli
array. Provate a modificarlo per ottenere una tavola che
raggiunga anche il 12 o che ritorni il risultato di un solo
valore scelto dall'utente tra quelli possibili
(Suggerimento: il risultato di 3*5 e' ...
Calcolate prima tutta la tabella ed usate in seguito un
ciclo che richieda un identificatore di riga e di colonna.
Visualizzate poi il risultato contenuto nell'elemento
indicato. Verificate che i valori introdotti da tastiera
rientrino nei limiti della tabella.)