venerdì 30 aprile 2010

Java: classi astratte

Talvolta può capitare che una classe sia eccessivamente generica ma di base a tutte le sue sottoclassi poiché implementa per queste i primi metodi di supporto. La stessa genericità della classe padre, poi, non permette a volte di implementare tutti i metodi nella stessa (ad esempio perché ogni sottoclasse implementa il metodo in questione in maniera diversa). Anziché ricorrere all'overriding del metodo si può dichiarare la classe padre come classe astratta.
Una classe astratta è una classe che non implementa tutti i metodi pur riportandone all'interno del codice la firma! Essa implementa solo i metodi più generici che coinvolgono i campi dati in comune con le sue sottoclassi. La classe, così come il metodo non definito (metodo astratto), è preceduto dall'identificatore abstract. Una classe astratta può contenere uno o più metodi astratti.
Ogni sottoclasse, allora, eredita la firma del metodo (non implementato) e può decidere se sviluppare il codice dello stesso oppure rimandare la sua scrittura al successivo livello dell'ereditarietà (il problema viene cioè passato alle successive sottoclassi). In tal caso la sottoclasse che non sviluppa il metodo incompleto (ereditato) dovrà necessariamente essere dichiarata di nuovo con l'identificatore abstract (poiché contiene nel codice un metodo astratto, quello appunto ereditato e non implementato). Non è possibile creare istanze da classi astratte, si possono tuttavia creare variabili di oggetti di classi astratte e farle riferire a oggetti delle sottoclassi!

package Elfo;

/**
* Un elfo (che non fa nulla!)
* @author Luca Petrosino
*/
public abstract class Elfo {
   public Elfo(String name) {
      this.name=name;
      this.attack=0;
      this.defense=0;
   }
   public String getName() {
      return name;
   }
   public int getAttack() {
      return attack;
   }
   public int getDefense() {
      return defense;
   }
   public void setAttack(int newAttack) {
      this.attack=newAttack;
   }
   public void setDefense(int newDefense) {
      this.defense=newDefense;
   }
   public abstract String getInfo();
   public abstract int attack();
   private String name;
   private int attack;
   private int defense;
}

La classe Elfo dell'esempio descrive una classe astratta. Implementa cioè alcuni metodi utili per le sue sottoclassi ma non precisa le istruzioni per i metodi getInfo() e attack(). Due possibili sottoclassi potrebbero essere la classe Ranger e la classe Cenn. Entrambi infatti descrivono due tipologie di elfi. Possiamo allora completare i metodi lasciati in sospeso nella classe genitore (superclasse): il metodo getInfo() restituisce una descrizione dell'elfo indicandone la tipologia, il metodo attack(), invece, restituisce l'intero rappresentativo dei danni da addebitare all'avversario (se entrambe le creature hanno ancora energia sufficiente per sferrare l'attacco).

import Elfo.*;

/**
* Un Cenn Rugosa: creatura 2/2
* @author Luca Petrosino
*/
public class Cenn extends Elfo {
   public Cenn(String name) {
      super(name);
      this.setAttack(2);
      this.setDefense(2);
   }
   public int attack() {
      if(this.getDefense()>0)
         return this.getAttack();
      else
         return 0;
   }
   public String getInfo() {
      String info="L'elfo "+this.getName()+" è un Cenn Rugosa!\n";
      return info;
   }
}

import Elfo.*;

/**
* Un Ranger della Capra Alata: creatura 3/3
* @author Luca Petrosino
*/
public class Ranger extends Elfo {
   public Ranger(String name) {
      super(name);
      this.setAttack(3);
      this.setDefense(3);
   }
   public int attack() {
      if(this.getDefense()>0)
         return this.getAttack();
      else
         return 0;
   }
   public String getInfo() {
      String info="L'elfo "+this.getName()+" è un Ranger della Capra Alata!\n";
      return info;
   }
}

Alla luce di quanto appena detto, l'istruzione Elfo c=new Elfo("Cenn Rugosa"); è errata poiché Elfo è una classe astratta e non può generare oggetti! L'istruzione Elfo c=new Cenn("Cenn Rugosa"); è invece lecita (ogni Cenn Rugosa, secondo la gerarchia dell'ereditarietà, è un elfo). In altre parole la variabile c contiene un riferimento a un oggetto della classe Cenn. Per la precisione c conterrà sempre un riferimento a un oggetto di una sottoclasse poiché la superclasse, essendo astratta, non può generare oggetti (non dimenticatelo). Pertanto anche la chiamata c.attack() è ancora valida!

Java: stop all'ereditarietà!

Una classe, così come le sue classi figlie, può essere estesa all'infinito. Nuove sottoclassi, allora, entreranno a far parte dell'albero delle classi ampliandone la profondità. Il blocco dell'ereditarietà può motivato dal giusto bilanciamento fra prestazioni ed efficienza: ogni chiamata a un metodo di una classe estesa genera un ulteriore carico (di ricerca del metodo nelle precedenti classi) per la JVM e quindi per il processore.
Una classe che non può essere estesa viene detta classe finale e l'identificatore che denota questa volontà è la parola chiave final (che precede l'identificatore class seguito poi dal nome della classe e segue eventualmente l'identificatore di visibilità, ad esempio public o private).
Quando una classe è descritta con l'identificatore final si estende tale proprietà a tutti i suoi metodi. Talvolta, anziché rendere finale un intera classe si decide di bloccare l'ereditarietà di uno o più metodi. In tal caso l'identificatore final precede il nome del metodo. La classe estesa erediterà, allora, il metodo finale (uno o più e gli altri metodi) ma non avrà modo di riscrivere lo stesso! Le chiamate ai metodi finali possono essere ottimizzate dalla JVM (che sa a che livello dell'ereditarietà andare a cercare il metodo quando viene invocato).
Attenzione, se i metodi di una classe final non possono essere ampliati è opportuno ricordare che l'accesso ai campi pubblici dell'istanza (di una classe final), se esistono, continuano ad essere accessibili! Qualcuno potrebbe rintracciarne i valori e scrivere con questi nuove funzioni! Tuttavia, se organizziamo bene le classi, incapsulando bene i dati, possiamo evitare anche quest'ultima eventualità. Infatti, se i campi dell'istanza sono privati i metodi dell'interfaccia pubblica della classe finale verranno ereditati come metodi finali e quindi non riscrivibili.

/* tutti i metodi sono automaticamente final */
import Elfo.*;
public final class Wizard extends Elfo {
   ...
}

/* solo il metodo attack() è final */
import Elfo.*;
public class Wizard extends Elfo {
   public final int attack() {
   ...
   }
   ...
}

giovedì 29 aprile 2010

Java: ereditarietà

In Java è possibile estendere una classe e aggiungere alla nuova classe generata altre funzionalità. La classe di partenza (quella da estendere) prende il nome di superclasse, la scelta di questo nome è secondo me infelice poiché lascia intendere che essa sia dotata di qualcosa in più! E' invece vero il contrario! La classe estesa (detta sottoclasse), infatti, spesso aggiunge nuovi campi e nuovi metodi alla superclasse che è quindi generica (rappresenta un insieme più generico). Per questo motivo suggerisco di adottare il termine classe genitore per la superclasse e classe figlia per la classe estesa. La parola chiave da usare per estendere una classe è il modificatore extends. A breve vedremo come estendere la seguente classe:

package Elfo;

/**
 * Un elfo (che non fa nulla!)
 * @author Luca Petrosino
 */
public class Elfo {
   public Elfo(String name) {
      this.name=name;
      this.enemy=5;
   }
   public String getName() {
      return name;
   }
   public int getEnemy() {
      return enemy;
   }
   public void setEnemy(int newEnemy) {
      this.enemy=newEnemy;
   }
   private String name;
   private int enemy;
}

Realizzeremo dalla classe Elfo una nuova classe, la classe Wizard (che almeno fa qualcosa, attacca!). La nuova classe eredita dalla classe genitore tutti i metodi (pubblici) in essa già implementati. E' poi possibile riscrivere questi metodi per adeguarli ai nuovi campi dati che si aggiungono. Attenzione, in tal caso, per invocare il metodo della classe genitore che si sta riscrivendo (override) è opportuno specificare la chiamata con l'identificatore super! Se ciò non viene fatto il metodo che si sta riscrivendo e che vorrebbe chiamare il metodo della classe genitore (per poi aggiungere al valore restituito qualcosa di nuovo) in realtà chiama se stesso! Se non si verificano ambiguità con le chiamate ai metodi della classe genitore si può usare l'identificatore this, che chiama il metodo della classe, anche quelli ereditati (consiglio tuttavia di usare sempre super, in questo modo si evidenzia ancora di più la provenienza del metodo). Il costruttore della nuova classe dovrà infine invocare il costruttore della classe genitore (passando a questo i parametri necessari, quelli cioè che servono alla classe genitore) e procedere quindi all'assegnazione dei valori per i nuovi campi. Ancora una volta, la chiamata al costruttore della classe genitore avviene con l'identificatore super.

import Elfo.*;

/**
 * Un mago (che fa qualcosa!)
 * @author Luca Petrosino
 */
public class Wizard extends Elfo {
   public Wizard(String name,int mana) {
      super(name);
      this.setEnemy(10);
      this.mana=5;
      this.attack=2;
   }
   public int getMana() {
      return mana;
   }
   public int getAttack() {
      return attack;
   }
   public void setMana(int newMana) {
      this.mana=newMana;
   }
   public void setAttack(int newAttack) {
      this.attack=newAttack;
   }
   public int attack() {
      if (mana>0) {
         mana-=attack;
         if (mana<0) super.setEnemy(mana);
         return attack;
      }
      else return 0;
   }
   private int mana;
   private int attack;
}


E' possibile invocare il metodo attack() solo su oggetti che appartengono alla classe Wizard. L'istruzione if (mana<0) super.setEnemy(mana) è necessaria poiché l'accesso ai campi dati privati della classe genitore è vietato, esso può avvenire solo attraverso l'interfaccia pubblica della classe!

mercoledì 28 aprile 2010

Java: documentazione con javadoc

La documentazione HTML di una classe Java può essere realizzata mediante un utile tool sviluppato in JDK ovvero javadoc. La stessa documentazione in linea di Java non è altro che il risultato generato da javadoc sulle librerie di Java. Per realizzare una buona documentazione occorre allora conoscere i tag usati dal tool, sono inoltre permessi i tag HTML (ad esempio: code, strong, em etc... etc...).
Ogni commento nella forma /** ... */ che precede una classe, un metodo oppure un campo dell'istanza viene riportato da javadoc nella documentazione in linea. E' in questi commenti che il programmatore deve spiegare le ragioni e le utilità di certi metodi.
Alcuni tag possono poi essere inseriti all'interno dei commenti per arricchire la documentazione. Con il tag @param variabile descrizione è possibile fornire una descrizione per la variabile usata! Con il tag @return descrizione, invece, è possibile riportare nella documentazione una descrizione del valore ritornato da un metodo. Se un metodo di una classe è in grado di generare un'eccezione si può segnalare l'evento all'interno del sorgente con il tag @throws classe descrizione.
Altri tag sono @author nome_autore e @version descrizione_versione che aggiungono, rispettivamente, alla documentazione informazioni sull'autore e sulla versione della classe.
Con il comando javadoc -d nomeDirectory nomePackage oppure javadoc -d nomeDirectory nomeClasse viene generata, nella cartella nomeDirectory, la documentazione di un package o della classe. Non dimenticate di omettere l'opzione -d, in tal caso la documentazione verrà generata nella stessa cartella che ospita il file sorgente!

Java: uso dei package

Java permette di organizzare le proprie classi all'interno di package e di importarne le funzionalità laddove esse servono. L'uso dei package garantisce l'unicità dei nomi dati alle classi ed evita di confondere le stesse con le classi già definite nei package standard di Java. Alcuni package di rilievo sono:
  • java.lang: implementa alcune funzionalità di base nonchè alcuni tipi fondamentali del linguaggio;
  • java.util: implementa le classi con i metodi per la gestione di strutture dati;
  • java.io: implementa le classi con i metodi per la gestione dell'input e output;
  • java.math: metodi statici di funzioni matematiche e costanti numeriche;
  • java.sql: oggetti e metodi per l'interazione con i database;
  • java.awt: oggetti di base per interfacce grafiche;
  • java.swing: oggetti avanzati per interfacce grafiche;
Due classi possono avere lo stesso nome se appartengono a package differenti. L'uso di una classe che appartiene a un pachage può avvenire in due modi:
  1. Far precedere al nome della classe l'intera sequenza del nome che individua il package: java.util.Date now=new java.util.Date();;
  2. Importare in maniera definitiva la classe nel sorgente che si sta scrivendo mediante la parola chiave import: import java.util.Date; Date now=new Date();;
Anziché importare la singola classe è poi possibile importare l'intero package usando il carattere jolly *: import java.util.*; Attenzione, è possibile importare un solo package per volta! Se non vogliamo far finire la nostra classe nel package predefinito di Java bisogna allora aggiungere la stessa all'interno di un package tutto nostro. Per fare ciò, prima ancora di iniziare a scrivere il codice della classe va allora specificato il nome di appartenenza al package preceduto dalla parola chiave package:

package myPackage;
public class Employee {
...
}

Un package viene organizzato, sul filesystem, in sottodirectory il cui nome (univoco) coincide con il nome dato al package. La cartella del package si trova nella cartella che ospita il programma principale. Se si vuole dare la visibilità dei package creati anche ad altri programmi (che risiedono in cartelle diverse) è necessario inserire le classi in una cartella speciale ovvero la cartella classdir (variabile di ambiente per JDK) oppure specificare, in fase di compilazione l'opportuno classpath.

Java: sovraccarico (overhead) e cotruttore predefinito

Java prevede il sovraccarico dei metodi e dei costruttori. Più metodi possono avere lo stesso nome ma tipi parametri di parametri diversi (stesso nome ma firma diversa). E' il compilatore che si fa carico di trovare la giusta corrispondenza fra i tipi di parametri dei metodi che hanno lo stesso nome. Se tale corrispondenza non viene trovata viene allora generato un errore.
Se una classe è priva di costruttori viene aggiunto di default un costruttore predefinito che assegna lo stato iniziale per le variabili dell'oggetto in questo modo: i campi numerici dell'istanza sono impostati a zero, i riferimenti a null e i valori booleani a false.
Qualcuno potrebbe fare affidamento su questa cosa per risparmiarne poi la scrittura. La cosa funziona, ma al primo errore spesse volte si dimentica di aver usato un assegnazione invisibile per le variabili del nuovo oggetto (il costruttore predefinito aggiunto di dafualt). Ecco allora l'utilità dell'aggiunta a mano di un costruttore predefinito, eviteremo così spiacevoli riferimenti a null e valori inattesi oltre a scrivere codice assai più completo (e leggibile).
Se dotiamo la classe di un costruttore proprio (almeno uno), Java non interferirà con le nostre idee e nessun costruttore predefinito verrà aggiunto (in tal caso, se tale costruttore è utile all'applicazione, dovrà essere scritto da noi!).

class Car {
   // costruttore predefinito
   public Car() {
      this.model="new_project";
      this.cc=0;
   }
   // altri costruttori
   public Car(int cc) {
      this.model="prototype";
      this.cc=cc;
   }
   public Car(String model, int cc) {
      this.model=model;
      this.cc=cc;
   }
   ...
}

martedì 27 aprile 2010

Java: le date

Date è stata la classe suggerita da Java per la gestione delle date fino alla versione 1.1 di JDK. L'uso di Date è adesso deprecato, vietato. La stessa classe è stata finora tenuta nelle versioni successive di JDK solo per problemi di compatibilità verso quei programmi ormai già scritti. Adesso Java suggerisce di usare la classe Calendar, una classe astratta, oppure la classe GregorianCalendar. Quest'ultima estende proprio la classe Calendar, assai più generica, realizzando di fatto la classe ormai più usata per la rappresentazione delle date.
Invocando il costruttore della classe GregorianCalendar, senza parametri, si genera una data che rappresenta l'istante di tempo corrente. E' poi possibile specificare, attraverso un secondo costruttore, i valori che dovranno caratterizzare la data. Entrambi i costruttori sono usati nell'esempio che a breve segue.
La classe GregorianCalendar implementa diversi metodi per la gestione delle date, eccone alcuni:
  • boolean equals(Object another): confronta l'oggetto (implicito) con l'oggetto another e ritorna true se entrambi si riferiscono allo stesso istante temporale, altrimenti false;
  • boolean before(Object another): confronta l'oggetto (implicito) con l'oggetto another e ritorna true se il primo precede l'altro, altrimenti false;
  • boolean after(Object another): confronta l'oggetto (implicito) con l'oggetto another e ritorna true se il primo viene dopo l'altro, altrimenti false;
  • int get(int field): ritorna un particolare valore del campo data. L'intero usato per specificare il campo può essere, ad esempio, uno delle seguenti costanti: Calendar.YEAR, Calendar.MONTH e Calendar.DATE. Per altri valori consultare la documentazione in linea di Java;
  • void set(int field, int value): imposta il campo al valore indicato;
  • void add(int field, int step): aggiunge al campo la quantità indicata;


/* Testing di alcuni metodi della classe GregorianCalendar */
import javax.swing.*;
import java.util.*;

public class DateTest {
   public static void main(String args[]) {
      GregorianCalendar now=new GregorianCalendar();
      int year=now.get(Calendar.YEAR);
      int month=now.get(Calendar.MONTH)+1;
      int day=now.get(Calendar.DATE);
      String input=JOptionPane.showInputDialog("Giorno: ");
      int birthdayDay=Integer.parseInt(input);
      input=JOptionPane.showInputDialog("Mese: ");
      int birthdayMonth=Integer.parseInt(input);
      input=JOptionPane.showInputDialog("Anno: ");
      int birthdayYear=Integer.parseInt(input);
      GregorianCalendar birthday=new GregorianCalendar(birthdayYear,birthdayMonth-1,birthdayDay);
      int temp=0;
      while (!birthday.after(now)) {
         birthday.add(Calendar.DATE,1);
         temp++;
      }
      System.out.println("Oggi:\t\t"+day+"/"+month+"/"+year);
      System.out.println("Compleanno:\t"+birthdayDay+"/"+birthdayMonth+"/"+birthdayYear);
      System.out.println("Anni:\t\t"+temp/365);
   }
}

lunedì 26 aprile 2010

Java: le stringhe

In Java non esiste il tipo stringa, nella libreria standard è invece implementata un utile classe: la classe String. Ogni stringa racchiusa da virgolette è un'istanza della classe String. All'interno della classe String sono poi implementati numerosi metodi (altri ancora sono ereditati). In definitiva, in Java per manipolare una stringa bisogna allora chiamare su un oggetto della classe String uno dei metodi della classe. Ecco un elenco dei metodi che più uso (cercatene altri all'interno della documentazione API in linea):
  • char charAt(int i): ritorna il carattere della stringa nella posizione i;
  • int compareTo(String B): confronta una stringa, ad esempio A, con una stringa B e restituisce un valore positivo se A è maggiore di B, uguale a 0 se A è uguale a B, oppure negativo se A è minore di B;
  • int length(): ritorna la lunghezza della stringa;
  • String replace(char x, char y): sostituisce, all'interno della stringa, il carattere x con y e ritorna la nuova stringa;
  • String subString(int i): genera una nuova stringa (quella ritornata) a partire dall'indice i;
  • String subString(int i, int j): genera una nuova stringa (quella ritornata) a partire dall'indice i e terminante in j (escluso);
  • String toLowerCase(): ritorna una stringa avente tutti caratteri minuscoli;
  • String toUpperCase(): ritorna una stringa avente tutti caratteri maiuscoli;
  • String trim(): elimina dalla stringa tutti i caratteri di spazio ad inizio e fine stringa e ritorna, quindi, la nuova stringa;
Un esempio:

/* Testing di alcuni metodi della classe String */
import javax.swing.*;

public class StringTest {
   public static void main(String args[]) {
      String nomeCognome=JOptionPane.showInputDialog("Nome e cognome:");
      int spacePosition=0;
      while (nomeCognome.charAt(spacePosition)!=' ')
         spacePosition++;
      String nome=nomeCognome.substring(0,spacePosition);
      String cognome=nomeCognome.substring(++spacePosition);
      System.out.println("Nome e cognome:\t"+nome+" "+cognome);
      System.out.println("Nome:\t\t"+nome.toUpperCase());
      System.out.println("Cognome:\t"+cognome.toUpperCase());
      System.exit(0);
   }
}

domenica 25 aprile 2010

Arduino: primo sketch

Arduino è una piattaforma open source di physical computing basata su una semplice scheda input/output e un ambiente di sviluppo che implementa il linguaggio Processing, assai simile al linguaggio C. Arduino può essere usato per sviluppare oggetti interattivi indipendenti o può essere collegato a un software in esecuzione sul computer. Le schede possono essere assemblate a mano oppure acquistate in rete. In questi giorni è stato aperto il primo store ufficiale a Milano.L'IDE (Integrated Development Environment) open source può essere scaricato gratuitamente da www.arduino.cc. Altre informazioni su Arduino possono essere trovate all'indirizzo già indicato. L'ideatore di Arduino è Massimo Banzi, diffidate dalle tante imitazioni che girano in rete, il vero Arduino è made in Italy!
I programmi scritti per Arduino sono chiamati sketch. La compilazione del programma, attraverso l'IDE, genera il bytecode (il programma tradotto in linguaggio macchina) che viene quindi trasferito nella memoria di Arduino.
Terminata la ricezione del programma, Arduino inizia ad eseguire quest'ultimo in un loop continuo, è possibile eventualmente effettuare il reset dello stato attraverso un interruttore posizionato sulla scheda.
Questo è uno dei primi sketch da me eseguito su Arduino per verificarne il funzionamento:

/* intermittenza e comunicazione - di Luca Petrosino */
#define GREEN 13
#define RED 12
#define PAUSE 500
#define STOP 60

int start=0;

/* setup dei pin e della porta seriale */
void setup() {
   pinMode(GREEN,OUTPUT);
   pinMode(RED,OUTPUT);
   Serial.begin(9600);
}

/* ciclo da eseguire */
void loop() {
   if (start==STOP) {
      start=0;
      blinkLed(RED);
   }
   else {
      blinkLed(GREEN);
   }
   start++;
}

/* fa lampeggiare il led collegato al pin number */
void blinkLed(int number) {
   Serial.println(start);
   digitalWrite(number,HIGH);
   delay(PAUSE);
   digitalWrite(number,LOW);
   delay(PAUSE);
}

Si tratta di un semplice programma per l'intermittenza di due led, uno verde e l'altro rosso. Per ogni ciclo di loop viene fatto lampeggiare il led verde. Una variabile conteggia il numero di cicli. Quando il conteggio raggiunge il valore STOP, la variabile viene azzerata e il led rosso viene fatto lampeggiare al posto di quello verde.
Ogni programma prevede due distinte funzioni. Attraverso la funzione setup vengono mappati gli input (ad esempio sensori e interruttori) e gli output (ad esempio attuatori e led) usati nel corso del programma. Con la funzione pinMode è possibile specificare se un pin deve funzionare da input o da output. Attenzione, pinMode imposta i pin digitali di Arduino (che ha anche pin analogici e di tipo pwm, pulse width modulation). La funzione riceve come argomenti il numero del pin (nel programma ho definito delle costanti per ogni pin usato) e una costante che può assumere i valori INPUT o OUTPUT a seconda dei casi.
Sempre in fase di setup ho indicato la possibilità di usare nel corso del programma la comunicazione seriale fra Arduino e il PC (con Serial.begin(9600) indico una comunicazione di 9600 bps).
L'altra funzione sempre presente all'interno di programmi per Arduino è la funzione loop. Tutte le istruzioni all'interno di loop vengono eseguite ciclicamente. E' qui che va scritto il codice per istruire Arduino affinché faccia quello che vogliamo! In altre parole la funzione loop è l'equivalente della funzione main per i programmi scritti in c.
Nel programma ho indicato a Arduino di usare i pin 13 e 12 rispettivamente per i led verde e rosso. Il nome dato alle costanti mi aiuta a capire il significato del pin! All'esterno delle due funzioni ho poi dichiarato una variabile per il conteggio, tale variabile sarà visibile all'interno della funzione loop. Essa viene infatti usata per verificare, attraverso il confronto con la costante STOP, il valore di conteggio.
Con Serial.println invio attraverso la porta seriale l'attuale valore di conteggio che leggerò quindi sul monitor del PC. L'accensione di un led avviene con la funzione digitalWrite che invia al pin indicato un livello di tensione HIGH tale da accendere il led collegato. Se si vuole invece spegnere il led bisogna rimettere a LOW il valore di tensione al medesimo pin associato al led. Fra un istruzione e quella successiva ho poi aggiunto dei ritardi attraverso la funzione delay. Il valore dato come argomento alla funzione delay indica il tempo (in millisecondi) da attendere prima di riprendere l'esecuzione delle istruzioni contenute in loop. Arduino ha una velocità di clock pari a 20 MHz e l'esecuzione del programma mostrato risulterebbe eccessivamente veloce!

mercoledì 21 aprile 2010

Lettura di una bitmap

Le immagini bitmap, introdotte nel 1990 con Windows 3.0, organizzano le informazioni all'interno di più strutture dati. Il file inizia con la struttura dati BITMAPFILEHEADER, di 14 byte. All'interno di questa prendono posto alcune importanti informazioni come la dimensione in byte del file, lo scostamento del primo pixel dell'immagine a partire dall'inizio del file e una stringa in ascii con il valore BM (valore decimale 19778, esadecimale 4D42). La successiva struttura dati, BITMAPINFOHEADER, di 40 byte, raccoglie invece altre informazioni, come ad esempio: le dimensioni in pixel dell'immagine, il numero di byte usato per ogni pixel, la risoluzione orizzontale e verticale etc... Qui trovate una descrizione dettagliata delle informazioni che è possibile rintracciare negli header di un file bmp. L'ultima struttura dati è infine la mappa dei pixel: ad ogni pixel corrisponde un colore sotto forma di codice (o indice se l'immagine usa una tavolozza di colori). Ogni codice specifica le componenti cromatiche dei colori primari (rosso, verde e blu) miscelati per ottenere il colore assegnato al pixel!
Possiamo leggere e scrivere queste informazioni (sia gli header che la mappa dei pixel) aprendo il file in modalità binaria e leggendo con fread n byte alla volta, a seconda della struttura dati. E' possibile assegnare i byte letti con fread a delle variabili (header1 e header2) e usare queste informazioni all'interno di un programma. Nel codice che propongo, dopo aver letto le informazioni (utili tra l'altro ad allocare in memoria un array di n char, uno per ogni pixel) eseguo prima con fseek il riposizionamento del puntatore all'inizio della mappa dei pixel (usando lo scostamento rimediato con la variabile header1), successivamente eseguo la lettura dei pixel. Il programma termina con la stampa della mappa dei pixel ma può essere esteso ulteriormente per l'editing dell'immagine (in tal caso si accederà al pixel con fwrite).

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

#pragma pack(2) // usa 2 byte per impachettare la struttura, che è di 14 byte (non è un multiplo del byte)!
/* BITMAPFILEHEADER (14 byte) */
typedef struct {
   unsigned short int bfType;
   unsigned int bfSize;
   unsigned short int bfReserved1,bfReserved2;
   unsigned int bfOffBits;
} BITMAPFILEHEADER;

#pragma pack()
/* BITMAPINFOHEADER (40 byte) */
typedef struct {
   unsigned int biSize;
   int biWidth;
   int biHeight;
   unsigned short int biBitCount;
   unsigned int biCompression;
   unsigned int biSizeImage;
   int biXPelsPerMeter;
   int biYPelsPerMeter;
   unsigned int biClrUsed;
   unsigned int biClrImportant;
} BITMAPINFOHEADER;

/* struttura per un pixel */
typedef struct {
   unsigned char blue;
   unsigned char green;
   unsigned char red;
} PIXEL;

/* codice (binari e esadecimali) relativi ai file bmp */
#define DECBMPCODE 19778
#define HEXBMPCODE 0x4D42

int main (int argc, char*argv[]) {
   FILE *input_file;
   BITMAPFILEHEADER header1;
   BITMAPINFOHEADER header2;
   PIXEL pixel, *image;
   int i,j,n_pixel=0;

   if (argc==1) {
      printf("Specifica il file da usare!\n");
      return -1;
   }
   else {
      if ((input_file=fopen(argv[1],"rb+"))!=NULL) {
         fread(&header1,sizeof(header1),1,input_file);
         if (header1.bfType==DECBMPCODE) {
            printf("Header del file %s:\n",argv[1]);
            printf(" - Tipo di file: %x\n",header1.bfType);
            printf(" - Dimensione del file: %u Kb (%u byte)\n",header1.bfSize/1024,header1.bfSize);
            printf(" - Offset al primo bit nella mappa: %u\n",header1.bfOffBits);
            fread(&header2,sizeof(header2),1,input_file);
            printf("Header delle informazioni:\n");
            printf(" - Dimensione del blocco informazione: %u byte\n",header2.biSize);
            printf(" - Larghezza dell'immagine: %u pixel\n",header2.biWidth);
            printf(" - Altezza dell'immagine: %u pixel\n",header2.biHeight);

            /* Calcolo del padding */
            int pitch = header2.biWidth * 3;
            if (pitch % 4 != 0) pitch += 4 - (pitch % 4);
            int padding = pitch - (header2.biWidth * 3);
            printf(" - Pitch: %u\n", pitch);
            printf(" - Padding: %u\n", padding);

            fseek(input_file,header1.bfOffBits,SEEK_SET);
            image=(PIXEL*)malloc((header2.biWidth*header2.biHeight)*sizeof(PIXEL));
            printf("\nComponenti dei pixel: #pixel(RGB)\n");
            for (i=0;i<header2.biHeight;i++) {
               for (j=0;j<header2.biWidth;j++) {
                  fread(&pixel, sizeof(PIXEL),1,input_file);
                  printf("%2d(%u,%u,%u) ",n_pixel,pixel.red,pixel.green,pixel.blue);
                  image[n_pixel]=pixel;
                  n_pixel++;
               }
               fseek(input_file, padding, SEEK_CUR);
               printf("\n");
            }
            free(image);
         }
         else {
            printf("Il file %s non è un file bmp!\n",argv[1]);
            return -1;
         }
      }
      else {
         printf("Non è stato possibile aprire il file %s!\n",argv[1]);
         return -1;
      }
   }
   return 0;
}


E' possibile verificare il programma con una delle seguenti immagini:



venerdì 16 aprile 2010

Il cifrario di Vigenere

Il cifrario di Vigenere applica al testo da codificare la seguente sostituzione: i caratteri che compongono le parole vengono spostati di n posti in virtù di una parola (detta worm, verme) che stabilisce per ognuno di questi lo scostamento. A differenza del cifrario di Cesare che applica ai caratteri della parola sempre lo stesso scostamento (Cesare, ad esempio, spostava in avanti i caratteri di tre posizioni), il cifrario di Vigenere applica ai caratteri uno scostamento variabile! Ad ogni carattere della parola da codificare viene sommato il carattere del verme scelto (che può essere una semplice parola o anche un versetto). Un indice punta sempre al carattere del verme, quando l'indice (che viene incrementato ad ogni somma) giunge all'ultimo carattere del verme viene di nuovo fatto puntare al primo carattere! Tale rotazione deve poi avvenire anche sul carattere codificato. Pertanto, se la somma fra i caratteri (quello del verme e quello della parola in chiaro) sposta il carattere codificato oltre la lettera Z, gli scostamenti riprendono allora dalla lettera A. La decodifica avviene allora sottraendo lo scostamento indicato dal carattere del verme al carattere codificato. Nel programma che propongo l'input e l'output avvengono su file. In fase di codifica il programma chiede il nome di un file da codificare e ne verifica l'esistenza. Viene poi chiesta la parola da usare come verme. L'operazione di codifica produce il file file_codificato.txt. In fase di decodifica viene invece chiesto il file da decodificare, al termine dell'operazione è possibile trovare il file file_decodificato.txt contenente il testo di partenza!

/* cifrario di vigenere */
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define MAX_LENGTH 1024

int exist(char file_name[MAX_LENGTH]);
void code(char source_file_name[MAX_LENGTH],char worm[MAX_LENGTH]);
void decode(char source_file_name[MAX_LENGTH],char worm[MAX_LENGTH]);

int main(void) {
   int choice,worm_length;
   char worm[MAX_LENGTH];
   char file_name[MAX_LENGTH];
   FILE *source_file;

   do {
      printf("1. codifica;\n");
      printf("2. decodifica;\n");
      printf("Scelta: ");
      scanf("%d",&choice);
   } while (choice!=1 && choice!=2);
   if (choice==1) { /*code */
      getchar();
      printf(" Codifica file:\n");
      printf(" - Seme: ");
      fgets(worm,sizeof(worm),stdin);
      worm[strlen(worm)-1]=0;
      worm_length=strlen(worm);
      printf(" - Lunghezza del seme: %d\n",worm_length);
      printf(" - File sorgente: ");
      fgets(file_name,sizeof(file_name),stdin);
      file_name[strlen(file_name)-1]=0;
      if (exist(file_name)==0) {
         printf("Attenzione: il file %s non esiste!\n",file_name);
         return -1;
      }
      printf(" - Codifica: ");
      code(file_name,worm);
      printf(" completata!\n");
   }
   else if(choice==2) { /* decode */
      getchar();
      printf(" Decodifica file:\n");
      printf(" - Seme: ");
      fgets(worm,sizeof(worm),stdin);
      worm[strlen(worm)-1]=0;
      worm_length=strlen(worm);
      printf(" - Lunghezza del seme: %d\n",worm_length);
      printf(" - File sorgente: ");
      fgets(file_name,sizeof(file_name),stdin);
      file_name[strlen(file_name)-1]=0;
      if (exist(file_name)==0) {
         printf("Attenzione: il file %s non esiste!\n",file_name);
         return -1;
      }
      printf(" - Decodifica: ");
      decode(file_name,worm);
      printf(" completata!\n");
   }
   return 0;
}

/* verifica l'esistenza di un file, 1 altrimenti 0 */
int exist(char file_name[MAX_LENGTH]) {
   int result;
   FILE *source_file;
   if ((source_file=fopen(file_name,"r"))==NULL) result=0;
   else {
      result=1;
      fclose(source_file);
   }
   return result;
}

/* codifica un file */
void code(char source_file_name[MAX_LENGTH],char worm[MAX_LENGTH]) {
   int worm_length=strlen(worm),j,i=0;
   FILE *source_file=fopen(source_file_name,"r");
   FILE *output_file=fopen("file_codificato.txt","w");
   char c,next_worm_char,code_char;
   while ((c=fgetc(source_file))!=EOF) {
      if(isalpha(c)!=0) {
         code_char=c;
         next_worm_char=worm[i++]-'A';
         for (j=0;j<next_worm_char;j++) {
            if(code_char>='A' && code_char<='Z') {
               if(code_char=='Z') code_char='A';
               else code_char++;
            }
            else if(code_char>='a' && code_char<='z') {
               if(code_char=='z') code_char='a';
               else code_char++;
            }
         }
         if (i==worm_length) i=0;
         fputc(code_char,output_file);
      }
      else {
         fputc(c,output_file);
      }
   }
   fclose(source_file);
   fclose(output_file);
}

/* decodifica un file */
void decode(char source_file_name[MAX_LENGTH],char worm[MAX_LENGTH]) {
   int worm_length=strlen(worm),j,i=0;
   FILE *source_file=fopen(source_file_name,"r");
   FILE *output_file=fopen("file_decodificato.txt","w");
   char c,next_worm_char,decode_char;
   while ((c=fgetc(source_file))!=EOF) {
      if(isalpha(c)!=0) {
         decode_char=c;
         next_worm_char=worm[i++]-'A';
         for (j=next_worm_char;j>0;j--) {
            if(decode_char>='A' && decode_char<='Z') {
               if(decode_char=='A') decode_char='Z';
               else decode_char--;
            }
            else if(decode_char>='a' && decode_char<='z') {
               if(decode_char=='a') decode_char='z';
               else decode_char--;
            }
         }
         if (i==worm_length) i=0;
         fputc(decode_char,output_file);
      }
      else {
         fputc(c,output_file);
      }
   }
   fclose(source_file);
   fclose(output_file);
}

Merge sort

Il merge sort, ordinamento a fusione, così come il quick sort, si basa sul paradigma del divide et impera. Il problema (nel nostro caso di ordinamento) viene suddiviso in tanti sottoproblemi di difficoltà man mano inferiore (l'insieme iniziale viene cioè suddiviso). Il merge sort opera dividendo l'array da ordinare in due met à e affina tale partizione fi no ad isolare ogni singolo elemento. La successiva fase, quindi, prevede la costruzione di un array ordinato operando una vera e propria fusione degli insiemi che man mano vengono ordinati. Di seguito propongo una realizzazione dell'algoritmo in C:

/* mergesort */
#include <stdio.h>

#define LENGTH 10

void mergeSort(int *array, int begin, int end);
void merge(int *array,int begin, int middle, int end);

int main(void) {
   int i,array[LENGTH]={1,8,4,0,2,5,6,3,7,9};
   mergeSort(array,0,LENGTH);
   printf("array: ");
   for (i=0;i<LENGTH;i++)
      printf("%d, ",array[i]);
   printf("\b\b;\n");
   return 0;
}

void mergeSort(int *array, int begin, int end) {
   int middle;
   if (begin<end) {
      middle=(end+begin)/2;
      mergeSort(array,begin,middle);
      mergeSort(array,middle+1,end);
      merge(array,begin,middle,end);
   }
}

void merge(int *array,int begin, int middle, int end) {
   int i,j,k,temp[LENGTH];
   i=begin;
   j=middle+1;
   k=0;
   while ((i<=middle) && (j<=end)) {
      if (array[i]<=array[j])
         temp[k++]=array[i++];
      else
         temp[k++]=array[j++];
   }
   while (i<=middle)
      temp[k++]=array[i++];
   while (j<=end)
      temp[k++]=array[j++];
   for (k=begin;k<=end;k++)
      array[k]=temp[k-begin];
}

Quick sort

Il quick sort, ordinamento rapido, si basa sul paradigma divide et impera. Durante le prime istruzioni l'algoritmo si preoccupa di scegliere un elemento dell'array come pivot (perno) e partiziona la sequenza in due sottoinsiemi. Il primo sottoinsieme conterr à tutti gli elementi dell'array che sono minori o uguali al pivot scelto. Il secondo sottoinsieme, invece, conterrà tutti gli elementi dell'array che sono maggiori del pivot. A tale proposito una funzione per la partizione fa scorrere due indici (uno di questi parte dal primo elemento dell'array, il secondo dall'ultimo). Gli indici vengono incrementati (il primo) e decrementati (il secondo) fi nch è viene rispettata la condizione d'ordine nei confronti del pivot.Non appena ciò non è possibile, è evidente che è necessario scambiare i due elementi puntati dai rispettivi indici. Quando i due indici, a seguito dei rispettivi incrementi e decrementi, si incrociano la procedura per la partizione si arresta e va allora richiamata, ricorsivamente sul primo sottoinsieme e sul secondo, fino a ordinare l'intero vettore. Di seguito propongo una realizzazione dell'algoritmo in linguaggio C:

/* quicksort */
#include <stdio.h>

#define LENGTH 10

void quickSort (int *array, int left, int right);
void swap(int *x, int *y);

int main (void) {
   int i,array[LENGTH]={1,8,4,0,2,5,6,3,7,9};
   quickSort(array,0,LENGTH-1);
   printf("array: ");
   for (i=0;i<LENGTH;i++)
      printf("%d, ",array[i]);
   printf("\b\b;\n");
}

void quickSort (int *array, int left, int right) {
   int i=left, j=right;
   int pivot=array[(left+right)/2];
   do {
      while (array[i]<pivot) i++;
      while (array[j]>pivot) j--;
      if (i<=j) {
         swap(&array[i],&array[j]);
         i++; j--;
      }
   } while (i<=j);
   if (left<j) quickSort(array,left,j);
   if (i<right) quickSort(array,i,right);
}

void swap(int *x, int *y) {
   int temp=*x;
   *x=*y;
   *y=temp;
}

Bubble sort

Il bubble sort, ordinamento a bolle, realizza l'ordinamento di una lista di valori eseguendo confronti fra coppie adiacenti. L'esito del confronto stabilisce il valore da spostare, man mano verso il fondo della lista. Se in una iterazione, ancora prima di scorrere tutte le coppie, non avvengono scambi la lista è allora già ordinata! Queste le fasi previste dall'algoritmo:
  • inizializzare una variabile per stabilire se durante una iterazione avviene almeno uno scambio fra coppie adiacenti e un indice che punta all'ultimo elemento del vettore ordinato;
  • scorrere la lista, confrontando le coppie adiacenti ed effettuando, eventualmente, lo scambio (segnando l'evento nell'apposita variabile) se queste non sono tra loro ordinate;
  • ripetere il precedente passaggio arrestando la serie di confronti al valore precedentemente già ordinato e puntato dall'indice (che man mano verrà quindi decrementato);
L'algoritmo, allora, termina quando l'indice che punta l'ultimo elemento ordinato raggiunge la testa della lista oppure, come già detto, se in una iterazione non si verificano scambi. Di seguito propongono una realizzazione dell'algoritmo in linguaggio C:

/* bubblesort */
#include <stdio.h>
#include <stdbool.h>

#define LENGTH 10

void bubbleSort(int *array,int length);
void swap(int *x, int *y);

int main(void) {
   int i,array[LENGTH]={1,8,4,0,2,5,6,3,7,9};
   bubbleSort(array,LENGTH);
   printf("array: ");
   for (i=0;i<LENGTH;i++)
      printf("%d, ",array[i]);
   printf("\b\b;\n");
   return 0;
}

void bubbleSort(int *array,int length) {
   int i,temp,step=0;
   bool change;
   do {
      change=false;
      for (i=0;i<length-1-step;i++) {
         if (array[i]>array[i+1]) {
            swap(&array[i],&array[i+1]);
            change=true;
         }
      }
      step++;
   }
   while(change==true);
}

void swap(int *x, int *y) {
   int temp=*x;
   *x=*y;
   *y=temp;
}

Selection sort

Il selection sort, ordinamento per selezione, è uno degli algoritmi di ordinamento più intuitivi (e di facile realizzazione). L'ordinamento avviene selezionando ad ogni iterazione il valore più piccolo e scambiando quest'ultimo con il valore in testa alla lista da ordinare e non ancora ordinato (prima iterazione). Le successive iterazioni effettueranno, allora, la ricerca del valore più piccolo su un insieme di valori sempre più ridotti. Un indice indica la posizione della lista che si sta ordinando. Ad ogni iterazione, dunque, la lista ordinata aumenta di un valore fino a prevedere tutti i valori della lista di partenza. Queste le fasi previste dall'algoritmo:
  • inizializzare un puntatore i che va da 0 ad n-1, con n lunghezza dell'array (il primo elemento dell'array ha indice 0);
  • cercare il valore più piccolo nell'array;
  • scambiare l'elemento più piccolo, appena trovato, con l'elemento puntato da i;
  • incrementare i e ripetere la ricerca del valore più piccolo fino a scorrere tutti gli elementi dell'array;
Di seguito propongo una realizzazione dell'algoritmo in linguaggio C:

/* selectionsort */
#include <stdio.h>

#define LENGTH 10

void selectionSort(int *array,int length);
int search_min(int *array,int begin,int end);
void swap(int *x, int *y);

int main(void) {
   int i,array[LENGTH]={1,8,4,0,2,5,6,3,7,9};
   selectionSort(array, LENGTH);
   printf("array: ");
   for (i=0;i<LENGTH;i++)
      printf("%d, ",array[i]);
   printf("\b\b;\n");
   return 0;
}

void selectionSort(int *array,int length) {
   int i,i_min;
   for (i=0;i<length;i++) {
      if ((i_min=search_min(array,i,length))==-1)
         printf("Errore nella chiamata alla funzione!");
      else swap(&array[i],&array[i_min]);
   }
}

int search_min(int *array,int begin,int end) {
   int i,temp_i;
   if (begin<end) {
      temp_i=begin;
      for (i=begin+1;i<end;i++) {
         if (array[i]<array[temp_i]) temp_i=i;
      }
      return temp_i;
   }
   else return -1;
}

void swap(int *x, int *y) {
   int temp=*x;
   *x=*y;
   *y=temp;
}

venerdì 9 aprile 2010

Java Server Pages

In questa pagina trovate una preview del libro pubblicato su issuu. La lettura può avvenire attraverso la pagina suggerita oppure scaricando il file pdf allegato alla stessa (per il download è sufficiente accedere alla pagina e cliccare su "Download"). Il documento è rilasciato seguendo i termini della Licenza per Documentazione libera GNU. Trovate la licenza all'interno del documento. Qui trovate il codice sorgente per generare il documento (file tex per LaTex). Lasciatemi un messaggio per le eventuali correzioni.

Servlet

In questa pagina trovate una preview del libro pubblicato su issuu. La lettura può avvenire attraverso la pagina suggerita oppure scaricando il file pdf allegato alla stessa (per il download è sufficiente accedere alla pagina e cliccare su "Download"). Il documento è rilasciato seguendo i termini della Licenza per Documentazione libera GNU. Trovate la licenza all'interno del documento. Qui trovate il codice sorgente per generare il documento (file tex per LaTex). Lasciatemi un messaggio per le eventuali correzioni.

giovedì 8 aprile 2010

Linguaggio C

In questa pagina trovate una preview del libro pubblicato su issuu. La lettura può avvenire attraverso la pagina suggerita oppure scaricando il file pdf allegato alla stessa (per il download è sufficiente accedere alla pagina e cliccare su "Download"). Il documento è rilasciato seguendo i termini della Licenza per Documentazione libera GNU. Trovate la licenza all'interno del documento. Qui trovate il codice sorgente per generare il documento (file tex per LaTex). Lasciatemi un messaggio per le eventuali correzioni.