Il terminale, la console e il linguaggio C

Lo scopo dell’istruzione è quello di trasformare gli specchi in finestre.

Sydney J. Harris

La console testuale, la mitica “finestra nera” per i non addetti ai lavori, è da sempre croce e delizia per noi sviluppatori software. Nella sua apparente semplicità, tale ambiente si mostra amichevole o perfido a seconda della conoscenza che noi abbiamo di lui, della sua struttura interna e dei comandi che accetta. Ho usato volutamente il pronome personale “lui” in quanto noi informatici siamo soliti attribuire una personalità agli oggetti con i quali sistematicamente interagiamo nella nostra vita quotidiana.

Ogni sistema software ha da sempre una interfaccia a caratteri, la cosiddetta shell testuale, che ci permette di interagire con il sistema sottostante: nel mondo Unix-Linux essa prende il nome di terminale mentre nel contesto Windows è chiamata più spesso console o anche cmd.exe, dal nome del file eseguibile che la manda in esecuzione.

In questo breve viaggio cercherò di mostrare come interagire, usando il linguaggio C, con la console in questione per realizzare delle semplici operazioni di spostamento del cursore da un punto ad un altro della finestra. Infatti, una delle difficoltà maggiori che abbiamo nella gestione di tale finestra è la sua assoluta rigidità dovuta al fatto che il prompt dei comandi è apparentemente immutabile nella sua acquisizione di un comando da tastiera e nella relativa restituzione dell’output del comando stresso. Se questa situazione è del tutto scontata e naturale in un contesto classico di gestione testuale lo stesso non vale nel momento in cui vogliamo gestire in maniera un po’ più “grafica” la nostra console.  Mi riferisco qui alla possibilità di realizzare un minimo di interattività all’interno della finestra muovendo, ad esempio, il cursore a piacimento in una specifica posizione al fine di simulare un classico ambiente a menu piuttosto che un rudimentale videogioco pseudo-grafico.

Il terminale e il mondo Unix-like

Nel mondo cosiddetto Unix-like, ovvero nel contesto di derivazione Unix quale Linux e macOS, il nostro terminale può essere gestito al meglio facendo uso della libreria di funzioni ncurses il cui sito ufficiale è:

https://invisible-island.net/ncurses/

Ovviamente ci si dovrà accertare della disponibilità della librerie in questione. A solo titolo di esempio, l’installazione in un sistema Debian-Ubuntu la si realizza con il gestore di pacchetti apt nel seguente modo:

sudo apt-get install libncurses5-dev libncursesw5-dev

Una volta risolti i problemi di installazione si potrà editare il proprio file sorgente con un editor di propria scelta. Io uso per lo più l’editor nano e, solo quando costretto dalle circostanze, il mitico vim.

Un “salve mondo” con ncurses, ovvero il tipico primo programma che si realizza per un qualsiasi ambiente di programmazione potrebbe essere il seguente:

 

#include <ncurses.h>

int main()

{                                                                                                                     

   initscr();

   printw("Salve mondo da ncurses!");

   refresh();

   getch();

   endwin();

   return 0;

}

 

Tale semplicissimo snippet (pezzo di codice sorgente) dovrebbe far comprendere almeno in linea generale la modalità di utilizzo della libreria in questione.

La prima cosa che faccio notare è il fatto che ho inserito il solo include della libreria ncurses.h in quanto la sola riga:

#include <ncurses.h>

Include in automatico altre librerie tra cui anche stdio.h.

La prima istruzione che incontriamo, la chiamata alla funzione initscr() inizializza l’ambiente ncurses, ambiente che viene poi chiuso con la successiva endwin(). Faccio notare che l’istruzione initscr()  non effettua la cancellazione dello schermo ma predispone tutta una serie di strutture dati  per interfacciare ncirses con l’hardware video del sistema su cu è in  esecuzione.

Di seguito incontriamo l’istruzione:

 

printw("Salve mondo da ncurses!");

 

che, senza sorprese predispone la stampa di una stringa di saluto. In realtà, la scrittura avviene preventivamente in uno specifico buffer che verrà poi dirottato in output grazie alla funzione refresh() che aggiornerà il nostro display video. Ci sono poi un altro paio di cose che vale la pena di sottolineare. La prima riguarda l’assoluta necessità di usare l’istruzione getch() per consentire la visualizzazione dell’output a video in quanto, in caso contrario, scomparirebbe immediatamente alla vista riportando la sola visualizzazione del prompt dei comandi. La seconda cosa riguarda il fatto che la funzione getch() effettua una sorta di aggiornamento  implicito e automatico dello schermo e che quindi, teoricamente, in questo caso specifico di esempio minimale anche senza la chiamata della funzione refresh, l’output a video verrebbe comunque mostrato.

Per la compilazione useremo il seguente comando:

 

gcc  mycurse.c -lncurses

 

Attenzione alla necessità di compilare usando l’opzione -lncurses in quanto ncurses non è semplicemente un file di intestazione ma una vera e propria libreria e  quindi -l ne impone il linking. Ovviamente, per lancoare il nostro eseguibile digiteremo il classico ./a.out.

Al solo scopo di far comprendere la semplicità d’uso della libreria vi mostro ora una funzione che consente di spostare il punto di editing, il nostro cursore, in una specifica posizione:

 

move(y,x);

 

dove y è il valore per la riga e quindi, partendo dall’angolo in alto a sinistra, che ha coordinata (0, 0), aumenta verso il basso mentre x è il valore per la colonna che quindi, banalmente, aumenta da sinistra verso destra.

Un’altra funzione semplice e interessante è:

 

mvprintw(y,x,formato,argomenti[...])

 

che consente di stampare una data stringa in una qualsiasi posizione del nostro terminale.

Questo brevissima panoramica sulla libreria ncurses voleva solo sollecitare l’interesse per un sistema software che, se be padroneggiato può dare grandi soddisfazioni come dimostra plasticamente la figura relativa al videogioco Rouge sviluppato appunto sfruttando tale libreria e che ha segnato un’epoca guadagnandosi un posto nella storia dei videogame.

Figura 1 Il videogioco Rouge realizzato con le librerie  ncurses.

 

 

 

 

 

 

 

 

 

Cmd.exe e la console testuale come non l’hai mai vista prima

La libreria ncurses è sicuramente uno strumento formidabile relativo al mondo Unix-like. Tuttavia, può essere interessante capire come gestire la console a caratteri in maniera evoluta anche sul sistema di zio Bill. Per farlo, ovviamente, possiamo sfrutta il WSL,  ovvero il Sottosistema Windows per Linux all’interno di Windows 10, così come usarlo in ambiente di emulazione Cygwin. Volendo potremmo addirittura sfruttare sotto Windows le librerie ncurses con il linguaggio di programmazione Python. Ma a noi piacciono le cose complesse e ardite per cui di seguito vi mostro come realizzare “a mano” qualcosa di simile alle ncurses direttamente sotto Windows. Pronti? Partiamo!

Sappiamo bene che la console cmd.exe è in effetti estremamente rigida ma cercheremo ora di forzarla a essere più duttile rispetto alle nostre necessità e di renderla, in un certo qual modo, “grafica”. Ovviamente, la prima cosa che dobbiamo cercare di fare è quella di poter scrivere un certo simbolo in una posizione qualsiasi della console e non quindi semplicemente sul nostro prompt dei comandi. Questa operazione, non propriamente banale, è possibile grazie alla libreria windows.h ed alla funzione SetConsoleCursorPosition che consente appunto di settare (collocare) il cursore in una data posizione della console. Per rendere il tutto più immediatamente comprensibile vi mostro subito un pezzo di codice minimale che realizza quanto detto.

 

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

void gotoxy(short x, short y);

int main()
{

    gotoxy(10,1);

    printf("X");

     getchar();

     return 0;

}



void gotoxy(short x, short y)
{
    COORD pos ={x,y};

    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}

 

 

Analizziamo quindi il codice precedente, osservando innanzitutto, come già anticipato, la necessità di includere la libreria windows.h. Subito dopo troviamo l’intestazione di una funzione gotoxy che abbiamo scritto con lo specifico scopo di spostare il nostro cursore in una determinata posizione che passeremo come argomento alla funzione stessa. La funzione in questione richiama quindi  al suo interno SetConsoleCursorPosition. Quest’ultima prende in input il riferimento alla finestra in uso e le coordinate  per la nuova posizione del cursore. Per ulteriori dettagli su tale funzione è possibile rifarsi al link:

 

https://docs.microsoft.com/it-it/windows/console/setconsolecursorposition

 

Qui si può facilmente scoprire che le coordinate da passare alla funzione sono la colonna e la riga di una cella del buffer dello schermo. Ovviamente, le coordinate in questione devono trovarsi all’interno dei limiti del buffer dello schermo della console. Nel caso in cui la funzione ha esito negativo verrà restituito il valore zero. Al momento, per una questione di semplicità non effettuiamo uno specifico controllo di errore che comunque dovrebbe essere sempre fatto con l’ausilio della funzione GetLastError.

 

Figura 2 – La console dopo lo spostamento del cursore

 

 

 

 

 

 

 

Come si intuisce, la coordinata 0, 0 è nell’angolo in  alto a sinistra e quindi è come se ci trovassimo in un piano cartesiano con x che cresce verso sinistra e y che cresce verso il basso.

Per rendere immediatamente interessante quello che stiamo facendo, possiamo immaginare di far muovere il nostro cursore sulla console usando i mitici e classici tasti freccia. Per farlo dobbiamo innanzitutto poter leggere i tasti della tastiera e quindi solo dopo spostare il cursore in maniera consistente. Per leggere la tastiera possiamo sfruttare le funzioni kbhit e getch.

È importante precisare che entrambe le funzioni in questione non sono il massimo della standardizzazione e che quindi devono essere usate consapevoli del fatto che sarà necessario verificare gli eventuali vincoli delle specifiche piattaforme. In ogni caso, kbhit legge la tastiera e restituisce un valore diverso da zero in caso di pressione di un tasto mentre getch restituisce il valore corrispondente al tasto digitato. Vediamo allora il codice che ci permette di leggere la digitazione dei vari tasti e interrompere l’esecuzione in caso di digitazione del tasto ESC, escape, che corrisponde al valore 27.

 

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <conio.h>

int main()
{
    printf("Premere ESC per interrompere.\n");

    int ch;

    while (TRUE)

    {

          if ( kbhit() )

          {

            //

            ch = getch();

            if ((ch == 27))

            {

            break;

             }

            printf("Tasto: %c numero: %d\n", ch, ch);

        }

    }

   
    printf("Programma terminato!");

    getchar();

    return 0;

}

 

Il programmino in questione presenta, nel più classico dei modi,  il main loop, ovvero il ciclo principale che si imposta su pressoché qualsiasi  software e che consente di iterare le varie operazioni fin quando non se ne richiede in maniera forzata l’uscita. Il codice stesso dovrebbe essere di immediata comprensione: il ciclo itera teoricamente all’infinito (while (TRUE)) e terminerà con un break non momento in cui il tasto digitato è appunto ESC.

 

Da notare che ho usato la funzione getch e non getchar in modo da non dover attendere la pressione del tasto Invio per confermare la digitazione del singolo tasto.

 

Figura 3- Il programma per la cattura dei valori dei tasti.

 

 

Muoviamo il cursore nella console

A questo punto, maturate le competenze per spostare in una certa posizione il nostro cursore e capito come possiamo intercettare i tasti, possiamo immaginare di muovere il nostro cursore nel punto in cui vogliamo tramite specifici tasti. Per semplificare il codice evitiamo di usare i tasti freccia. Infatti, la pressione dei tasti freccia non è particolarmente standard e tra l’altro restituisce 2 differenti valori piuttosto che uno solo, così come per gli altri tasti normali. In ogni caso, non è un gran problema in quanto possiamo immaginare di usare, al posto dei tasti freccia, una combinazione classica nota come WASD. Si tratta dei tasti che hanno appunto queste quattro lettere e che vengono usate nei giochi per sostituire le frecce stesse in quanto questi hanno una organizzazione a T rovesciata del tutto simile.

 

 

Figura 4 – Il confronto tra i tasti freccia  e la combinazione WASD

 

 

 

Come si intuisce anche dalla figura, i tasti A e D corrispondono ad avanti e indietro mentre W e S ai tasti sopra e sotto. Vediamo allora come leggere questi tasti e spostare il cursore in logica  conseguenza.

Servendoci del precedente codice scopriamo anche immediatamente quali sono i valori corrispondenti ai tasti di nostro interesse e che vi riporto in figura.

 

Figura 5 – I codici dei tasti della combinazione WASD.

 

 

 

 

 

 

 

 

 

Per evitare problemi dovremo ovviamente intercettare sia il codice per le lettere minuscole sia quelle per le maiuscole. Di seguito vi riporto una possibile e semplice implementazione di quanto ci siamo proposti di fare.

 

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <conio.h>


#define UP 1
#define DOWN 2
#define LEFT 3
#define RIGHT 4

#define MAX_X 80
#define MAX_Y 25

void gotoxy(short x, short y);

short pos_x, pos_y;

int main()
{
   short pos_x=0;

   short pos_y=0;
   
   gotoxy(0, MAX_Y+1);
   printf("Premere ESC per interrompere.\n");
   gotoxy(0,0);

   int ch;

   while (TRUE)
   {

   if ( kbhit() )
   {
   ch = getch();

   if ((ch == 27))
   {
       break;
   }

   else
   {

   switch(read_key(ch))
   { 
   case UP:

   pos_y--;

   break;

   case DOWN:

   pos_y++;

   break;

   case LEFT:

   pos_x--;

   break;

   case RIGHT:

   pos_x++;

   break;

   }

   //evito che valori diventino negativi o superiori al max consentito
   if (pos_x <0) pos_x=0;
   if (pos_y <0) pos_y=0;

   if (pos_x > MAX_X) pos_x=MAX_X;
   if (pos_y > MAX_Y) pos_y=MAX_Y;

   gotoxy(pos_x, pos_y);

   printf("X");

   }




   }

   }




   printf("Game over ;) ");

   getchar();

   return 0;

}




//

int read_key(int ch)

{

int direction=0;

if (ch==119 || ch == 87) //tasto W - sopra

{

direction=UP;

}

else if (ch==97 || ch == 65) //tasto A - sinistra

{

direction=LEFT;

}

else if (ch==115 || ch == 83) //tasto S - sotto

{

direction=DOWN;

}

else if (ch==100 || ch == 68) //tasto D - destra

{

direction=RIGHT;

}

return direction;

}


void gotoxy(short x, short y)
{
COORD pos ={x,y};
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}


Cercherò ora di commentare gli elementi più salienti di quanto proposto. Innanzitutto, definiamo alcune costanti utile per semplificare la leggibilità del codice stesso, iniziando con quelle utili a definire la direzione per i nostri tasti.

 

#define UP 1

#define DOWN 2

#define LEFT 3

#define RIGHT 4

 

Successivamente definiamo il limiti del nostro campo di gioco con le seguenti:

 

#define MAX_X 80
#define MAX_Y 25

 

Ovvero, in questo caso, 25 righe per 80 colonne. Per gestire la posizione corrente usiamo, invece, le seguenti variabili globali:

 

short pos_x, pos_y;

 

Detto questo, il codice è abbastanza semplice da interpretare considerando quanto già visto in precedenza. Cicliamo, dunque, fino alla pressione del tasto escape e con il seguente switch effettuiamo, modificando le variabili pos_x e pos_y  il movimento corrispondente alla scelta effettuata:

 

switch(read_key(ch))
{
   case UP:
      pos_y--;
      break;

    case DOWN:
         pos_y++;
         break;
 
    case LEFT:
        pos_x--;
        break;

    case RIGHT:
    pos_x++;
    break;
}

Come si può facilmente comprende abbiamo creato una specifica funzione read_key per capire in quale direzione andare in quanto la funzione in questione legge i codici dei tasti e restituisce la costante della direzione da prendere.

Infine, vale forse la pena di notare le righe di codice:

 

gotoxy(0, MAX_Y+1);
printf("Premere ESC per interrompere.\n");
gotoxy(0,0);

 

che rappresentano, a livello minimale, un modo per rendere l’interfaccia più pulita spostando le istruzioni per l’uscita dal gioco in una zona esterna al nostro campo di azione.

Ovviamente, il tutto è assolutamente embrionale e vuole solo dare indicazioni di massima su come organizzare un tipico gioco. Possiamo infatti immaginare, a solo titolo di esempio, come potremmo generare in una posizione casuale del campo di gioco un qualsiasi elemento e poi far raggiungere al nostro giocatore quella posizione, ad esempio, calcolando quanto tempo ci mette per raggiungerla e restituire un punteggio in base alla sua velocità di azione. Come al solito, l’unico limite è la fantasia!

 

Carlo A. Mazzone

 

Supportaci condividendo sui social il nostro articolo!
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

La struttura dati Union del C: a cosa serve realmente?

“Non nei numeri ma nell’unità sta la nostra grande forza.”

Thomas Paine

 

La struttura dati union del linguaggio C è di sicuro particolare e il suo  reale e concreto utilizzo può essere a volte sfuggente. Come dovremmo sapere, una union è del tutto simile ad una struct con l’unica  apparente differenza che di una union possiamo usare, in un dato momento, uno ed uno solo dei suoi membri. Infatti, l’occupazione di spazio di una union è relativa al massimo dello spazio occupato dal più grande dei suoi membri. Per averne una prova facciamo un esempio supponendo di avere due strutture dati: una struct e una union aventi entrambe tre membri di cui un intero, un float e un double:

#include <stdio.h>
#include <stdlib.h>

struct s
{
   int i;
   float f;
   double d;
};

union u
{
   int i;
   float f;
   double d;
};


int main(int argc, char *argv[])
{
   struct s myStruct;
   union u myUnion;

   printf("\nIntero byte: %d", sizeof(myStruct.i));
   printf("\nFloat byte: %d", sizeof(myStruct.f));
   printf("\nDouble byte: %d", sizeof(myStruct.d));

   printf("\nLa struct occupa %d byte.", sizeof(myStruct));
   printf("\nLa union occupa %d byte.", sizeof(myUnion));

   return 0;
}

 

In relazione al nostro test, prima stampiamo l’occupazione in byte dei singoli membri e poi l’occupazione delle singole strutture dati. Otterremo quanto presentato in Figura.

Struct union

 

 

 

 

 

 

Figura: L’occupazione in byte di struct e union.

Come si evince da una immediata osservazione, la struct occupa ben 16 byte sommando, infatti, i 4 dell’intero, i 4 del float e gli 8 byte del double. Al contrario, la union occupa solo 8 byte, ovvero la dimensione del suo membro massimo, il double.

Se questo è polimorfismo

Resta comunque la domanda di fondo: ma a cosa serve una union? Ebbene, il suo scopo è di norma quello di gestire in un’unica struttura dati valori di tipo differente che riguardano una stessa variabile. In un certo senso è un tentativo del C di essere polimorfo (cosa che il C++ fa in maniera assolutamente naturale). In somma sintesi, per polimorfismo si intende la possibilità di gestire con una stessa variabile differenti tipologia di dato (un intero, un float, …) a seconda delle circostanze di nostro interesse. Vediamo allora il seguente codice in cui immaginiamo una union per gestire una sorta di variant ovvero una variabili che può assumere differenti valori a seconda di una specifica assegnazione:

 

#include <stdio.h>
#include <stdlib.h>

enum Type {INTEGER, FLOATS, DOUBLE};

union u
{
   int i;
   float f;
   double d;
};

void stampa(union u *, int);

int main(int argc, char *argv[])
{
   union u myVar;
   int v_type; //il tipo attualmente utilizzato

   v_type = INTEGER;
   myVar.i = 12.66;
   stampa(&myVar, v_type);

   v_type = FLOATS;
   myVar.f = 12.66;
   stampa(&myVar, v_type);

   v_type = DOUBLE;
   myVar.d = 12.66;
   stampa(&myVar, v_type);
  
   return 0;
}

void stampa(union u *u, int v_type)
{
   switch(v_type)
   {
      case INTEGER:
      printf("Valore intero: %d\n", u->i);
      break;

      case FLOATS:
      printf("Valore float: %f\n", u->f);
      break;

      case DOUBLE:
      printf("Valore double: %lf\n", u->d);
      break;
   }
}

 

Vediamo allora di interpretare il codice in questione. Innanzitutto, definiamo la nostra union con 3 differenti membri: un intero, un float e un double.

 

union u
{
   int i;
   float f;
   double d;
};

 

Per gestire al meglio le tre differenti situazione utilizziamo una enumerazione:

 

enum Type {INTEGER, FLOATS, DOUBLE};

 

mentre per controllare quale tipo di dato andiamo di volta in volta ad utilizzare usiamo la variabile

 

int v_type;

 

Infine, per simulare un caso reale di utilizzo sfruttiamo uno switch all’interno di una funzione di stampa:

 

switch(v_type)
{
   case INTEGER:
   printf("Valore intero: %d\n", u->i);
   break;

   case FLOATS:
   printf("Valore float: %f\n", u->f);
   break;

   case DOUBLE:
   printf("Valore double: %lf\n", u->d);
   break;
}

 

Struct e union: quando l’unione fa la forza

In alcune situazioni è possibile preferire un approccio in cui l’informazione relativa al tipo di dato che vogliamo utilizzare è direttamente e strettamente collegato al dato stesso. In tali contesti è possibile integrare una union all’interno di una struct così come vi mostro di seguito:

 

#include <stdio.h>
#include <stdlib.h>

enum Type {INTEGER, FLOATS, DOUBLE};

struct mydata
{
   int which_one;
   union _value
   {
      int i;
      float f;
      double d;
   } value;
};

void stampa(struct mydata *);

int main(int argc, char *argv[])
{
   struct mydata x;

   x.value.i =12;
   x.which_one = INTEGER;
   stampa(&x);

   x.value.f =10.66;
   x.which_one = FLOATS;
   stampa(&x);

   return 0;
}


void stampa(struct mydata *x)
{
   switch(x->which_one)
   {
      case INTEGER:
      printf("Valore intero: %d\n", x->value.i);
      break;

      case FLOATS:
      printf("Valore float: %f\n", x->value.f);
      break;

      case DOUBLE:
      printf("Valore double: %lf\n", x->value.d);
      break;
   }
}

 

Come si può scoprire dopo un primo attento esame, il punto nodale della nostra organizzazione è data dalla struct seguente:

 

struct mydata
{
   int which_one;
   union _value
   {
      int i;
      float f;
      double d;
   } value;
};

 

La struct in questione ha innanzitutto il membro which_one che serve per tener traccia del tipo di variabile che vogliamo gestire. Anche in questo caso, così come nell’esempio precedente, usiamo la seguente enumerazione per tracciare in modo semplice il tipo di dati utilizzato in un dato momento:

 

enum Type {INTEGER, FLOATS, DOUBLE};

 

All’interno della struct abbiamo invece la nostra union con i vari tipi di variabili:

 

union _value
{
   int i;
   float f;
   double d;
} value;

 

Per il resto il codice dovrebbe essere di relativa semplice interpretazione.

 

Carlo A. Mazzone

Supportaci condividendo sui social il nostro articolo!
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Riflessioni e proposte per un nuovo Umanesimo Digitale

Carlo Mazzone

Siamo nel pieno di una rivoluzione tecnologica guidata dalle innovazioni basate sul digitale di cui è difficile definire con precisione i frastagliati contorni. Se da un lato cogliamo con favore i frutti di queste nuove tecnologie, nella forma di strumentazioni sempre più evolute quali TV, computer e cellulari, facciamo fatica a gestire le loro ripercussioni nel contesto che genericamente possiamo definire “sociale”.

In effetti, le nuove tecnologie stanno comportando mutamenti epocali sotto innumerevoli punti di vista che vanno, solo a titolo di esempio, dalle modalità di produzione e consumo dell’informazione, alla creazione di nuovi posti di lavoro, per arrivare alla necessità di rendere queste tecnologie democratiche affinché esse possano essere di beneficio per la maggioranza assoluta della popolazione mondiale.

Lo scopo di questo mio intervento è quello di porre in maggior risalto possibile il necessario sforzo collettivo verso un nuovo Umanesimo Digitale che ponga al centro del dibattito e delle relative scelte risultanti l’uomo inteso come collettività ma, innanzitutto, come singolo individuo.

La scuola, come sempre, può e deve fare da perno e da saldo riferimento per consentire che questi mutamenti epocali indotti nella società siano controllabili e quindi gestibili in maniera tale da consentire ai più di reggere l’urto della già citata rivoluzione. Compito primario della scuola è quello di formare individui, consapevoli delle proprie unicità e possibilità, affinché possano vivere appieno e con soddisfazione nella società. Tale compito, tuttavia, si sta facendo sempre più arduo in un momento in cui le professioni a cui la scuola prepara sono cambiate in maniera radicale e continueranno a cambiare, governate dal ritmo dell’incessante evoluzione tecnologica.

Di sicuro deve esservi l’impegno forte nel ripensare al contesto educativo esaltando una reale didattica basata sulle competenze che riesca a inserire i nostri giovani nel nuovo flusso di richieste di lavoro altamente specializzato. D’altra parte, a monte, si rende assolutamente necessario una rinnovato impegno nella realizzazione di interventi di orientamento sempre più efficienti e motivanti affinché gli studenti possano scegliere percorsi di studio che siano realmente appaganti rispetto alle loro giuste aspettative.

In un contesto già tumultuoso di suo, per l’enormità degli impatti dovuti all’evoluzione digitale, si è innestato negli ultimi mesi, in una sorta di “tempesta perfetta” il cambiamento radicale di vita imposto dal Covid-19. I due elementi si sono in questa situazione, per certi aspetti, fusi l’uno nell’altro: il digitale è stato un baluardo per mantenere una certa normalità di vita, sia all’interno della scuola, ad esempio con la didattica e distanza, sia nella società tutta, attraverso un uso ancora più massiccio dei social e delle videoconferenze.

Le conseguenze di tutto ciò sono state addirittura storiche e probabilmente condizioneranno in maniera sostanziale anche un futuro post Covid. Penso, ad esempio, ai nuovi modelli di lavoro che si sono spostati tra le mura domestiche creando, da una parte, enormi contraccolpi economici nell’ambito di tutto quello che ruotava intorno agli ex luoghi di lavoro fatti di grandi palazzi, quando non grattacieli, e, d’altra parte, nelle enormi possibilità che la libertà di non doversi per forza spostare per lavoro può comportare.

Ma anche in questa ottica dobbiamo mettere al centro questo nuovo Umanesimo Digitale per evitare impatti sociali futuri che potrebbero scuotere nelle fondamenta l’intera nostra società.

Un primo elemento riguarda ovviamente il rischio che solo una nuova élite di lavoratori possa usufruire di tali opportunità, una classe fatta da personale altamente specializzate in quanto i lavori di tipo manuale stanno scomparendo dallo scenario occupazionale.

D’altra parte, penso alla possibilità di riportare vita ai piccoli borghi e paesi, in particolare delle zone più svantaggiate del Sud Italia, facendo sì che si torni ad abitare dai luoghi dai quali si è fuggiti per trovare lavoro nei grandi centri urbani. Ma voglio pensare non più in termini di agglomerati urbani ma piuttosto di agglomerati umani in cui riscoprire il senso del lavoro come strumento per vivere una vita appagante fatta di riscoperti, semplici, autentici, rapporti umani favoriti dal piccolo centro.

Compito nostro, come pensatori e attori della società, è quello di interpretare il tempo corrente e fornire indicazioni utili per tracciare una via che, volendo, sia anche una visione a cui tendere. Il compito, invece, di realizzare in modo concreto queste tensioni e aspettative è affidato, come normale che sia, alla politica che deve guidare attraverso scelte sagge e ponderate al nostro prossimo, immediato, futuro.

In queste scelte vedo, ancora una volta, una rinnovata richiesta di umanesimo digitale che concretizzi l’accesso alla connettività Internet come bene primario e pubblico per tutti i cittadini in tutto il territorio nazionale.

Ancora, missione primaria della politica sarà quella di consentire una efficienza redistribuzione del benessere evitando che il digitale aumenti le differenze già esistenti tra le classi sociali non consentendo che la crescita economica di taluni comporti l’automatico peggioramento delle condizioni di altri.

 

Carlo Mazzone

Carlo A. Mazzone, sviluppatore software e sistemista, è docente di informatica nella Scuola Superiore. Insegnante imprenditivo di Junior Achievement Italia ha ricevuto il JA Italia Top Teacher Lifetime Achievement Award ed è l’unico italiano ad essere arrivato nella Top 10 del Global Teacher Prize, il Nobel dei docenti. È inoltre un digital evangelist e autore di pubblicazioni di divulgazione informatica, best seller in Italia.

Supportaci condividendo sui social il nostro articolo!
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Un assaggio di STL con i vettori

“Nell’insegnamento non si può vedere il frutto di una giornata di lavoro. È invisibile e rimane così, forse per venti anni.”
Jacques Barzun

Si dice che i programmatori siano pigri. Di sicuro, se qualcuno ha già lavorato per noi, non si vede il motivo per rifare lo stesso lavoro una seconda volta. Uno di questi “qualcuno”, nel contesto del C++, si chiama Alexander Stepanov, il quale è il principale artefice di una specifica libreria di strumenti realizzata appositamente nella visione della programmazione generica: la Standard Template Library, meglio conosciuta con l’acronimo di STL.

Alexander Stepanov
Alexander Alexandrovich Stepanov, il principale artefice della Libreria STL.

 

Volendo schematizzare al massimo la libreria STL possiamo vederla come composta di tre elementi:

Contenitori
Iteratori
Algoritmi

I contenitori, detti anche in modalità anglosassone containers, sono i veri e propri aggregati di dati. Si tratta quindi di collezioni organizzate di elementi. A scopo di esempio pensate alla struttura dati coda.
Gli iteratori sono degli speciali puntatori che consentono di navigare, cioè di muoversi, all’interno delle strutture dati.
Gli algoritmi sono delle procedure preconfezionate per risolvere i classici problemi che si presentano in relazione alle strutture dati: ordinamento di dati, ricerca di specifici valori, ecc.
Per cercare di essere minimamente concreti di seguito vi propongo un codice minimale relativo all’uso dei vettori. Sebbene il termine vettore venga spesso considerato come sinonimo di array, un vettore (vector) è una struttura dati di tipo parametrico che può essere lavorata in maniera dinamica in quanto, contrariamente ad un array che viene gestito nell’area di memoria stack, esso vive nella sezione di memoria heap.

 

#include <vector>
using namespace std; //oppure devi usare istruzioni del tipo std::vector<int> v;

int main(int argc, char** argv)
{
   printf("I vettori (vectors) non sono array ;)\n\n");
   //Dichiaro un vector (le parentesi angolari sono giustificate dal fatto che si tratta di una classe template)
   //Un vector consente la gestione di memoria contigua permettendo di riferirsi ad essa in un modo del tipo: v[0], v[1]...
   //creiamo un vector per 10 interi (vengono inizializzati a 0)

   int n = 10;

   vector<int> array(n);

   //verifichiamo il suo contenuto
   printf("Vector inizializzato:\n");
   for(int i=0; i<n; i++)
   {
      printf("%d ", array[i]);
   }
   printf("\n\n");

   // valorizziamo il vector con interi da 1 a 10
   int x =1;
   for(int i=0; i<n; i++)
   {
      array[i] = x++;
   }

   //verifichiamo il suo contenuto
   printf("Vector valorizzato con interi da 1 a 10:\n");
   for(int i=0; i<n; i++)
   {
      printf("%d ", array[i]);
   }
   printf("\n\n");

   //inserisco alla fine dell'array un nuovo elemento intero con valore 66
   array.push_back(66);

   //verifichiamo il suo contenuto
   //Usiamo ora la il metodo size() per determinare la nuova dimensione del vector
   printf("Contenuto vector dopo inserimento di un elemento in coda:\n");
   for(int i=0; i<array.size(); i++)
   {
      printf("%d ", array[i]);
   }
   printf("\n\n");

   //ora voglio ridimensionare il vector eliminando così il valore inserito
   //determino quindi l'attuale dimensione del vector
   int temp=array.size();

   //ridimensiono il vector alla dimensione attuale meno 1
   array.resize(temp-1);

   //verifichiamo il contenuto del vettore
   printf("Vector dopo il resize:\n");
   for(int i=0; i<array.size(); i++)
   {
      printf("%d ", array[i]);
   }
   printf("\n\n");

   //Un altro modo per eliminare l'ultimo elemento del vettore
   array.pop_back();
   printf("Vector dopo eliminazione elemento in coda:\n");
   for(int i=0; i<array.size(); i++)
   {
      printf("%d ", array[i]);
   }
   printf("\n");

   return 0;
}


Di seguito l’output del codice in questione:

 

Ovviamente, si tratta di un codice del tutto minimale che ha il solo scopo di far comprendere le potenzialità dei vettori del C++. Nello specifico, dichiariamo innanzitutto il nostro vector affinché contenga 10 elementi interi

int n = 10;
vector<int> array(n);

La prima cosa degna di interesse da notare è poi data dal fatto che inseriamo in coda all’array, in manoera assolutamente dinamica, un nuovo elemento, usando la seguente sintassi.

 

array.push_back(66);

 

Faccio notare, incidentalmente, la classica notazione in stile programmazione orientata agli oggetti dove push_back è il metodo dell’oggetto array.

Ancora di grande interesse è l’effettuazione del ridimensionamento del vettore con l’istruzione resize.

Infine, vi mostro come sia possibile effettuare una sorta di nuovo ridimensionamento dell’array buttando via l’ultimo elemento con l’istruzione:

 

array.pop_back();

 

Ovviamente, come detto, il precedente è un esempio assolutamente minimale che vuole solo sollecitare alla scoperta di modalità differenti per la gestione delle strutture dati.

 

Carlo A. Mazzone

Supportaci condividendo sui social il nostro articolo!
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Il C, la console testuale e i colori

Gli artisti possono colorare il cielo di rosso perché sanno che è blu. Quelli di noi che non sono artisti devono colorare le cose come realmente sono o la gente penserebbe che sono stupidi.

 Jules Feiffer

La console testuale, la nostra fidata cmd.exe o il nostro terminale Linux o macOS, sono notoriamente dei mondi spartani e minimalisti. Tuttavia, anche in questi contesti un tocco di colore può aiutare a rendere l’output maggiormente intuitivo e piacevole da gestire. Il problema è che per realizzare anche il più piccolo inserimento di colori, utilizzando il nostro codice C, dobbiamo faticare non poco a causa della eterogeneità dei vari sistemi operativi e ambienti di sviluppo.

Le sequenze di escape

Un primo approccio che possiamo provare a sfruttare è quello di pilotare il nostro terminale attraverso specifiche sequenze di comandi note come sequenze di escape. Più precisamente, si tratta di uno standard ANSI (ovvero dell’American National Standards Institute) che consente di inviare al terminale una serie di comandi, di norma utilizzando il carattere Esc seguito dalla parentesi quadra ‘[‘, con i quali controllare non solo i colori ma anche la posizione del cursore sullo schermo. Storicamente, si tratta di uno stratagemma molto datato che fu introdotto addirittura a partire dagli anni 70 e particolarmente 80 del secolo scorso per sostituire il vecchio metodo che consisteva nell’utilizzare comandi legati all’hardware dei singoli e specifici dispositivi. Nonostante siano passati moltissimi anni, questo metodo è ancora utilizzabile con una certa efficienza grazie al fatto che è possibile inviare comandi al terminale utilizzando caratteri ASCII standard.

Il terminale VT 100 della Digital

Figura 1  –  Il terminale VT 100 della Digital (https://commons.wikimedia.org/wiki/File:DEC_VT100_terminal.jpg).

Può essere interessante sapere che uno dei primi e più popolari terminali a supportare questo nuovo standard fu il VT100 della Digital. Personalmente, lo ricordo ancora con un po’ di malinconia in quanto questo fu il primo terminale con il quale mi imbattei all’Università degli Studi di Salerno nella seconda metà degli anni 80.

Unix sì, Windows “nì”

Il primo problema che riscontriamo con l’utilizzo delle sequenze di escape è che esse sono supportate in maniera pressoché nativa solo in ambiente Unix like e non sotto Windows. Cominciamo comunque a verificarne il loro utilizzo almeno sotto il contesto Linux per prendere confidenza con questo strumento.

Un primo stralcio di codice minimale con il quale fare una veloce prova può essere il seguente:

 

#include <stdio.h>

int main ()
{
   printf("\x1b[32mUn mondo a colori\n");
}

 

Come si intuisce il contenuto da analizzare è il seguente:

 

"\x1b[32mUn mondo a colori\n"

 

In tale contenuto riconosciamo facilmente che prima della stringa “Un mondo a colori” è presente una arcana sequenza di simboli:

 

"\x1b[32m"

 

Ebbene, in realtà non è nulla di particolarmente complesso. Infatti, come già detto, le sequenze di escape iniziano con il codice del carattere Esc seguito da una parentesi quadra aperta. Tale carattere di escape corrisponde al valore decimale 27 che, in esadecimale, corrisponde a 1b ed è proprio tale valore che inviamo al terminale usando la sequenza di due simboli “\x”.

In successione troviamo il valore 32 che corrisponde al colore verde e infine la lettera m che serve a segnalare al terminale che stiamo inviando un comando di gestione di tipo grafico.

Se compiliamo il file sotto Windows e proviamo a lanciarlo nella console cmd.exe otterremo un deludente risultato simile a quanto riportato in figura:

Figura 2 –  L’esecuzione con l’invio dei codici di escape con la cmd.exe.

Come si può osservare, in output troviamo in maniera grezza parte della sequenza inviata senza però nessuna modifica di colore. Al contrario, se lanciamo lo stesso eseguibile in un contesto Unix like, ad esempio nella finestra Bash del sottosistema Windows per Linux (configurabile sotto Windows 10) otterremo la corretta interpretazione del comando grafico, così come mostrato in figura:

Figura 3 –  L’esecuzione con l’invio dei codici di escape nel sottosistema Windows per Linux.

La cosa interessante da notare è che una volta impartito un certo comando di impostazione di un dato colore, tale setting rimane invariato finché non decidiamo di inviare il codice per un colore diverso oppure per resettare il sistema al bianco e nero inviando il valore zero. Di seguito, vi riporto del codice che dovrebbe chiarire il meccanismo in questione:

 

#include <stdio.h>

int main ()
{
   printf("\x1b[32mUn mondo a colori\n");
   printf("... tutto verde\n");
   printf("\x1b[0m");
   printf("ma fino a un certo punto.\n");
}

 

In output otterremo, come mostrato in Figura, che le prime due righe risulteranno in verde mentre la riga di testo finale, “ma fino a un certo punto.”, verrà mostrata in bianco su nero a causa del comando di reset:

 

"\x1b[0m"

 

dove riconosciamo ancora la sequenza \x1b che invia in esadecimale in carattere Esc, poi la parentesi quadra aperta e quindi il valore zero per il reset seguito dalla lettera m che segnala, come già detto, l’invio di un comando di tipo grafico.

Figura 4 –  L’esecuzione con l’invio dei codici per il reset delle impostazioni di colore predefinite.

 

Vediamo ora di scoprire l’elenco dei colori che possiamo gestire. Ebbene, i colori di base solo sostanzialmente solo otto ed i loro codici vanno da 30 a 37. Tuttavia, gli stessi otto colori possono essere utilizzati per cambiare il colore di sfondo della console con dei codici che vanno da 40 a 47. Di seguito vi propongo uno schema riassuntivo.

 

COLORE CODICE TESTO CODICE SFONDO
Nero 30 40
Rosso 31 41
Verde 32 42
Giallo 33 43
Blu 34 44
Magenta 35 45
Azzurro 36 46
Bianco 37 47

 

 

Ovviamente, volendo cambiare contemporaneamente il colore del testo e quello dello sfondo dobbiamo inviare due specifici e differenti comandi. Ad esempio, volendo impostare il testo nero su sfondo bianco dovremo inviare, rispettivamente, i valori 30 e 47. Di seguito un semplice esempio:

 

#include <stdio.h>

int main ()
{
   printf("\x1b[30m");
   printf("\x1b[47m");
   printf("Un mondo al contrario.\n");
   printf("\x1b[0m");
}

 

Incidentalmente, vi faccio notare come sarebbe possibile inserire i due comandi in un’unica stringa di testo accodando le due sequenze di escape scrivendo:

 

printf("\x1b[30m\x1b[47m");

 

Appurato che gli ambienti di derivazione Unix gestiscono senza problemi le sequenze di escape vediamo ora cosa ci riserva il mondo Windows. Purtroppo la possibilità di usare in maniera quasi naturale tali sequenze è limitata alle sole versioni di Windows 10.

 

NOTA. Vedi anche https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences.

 

Vediamo allora un programmino minimale che imposta l’ambiente al fine di utilizzare le sequenze in questione:

 

#include <stdio.h>
#include <windows.h>

#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING  0x0004
#endif

int main()
{
   //Catturo l'handle del dispositivo standard di output
   HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);

   //uso tale handle per ottenere l'attuale modalità della console
   DWORD dwMode = GetConsoleMode(hOut, &dwMode);

   //Abilito le sequenze di escape mettendo in OR alla modalità corrente
   //del buffer dello schermo della console il valore ENABLE_VIRTUAL_TERMINAL_PROCESSING
   dwMode = dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING;

   //Imposto la nuova modalità con la funzione  SetConsoleMode
   SetConsoleMode(hOut, dwMode);

   printf("\x1b[32mUn mondo a colori\n");
   printf("... tutto verde\n");
   printf("\x1b[0m");
   printf("ma fino a un certo punto.\n");

   return 0;
}

 

 

Come detto, l’esecuzione di tale codice su sistemi antecedenti ad un aggiornato Windows 10 fallirà miseramente.

Proviamo allora ad analizzare il codice in questione partendo dalla necessità di definire la costante ENABLE_VIRTUAL_TERMINAL_PROCESSING. Usiamo quindi le cosiddette include guard #ifndef per evitare la eventuale redefinizione della costante in questione che viene richiesta dal sistema per poter gestire in maniera corretta le sequenze di escape.

Per il resto, il commento nel codice dovrebbe essere sufficiente a comprendere il meccanismo di funzionamento del sistema in questione. Quello che facciamo è innanzitutto prelevare il riferimento (noto come handle) del dispositivo di output, ovvero della console, con l’istruzione:

 

HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);

 

Usiamo allora questo handle per poter leggere l’attuale impostazione della console:

 

DWORD dwMode = GetConsoleMode(hOut, &dwMode);

 

Incidentalmente, segnalo che un dword, abbreviazione di “double word,” è un tipo di dati specifico di Windows. Tale tipo è definito nel file windows.h che abbiamo incluso in testa al nostro codice. Un dword è un intero senza segno a 32 bit e può quindi contenere un valore che va da 0 a 4.294.967.295.

 

Successivamente, aggiungiamo, tramite l’operatore OR la nuova impostazione al contesto corrente, usando la funzione SetConsoleMode:

 

dwMode = dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING;
SetConsoleMode(hOut, dwMode);

 

A questo punto, il gioco è fatto e possiamo inviare le sequenze di escape di nostro interesse verso il dispositivo console.

 

La gestione degli errori

Ovviamente, per rendere il codice più robusto dovremmo preoccuparci di controllare con apposito trapping di errore se le operazioni richieste vanno a buon fine. A tal fine, vi segnalo come si possa usare la funzione GetLastError() per catturare l’eventuale errore generato dalle varie chiamate viste in precedenza. Per essere più chiaro, vi propongo la riscrittura del codice precedente con l’inserimento di vari check per testare se le operazioni richieste vanno o meno a buon fine.

 

#include <stdio.h>
#include <windows.h>

#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING  0x0004
#endif

int main()
{
   //Catturo l'handle del dispositivo standard di output
   HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);

   if (hOut == INVALID_HANDLE_VALUE)
   {
      printf("Funzione GetStdHandle - Errore: %d\n", GetLastError());
   }

   //uso tale handle per ottenere l'attuale modalità della console
   DWORD dwMode = 0;

   if (!GetConsoleMode(hOut, &dwMode))
   {
      printf("Funzione GetConsoleMode - Errore: %d\n", GetLastError());
   }

   //Abilito le sequenze di escape mettendo in OR alla modalità corrente
   //del buffer dello schermo della console il valore ENABLE_VIRTUAL_TERMINAL_PROCESSING
   dwMode = dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING;

   //Imposto la nuova modalità con la funzione  SetConsoleMode
   if (!SetConsoleMode(hOut, dwMode))
   {
      printf("Funzione SetConsoleMode - Errore: %d\n", GetLastError());
   }

   printf("\x1b[32mUn mondo a colori\n");
   printf("... tutto verde\n");
   printf("\x1b[0m");
   printf("ma fino a un certo punto.\n");

   return 0;
}

 

Come si vede, effettuiamo il controllo in relazione alla chiamate delle varie funzioni e, nel caso di effettivo errore, stampiamo con GetLastError lo specifico numero di errore. Ovviamente, per verificare sul campo tali situazioni dobbiamo porci in un contesto che generi effettivamente un errore. In figura vi mostro quello che può succedere in ambiente Windows 7 che, come detto, non consente l’uso delle sequenze di escape.

Figura 5  –  La generazione e il relativo trapping di un errore con le sequenze di escape sotto Windows 7.

Come si vede dalla figura in questione, l’errore generato è il numero 87. Spulciando la documentazione Microsoft, ad esempio all’URL https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes–0-499-, si scopre che tale numero corrisponde all’errore “The parameter is incorrect.”. In ogni caso, può essere utile sapere che è possibile rendere la gestione degli errori ancora più espressiva utilizzando la funzione FormatMessage per farsi restituire la specifica stringa descrittiva dello specifico errore.

Su Windows usiamo le API

Una valida alternativa su Windows per controllare i colori della console può essere quella di sfruttare una funzione dell’API di Windows nota come SetConsoleTextAttribute. Di seguito vi mostro la sua struttura e modalità di utilizzo.

Il suo prototipo è:

 

BOOL SetConsoleTextAttribute(
HANDLE hConsoleOutput, // handle del buffer dello schermo della console
WORD wAttributes  // colori per il testo e lo sfondo
);

 

Per rendere immediatamente comprensibile il suo funzionamento vi mostro subito un semplice esempio:

 

#include <stdio.h>
#include <windows.h>

int main() 
{
   HANDLE output = GetStdHandle(STD_OUTPUT_HANDLE);
   
   printf("Questo e' il colore di default\n");

   SetConsoleTextAttribute(output, FOREGROUND_RED|FOREGROUND_INTENSITY);
   printf("Il colore rosso e' proprio bello\n");

   SetConsoleTextAttribute(output, FOREGROUND_BLUE|FOREGROUND_INTENSITY);
   printf("ma anche il blu non e' male\n");
  
   return 0;
}

 

La prima cosa che possiamo notare è l’include relativo al file windows.h. Subito dopo, con la riga:

 

HANDLE output = GetStdHandle(STD_OUTPUT_HANDLE);

 

otteniamo un handle per il buffer dello schermo. Usiamo tale handle come primo argomento della funzione SetConsoleTextAttribute mentre come secondo argomento impostiamo, in due momenti diversi, due differenti colori con le costanti FOREGROUND_RED e FOREGROUND_BLUE. Banalmente si tratta dei colori rosso e blu. Vi faccio notare come si possa accodare, con l’operatore OR, rappresentato dal simbolo “|” (noto come pipe), un ulteriore attributo rappresentato dalla costante FOREGROUND_INTENSITY. Tale costante serve per far sì che il colore appena selezionato sia visualizzato con una maggiore luminosità.

Ovviamente, i colori disponibili sono diversi. Di seguito un estratto riassuntivo:

 

COLORE TESTO SFONDO
Blu FOREGROUND_BLUE BACKGROUND_BLUE
Verde FOREGROUND_GREEN BACKGROUND_GREEN
Rosso FOREGROUND_RED BACKGROUND_RED
Intensificazione del colore FOREGROUND_INTENSITY BACKGROUND_INTENSITY

 

 

 

NOTA. Per ulteriori informazioni sui buffer della console https://docs.microsoft.com/en-us/windows/console/console-screen-buffers.

 

Un altro elemento interessante da analizzare è dato dal fatto che le precedenti costanti di colore possono essere combinate, con l’operatore OR, non solo con la costante per esaltare la luminosità del colore stesso ma anche con altre costanti di colore per ottenere varie e differenti combinazioni. Ad esempio, la combinazione:

 

BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED

 

produrrà uno sfondo bianco che potrà essere esaltato in intensità accodando la costante BACKGROUND_INTENSITY e scrivendo quindi un comando del tipo:

 

SetConsoleTextAttribute(output, BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY);

 

Carlo A. Mazzone

Supportaci condividendo sui social il nostro articolo!
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

JavaScript e gli oggetti

JavaScript

Il concetto di oggetto informatico è assolutamente comprensibile se lo si paragona ad un oggetto del mondo reale. Non a caso, uno scopo degli oggetti della programmazione è proprio quello di simulare le proprietà e i comportamenti del mondo fisico. Un oggetto è dunque un’entità caratterizzata da una serie di caratteristiche specifiche (note come proprietà) e da un serie di possibili azioni (noti come metodi) che l’oggetto può realizzare eventualmente in risposta ad situazioni (note come eventi) che si verificano nel  contesto attinente all’oggetto. Facciamo un banale esempio volendo rappresentare un oggetto per codificare i dati di una persona e vediamo come codificarlo in JavaScript.

 

<!DOCTYPE html>

<html>

<head>

<title>Gli oggetti di JavaScript</title>

</head>

<body>

<script>

var persona = { nome: “Carlo”, cognome: “Mazzone”, altezza: 180, capelli: “castani” };

document.write(“Io sono ” + persona.nome + ” ” + persona.cognome);

</script>

</body>

</html>

 

Per completezza ho riportato anche l’intero codice per una pagina HTML di esempio. Si intuisce, con grande semplicità, che la riga:

 

var persona = { nome: “Carlo”, cognome: “Mazzone”, altezza: 180, coloreOcchi: “castani” };

 

rappresenta una serie di coppie, separate da virgole, del tipo chiave:valore. ad esempio nome: “Carlo”. Quella presentata è la forma di creazione di un oggetto minimale e le coppie in questione sono le proprietà dell’oggetto. Nell’istruzione di stampa:

 

document.write(“Io sono ” + persona.nome + ” ” + persona.cognome);

 

si vede poi l’uso dell’operatore punto per accedere al valore delle varie proprietà. Faccio notare che la modalità di scrittura di un oggetto su di una singola linea è solo una possibilità e che, in generale, se ne preferisce una differente che vede ogni singola proprietà su di una specifica linea di codice come mostrato di seguito:

 

var persona = {

nome: “Carlo”,

cognome: “Mazzone”,

altezza: 180,

capelli: “castani”

};

 

Ovviamente, possiamo cambiare a piacimento il valore delle proprietà di un oggetto. Ad esempio, potremmo cambiare la proprietà nome scrivendo:

 

persona.nome=”Ludovica”;

 

Ancora, è possibile utilizzare la parola chiave new per la creazione, in un certo modo in differita dell’oggetto, scrivendo:

 

var persona = new Object();

 

persona.nome=”Carlo”;

persona.cognome=”Mazzone”;

persona.altezza=180;

persona.capelli=”castani”;

 

Tuttavia, in generale, si sconsiglia l’uso di questo secondo metodo, anche per una questione di velocità di esecuzione del codice,  preferendo la modalità:

 

var persona = {

nome: “Carlo”,

cognome: “Mazzone”,

altezza: 180,

capelli: “castani”

};

 

Nella terminologia anglosassone questo tipo di oggetto viene definito object literal. Literal, infatti, è il modo con cui ci si riferisce ai valori assegnati alle costanti (in contrapposizione al termine variabile). In questo caso, infatti, quello che facciamo è proprio definire un oggetto con dei valori costanti preimpostati.

Sappiamo che è possibile assegnare un nuovo valore ad una proprietà scrivendo:

 

persona.nome=”Ludovica”;

 

Ebbene, sempre nell’ordine delle cose per cui è possibile realizzare le stesse operazioni in modo differente, vi segnalo che è possibile riferirsi al valore di una proprietà anche nel seguente modo:

 

persona[“nome”]=”Ludovica”;

 

Inoltre, è possibile riferirsi al nome di una proprietà di un dato oggetto passando ad esso una variabile contenente quel nome. Sembra complicato, ma si tratta di una banalità che vi mostro di seguito. Posso assegnare ad una variabile generica il nome del campo cognome in questo modo:

 

var temp=”cognome”;

 

e successivamente accedere, ad esempio per la stampa, alla corrispondente proprietà:

 

document.write(persona[temp]);

 

Faccio notare incidentalmente come sia possibile creare un oggetto vuoto attraverso la seguente sintassi:

 

var persona = {};

 

per poi andare ad inserire in esso delle proprietà, così come già visto in precedenza, scrivendo, ad esempio:

 

persona.nome=”Carlo”;

 

Inoltre, così come tali proprietà possono essere create sarà anche possibile eliminarle scrivendo, ad esempio:

 

delete persona.nome;

 

oppure:

 

delete persona[“nome”];

 

Ovviamente, una volta eliminata una specifica proprietà, andandone a stampare il valore, ad esempio con:

 

document.write(persona[“nome”]);

 

otterremmo in output un valore di tipo undefined segnalando che la proprietà in esame non è appunto definita.

Inutile dire che provare ad eliminare delle proprietà di oggetti predefiniti può essere un’idea quantomeno folle che può inevitabilmente portare a blocchi del sistema.

Dal dato all’operatore: il vero senso dell’oggetto

Finora abbiamo capito come sia possibile gestire un oggetto visto sostanzialmente come un contenitore di dati. Se ci pensiamo bene, infatti, gli oggetti appena studiati non sono altro che una sorta di array un po’ speciali in quanto possiamo inserire in essi dei valori di tipo differente (ad esempio, nell’oggetto persona abbiamo utilizzato dei valori stringa ed un numero). Tuttavia, ciò che rende un oggetto un “tipo” davvero speciale è la possibilità di gestire al proprio interno, oltre ai semplici dati (le proprietà), anche delle specifiche azioni realizzate attraverso delle funzioni che abbiamo definito con il nome di metodi. La creazione di specifici metodi all’interno di un oggetto rende quest’ultimo un’entità quasi autonoma capace di azioni proprie. Non dico che realizziamo un’entità capace di intendere e di volere ma la direzione è forse quella.

Vediamo allora come cominciare ad instillare un anelito vitale all’interno dell’oggetto inserendo in esso una funzione che gli dia la possibilità di “parlare”. Inizialmente l’oggetto si presenterà dicendo il proprio nome e cognome. Dunque, procediamo modificando l’oggetto persona come segue:

 

 

var persona = {

nome: “Carlo”,

cognome: “Mazzone”,

altezza: 180,

capelli: “castani”,

parla: function() {

document.write(“Io sono ” + persona.nome + ” ” + persona.cognome);

}

};

 

Per far parlare la nostra “persona” invocheremo il metodo parla scrivendo:

 

persona.parla();

 

Come è possibile osservare, quello che facciamo è di inserire, insieme alle proprietà separando normalmente i vari elementi con una virgola, una funzione che abbiamo chiamato banalmente parla. Notiamo come si debba scrivere prima il nome della funzione e poi, dopo i due punti, la parola chiave function. Si tratta di una sintassi che rende omogenea la dichiarazione delle proprietà e dei metodi. Ovviamente, bisogna fare attenzione al corretto uso delle virgole che servono per separare le varie dichiarazioni onde evitare errori durante l’esecuzione del codice.

Qualcuno potrebbe obiettare che tale modifica rispetto allo script precedente non introduce alcuna innovazione di comportamento. In effetti è così, almeno all’apparenza. Il senso è che dal punto di vista del risultato pratico i due pezzi di codice danno in output lo stesso risultato:

 

Io sono Carlo Mazzone

 

Tuttavia, essi lo realizzano in maniera, anche filosoficamente, del tutto diversa. Mentre nel primo caso l’istruzione di stampa:

 

document.write(“Io sono ” + persona.nome + ” ” + persona.cognome);

 

è esterna all’oggetto e viene quindi eseguita all’esterno dell’oggetto stesso, nel secondo caso, è l’oggetto stesso ad avere la “capacità di parola” e tutto quello che dobbiamo fare è di dirgli di usarla (si parla di “invio di messaggi all’oggetto”). Si tratta di una inversione di vedute, di un cambio assoluto di paradigma di programmazione, per cui è l’oggetto ad essere al centro del contesto di programmazione racchiudendo in esso, come in una scatola nera tutta la logica di funzionamento. Faccio notare incidentalmente che questo approccio che consiste nel nascondere all’interno di un oggetto i dettagli del codice per cui il programmatore non deve preoccuparsi del modo in cui inviare messaggi agli oggetti prende il nome, in questo paradigma, di information hiding (occultamento dell’informazione) attraverso una tecnica nota come incapsulamento.

Tutto ciò rappresenta la base per costruire  moduli software che siano facilmente utilizzabili e che abbiano grande scalabilità, ovvero possano essere modificati per contenere nuove funzionalità in maniera non traumatica. Inoltre, questo approccio ad oggetti consente uno sviluppo a più mani (diversi programmatori sullo stesso progetto) in maniera più efficiente.

Per rafforzare il senso della vita interna di un oggetto vi invito ad osservare la seguente modifica all’oggetto persona:

 

var persona = {

nome: “Carlo”,

cognome: “Mazzone”,

altezza: 180,

capelli: “castani”,

parla: function() {

document.write(“Io sono ” + this.nome + ” ” + this.cognome);

}

};

 

in cui abbiamo utilizzato la parola chiave this. Tale riferimento, che banalmente ha il significato di “questo” si riferisce all’oggetto stesso e serve per generalizzare e semplificare la scrittura del codice.

Faccio ora notare come il metodo realizzato sia una procedura e non una vera e propria funzione. Infatti, esso provvede direttamente a stampare la stringa di presentazione e non restituisce nulla all’esterno. In realtà, per  rendere il codice più professionale e manutenibile è necessario trasformare il metodo in una vera e propria funzione scrivendo:

 

var persona = {

nome: “Carlo”,

cognome: “Mazzone”,

altezza: 180,

capelli: “castani”,

parla: function() {

return(this.nome + ” ” + this.cognome);

}

};

 

document.write (“Io sono ” + persona.parla());

 

Giusto per fare una semplice considerazione rispetto alla bontà di questa soluzione di codifica alternativa possiamo pensare al fatto che in questo modo l’oggetto è maggiormente indipendente rispetto al resto del codice. In buona sostanza, abbiamo in un certo senso separato la parte di “business logic”, ovvero di funzionamento vero e proprio, rispetto alla parte di “interfaccia” realizzata con la chiamata document.write. In questo modo, giusto per intenderci, anche una possibile traduzione in altre lingue del nostro codice sarebbe più semplice in quanto realizziamo lo specifico output all’esterno dell’oggetto. Ad esempio, avremmo potuto scrivere:

 

document.write (“My name is ” + persona.parla());

 

Oggetti, costruttori e paradigmi di programmazione

L’oggetto persona visto in precedenza ha sicuramente grande utilità ma nasconde anche alcuni limiti. Innanzitutto, come già detto, esso è un sorta di costante. Infatti, lo abbiamo definito un object literal. Può essere utile a questo proposito spendere qualche parola in più relativamente allo sviluppo software in un senso più generale. Scrivere codice non è semplicemente l’azione consistente nel mettere una dopo l’altra una serie di righe di codice. Ogni progetto, anche il più piccolo, prevede uno specifico approccio alla sua realizzazione tramite codice. Si parla, in linea generale, di paradigmi di programmazione intendendo appunto la metodologia usata per creare uno specifico software. Infatti, indipendentemente dal fatto che si possano realizzare con un dato codice  le medesime funzionalità, queste possono essere codificate in maniera sostanzialmente diversa. Seppure questo testo non sia un trattato di programmazione in senso stretto, trovo utile fornire qui alcune informazioni di base. Fondamentalmente e semplificando al massimo, un certo problema può essere risolto in due modalità principali. La prima, nota essenzialmente come approccio top-down (dall’alto verso il basso), prevede di scomporre il problema principale in tanti problemi più piccoli (noti come sottoproblemi) e per ognuno di questi scrivere una specifica funzione che risolva il particolare problema. Spesso, un dato sottoproblema è troppo complesso per essere risolto da una singola funzione e così esso viene ulteriormente scomposto in sottoproblemi e relative funzioni associate per risolverli. Per essere concreti, supponiamo di dover gestire in un nostro sito web la registrazione degli utenti tramite specifiche credenziali. Ebbene, il problema nella sua totalità può innanzitutto essere scomposto in un serie di sottoproblemi quali, ad esempio, la registrazione tramite i propri dati personali (logon) e l’accesso al sito attraverso i dati in proprio possesso (login). D’altra parte il logon prevede almeno due ulteriori sottoproblemi: il primo per la registrazione dei dati immessi dall’utente ed un secondo per la validazione, ad esempio tramite l’invio di una mail, della veridicità dei dati forniti dall’utente in questione. Come si vede, partiamo dall’alto (top) intendendo il problema nella sua totalità e andiamo verso il basso (down) scomponendo il tutto in sottoproblemi ai quali assoceremo specifiche funzioni. Questo approccio viene normalmente definito approccio procedurale: non a caso i sottoproblemi vengono gestiti attraverso specifiche procedure.

Tuttavia, oltre a questo approccio ne esiste un altro che per certi aspetti si muove in direzione opposta: si parte dal basso e si va verso l’alto. Si parla, infatti, di approccio bottom-up che si estrinseca in maniera pratica attraverso il paradigma ad oggetti. Cerchiamo allora di fare un po’ più di chiarezza. Dire che si parte dal basso (bottom) vuol dire iniziamo il nostro studio per la risoluzione del problema lavorando sui dati piuttosto che sulle procedure. Mi spiego meglio facendo riferimento all’esempio della gestione dei dati del nostro utente via web. Nell’approccio ad oggetti partiamo proprio definendo innanzitutto le caratteristiche dell’utente quali nome, cognome, password, email, ecc.. Altra caratteristica dell’utente sarà, inoltre, uno stato che definisce se l’utente è in un certo momento loggato al sistema oppure no. Ciò, infatti,   condiziona le attività da realizzarsi in quanto se l’utente non risulta loggato dovremo eventualmente redirigere la sua navigazione verso la fase di login. Dunque, si definiscono le proprietà degli oggetti e anche le funzioni (i metodi) ad essi associati. Mettendo insieme le funzionalità dei vari oggetti “saliamo verso l’alto” rispettando il senso dell’approccio bottom-up.

È importante precisare che da un lato abbiamo il paradigma e dall’altro il linguaggio di programmazione. Il senso è che teoricamente con lo stesso linguaggio possiamo operare sfruttando l’uno o l’altro paradigma. Ovviamente ciò deve essere permesso dal linguaggio stesso: si parla in questo caso di linguaggi multi-paradigma. Per fare qualche esempio concreto, il linguaggio C supporta nativamente il solo paradigma procedurale mentre linguaggi come C++ o Java sono stati costruiti per essere utilizzato con lo sviluppo ad oggetti ma teoricamente possono essere usati anche con un approccio procedurale. Nel campo dello sviluppo web, sia JavaScript che PHP (che vedremo nel prosieguo del nostro viaggio) possono essere usati in entrambi i modi anche se, comunque, il suggerimento è di sfruttare le più moderne possibilità di sviluppo del paradigma ad oggetti.

Torniamo ora a parlare di oggetti da un punto di vista più tecnico. Affinché si possa parlare di programmazione orientata agli oggetti è importante che gli oggetti abbiamo alcune caratteristiche molto precise tra cui il fatto che essi possano essere creati a partire da uno specifico modello che indichi le caratteristiche comuni a tutti gli oggetti: la classe. Per classe si intende una sorta di stampo a partire dal quale creare oggetti tutti simili ma comunque singoli e unici. Può essere utile immaginare una classe come una formina che i bambini usano per giocare con la sabbia. La formina consente di creare elementi di sabbia tutti uguali ma ognuno è indipendente dagli altri avendo, ad esempio, una posizione sulla spiaggia diversa dagli altri. Di seguito un esempio di classe per l’oggetto persona visto in precedenza:

 

function Persona(nome, cognome, altezza, capelli) {

this.nome = nome;

this.cognome = cognome;

this.altezza = altezza;

this.capelli = capelli;

this.parla = function () {

return(this.nome + ” ” + this.cognome);

};

}

 

Vediamo dunque che cosa abbiamo realizzato. In buona sostanza abbiamo scritto una funzione, che viene chiamata costruttore in quanto costruisce l’oggetto. Vi faccio notare come abbia scelto come nome per la funzione il termine Persona scritto con l’iniziale in maiuscolo. Si tratta di una sorta di convenzione per indicare il fatto che abbiamo a che fare con una funzione costruttore. All’interno della funzione assegniamo i vari argomenti presi in input dalla funzione stessa (nome, cognome, altezza, capelli)  a specifiche proprietà realizzate con la parola chiave this. Allo stesso modo definiamo il metodo parla, in maniera simile a quanto visto in precedenza con gli object literal. Tuttavia, una volta realizzato il costruttore dobbiamo invocare in maniera esplicita la creazione di un particolare oggetto. La creazione in questione avviene con la parola chiave new come vi mostro di seguito:

 

var Carlo = new Persona(“Carlo”, “Mazzone”, 180, “castani”);

 

dove abbiamo creato un nuovo oggetto, Carlo, con le proprietà elencate nello specifico passaggio di parametri. Ovviamente possiamo poi richiamare le specifiche proprietà:

 

document.write (Carlo.altezza);

 

e invocare i metodi di nostro interesse:

 

document.write (“Io sono ” + Carlo.parla());

 

Se ci riflettiamo un attimo, quello che abbiamo realizzato non è da poco. Siamo ora in grado di creare tutti gli oggetti di un certo tipo di cui necessitiamo: si parla di istanze della classe. Ad esempio, possiamo scrivere:

 

var Ciccio = new Persona(“Ciccio”, “Pasticcio”, 160, “bianchi”);

 

per creare un nuovo oggetto di tipo Persona che si chiama Ciccio. Ovviamente, possiamo creare un oggetto anche solo indicando alcuni parametri alla funzione costruttore:

 

var Topolino = new Persona(“Mickey”, “Mouse”);

 

Ovviamente, nel caso del nostro esempio, la stampa di una proprietà non inizializzata quale:

 

document.write (Topolino.altezza);

 

darà come risultato undefined. In ogni caso, addirittura, possiamo creare un oggetto privo di inizializzazione con:

 

var Topolino = new Persona();

 

e successivamente valorizzare i suoi membri:

 

Topolino.nome=”Mickey”;

Topolino.cognome=”Mouse”;

 

Un altro aspetto importante da sottolineare relativamente alla grande utilità di questo approccio è che, contrariamente a quanto succede con gli object literal, non siamo costretti a definire i vari metodi degli oggetti per ogni singola istanza. Infatti, definiremo le funzioni di interesse dell’oggetto una volta per tutte nel costruttore e potremo poi richiamarle dal singolo oggetto scrivendo, ad esempio:

document.write (“Io sono ” + Topolino.parla());

 

Prototipi: se questa è eredità

Gli oggetti JavaScript hanno in serbo ancora alcune sorprese. Tanto per dirne una, è possibile associare al volo una nuova proprietà ad un certo oggetto. Ad esempio, in relazione alla situazione seguente:

 

function Persona(nome, cognome, altezza, capelli) {

this.nome = nome;

this.cognome = cognome;

this.altezza = altezza;

this.capelli = capelli;

 

this.parla = function () {

return(this.nome + ” ” + this.cognome);

};

}

 

var Carlo = new Persona(“Carlo”, “Mazzone”, 180, “castani”);

var Ciccio = new Persona(“Ciccio”, “Pasticcio”, 160, “bianchi”);

 

 

possiamo scrivere:

 

Carlo.userName=”carlomazzone”;

 

dove abbiamo inserito la nuova proprietà userName al solo oggetto Carlo mentre l’oggetto Ciccio rimane immutato. In maniera similare, possiamo aggiungere anche un metodo ad un singolo oggetto (istanza di una classe) scrivendo, ad esempio:

 

Carlo.saluta = function () {

return(“Ciao, ciao da ” + this.nome);

};

 

Abbiamo così aggiunto la funzione saluta al solo oggetto Carlo. La cosa interessante da notare è che dentro la funzione in questione ho comunque potuto usare la parola chiave this per fare riferimento ad una proprietà interna all’oggetto.

Tuttavia, ribadisco che nuove proprietà e  nuovi metodi possono essere aggiunti, con il semplice uso del carattere punto legato al nome dell’oggetto, solo in relazione alla specifica istanza e quindi al singolo oggetto. Infatti, se volessimo aggiungere determinate proprietà e metodi a tutti gli oggetti derivanti da una data classe dovremmo per forza di cose lavorare sul costruttore dell’oggetto. Vediamo allora come procedere in questo scenario. Per farlo è necessario ricorrere ad una nuova parola chiave: prototype. Usiamo dunque tale nuova keyword in un esempio concreto, sempre relativo alla classe Persona, scrivendo:

 

Persona.prototype.email=”info@example.com”;

 

Quello che abbiamo fatto è stato associare al costruttore di Persona una nuova proprietà che abbiamo chiamato email ed inizializzato al valore info@example.com. Ciò significa che tale proprietà con relativo valore è associata in automatico a tutti gli oggetti già istanziati a partire dalla classe Persona e che quindi, sempre in relazione all’esempio precedente, le seguenti istruzioni:

 

document.write (Carlo.email);

document.write (Ciccio.email);

 

restituiranno in output due volte la stringa info@example.com.

La tecnologia che si nasconde dietro ai prototipi è la base sulla quale di fonda la cosiddetta ereditarietà della programmazioni ad oggetti nel mondo di JavaScript. Tuttavia, a questo punto credo sia necessario spendere qualche ulteriore parola su questo argomento data la sua importanza in relazione allo sviluppo software che usa gli oggetti come principale paradigma di programmazione. L’ereditarietà consente di definire degli oggetti di base che possono poi essere utilizzati per costruire nuovi oggetti che dai primi prendono le proprietà e gli eventi già in essi presenti e ne aggiungono di nuovi per specializzare i propri comportamenti. In buona sostanza un modo per non riscrivere ogni volta tutto il codice ma sfruttare quanto già presente in altri oggetti. Questo meccanismo che consente di sfruttare caratteristiche di altri oggetti in una sorta di albero genealogico è appunto chiamato ereditarietà. Ogni linguaggio che usa il paradigma ad oggetti deve per forza di cosa implementare questo meccanismo ed ognuno, tuttavia, lo realizza in maniera leggermente diversa. Java, ad esempio, implementa in maniera leggermente diversa dal C++ l’ereditarietà e ancora differentemente lo fa JavaScript che, come detto, sfrutta il meccanismo dei prototipi.

Supportaci condividendo sui social il nostro articolo!
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Programmi, Program Files e Program Files (x86): un po’ di chiarezza

All’interno di Windows 10, la cartella C:\Program Files\ è visibile con questo nome solo accedendo tramite prompt dei comandi. Infatti, se proviamo a visualizzare la struttura di cartella tramite Esplora file, vedremo, al di sotto della radice, due cartelle simili che hanno a che fare con i programmi e la loro installazione: C:\Programmi e C:\Programmi (x86).

Il motivo di questa organizzazione è dato dal fatto che quello che vediamo tramite Esplora file è una localizzazione, ovvero la traduzione nella lingua del sistema operativo, ad esempio l’italiano, rispetto al nome reale delle cartelle.

In ogni caso, la cartella visibile come C:\Programmi corrisponde, come detto, alla cartella reale C:\Program Files. Tale cartella, nei sistemi a 64 bit, ormai la quasi totalità, contiene le installazioni degli applicativi, appunto a 64 bit. Invece, la cartella visibile come C:\Programmi (x86) corrisponde alla cartella reale C:\Program Files (x86) nella quale vengono installate le versioni a 32 bit dei vari programmi di cui si effettua il setup (installazione).

Le applicazioni scritte per l’ambiente a 64 bit vengono eseguite in maniera normale, tecnicamente di dice nativa, mentre quelle a 32 bit vengono emulate nell’ambito di un processo di nome WOW64 (Win32 On Win64).

Carlo A. Mazzone

 

Supportaci condividendo sui social il nostro articolo!
  • 3
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
    3
    Shares

scanf e getchar: problema di input di dati per mescolanza di metodi diversi

Anche se si tratta di un problema abbastanza banale, ho notato che spesso si incespica in errori dovuti alla differente natura delle istruzioni di input del C, scanf e getchar, quando queste sono accodate l’una dopo l’altra per prelevare determinati valori inseriti dall’utente. Il problema è dato dal fatto che dopo una scanf rimane nel buffer della tastiera il  carattere di new line (nuova linea), ovvero, più semplicemente, la pressione del tasto “invio” stesso. Tale invio forza in automatico una “digitazione fantasma” nella eventuale successiva istruzione getchar.

Il piccolo programmino di seguito dovrebbe chiarire il problema e presentare la possibile soluzione:

 

 

 

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char argv[])
{
   printf("INPUT DA ESAURIMENTO\n");
   char ch1, ch2;

   printf("Inserisci il primo carattere: ");
   scanf("%c", &ch1);

   printf("Inserisci il secondo carattere: ");
   ch2 = getchar();

   printf("ch1=%c, Valore ASCII = %d\n", ch1, ch1);
   printf("ch2=%c, Valore ASCII = %d\n", ch2, ch2);

   printf("RIPROVA, SARAI PIU' FORTUNATO ;)\n");
   printf("Inserisci il primo carattere: ");
   scanf("%c", &ch1);
   fflush(stdin);

   printf("Inserisci il secondo carattere: ");
   ch2 = getchar();
   printf("ch1=%c, Valore ASCII = %d\n", ch1, ch1);
   printf("ch2=%c, Valore ASCII = %d\n", ch2, ch2);

   return 0;
}

 

Come si può vedere, il codice prima simula la situazione di errore e poi ci mette una pezza. Ci sono due modi principali per farlo.

Il primo è di usare la funzione

fflush(stdin);

che provvede a svuotare il buffer in questione, subito dopo la scanf e prima della getchar. Il problema che potremmo avere con tale funzione è che essa non è propriamente standard.

L’alternativa è di usare, al suo posto,  la riga di codice:

while ( getchar() != '\n' );

che cicla fino a beccare, e mandare ramengo 😉 il newline.

Per rendere ancora più interessante l’esempio stampiamo anche il codice ASCII dei caratteri inseriti. Nella prima parte dell’esecuzione si può allora toccare con mano la problematica appena illustrata osservando che il carattere ch2 ha come codice ASCII il valore 10 che corrisponde proprio al LF (Line Feed – https://it.wikipedia.org/wiki/Ritorno_a_capo) .

Carlo A. Mazzone

Supportaci condividendo sui social il nostro articolo!
  • 3
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
    3
    Shares

Editor di testi per programmatori

Dovrebbe essere notorio che per sviluppare codice non è possibile usare editor “evoluti” come Microsoft Word oppure il Writer presente il LibreOffice e OpenOffice in quanto questi salvano i file NON in formato testo semplice ma aggiungono ai contenuti tutta una serie di codici proprietari che altererebbero il sorgente della nostra software. Abbiamo quindi bisogno di editor di testo puro.

Seppure, il Blocco Note possa risultare utile in un primissimo momento di avvicinamento allo specifico linguaggio, immediatamente dopo (e sto parlando di qualche minuto se non di pochi secondi ;), è il caso di munirsi di strumenti più efficienti. L’elenco di editor di testo all’interno del quale scegliere il proprio applicativo preferito è davvero vasto. Si tratta di ottimi software, spesso gratuiti, che consentono di scrivere, ad esempio  i tag HTML, con semplificazioni tipo la colorazione del codice, la possibilità di gestire più file aperti contemporaneamente e diversi altri strumenti. Di seguito suggerisco alcune possibilità.

Notepad++

Notepad++, scaricabile all’indirizzo http://notepad-plus-plus.org/, è un editor di testo gratuito che supporta diversi linguaggi. Come altri software del genere, esso è basato su un componente base noto come Scintilla, http://www.scintilla.org/, ed è realizzato usando il  linguaggio di programmazione C++ per l’ambiente Microsoft Windows.

Il codice HTML all’interno di NotePad++

 

 

PSPad

PSPad è un altro editor per Microsoft Windows che risulta utile in diverse circostanze in quanto, oltre alle classiche funzionalità, come la colorazione del codice o la possibilità di apertura contemporanea di diversi documenti, dispone di diversi strumenti aggiuntivi. Ad esempio, possiede un client FTP integrato che consente l’apertura e la modifica di file in remoto. Inoltre, esso incorpora un registratore di macro per registrare, memorizzare e riprodurre sequenze di operazioni ripetitive. Ancora, dispone di strumenti di selezione dei colori con la traduzione dei loro codici nelle differenti notazioni.

L'editor PSPad
L’editor PSPad

 

Atom

Un altro editor di sicuro interesse è Atom, il cui sito di riferimento è all’indirizzo atom.io. Atom è open source, multipiattaforma e quindi disponibile per OS X, Windows e Linux.  Gli autori di Atom tengono a sottolineare il fatto che il software in questione sia altamente personalizzabile adattando il cosiddetto “look and feel”  dell’interfaccia grafica ai propri specifici gusti.

 

L'editor Atom
L’editor Atom

 

Brackets

Brackets è un’altra alternativa di sicuro interesse. Si tratta di un edito di testi open source che strizza l’occhio agli sviluppatori web. Il sito di riferimento è brackets.io.

L'editor Brackets
L’editor Brackets

 

Microsoft Visual Studio Code

Visual Studio Code è un editor di codice sorgente che, in qualche modo in maniera sorprendente per essere un prodotto Microsoft, è disponibile, oltre che ovviamente per Windows, anche per Mac e Linux. Il sito di riferimento è code.visualstudio.com. Esso supporta in maniera nativa i linguaggi JavaScript e TypeScript ma gestisce una serie di estensioni per altri linguaggi come, ad esempio, C++, C#, Python e PHP. Inoltre, Visual Studio Code supporta lo sviluppo con diversi runtime tra i quali  Node.js e Unity.

Microsoft Visual Studio Code
Microsoft Visual Studio Code

 

Supportaci condividendo sui social il nostro articolo!
  • 12
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
    12
    Shares