Mastering Delphi
Mastering Delphi
linguaggio Pascal in Delphi. A causa di problemi di spazio e visto che molti programmatori Delphi vanno in
cerca di informazioni avanzate, nelle ultime versioni questo materiale e' stato completamente omesso. Per
ovviare all'assenza di queste informazioni, ho cominciato a mettere insieme questo libro online, intitolato
Essential Pascal.
Questo e' un libro dettagliato sul Pascal, che per il momento sara' disponibile gratuitamente sul mio sito web
(sinceramente non so cosa accadra' in seguito, potrei anche trovare un editore!). Questo e' libro e'
costantemente aggiornato, quindi qualsiasi commento o suggerimento e' il benvenuto. La prima versione
completa di questo libro, datata Luglio 1999, e' stata pubblicata sul Companion CD di Delphi 5.
Copyright
Il testo ed il codice sorgente di questo libro sono sotto copyright di Marco Cantù. Ovviamente e' possibile
usare i programmi e adattarli alle proprie necessita', solamente non ne e' permesso l'uso in libri, materiale
per insegnamento, e altri formati soggetti al copyright. E' possibile collegare il proprio sito con questo, ma
prego di non duplicare il materiale, visto che sara' oggetto di frequenti cambiamenti e aggiornamenti.
Codice sorgente
Il codice sorgente di tutti gli esempi riportati in questo libro e' disponibile. Il codice ha lo stesso Copyright del
libro: e' liberamente disponibile (e usabile) ma non pubblicabile in altri documenti o siti web. I link a questo
sito sono benvenuti.
Il codice sorgente e' disponibile per il download in un singolo file zip, EPasCode.zip (solo 26KB).
Feedback
Vi prego di riportare eventuali errori ma anche i punti che non sono abbastanza chiari per un principiante.
Dedichero' tempo a questo progetto in funzione delle reazioni che ricevero', quindi fatemi sapere se gradite il
libro e volete vederlo finito, ma comunicatemi anche altri argomenti (non trattati in Mastering Delphi 4) che
vorreste vedere qui. Seguite comunque anche i newsgroup e consultate la sezione dei libri sul mio sito
oppure spedite una mail a Marco.
Ringraziamenti
L'idea di pubblicare un libro sul web, la devo soprattutto all'esperienza di Bruce Eckel con il libro Thinking in
Java. Bruce e' un amico e penso che abbia fatto veramente un grande lavoro con il suo libro.
Parlando del progetto al team di Delphi in Borland, ho ricevuto diverse reazioni positive. Ringrazio quindi
Borland per aver realizzato la prima serie di compilatori Turbo Pascal e adesso la serie dell'ambiente visuale
Delphi.
Comincio a ricevere diversi feedback molto preziosi. I primi lettori che mi hanno aiutato a migliorare questo
materiale sono Charles Wood e Wyatt Wong. Mark Greenhaw mi ha aiutato a scrivere il testo. Grazie.
Traduzioni
Essential Pascal e' stato tradotto in italiano e francese, sta per essere tradotto in diversi linguaggi incluso il
giapponese e l'olandese. Le versioni tradotte saranno disponibili online e collegate da questo sito. Se siete
interessati alla traduzione di Essential Pascal o state cercando una versione tradotta, guardate alla pagina
delle Traduzioni.
L'autore
Marco Cantù vive a Piacenza, in Italia. Dopo aver scritto libri sul C++ e su Object Windows Library e
numerosi articoli, ha approfondito la programmazione Delphi. Marco e' l'autore della serie Mastering Delphi,
pubblicata da Sybex, cosi' come del libro avanzato Delphi Developers Handbook. Scrive articoli per diverse
riviste specializzate, incluso The Delphi Magazine, presenta alle conferenze Delphi e Borland in tutto il
mondo, e si occupa di formazione Delphi ai livelli base e avanzati.
Per dettagli su Marco e sul suo lavoro visitare il suo sito web, www.marcocantu.it.
© Copyright
www.marcocantu.it
Marco's Delphi Books
Essential Pascal - Web Site
Il linguaggio Object Pascal utilizzato in Delphi, non fu inventato nel 1995 assieme all'ambiente di
programmazione visuale di Borland. Esso e' semplicemente un'estensione dell'Object Pascal usato nella linea
di prodotti Borland Pascal. Borland non invento' il Pascal ma lo rese popolare estendendolo un poco...
Nota: Questo capitolo conterra' la storia del linguaggio Pascal e della sua evoluzione. Per il momento
contiene solo brevi spunti.
Il Pascal di Wirth
Il linguaggio Pascal fu in origine progettato da Niklaus Wirth, un professore svizzero, alla fine degli anni '70.
Quando il Pascal fu progettato, esistevano gia' molti altri linguaggi di programmazione, ma pochi largamente
usati: FORTRAN, C, Assembly, COBOL. L'idea chiave del nuovo linguaggio di programmazione fu l'ordine,
raggiunto tramite un forte concetto di tipizzazione dei dati, il controllo delle dichiarazioni e la
programmazione strutturata. Il linguaggio fu anche sviluppato come strumento di insegnamento nelle
scuole.
Il Turbo Pascal
Il famoso compilatore Pascal di Borland, chiamato Turbo Pascal, fu introdotto nel 1985. Il compilatore Turbo
Pascal fu un best-seller dell'epoca e rese il linguaggio popolare particolarmente nella piattaforma PC, grazie
anche all'unione di semplicita' e potenza.
Il Turbo Pascal introdusse un ambiente di sviluppo integrato (IDE) in cui era possibile editare il codice (in un
editor WordStar compatibile), compilare, vedere gli errori e saltare alle linee contenenti gli stessi. Oggi
questo puo' sembrare banale e scontato, ma in precedenza si doveva uscire dall'editor tornando al DOS,
compilare, annotarsi le righe di errore e aprire di nuovo l'editor puntando alle righe incriminate.
Inoltre Borland vendeva Turbo Pascal per 49 dollari, dove Microsoft vendeva il suo compilatore Pascal per
alcune centinaia di dollari. I molti anni del successo del Turbo Pascal contribuirono alle decisione di Microsoft
di cancellare la linea dei suoi compilatori Pascal.
Il Pascal di Delphi
Dopo 9 versioni dei compilatori Turbo e Borland Pascal, che gradualmente estesero il linguaggio, Borland
rilascio' nel 1995 Delphi, rendendo cosi' il Pascal un linguaggio di programmazione visuale.
Delphi estende il linguaggio Pascal in diversi punti, incluso diverse estensioni object-oriented, che
differiscono da altre versioni dell'Object Pascal, incluso quelle del compilatore Borland Pascal with Objects.
www.marcocantu.it
Marco's Delphi Books
Essential Pascal - Web Site
Prima di passare alla scrittura di codice Pascal, e' importante sottolineare alcuni elementi di stile di
programmazione Pascal. La questione che vorrei affrontare e' questa: A parte le regole di sintassi, come
scrivere il codice? Non esiste una singola risposta a questa domanda, visto che le preferenze personali
generano diversi stili. Comunque ci sono alcune regole da seguire riguardo ai commenti, maiscole, spazi e la
cosidetta pretty-printing. In generale l'obbiettivo di ogni stile di programmazione e' la chiarezza. Le decisioni
di formattazione, che si prendono sono una forma di stenografia indicante la funzione di un dato pezzo di
codice. Un essenziale strumento per la chiarezza e' la coerenza ovunque dello stile che si sceglie, quindi
assicurarsi di seguirlo attraverso tutto il progetto.
Commenti
In Pascal, i commenti sono racchiusi tra parentesi graffe o parentesi tonde seguite da un asterisco. Delphi
accetta anche i commenti nello stile C++, che si possono mettere anche alla fine di una riga:
{this is a comment}
(* this is another comment *)
// this is a comment up to the end of the line
La prima forma e' la piu' breve e la piu' usata. La seconda forma e' spesso preferita in Europa visto che
diverse tastiere Europee non hanno il simbolo della parentesi graffa. La terza forma di commento e' stata
presa dal C++ ed e' disponibile soltanto nelle versioni di Delphi a 32 bit. I commenti alla fine della linea sono
di grande aiuto per brevi commenti e per commentare una specifica linea di codice.
Nota: Nei listati di questo libro cerchero' di scrivere i commenti in corsivo (e le keyword in grassetto), per
essere consistente con i settaggi della sintassi in Delphi.
Avere tre differenti tipi di commenti puo' essere d'aiuto per costruire commenti nidificati. Se si cerca di
commentare diverse righe di codice per disabilitarle e queste linee contengono gia' alcuni commenti, non si
puo' usare lo stesso stile di commento:
{ ... code
{comment, creating problems}
... code }
{ ... code
//this comment is OK
... code }
Da notare che se la parentesi graffa o la parentesi-asterisco e' seguita dal simbolo del dollaro ($), essa
diventa una direttiva del compilatore, come in {$X+}.
Nota: Attualmente, le direttive del compilatore sono ancora commenti. Ad esempio, {$X+ Questo e' ancora
un commento} e' valido. E' sia una valida direttiva sia un valido commento, anche se un buon
progammatore tendera' probabilmente a separare direttive e commenti.
Nota: C'e' solo un'eccezione in Object Pascal: La procedura di registrazione di un componente Register deve
sempre cominciare con la R maiuscola, a causa di problemi di compatibilita' con C++ Builder.
Ci sono comunque leggeri inconvenienti. Primo, bisogna comprendere che questi identificatori sono
veramente gli stessi, bisogna quindi evitare di usarli come elementi diversi. Secondo, bisogna tentare di
essere coerenti nell'uso del maiuscolo per aumentare la leggibilita' del codice.
Una coerenza nell'uso del maiuscolo non e' forzata dal compilatore, ma e' buona abitudine seguirla. Un
comune approccio e' quello di mettere maiuscolo solo il primo carattere di ogni identificatore. Quando un
identificatore e' formato da diverse parole (non si possono inserire spazi in un identificatore), ogni prima
lettera di ogni singola parola va in maiuscolo:
MyLongIdentifier
MyVeryLongAndAlmostStupidIdentifier
Altri elementi ignorati dal compilatore sono spazi, a capo e tabulazioni inseriti nel codice sorgente. Tutti
questi elementi sono conosciuti come spazi bianchi. Gli spazi bianchi sono usati per aumentare la leggibilita'
del codice e non influenzano la compilazione.
Diversamente dal BASIC, il Pascal permette di scrivere un'istruzione composta da diverse linee di codice,
dividendo una singola istruzione su due o piu' righe. L'inconveniente (almeno per i programmatori BASIC) di
permettere istruzioni su piu' righe, e' che bisogna ricordare di terminarle con un punto e virgola. Da notare
che la sola restrizione a questa regola sono le stringhe letterali, le quali non possono continuare su diverse
linee.
Non ci sono nemmeno regole prefissate per l'uso di spazi bianchi e istruzioni multi-riga, soltanto alcuni
consigli:
L'editor di Delphi ha una linea verticale posizionabile dopo 60 o 70 caratteri. Usando questa linea ed
avendo cura di non sorpassarla, il codice sorgente apparira' migliore una volta stampato, altrimenti,
in stampa, le linee lunghe possono essere spezzate in qualsiasi posizione, anche nel mezzo di una
parola.
Quando una funzione o procedura ha diversi parametri, e' pratica comune mettere i parametri su
piu' linee.
Si puo' lasciare una linea completamente bianca prima di un commento o per dividere un lungo
pezzo di codice. Anche questa semplice idea puo' aumentare la leggibilita' del codice, sia a video, sia
in stampa.
Usare spazi per separare i parametri di una chiamata a funzione e aggiungere anche uno spazio
prima dell'apertura delle parentesi. Tenere separati da spazi anche gli operandi di un espressione. So
che molti programmatori non saranno d'accordo con questa idea, ma io insisto: gli spazi sono
gratuiti, non si paga per il loro uso. (Ok, so che occupano spazio su disco e tempo di connessione
quando il file viene trasmesso, ma questo e' sempre meno rilevante al giorno d'oggi)
Pretty-Printing
L'ultimo suggerimento sull'uso degli spazi bianchi in relazione alla formattazione del codice Pascal e'
conosciuta come pretty-printing. La regola e' semplice: ogni volta che si deve scrivere un'istruzione
composta, rientrare di due spazi a destra. Un'istruzione composta nidificata in un'altra istruzione e' rientrata
di quattro spazi e cosi' via:
if ... then
statement;
if ... then
begin
statement1;
statement2;
end;
if ... then
begin
if ... then
statement1;
statement2;
end;
Nota: La formattazione sopra e' basata sulla pretty-printing, ma i programmatori danno differenti
interpretazioni a questa regola generale. Alcuni programmatori rientrano le istruzioni begin-end al livello del
codice interno, altri rientrano il begin-end ed anche il codice interno, altri infine mettono il begin sulla stessa
riga dell'if. Questa e' una questione solo di gusti personali.
Un simile formato rientrato, e' spesso usato per le liste di variabili o tipi di dato e per continuare
un'istruzione dalla precedente riga:
type
Letters = set of Char;
var
Name: string;
begin
{ long comment and long statement, going on in the
following line and indented two spaces }
MessageDlg ('This is a message',
mtInformation, [mbOk], 0);
Ovviamente queste convenzioni vuogliono solo essere un suggerimento per rendere il codice piu' leggibile
agli altri programmatori, visto che sono completamente ignorate dal compilatore. Ho cercato di usare queste
convenzioni in tutti gli esempi e frammenti di codice di questo libro. I sorgenti di Delphi, i manuali e gli
esempi inclusi nell'help in linea usano uno stile di formattazione simile.
Syntax Highlighting
Per rendere piu' facile la lettura e la scrittura del codice Pascal, l'editor di Delphi ha una funzionalita'
chiamata color syntax highlighting. In relazione al significato che assumono, le parole scritte nell'editor, sono
visualizzate usando differenti colori e stili. Di default le parole riservate sono in grassetto, le stringhe e i
commenti sono colorate (spesso in corsivo) e cosi' via.
Le parole riservate, i commenti e le stringhe sono probabilmente i tre elementi che beneficiano
maggiormente di questa funzionalita'. Si possono immediatamente vedere le keyword digitate non
correttamente, le stringhe non terminate e la lunghezza dei commenti multi-linea.
Si possono facilmente personalizzare i settaggi della colorazione della sintassi usando la pagina Editor Color
della finestra di dialogo Environment Options (vedere Figura 2.1). Se si lavora in gruppo con altri
programmatori bisogna optare per i settaggi di default. Trovo infatti che lavorare su un computer con un
diverso schema di colori e' veramente difficile.
array [0..] of ;
Siccome i code template predefiniti normalmente includono diverse versioni dello stesso costrutto, il testo
chiave termina con una lettera indicante la versione alla quale si e' interessati. Comunque si puo' scrivere
anche la parte iniziale del testo chiave. Per esempio se si scrive ar e si preme Ctrl-J, l'editor visualizzera' un
menu con una lista di scelte disponibili con una breve descrizione, come si puo' vedere in Figura 2.2.
Keyword
Le keyword sono identificatori riservati dell'Object Pascal, che hanno un ruolo nel linguaggio. L'help di Delphi
distingue tra parole riservate e direttive: le parole riservate non possono essere usate come identificatori,
mentre le direttive potrebbero, in teoria, essere usate come identificatore anche se in pratica e' meglio non
farlo.
Nella Tabella 2.1 si puo' vedere una lista completa di identificatori che hanno uno specifico ruolo in Object
Pascal (in Delphi 4), incluse parole chiave e parole riservate.
Tabella 2.1: Keywords e altre parole riservate nel linguaggio Object Pascal:
Keyword Ruolo
absolute directive (variables)
abstract directive (method)
and operator (boolean)
array type
as operator (RTTI)
asm statement
assembler backward compatibility (asm)
at statement (exceptions)
automated access specifier (class)
begin block marker
case statement
cdecl function calling convention
class type
const declaration or directive (parameters)
constructor special method
contains operator (set)
default directive (property)
destructor special method
dispid dispinterface specifier
dispinterface type
div operator
do statement
downto statement (for)
dynamic directive (method)
else statement (if or case)
end block marker
except statement (exceptions)
export backward compatibility (class)
exports declaration
external directive (functions)
far backward compatibility (class)
file type
finalization unit structure
finally statement (exceptions)
for statement
forward function directive
function declaration
goto statement
if statement
implementation unit structure
implements directive (property)
in operator (set) - project strucure
index directive (dipinterface)
inherited statement
initialization unit structure
inline backward compatibility (see asm)
interface type
is operator (RTTI)
label declaration
library program structure
message directive (method)
mod operator (math)
name directive (function)
near backward compatibility (class)
nil value
nodefault directive (property)
not operator (boolean)
object backward compatibility (class)
of statement (case)
on statement (exceptions)
or operator (boolean)
out directive (parameters)
overload function directive
override function directive
package program structure (package)
packed directive (record)
pascal function calling convention
private access specifier (class)
procedure declaration
program program structure
property declaration
protected access specifier (class)
public access specifier (class)
published access specifier (class)
raise statement (exceptions)
read property specifier
readonly dispatch interface specifier
record type
register function calling convention
reintroduce function directive
repeat statement
requires program structure (package)
resident directive (functions)
resourcestring type
safecall function calling convention
set type
shl operator (math)
shr operator (math)
stdcall function calling convention
stored directive (property)
string type
then statement (if)
threadvar declaration
to statement (for)
try statement (exceptions)
type declaration
unit unit structure
until statement
uses unit structure
var declaration
virtual directive (method)
while statement
with statement
write property specifier
writeonly dispatch interface specifier
xor operator (boolean)
Espressioni ed Operatori
Queste non sono regole generali per costruire espressioni, visto che principalmente dipendono dagli
operatori sui set, piu' diversi altri. Le espressioni possono essere usate per determinare il valore da
assegnare ad una variabile, per calcolare il parametro di una funzione o procedura, o per testare una
condizione. Le espressioni possono anche includere chiamate a funzioni. Ogni volta che si esegue
un'operazione sul valore di un identificatore, invece di usare l'identificatore stesso, questa e' un'espressione.
Le espressioni sono comuni a quasi tutti i linguaggi di programmazione. Un'espressione e' una qualsiasi
combinazione valida di costanti, variabili, valori letterali e risultati di funzione. Le espressioni possono anche
essere passate ai parametri valore di una procedura o funzione, ma mai ad un parametro passato per
riferimento (il quale richiede un valore al quale assegnarlo).
Operatori e Precedenza
Se si e' scritto almeno un programma, si sa che cosa e' un'espressione. Qui mettero' in evidenza elementi
specifici degli operatori Pascal. Si puo' vedere una lista di operatori raggruppati per precedenza (Tabella
2.1).
Nota: Contrariamente a diversi altri linguaggi di programmazione, gli operatori and e or hanno la precedenza
sugli operatori relazionali. Cosi' se si scrive a < b and c < d, il compilatore valutera' per prima l'operazione
and, generando quindi un errore. Per questa ragione bisogna racchiudere ogni espressione < in parentesi: (a
< b) and (c < d).
Diversi operatori hanno diversi significati a seconda dei tipi di dato. Per esempio l'operatore + puo' essere
usato per sommare due numeri, concatenare due stringhe, unire due insiemi e anche aggiungere un offset
ad un puntatore PChar. In Pascal pero', non si possono sommare due caratteri come in C.
Un altro strano operatore e' div. In Pascal, si puo' eseguire la divisione tra due numeri qualsiasi (reali o
interi) con l'operatore /, invariabilmente si otterra' un risultato reale. Se bisogna dividere due interi ed
ottenere un risultato di tipo integer, si deve usare l'operatore div.
Gli operatori sugli insiemi (set) includono l'unione (+), la differenza (-), l'intersezione (*), test sugli elementi
contenuti (in) piu' diversi altri operatori relazionali. Per aggiungere un elemento ad un insieme, si puo' fare
l'unione del set con un altro che contiene solo l'elemento che serve. Ecco un esempio in Delphi relativo ai
font:
Come alternativa, si possono usare le procedure standard Include e Exclude, che sono molto piu' efficienti
(ma non possono essere usate con le proprieta' di tipo set dei componenti, siccome richiedono un parametro
l-value):
Conclusioni
Adesso che si conosce lo schema di base di un programma Pascal, si e' pronti per comprenderne i dettagli.
Partiremo con l'esplorazione delle definizioni di tipi di dato predefiniti e creati dall'utente, continuando con
l'uso delle keyword per formare istruzioni nei programmi.
www.marcocantu.it
Marco's Delphi Books
Essential Pascal - Web Site
Il linguaggio Pascal originario era basato su pochi semplici concetti, i quali sono ora diventati abbastanza
comuni nei linguaggi di programmazione. Il primo e' il concetto di tipo di dato. Il tipo determina i valori che
una variabile puo' assumere e le operazioni che si possono eseguire su di essa. Il concetto di tipo e' piu'
forte in Pascal che in C, dove l'aritmetica dei tipi e' praticamente intercambiabile e piu' forte che nella
versione originale del BASIC, che non ha un simile concetto.
Variabili
Il Pascal richiede che tutte le variabili siano dichiarate prima del loro effettivo uso. Ogni volta che si dichiara
una variabile, bisogna specificarne il tipo. Ecco alcuni esempi di dichiarazioni di variabili:
var
Value: Integer;
IsCorrect: Boolean;
A, B: Char;
La keyword var puo' essere usata in diversi punti del codice, come all'inizio del codice di una funzione o
procedura, per dichiarare variabili locali alla routine, o in una unit per dichiarare variabili locali. Dopo la
keyword var segue una lista di nomi di variabile, seguite da un due punti (:) e dal nome del tipo. Si puo'
scrivere piu' di un nome di varabile su una singola riga, come nell'esempio sopra.
Una volta definita una variabile di un certo tipo, si possono eseguire su di essa soltanto le operazioni
supportate dal tipo di dato. Ad esempio, si puo' usare un valore booleano in un test ed un valore intero in
un'espressione numerica. Non si possono mischiare booleani e interi (come permesso in C).
Value := 10;
IsCorrect := True;
La prossima istruzione non e' corretta, poiche' le due variabili hanno differenti tipi di dato:
Value := IsCorrect; // error
Se si prova a compilare questo codice, Delphi genera un errore di compilazione con questa descrizione:
Incompatible types: 'Integer' and 'Boolean'. Di solito, errori come questo sono errori di programmazione,
siccome non ha senso assegnare ad un intero un valore booleano (true o false). Non si puo' incolpare Delphi
per questo errore. Delphi avvisa solo che c'e' qualcosa di sbagliato nel codice.
Certamente e' spesso possibile convertire il valore di una variabile da un tipo all'altro. In diversi casi questa
conversione e' automatica ma spesso bisogna invocare una specifica funzione di sistema che cambia la
rappresentazione interna dei dati.
In Delphi si puo' assegnare un valore iniziale ad una variabile globale mentre si dichiara. Ad esempio, si puo'
scrivere:
var
Value: Integer = 10;
Correct: Boolean = True;
Questa tecnica di inizializzazione funziona solo per le variabili globali, non per le variabili dichiarate all'interno
di procedure o metodi.
Costanti
Il Pascal permette anche la dichiarazione di costanti per dare il nome a valori che non cambiano durante
l'esecuzione del programma. Per dichiarare una costante non serve specificare il tipo di dato, ma solo
assegnare un valore iniziale. Il compilatore guardera' il valore ed automaticamente usera' il tipo di dato
appropriato. Ecco alcuni esempi:
const
Thousand = 1000;
Pi = 3.14;
AuthorName = 'Marco Cantù';
Delphi determina il tipo basandosi sul valore. Nell'esempio sopra, la costante Thousand e' di tipo SmallInt, il
tipo piu' piccolo che puo' contenerla. Se si vuole che Delphi assegni uno specifico tipo, si aggiunge
semplicemente il nome del tipo nella dichiarazione, come nel seguente esempio:
const
Thousand: Integer = 1000;
Quando si dichiara una costante il compilatore puo' scegliere se assegnare una locazione di memoria alla
costante e salvarne il valore oppure duplicare il valore attuale ogni volta che la costante e' usata. Questo
secondo approccio ha senso particolarmente per costanti semplici.
Nota: La versione a 16 bit di Delphi permette di cambiare il valore di una costante tipizzata a run-time, come
se fosse una variabile. La versione a 32 bit permette ancora questo comportamento per compatibilita'
quando si abilita la direttiva $J o si spunta il checkbox Assignable typed constants nella pagina Compiler
della finestra di dialogo Project Options. Sebbene questo sia il default, e' decisamente sconsigliato l'uso di
questo trucchetto come tecnica di programmazione. Assegnare un nuovo valore ad una costante disabilita
tutte le ottimizzazioni sulle costanti. In casi come questo, dichiarare invece una variabile.
resourcestring
AuthorName = 'Marco Cantù';
In entrambi i casi si sta' definendo una costante, che e' un valore che non cambia durante l'esecuzione del
programma. La differenza e' solo nell'implementazione. Una costante di tipo stringa definita con la direttiva
resourcestring e' memorizzata nelle risorse del programma nella tabella delle stringhe.
Per vedere questa particolarita' in azione, si puo' guardare l'esempio ResStr, che ha un pulsante con il
seguente codice:
resourcestring
AuthorName = 'Marco Cantù';
BookName = 'Essential Pascal';
L'output delle due stringhe e' su due righe a perche le stringhe sono separate dal carattere newline (indicato
dal suo valore numerico nella costante carattere #13).
L'aspetto interessante di questo programma e' che se lo si esamina con un Resource Explorer (se ne trova
uno negli esempi forniti con Delphi) si possono notare le stringhe nelle risorse. Cio' vuol dire che le stringhe
non fanno parte del codice compilato ma sono memorizzate in un'area separata del file eseguibile (il file
EXE).
Nota: In breve i vantaggi delle risorse sono: un'efficiente gestione della memoria da parte di Windows e la
possibilita' di tradurre un programma senza dover modificare il codice sorgente.
Tipi di dato
In Pascal ci sono diversi tipi di dato predefiniti, che possono essere divisi intre gruppi: tipi ordinali, tipi reali e
stringhe. I tipi ordinali e reali verranno discussi nella seguente sezione, mentre le stringhe saranno discusse
piu' avanti in questo capitolo. In questa sezione introdurro' alcuni tipi definiti nelle librerie di Delphi (quindi
non predefiniti dal compilatore). Questi tipi possono essere considerati predefiniti.
Delphi include anche tipi di dato non tipizzati, chiamati variant, non discussi in questo libro (vedere il capitolo
2 di Mastering Delphi 4). Stranamente un variant non ha controllo sul tipo (type-checking). I variant furono
introdotti in Delphi 2 per gestire OLE Automation.
Tipi Ordinali
I tipi ordinali sono basati sul concetto di ordine o sequenza. Non solo si possono comparare due valori per
vedere il piu' alto, ma si puo' anche richiedere il valore precedente o successivo di un dato valore o calcolare
il piu' piccolo o il piu' grande valore possibile.
I tre piu' importanti tipi ordinali predefiniti sono: Integer, Boolean e Char (caratteri). Comunque ci sono
diversi altri tipi che hanno uno stesso stesso significato, ma una differente rappresentazione interna e
intervallo di valori. La seguente Tabella 3.1 elenca i tipi ordinali per rappresentare i numeri.
Tabella 3.1: Tipi Ordinali per rappresentare i numeri
ShortInt Byte
8 bits
-128 to 127 0 to 255
SmallInt Word
16 bits
-32768 to 32767 0 to 65,535
64 bits Int64
Come si puo' vedere, questi tipi corrispondono a differenti rappresentazioni di numeri e differiscono dal
numero di bit usati per esprimere il valore e dalla presenza o assenza del bit del segno. I valori con segno
possono essere positivi o negativi, ma hanno un intervallo piu' piccolo di valori, siccome e' per i valore e'
disponibile un bit in meno. Ci si puo' riferire all'esempio Range, discusso nella prossima sezione, per
l'intervallo di valori di ogni tipo.
L'ultimo gruppo (segnato come 16/32) indica valori che hanno una diversa rappresentazione nelle versioni
16 e 32 bit di Delphi. Inetger e Cardinal sono usati di frequente siccome corrispondono alla rappresentazione
nativa dei numeri nella CPU.
In Delphi 3, i numeri a 32 bit senza segno indicati dal tipo Cardinal sono in realta' valori a 31 bit, con un
intervallo fino a 2 megabyte. Delphi 4 introduce un nuovo tipo numerico senza segno, LongWord, il quale
usa un valore a 32 bit che arriva fino a 4 megabyte. Il tipo Cardinal e' adesso solo un alias al nuovo tipo. Il
tipo LongWord permette di indirizzare piu' di 2MB di dati come numero senza segno. Oltretutto corrisponde
alla rappresentazione nativa della CPU.
Un altro nuovo tipo introdotto da Delphi 4 e' il tipo Int64, il quale rappresenta numeri interi fino a 18 cifre.
Questo nuovo tipo e' completamente supportato da diverse routine di tipo ordinale (come le funzioni Low e
High), le routine numeriche (come Inc e Dec) e le routine di conversione delle stringhe. Per la conversione
opposta (da stringa a numero), ci sono due nuove specifiche funzioni: StrToInt64 e StrToInt64Def.
Boolean
I valori booleani al di fuori del tipo Boolean sono raramente usati. Alcuni valori booleani con una specifica
rappresentazione sono richiesti dalle funzioni API di Windows. I tipi sono ByteBool, WordBool e LongBool.
In Delphi 3 per compatibilita' con Visual Basic e OLE Automation, i tipi ByteBool, WordBool e LongBool sono
stati modificati per rappresentare il valore True con -1, mentre il valore False e' ancora 0. Il tipo Boolean e'
rimasto invariato (True e' 1, False e' 0). Portare codice Delphi 2 contenente typecast espliciti alle successive
versioni di Delphi puo' portare quindi degli errori.
Caratteri
Ci sono 2 differenti rappresentazioni per i caratteri: ANSIChar e WideChar. Il primo tipo rappresenta i
caratteri a 8 bit, che corrispondono al set di caratteri ANSI tradizionalmente usati da Windows, il secondo
tipo rappresenta caratteri a 16 bit, che corrispondono ai nuovi caratteri Unicode supportati da Windows NT e
solo parzialmente da Windows 95 e 98. Solitamente si usera' il tipo Char, che in Delphi 3 corrisponde a
ANSIChar. Ricordarsi, tuttavia, che i primi 256 caratteri Unicode corrispondono esattamente ai caratteri
ANSI.
Le costanti carattere possono essere rappresentate con la loro notazione simbolica, come in 'k', o con una
notazione numerica, come in #78. L'ultima puo' anche essere scritta usando la funzione Ord, come in
Ord(78).
In genere e' meglio usare la notazione simbolica per indicare lettere, numeri o simboli. Quando si usano
caratteri speciali, invece, e' meglio usare la notazione numerica. La lista che segue elenca diversi caratteri
speciali comunemente usati:
#9 tabulator
#10 newline
#13 carriage return (enter key)
L'esempio Range
Per dare un'idea dei diversi intervalli dei tipi ordinali, ho scritto un esempio in Delphi chiamato Range. I
risultati sono mostrati in Figura 3.1.
Figura 3.1: L'esempio Range mostra informazioni riguardo i tipi ordinali (in questo caso interi).
Il programma Range e' basato su un semplice form, il quale ha 6 pulsanti (ognuno chiamato con il nome del
tipo) e alcune label per le categorie d'informazione, come si puo' vedere in Figura 3.1. Alcune label
contengono testo statico, le altre mostrano le informazioni riguardo al tipo di dato ogni volta che preme un
pulsante.
Ogni volta che un pulsante viene premuto, il programma aggiorna le etichette con i dati opportuni. Le
informazioni mostrate sono: il tipo di dato, il numero di bytes usati e il massimo ed il minimo valore che il
particolare tipo puo' contenere. Ogni pulsante ha la propria risposta all'evento OnClick visto che il codice
usato per calcolare i tre valori e' leggermente diverso da pulsante a pulsante. Ad esempio, ecco il sorgente
dell'evento OnClick del pulsante Integer (BtnInteger):
Se si ha esperienza di programmazione Delphi, si puo' esaminare il codice sorgente del programma per
capire come funziona. Per i principianti, basta sottolineare l'uso di tre funzioni: SizeOf, High, e Low. I risultati
delle ultime due funzioni sono ordinali dello stesso tipo (in questo caso interi) e il risultato della funzione
SizeOf e' ancora un intero. Il valore di ritorno di ognuna di queste funzioni e' prima convertito in stringhe
usando la funzione IntToStr, quindi copiato nella proprieta' Caption delle tre label.
I metodi associati con gli altri pulsanti sono simili a quello visto sopra. La sola differenza significativa e' nel
tipo di dato passato come parametro alle varie funzioni. La Figura 3.2 mostra i risultati dell'esecuzione di
questo programma sotto Windows 95 dopo essere stato compilato con la versione a 16 bit di Delphi.
Paragonando la Figura 3.1 con la Figura 3.2, si possono notare le differenze tra il tipo Integer a 16 bit e a 32
bit.
Figura 3.2: L'output della versione a 16 bit dell'esempio Range, che mostra informazioni sul
tipo Integer.
La dimensione del tipo Integer dipende dalla CPU e dal sistema operativo che si sta usando. In Windows a
16-bit, una variabile intera occupa due byte. In Windows a 32-bit, un intero occupa invece quattro byte. Per
questa ragione, quando si ricompila l'esempio Range, si ottengono differenti risultati.
Le due differenti rappresentazioni del tipo Integer non sono un problema, fino a che il programma non si
basa direttamente sulla dimensione degli interi. Se capita di salvare un intero in un file usando una versione
e caricarlo usando l'altra versione, si avra' percio' lo stesso problema. In questo caso, bisogna scegliere un
tipo di dato indipendente dalla piattaforma (come ad esempio il LongInt o lo SmallInt). Per i calcoli
matematici o codice generico, e' meglio lavorare integralmente con la rappresentazione standard per la
piattaforma in uso, ovvero usare il tipo Integer visto che e' il tipo che la CPU tratta in modo nativo. Il tipo
Integer e' la scelta piu' ovvia quando si manipolano numeri. Usare quindi una differente rappresentazione
solo quando c'e' una ragione per fare cio'.
Ci sono diverse routine di sistema (routine definite nel linguaggio Pascal e nelle unit di sistema di Delphi) che
lavorano su tipi ordinali. Esse sono mostrate nella tabella 3.2. I programmatori C++ noteranno che le due
versioni della procedura , con uno o due parametri, corrispondono agli operatori ++ e += (la stessa cosa
vale per la procedura Dec).
Routine Scopo
Decrementa la variabile passata come parametro, di uno o del valore del secondo parametro
Dec
opzionale.
Incrementa la variabile passata come parametro, di uno o del valore del secondo parametro
Inc
opzionale.
Odd Ritorna True se l'argomento e' un numero dispari.
Pred Ritorna il valore precedente dell'argomento nell'ordine determinato dal tipo di dato.
Succ Ritorna il valore successivo dell'argomento.
Ord Ritorna un numero indicante l'ordine che l'argomento assume nell'insieme di valori del tipo di dato.
Low Ritorna il minimo valore nell'intervallo del tipo ordinale passato come parametro.
High Ritorna il massimo valore nell'intervallo del tipo ordinale passato come parametro.
Notare che alcune di queste routine, quando applicatye su costanti, sono automaticamente valutate dal
compilatore e rimpiazzate dal loro valore. Ad esempio se si scrive High(X) dove X e' difinito come intero, il
compilatore puo' semplicemente sostituire l'espressione con il valore piu' alto del tipo Integer.
Tipi reali
I tipi reali rappresentano i numeri in virgola mobile (floating-point) in diversi formati. L'occupazione in
memoria piu' piccola e' data dai numeri Single, che occupano 4 bytes. Poi ci sono i Double numeri in virgola
mobile, che occupano 8 bytes e gli Extended, che occupano 10 bytes. Questi, sono tutti numeri floating-
point con differente precisione, che corrispondono alla rappresentazione standard IEEE e sono direttamente
supportati dal coprocessore numerico della CPU (FPU), per la massima velocita' di esecuzione.
In Delphi 2 e Delphi 3 il tipo Real ha la stessa definizione della versione di Delphi a 16-bit, era un tipo a 48-
bit. L'uso del tipo Real e' sconsigliato da Borland, che suggerisce di usare invece i tipi Single ed Extended. La
ragione e' che il vecchio formato a 6 bytes non e' supportato dalle CPU Intel e nemmeno elencato nei tipi
reali IEEE.
Oltre ad avere il vantaggio di usare una definizione standard, questo cambio permette anche ai componenti
di esporre proprieta' basate sui tipi reali, Delphi 3 non lo permetteva. Tra gli svantaggi ci possono essere
problemi di compatibilita'. Se necessario, si puo' superare la possibilita' di incompatibilita' restando con la
definizione del tipo di Delphi 2 e 3, per fare cio' usare la seguente opzione di compilazione:
{$REALCOMPATIBILITY ON}
Ci sono anche due strani tipi: il tipo Comp descrive interi di grosse dimensioni usando 8 bytes (possono
contenere numeri di 18 cifre) e il tipo Currency (non disponibile nella versione a 16-bit di Delphi) che indica
valori decimali a virgola fissa con 4 cifre decimali e la stessa rappresentazione a 64-bit del tipo Comp. Come
suggerisce il nome, il tipo Currency e' stato aggiunto per gestire valori monetari estremamente precisi, con 4
posizioni decimali.
Non si puo' creare un programma simile all'esempio Range per i tipi reali, siccome non si possono usare le
funzioni High e Low o la funzione Ord sulle variabili di tipo reale. I tipi reali rappresentano (in teoria) un
insieme infinito di numeri, i tipi ordinali rappresentano un insieme finito di valori.
Nota: Spiego meglio la cosa. Quando si ha il numero intero 23 si puo' determinare con precisione quale e' il
valore successivo. Gli interi sono finiti (hanno cioe' un intervallo definito e anche un ordine). I numeri in
virgola mobile (appunto i numeri reali) sono infiniti anche su di un piccolo intervallo e non hanno un ordine:
ad esempio, quanti valori ci sono tra 23 e 24? E che numero segue 23.46? Forse 23.47, 23.461 o 23.4601?
E' davvero difficile saperlo!
Per questa ragione, ha senso chiedere la posizione ordinale del carattere w nell'intervallo del tipo Char, ma
non ha nessun senso chiedere la stessa cosa per il numero 7143.1562 nell'intervallo di valori dei tipi reali.
Tuttavia si puo' sapere se un numero reale ha un valore piu' grande di un altro numero, ma non ha senso
chiedere quanti numeri esistono prima di un dato valore (questo e' il significato della funzione Ord).
I tipi reali hanno un ruolo limitato nella parte dell'interfaccia utente (dal lato Windows), ma essi sono
pienamente supportati in Delphi, incluso la parte database. Il supporto dello standard IEEE per i tipi in
virgola mobile rende l'Object Pascal assolutamente appropriato per una vasta scelta di programmi che
richiedono calcoli numerici. Se si e' interessati a questo aspetto, si puo' guardare la unit system a proposito
delle funzioni aritmetiche (vedere l'help di Delphi per maggiori dettagli)
Nota: Delphi ha anche una unit Math che definisce routine matematiche avanzate: funzioni trigonometriche
(come la funzione ArcCosh), funzioni finanziarie (come la funzione InterestPayment) e funzioni statistiche
(come la procedura MeanAndStdDev). Ci sono diverse routine, alcune delle quali mi risultano abbastanza
strane, come la procedura MomentSkewKurtosis.
Data e Ora
Delphi usa i tipi reali per gestire le informazioni sulla data e l'ora. Per essere piu' precisi, Delphi definisce uno
specifico tipo, il TDateTime. Questo e' un tipo floating-point, visto che deve essere grande abbastanza per
memorizzare anni, mesi, giorni, minuti e secondi fino ad una risoluzione dei millisecondi in una singola
variabile. Le date sono memorizzate in numeri di giorni dal 30/12/1899 (i valori negativi indicano le date
prima del 1899) nella parte intera del valore TDateTime. L'ora e' memorizzata nella parte decimale del
valore.
TDateTime non e' un tipo predefinito che il compilatore riconosce, ma e' definito nella unit system come:
type
TDateTime = type Double;
Usare il tipo TDateTime e' abbastanza semplice, visto che Delphi mette a disposizione diverse funzioni per
operare con questo tipo. Si puo' trovare un elenco di queste funzioni in Tabella 3.3.
Routine Descrizione
Now Ritorna la data e l'ora corrente in un singolo valore di tipo TDateTime.
Date Ritorna solamente la data corrente.
Time Ritorna solamente l'ora corrente.
Converte una data e ora in una stringa, usando la formattazione di default, per avere
DateTimeToStr
maggior controllo sulla conversione usare invece la funzione FormatDateTime.
DateTimeToString Copia la data e l'ora in un buffer di tipo stringa, con la formattazione di default.
DateToStr Converte la parte data di una valore TDateTime in una stringa.
TimeToStr Converte la parte ora di una valore TDateTime in una stringa.
Formatta la data e l'ora usando uno specifico formato, si puo' specificare quale valori
FormatDateTime
vedere e quale formato usare, fornendo una stringa formattata dettagliatamente.
Converte una stringa con informazioni di data e ora in un valore TDateTime, sollevando
StrToDateTime
un'eccezione nel caso di un errore nel formato della stringa.
StrToDate Converte una stringa con informazioni sulla data in un valore TDateTime.
StrToTime Converte una stringa con informazioni sull'ora in un valore TDateTime.
Ritorna il numero corrispondente al giorno della settimana di un avlore TDateTime
DayOfWeek
passato come parametro.
DecodeDate Ritorna l'anno, il mese ed il giorno di un valore TDateTime.
DecodeTime Ritorna l'ora di un valore TDateTime.
EncodeDate Converte anno, mese, giorno in un valore TDateTime.
EncodeTime Converte ore, minuti, secondi, millisecondi in un valore TDateTime.
Per mostrare come usare questo tipo di dato e le funzioni ad esso correlate, ho creato un semplice esempio,
chiamato TimeNow. Il form principale di qesto esempio ha un pulsante e un listbox. Quando il programma
parte, automaticamente calcola e visualizza la data e l'ora corrente. Ogni volta che il pulsante e' premuto, il
programma mostra il tempo passato da quando il programma e' partito.
La prima istruzione e' la chiamata alla funzione Now, la quale ritorna la data e l'ora corrente. Questo valore
e' memorizzato nella variabile StartTime, dichiarata come variabile globale:
var
FormTimeNow: TFormTimeNow;
StartTime: TDateTime;
Ho aggiunto solo la seconda dichiarazione, siccome la prima e' opera di Delphi. Di solito, e' la seguente:
var
Form1: TForm1;
Cambiando il nome del form, questa dichiarazione e' automaticamente aggiornata. Usando una variabile
globale non e' davvero il miglior approccio: sarebbe meglio usare un campo privato della classe del form, un
argomento correlato alla programmazione orientata agli oggetti e discussa in Mastering Delphi 4.
Le prossime tre istruzioni aggiungono tre elementi al listbox sul lato sinistro del form, con il risultato che si
puo' vedere in Figura 3.3. La prima linea contiene la parte dell'ora del valore TDateTime convertita in
stringa, la seconda linea contine la prte della data dello stesso valore. Alla fine il codice aggiunge una
semplice nota.
Questa terza stringa e' sostituita dal programma quando l'utente preme il pulsante Elapsed:
procedure TFormTimeNow.ButtonElapsedClick(Sender: TObject);
var
StopTime: TDateTime;
begin
StopTime := Now;
ListBox1.Items [2] := FormatDateTime ('hh:nn:ss',
StopTime - StartTime);
end;
Questo codice recupera il nuovo valore dell'ora corrente e calcola la differenza con il valore memorizzato
nella variabile quando il programma parte. Siccome si deve usare un valore che si calcola in un evento
differente, bisogna immagazzinarlo in una variabile globale. Ci sono in realta' alternative migliori, basate
sulle classi.
Nota: Il codice che sostituisce il valore della terza stringa usa l'indice 2. La ragione di questo e' che gli
elementi di un listbox partono da zero: il primo quindi e' il numero 0, il secondo il numero 1, il terzo il
numero 2.
Oltre alle funzioni TimeToStr e DateToStr si possono usare la piu' potente funzione FormatDateTime, come
ho fatto nell'ultimo motodo sopra (guardare l'help di Delphi per maggiori dettagli sui parametri di
formattazione). Notare anche che i valori di data e ora sono trasformati in stringhe seguendo i settaggi
internazionali di Windows. Delphi legge questi valori dal sistema e li copia in costanti globali dichiarate nella
unit SysUtils. Ecco alcune di queste costanti:
DateSeparator: Char;
ShortDateFormat: string;
LongDateFormat: string;
TimeSeparator: Char;
TimeAMString: string;
TimePMString: string;
ShortTimeFormat: string;
LongTimeFormat: string;
ShortMonthNames: array [1..12] of string;
LongMonthNames: array [1..12] of string;
ShortDayNames: array [1..7] of string;
LongDayNames: array [1..7] of string;
Diverse costanti sono relative alla formattazione delle valute e dei numeri floating-point. Si puo' trovare la
lista completa nell'help di Delphi cercando l'argomento Currency and date/time formatting variables.
Nota: Delphi include un componente DateTimePicker, il quale fornisce un metodo sofisticato per l'input delle
date, selezionabili da un calendario.
Tra i tipi di Windows, il piu' importante e' rappresentato dal tipo handle. Il nome di questo tipo e' THandle e
il tipo e' definito nella unit Windows come:
type
THandle = Integer;
Il tipo Handle e' implementato come numero, ma non sono usati come tali. In Windows, un handle e' un
riferimento ad una struttura dati interna al sistema. Ad esempio, quando si lavora con le finestre (o un form
di Delphi), il sistema fornisce un handle a questa finestra. Il sistema informa che la finestra in cui si sta
lavorando e' la finestra numero 142, ad esempio. Da qui in poi, l'applicazione puo' chiedere al sistema di
operare sulla finestra 142 per muoverla, ridimensionarla, ridurla ad icona e cosi' via. Diverse funzioni API di
Windows, di fatto, hanno un handle come primo parametro. Questo non vale solo per le funzioni operanti su
finestre: altre funzioni API di Windows hanno come primo parametro un GDI handle, un menu handle, un
instance handle, o uno degli altri numerosi tipi di handle.
In altre parole, un handle e' un codice interno che si puo' usare per riferirsi ad uno specifico elemento
gestito dal sistema, icluso finestre, bitmap, icone, blocchi di memoria, font, e cosi' via. In Delphi, raramente
serve usare un handle direttamente, siccome essi sono nascosti all'interno dei for, dei bitmap e altri oggetti
di Delphi. Essi diventano utili quando bisogna chiamare una funzione API di Windows non supportata da
Delphi.
Nota: La dimensione del tipo handle, varia nelle versioni a 16-bit e 32-bit di Windows. La stessa cosa vale
per le versioni a 16-bit e a 32-bit di Delphi. Se non vengono adottate le opportune cautele, si puo' incorrere
in problemi di compatibilita' quando si passa l'applicazione tra le due piattaforme. Nella maggior parte dei
casi, comunque, la dimensione degli handle non e' un problema.
var
N: Integer;
C: Char;
B: Boolean;
begin
N := Integer ('X');
C := Char (N);
B := Boolean (0);
Si puo' effettuare il typecast tra tipi che hanno la stessa dimensione. Di solito e' sicuro il typecast tra tipi
ordinali, o tra tipi reali ma si puo' effettuare il typecast anche tra puntatori (e quindi anche oggetti) purche'
si sappia quel che si sta facendo.
Il typecasting, e' generalmente una tecnica di programmazione pericolosa, siccome permette di accedere un
valore anche se rappresenta qualcosa d'altro. Siccome la rappresentazione interna dei tipi di dato
generalmente non corrisponde, il rischio in cui si incorre e' un errore difficilmente rintracciabile. Per questa
ragione, bisogna in genere evitare il typecasting.
La seconda scelta e' di usare le routine di conversione dei tipi. Le routine per i vari tipi di conversioni sono
elencate nella Tabella 3.4. Alcune di queste routine lavorano sui tipi che verranno discussi nelle sezioni
seguenti. Natare che la tabella non include le routine per i tipi speciali (come i TDateTime o i Variant) o
routine specifiche per la formattazione, come le potenti funzioni Format e FormatFloat.
Routine Descrizione
Chr Converte un numero ordinale in un carattere ANSI.
Ord Converte un valore di tipo ordinale nel numero indicante la sua posizione.
Round Converte un valore di tipo reale in un valore di tipo intero, arrotondando il valore.
Trunc Converte un valore di tipo reale in un valore di tipo intero, troncando il valore.
Int Ritorna la parte intera del valore floating-point passato come argomento.
IntToStr Converte un numero in una stringa.
IntToHex Converte un numero in una stringa con la sua rappresentazione esadecimale.
Converte una stringa in un numero, sollevando un'eccezione se la stringa non rappresenta
StrToInt
un intero valido.
Converte una stringa in un numero, usando un valore di default se la stringa non e'
StrToIntDef
corretta.
Val Converte una stringa in un numero (routine del TurboPascal, mantenuta per compatibilita').
Converte un numero in una stringa, usando parametri di formattazione (routine del
Str
TurboPascal, mantenuta per compatibilita').
Converte una stringa null-terminated in una stringa Pascal. Questa conversione e'
StrPas automatica per il tipo AnsiString nelle versioni a 32-bit di Delphi. (Vedere la sezione sulle
stringhe in questo capitolo)
Copia una stringa Pascal in una stringa null-terminated. Questa conversione e' effettuata
StrPCopy con un semplice typecast su PChar in Delphi a 32-bit. (Vedere la sezione sulle stringhe in
questo capitolo)
StrPLCopy Copia una porzione di una stringa Pascal in una stringa null-terminated.
Converte un valore floating-point in un record includendo la sua rappresentazione decimale
FloatToDecimal
(esponente, cifre, segno).
Converte un valore floating-point nella sua rappresentazione in stringa usando la
FloatToStr
formattazione di default.
Converte un valore floating-point nella sua rappresentazione in stringa usando la
FloatToStrF
formattazione specificata.
Copia un valore floating-point in un buffer di tipo stringa, usando al formattazione
FloatToText
specificata.
Come la precedente routine, copia un valore floating-point in un buffer di tipo stringa,
FloatToTextFmt
usando la formattazione specificata
StrToFloat Converte una stringa Pascal in un valore floating-point.
TextToFloat Converte una stringa null-terminated in un valore floating-point.
Conclusioni
In questo capitolo si sono viste le nozioni di base dei tipi in Pascal. Il linguaggio, pero', ha un'altra
importantissima caratteristicha: permette ai programmatori di definire nuovi tipi do dato, chiamati tipi di dato
definiti dall'utente. Questo e' l'argomento del prossimo capitolo.
Insieme alla nozione di tipo, una delle grandi idee introdotte dal linguaggio Pascal e' l'abilita' di definire nuovi
tipi di dati in un programma. I programmatori possono definire i loro tipi di dati per mezzo dei type
constructor, sono un esempio i tipi subrange, i tipi array, i tipi enumerativi, i tipi puntatore ed i tipi set. Il piu'
importante tipo di dato definito dall'utente e' la classe, la quale e' parte delle estensione orientate agli
oggetti dell'Object Pascal, non descritta in questo libro.
E' giusto pensare che i type contructor sono comuni a diversi linguaggi di programmazione, ma il Pascal e'
stato il primo linguaggio ad introdurre l'idea in modo formale e molto preciso. Esistono solo pochi linguaggi
con cosi' tanti meccanismi per definire nuovi tipi di dato.
type
// subrange definition
Uppercase = 'A'..'Z';
// array definition
Temperatures = array [1..24] of Integer;
// record definition
Date = record
Month: Byte;
Day: Byte;
Year: Integer;
end;
// set definition
Letters = set of Char;
Simili definizioni di tipo possono essere usate direttamente per definire una variabile senza un esplicito nome
di tipo, come nel seguente codice:
var
DecemberTemperature: array [1..31] of Byte;
ColorCode: array [Red..Violet] of Word;
Palette: set of Colors;
Nota: In generale, bisogna evitare di usare i tipi unnamed come nel codice sopra, visto che non si puo'
passarli come parametri alle routine o dichiarare altre variabili dello stesso tipo. Le regole di compatibilita' di
tipo del Pascal sono basate di fatto sui nomi del tipo, non sulla definizione corrente del tipo. Due variabili di
due identici tipi non sono ancora compatibili, a meno che i loro tipi abbiano esattamente lo stesso nome, e ai
tipi unnamed viene attribuito un nome interno dal compilatore. Bisogna abituarsi a definire un tipo di dato
ogni volta che si ha bisogno di una variabile complessa, e sicuramente non ci si pentira' del tempo speso.
Ma cosa vogliono dire queste definizioni ? Daro' alcune definizioni per quelli che non hanno familiarita' con le
dichiarazioni di tipo del Pascal. Tentero' anche di sottolineare le differenze dagli stessi costrutti negli in altri
linguaggi di programmazione, cosi' la lettura di questa sezione sara' interessante anche per chi ha familiarita'
con le definizioni mostrate sopra. Finalmente mostrero' alcuni esempi in Delphi ed introdurro' alcuni
strumenti che permettaranno l'accesso alle informazioni sul tipo in modo dinamico.
Tipi Subrange
Un tipo subrange definisce un intervallo di valori entro un intervallo di un altro tipo(da qui il nome di
subrange)). Si puo' definire un subrange del tipo Integer, da 1 a 10 o da 100 a 1000, oppuresi puo'
definireun subrange del tipo Char, come in:
type
Ten = 1..10;
OverHundred = 100..1000;
Uppercase = 'A'..'Z';
Nella definizione di un subange, non serve specificare il nome del tipo di base. Bisogna solo specificare due
costanti di questo tipo. Il tipo originale deve essere un tipo ordinale e il tipo risultante deve assere ancora un
tipo ordinale.
Una volta definito un subrange, si puo' legalmente assegnare ad esso un valore compreso in questo
intervallo. Il codice seguente pertanto risulta valido:
var
UppLetter: UpperCase;
begin
UppLetter := 'F';
var
UppLetter: UpperCase;
begin
UppLetter := 'e'; // compile-time error
Il codice sopra produrra' un errore di run-time, "Constant expression violates subrange bounds." Se si scrive
il seguente codice invece:
var
UppLetter: Uppercase;
Letter: Char;
begin
Letter :='e';
UppLetter := Letter;
Delphi lo compilera'. A run-time, se e' stato abilitato l'opzione del compilatore Range Checking (nella pagina
Compiler della finestra Project Options), si otterra' questo messaggio d'errore: Range check error.
Nota: Suggerisco di attivare questa opzione mentre si sta sviluppando un programma, cosi' sara' piu'
robusto e piu' facile il debug, visto nel caso di errori si si otterra' uno specifico messaggio e non un
comportamento anomalo del programma. Eventualmente si puo' disabilitare questa opzione per la
compilazione finale del programma, per generarlo piu' veloce e compatto. Comunque la differenza e' davvero
limitata, e per questo suggerisco di lasciare tutte questa opzioni di controllo a run-time abilitate, anche nel
programma finito. La stessa cosa vale per le altre opzioni di controllo a run-time tipo il controllo di overflow e
dello stack.
Tipi Enumerativi
I tipi enumerativi costituiscono un altro tipo ordinale definibile dall'utente. Invece di indicare un intervallo di
un tipo esistente, in un'enumerazione bisogna elencare tutti i possibili valori del tipo. In altre parole,
un'enumerazione e' un elenco di valori. Ecco alcuni esempi:
type
Colors = (Red, Yellow, Green, Cyan, Blue, Violet);
Suit = (Club, Diamond, Heart, Spade);
Ogni valore nell'elenco ha una ardinalita' associata che parte da zero. Quando si applica la funzione Ord ad
un valore di un tipo enumerativo, si ottiene questo valore zero-based. Per esempio Ord (Diamonds) ritorna
1.
Nota: I tipi enumerativi possono avere differenti rappresentazioni interne. Per default, Delphi usa una
rappresentazione a 8 bit, a meno che non vi siano piu' di 256 valori, nel qual caso viene usata una
rappresentazione a 6 bit. Esiste anche una rappresentazione a 32 bit, la quale puo' essere utile per
compatibilita' con le librerie C o C++. Attualmente si puo' comunque cambiare questo comportamento di
default, domandando una rappresentazione maggiore usando la direttiva di compilatore $Z.
La VCL (Visual Component Library) di Delphi usa i tipi enumerativi in deversi punti. Per esempio, gli stili del
bordo di un form sono definiti come segue:
type
TFormBorderStyle = (bsNone, bsSingle, bsSizeable,
bsDialog, bsSizeToolWin, bsToolWindow);
Quando il valore di una proprieta' e' un'enumerazione, si puo' scegliere da una lista di valori visualizzata nell
Object Inspector, come visibile in Figura 4.1.
L'help file di Delphi generalmente elenca i possibili valori di un'enumerazione. Come alternativa si puo' usare
il programma OrdType, disponibile su www.marcocantu.com, per vedere la lista dei valori di ogni
enumerazione, set, subrange e ogni altro tipo ordinale di Delphi. Si puo' vedere un esempio dell'output di
questo programma in Figura 4.2.
Figura 4.2: Informazioni dettagliate riguardo i tipi enumerativi, come sono mostrati dal
programma.
Tipi Set
I tipi set indicano un gruppo di valori, dove la lista dei valori disponibili e' indicata dal tipo ordinale su cui il
tipo set e' basato. Questi tipi ordinali sono usualmente limitati, e abbastanza spesso rappresentati da
un'enumerazione o un subrange. Se si prende il subrange 1..3, i possibili valori del set basato su esso
includono solo 1, solo 2, solo 3, sia 1 che 3, sia 2 che 3, tutti i tre valori, o nessuno di essi.
Una variabile di solito contiene uno dei possibili valori dell'intervallo di questo tipo. Una variabile di tipo set,
invece, puo' contenere nessuno, uno, due, tre o piu' valori dell'intervallo. La variabile set puo' anche
includere tutti i valori. Ecco un esempio di un set:
type
Letters = set of Uppercase;
Adesso posso definire una variabile di questo tipo ed assegnare alcuni valori del tipo originale. Per indicare i
valori in un set, si puo' scrivere un elenco separato da virgole, racchiuso tra parentesi quadre. Il seguente
codice mostra l'assegnazione ad una variabile di diversi valori, un singolo valore, e un valore vuoto:
var
Letters1, Letters2, Letters3: Letters;
begin
Letters1 := ['A', 'B', 'C'];
Letters2 := ['K'];
Letters3 := [];
In Delphi, un set e' generalmente usato per indicare un flag non esclusivo. Per esempio, le seguenti due
linee di codice (che sono parte della libreria di Delphi) dichiarano un'enumerazione di icone per il bordo di
una finestra e il corrispondente tipo set:
type
TBorderIcon = (biSystemMenu, biMinimize, biMaximize, biHelp);
TBorderIcons = set of TBorderIcon;
Di fatto, una data finestra puo' avere nessuna di queste icone, una, o piu' di una. Quando si lavora con
l'Object Inspector (vedi Figura 4.3), si possono provvedere i valori di un set espandendo la selezione (doppio
click sul nome della proprieta' o click sul segno di piu' sulla sinistra) e mettere on oppure off la presenza di
ogni valore.
Un'altra proprieta' basata su un tipo set e' lo stile di un font. I possibili valori indicano un font in grassetto, in
corsivo, sottolineato o barrato. Naturalmente lo stesso font puo' essere corsivo e grassetto, non avere
nessun attributo oppure tutti gli attributi. Per questa ragione e' dichiarato come un set. Si possono
assegnare valori a questo set nel codice del programma come segue:
Si puo' anche operare su di un set in diversi modi, incluso aggiungere due variabili dello stesso tipo set (o,
per essere piu' precisi, calcolare l'unione delle due variabili set):
Ancora, si puo' usare l'esempio OrdType per vedere la lista dei possibili valori di diversi set definiti nelle
librerie di Delphi.
Tipi Array
I tipi array definiscono un elenco di un numero prefissato di elementi di uno specifico tipo. Generalmente si
puo' usare un indice all'interno di parentesi quadre per accedere ad un elemento dell'array. Le parentesi
quadre sono usate anche per specificare i possibili valori dell'indice quando l'array e' definito. Ad esempio, si
puo' definire un gruppo di 24 numeri interi con il seguente codice:
type
DayTemperatures = array [1..24] of Integer;
Nella definizione dell'array, bisogna passare un tipo subrange nelle parentesi quadre oppure definire un
nuovo typo subrange usando due costanti di un tipo ordinale. Questo subrange specifica gli indici validi
dell'array. Siccome si specifica sia l'estremo inferiore che quello superiore, l'indice non deve per forza essere
zero-based, come invece necessario in C, C++, Java e altri linguaggi di programmazione.
Siccome gli indici dell'array sono basati su di un subrange, Delphi puo' controllare il loro intervallo come
abbiamo gia' visto. Una costante subrange non valida produrra' un errore in compilazione e usare un indice
fuori dai valori consentiti a run-time produrra' un errore di run-time se la corrispondente opzione del
compilatore e' attivata.
Usando la definizione di array vista sopra, si puo' settare il valore di una variabile DayTemp1del tipo
DayTemperatures come segue:
type
DayTemperatures = array [1..24] of Integer;
var
DayTemp1: DayTemperatures;
procedure AssignTemp;
begin
DayTemp1 [1] := 54;
DayTemp1 [2] := 52;
...
DayTemp1 [24] := 66;
DayTemp1 [25] := 67; // compile-time error
Un array puo' avere piu' di una dimensione, come nel seguente esempio:
type
MonthTemps = array [1..24, 1..31] of Integer;
YearTemps = array [1..24, 1..31, Jan..Dec] of Integer;
Questi due tipi di array sono costruiti sugli stessi tipi base. In questo modo si possono dichiarare usando il
precedente tipo di dato, come nel seguente codice:
type
MonthTemps = array [1..31] of DayTemperatures;
YearTemps = array [Jan..Dec] of MonthTemps;
Questa dichiarazione inverte l'ordine degli indici come mostrato sopra, ma permette anche assegnamenti di
interi blocchi tra variabili. Ad esempio, la seguente istruzione copia la temperatura di gennaio in febbraio:
var
ThisYear: YearTemps;
begin
...
ThisYear[Feb] := ThisYear[Jan];
Si puo' anche definire un array zero-based , un array con il limite inferione uguale a zero. Generalmente,
l'uso di limiti piu' logici e' un vamtaggio, siccome non serve usare l'indice 2 per acceder al terzo elemento, e
cosi' via. Windows, tuttavia, usa invariabilmente array zero-based (visto che windows e' basato sul
linguaggio C), e la libreria dei componenti di Delphi tende a fare lo stesso.
Se serve lavorare con un array, si puo' comunque testare i limiti con le funzioni standard Low e High, le quali
ritornano il limite inferiore e superiore dell'array. Usare Low e High quando si opera su un array e' altamente
consigliato, specialmente nei cicli, siccome rende il codice indipendente dall'intervallo dell'array.
Successivamente, si puo' cambiare l'intervallo dell'array e il codice che usa Low e High funzionera' ancora. Se
si scrive un ciclo con i valori correnti dell'intervallo, bisognera' aggiornare il codice quando la dimensione
dell'array cambia. L'uso di Low e High rende il codice facile da mantenere e piu' affidabile.
Nota: A proposito, non c'e' una penalizzazione delle prestazioni a run-time quando si usano le funzioni Low e
High con gli array. Esse sono risolte in costanti al momento della compilazione. Questa conversione da
funzione a costante durante compilazione accade anche per altre semplici funzioni di sistema.
Delphi usa gli array principalmente nella forma di array di proprieta'. Si e' gia' visto un esempio di queste
proprieta' nell'esempio TimeNow, per accedere alla proprieta' Items di un componente ListBox. Mostrero'
ulteriori esempi di proprieta' array nel prossimo capitolo, quando si discuteranno i cicli.
Nota: delphi 4 introduce i dynamic array, che sono array che possono essere ridimensionati a run-time
allocando una giusta quantita' di memoria. Usando i dynamic array e' facile, ma in questa discussione di
Pascal ritengo che non siano un argomento adatto. Si puo' trovare una descrizione dei dynamic array di
Delphi nel capitolo 8.
Tipi Record
I tipi record definiscono una collezione fissa di elementi di tipi differenti. Ogni elemento, o campo, ha il
proprio tipo. La definizione di un tipo record elenca tutti questi campi, dando ad ognumo un nome che verra'
usato piu' tardi per accedervi.
Ecco un piccolo listato con la definizione di un record, la dichiarazione di una variabile di questo tipo, e
qualche istruzione che usa questa variabile:
type
Date = record
Year: Integer;
Month: Byte;
Day: Byte;
end;
var
BirthDay: Date;
begin
BirthDay.Year := 1997;
BirthDay.Month := 2;
BirthDay.Day := 14;
Le classi e gli oggetti possono essere considerati un'estensione del tipo record. Le librerie di Delphi tendono
ad usare i tipi classe piuttosto che i tipi record, ma ci sono diversi tipi record definiti nelle API di Windows.
I tipi record possono avere anche una parte variabile, cioe': campi multipli possono essere mappati sulla
stessa area di memoria, anche se hanno differenti tipi (Questo corrisponde al tipi union nel linguaggio C).
Alternativamente, si possono usare questi campi varianti o gruppi di campi per accedere alla stessa locazione
di memoria dentro ad un record, ma considerando questi valori da una differente prospettiva. L'uso
principale di questo tipo e' di archiviare simili ma differenti dati e di ottenere effetti sinili a quelli del
typecasting (meno utili adesso che il type casting e' stato introdotto anche in Pascal). L'uso dei record
variant e' stato rimpiazzato da tecniche object oriented e altre tecniche moderne, benche' Delphi li usa in
alcuni casi peculiari.
L'uso di un record variant non e' type-safe e non e' raccomandato come tecnica di programmazione,
particolarmente per i principianti. I programmatori esperti possono invece usare i variant record e nel nucleo
delle librerie di Delphi ci sono esempi d'uso. Ad ogni modo, non serve affrontarli finche' non ci si sente
esperti di Delphi.
Puntatori
Un tipo puntatore definisce una variabile che contiene l'indirizzo di memoria di un'altra variabile di un dato
tipo (o di un tipo indefinito). Cosi' una variabile puntatore indirettamente punta ad una variabile. La
definizione di un tipo puntatore non e' basata su una specifica keyword, ma usa invece uno speciale
carattere. Questo simbolo speciale e' il carattere (^):
type
PointerToInt = ^Integer;
Una volta che e' stata definita una variabile puntatore, si puo' assegnare ad essa l'indirizzo di un'altra
varabile dello stesso tipo, usandol'operatore @:
var
P: ^Integer;
X: Integer;
begin
P := @X;
// change the value in two different ways
X := 10;
P^ := 20;
Quando si ha un puntatore P, con l'espressione P ci si riferisce all'indirizzo di memoria cui punta P, con
l'espressione P^ si indica il contenuto di questa locazione di memoria. Per questa ragione nel frammento di
codice sopra P^ corrisponde a X.
Invece di puntare ad una locazione di memoria esistente, un puntatore puo' indirizzare un nuovo blocco di
memoria allocata dinamicamente (nell'area di memoria heap) con la procedura New. In questo caso non
serve piu' il puntatore, bisogna anche ricordarsi di liberarsi della memoria dinamicamente allocata chiamando
laprocedura Dispose.
var
P: ^Integer;
begin
// initialization
New (P);
// operations
P^ := 20;
ShowMessage (IntToStr (P^));
// termination
Dispose (P);
end;
Se un puntatore non ha valore, si puo' assegnare il valore nil ad esso. Si puo' quindi testare quando un
puntatore e' nil per vedere se attualmente referenzia qualche valore. Questo metodo e' spesso usato, visto
che dereferenziare un puntatore non valido causa una violazione d'accesso (conosciuta anche come General
Protection Fault, GPF):
Figura 4.4: L'errore di sistema risultante dall'accesso ad un puntatore nil, dall'esempio GPF
Nello stesso programma si puo' trovare un esempio di accesso ai dati sicuro. In questo secondo caso il
puntatore e' assegnato ad una variabile locale esistente, e puo' essere usato senza rischi, ma ho aggiunto un
controllo per sicurezza:
Delphi definisce anche un tipo di dato Pointer, che indica un puntatore senza tipo (come void* nel linguaggio
C). Se si usa un puntatore senza tipo bisogna usare GetMem invece di New. La procedura GetMem e'
richiesta ogni volta che la dimensione della variabile di memoria da allocare non e' definita.
Il fatto che i puntatori sono raramente necessari in Delphi costituisce un interessante vantaggio di questo
ambiente. Nonostante cio', capire i puntatori e' importante per la programmazione avanzata e per capire
completamente il modello a oggetti di Delphi, che usa i puntatori "dietro le quinte."
Nota: Sebbene non si usino i puntatori spesso in Delphi, si usa frequentemente un costrutto similare, le
references. Ogni istanza di oggetto e' in realta' un puntatore o una referenza al suo dato corrente.
Comunque, questo e' completamente trasparente al programmatore, che usa le variabili oggetto come
qualsiasi altro tipo di dato.
Tipi File
Un altro tipo specifico del Pascal e' il tipo File. Il tipo file rappresenta i file fisici su disco, di sicuramente una
peculiarita del linguaggio Pascal. Si puo' definire un nuovo tipo file come segue:
type
IntFile = file of Integer;
Adesso si puo' aprire un file fisico associato con questa struttura e scrivere in esso valori interi o leggerne il
valore corrente.
Nota dell'autore: Gli esempi relativi ai file sono parte delle vecchie edizioni di Mastering Delphi e progetto
di aggiungerli al piu' presto.
l'uso dei file in Pascal e' abbastanza semplice, ma in Delphi ci sono anche altria componenti che sono capaci
di memorizzare o caricare il proprio contenuto su o da un file. C'e' un supporto per la serializzazione, nella
forma di stream, e c'e' anche il supporto per i database.
Conclusioni
Questo capitolo che tratta dei tipi di dati definibili dall'utente completa la copertura del sistema del Pascal.
Adesso si puo' guardare alle istruzioni che il linguaggio provvede per operare sulle variabili che abbiamo
definito.
www.marcocantu.it
Marco's Delphi Books
Essential Pascal - Web Site
Se i tipi di dato sono una delle fondamenta della programmazione Pascal le istruzioni sono le altre. Le
istruzioni dei linguaggi di programmazione sono basate sulle keyword e altri elementi i quali permettono di
indicare al programma una sequenza di operazioni da compiere. Le istruzioni sono spesso racchiuse in
procedure o funzioni, come si potra' vedere nel prossimo capitolo. Adesso si concentrera' l'attenzione solo
sui comandi di base che si possono usare per creare un programma.
X := Y + Z; // assignment
Randomize; // procedure call
Di solito, le istruzioni sono parte di un'istruzione complessa, raggruppata tra un begin e un end.
Un'istruzione composta puo' apparire al posto di un'istruzione Pascal generica. Ecco un esempio:
begin
A := B;
C := A * 2;
end;
Il punto e virgola dopo l'ultima istruzione prima dell'end non e' richiesto, come nel seguente codice:
begin
A := B;
C := A * 2
end;
Entrambe le versioni sono corrette. La prima versione ha un inutile (ma innocuo) punto e virgola. Questo
punto e virgola e', di fatto, un'istruzione nulla che e' un'istruzione senza codice. Notare che, talvolta, le
istruzioni nulle possono essere usate all'interno di un ciclo o in altri particolari casi.
Nota: Benche' questi punto e virgolafinali non hanno nessuno scopo, tendo ad usarli e suggerisco di fare
altrettanto. Alcune volte dopo aver scritto diverse righe si puo' aver bisogno di aggiungere ancora alcune
istruzioni. Se l'ultimo punto e virgola e' mancante bisogna per forza ricordarsi di aggiungerlo, cosi' puo'
essere consigliabile inserirlo da subito.
Istruzioni di assegnamento
Gli assegnamenti in Pascal usano l'oeratore := (due punti-uguale), una singolare notazione per i
programmatori che hanno usato altri linguaggi. L'operatore = (uguale), che e' usato per l'assegnamento in
altri linguaggi, in Pascal e' usato per eseguire i test di uguaglianza.
Nota: Usando differenti simboli per un assegnamento e per il test d'uguaglianza, il compilatore Pascal (come
il compilatore C) puo' tradurre il codice sorgente piu' velocemente, siccome esso non deve esaminare il
contesto nel quale l'operatore e' usato per determinarne il significato. L'uso di differenti operatori rende
anche il codice piu' facilmente leggibile.
Istruzioni Condizionali
Un'istruzione condizionale e' usata per eseguire sia un'istruzione che contiene o nessuna di esse, dipende da
alcuni test. Ci sono due tipi di istruzioni condizionali: l'istruzione if e l'istruzione case.
Istruzione If
L'istruzione if puo' essere usata per eseguire un'istruzione solo se una certa condizione e' verificata (if-then),
o per scegliere tra due differenti alternative (if-then-else). La condizione e' descritta mediante un'espressione
booleana. Un semplice esempio di Delphi dimostra come scrivere istruzioni condizionali. Primo creare una
nuova applicazione, mettere due check-box e quattro button nel form. Non cambiare il nome dei pulsanti o
dei checkbox, fare doppio click su ogni pulsante per aggiungere un gestore per l'evento OnClick. Ecco una
semplice istruzione if per il primo pulsante.
Quando si clicca sul pulsante, se il primo checkbox e' selezionato, il programma mostrera' un semplice
messaggio (vedere Figura 5.1). Ho usato la funzione ShowMessage siccome e' la funzione Delphi piu'
semplice per mostrare un piccolo messaggio all'utente.
Figura 5.1: Il messaggio visualizzato dall'esempio IfTest quandosi preme il primo pulsante e il
primo checkbox e' selezionato.
Se si clicca il pulsante e non accade nulla, vuol dire che il checkbox non e' stato selezionato. In un caso
come questo, sarebbeprobabilmente meglio rendere cio' piu' esplicito, come con il codice per il secondo
pulsante, il quale usa un'istruzione if-then-else:
Notare che nopn si puo' avere un punto e virgola dopo la prima istruzione e prima della keyword else, o il
compilatore generera' un errore di sintassi. L'istruzione if-then-else, di fatto, e' una singola istruzione, cosi'
non si puo' mettere un punto e virgola in mezzo ad essa.
Un'istruzione if puo' essere abbastanza complessa. La condizione puo essere trasformata in una serie di
condizioni (usando gli operatori booleani and, or e not), o l'istruzione if puo' contenere una seconda
istruzione if. Gli ultimi due pulsanti dell'esempio IfTest dimostrano questi casi:
Si prega di guardare il codice con attenzione ed eseguire il programma per veder se si comprende tutto.
Quando si hanno dei dubbi riguardo un costrutto di programmazione, scrivere un semplice programma come
questo puo' aiutare molto la comprensione. Si possono aggiungere piu' checkbox ed incrementare la
complessita' di questo piccolo esempio, costruento qualsiasi test.
L'istruzione Case
Se un'istruzione if diventa molto complessa, a volte si puo' sostituire con un'istruzione case. Un'istruzione
case consiste in un'espressione usata per scegliere un valore, una lista di possibili valori, o un intervallo di
valori. Questi valori sono costanti, e devono essere unici e di tipo ordinale. Eventualmente, ci puo' essere
un'istruzione else che e' eseguita se nessuna etichetta corrisponde al valore del selettore. Ecco due semplici
esempi:
case Number of
1: Text := 'One';
2: Text := 'Two';
3: Text := 'Three';
end;
case MyChar of
'+' : Text := 'Plus sign';
'-' : Text := 'Minus sign';
'*', '/': Text := 'Multiplication or division';
'0'..'9': Text := 'Number';
'a'..'z': Text := 'Lowercase character';
'A'..'Z': Text := 'Uppercase character';
else
Text := 'Unknown character';
end;
Cicli in Pascal
Il linguaggio Pascal ha le tipiche istruzioni di ripetizione di molti altri linguaggi di programmazione, incluso le
istruzioni for, while, e repeat. La maggioranza di queste istruzioni saranno familiari a chi conosce altri
linguaggi, cosi verranno discusse brevemente.
Il Ciclo For
Il ciclo for in Pascal e' strettamente basato su un contatore, il quale puo' essere sia incrementato che
decrementato ogni volta che il ciclo viene eseguito. Ecco un semplice esempio di un ciclo for usato per
sommare i primi dieci numeri:
var
K: Integer;
begin
K := 0;
for I := 1 to 10 do
K := K + I;
begin
K := 0;
for I := 10 downto 1 do
K := K + I;
Il ciclo for in Pascal e' meno flessibile che in altri linguaggi (non e' possibile specificare un incremento
diverso da uno) ma e' semplice e facile da comprendere. Se si vuole testare una condizione piu' complessa o
provveder un contatore personalizzato, bisogna usare l'istruzione while o repeat invece di un ciclo for.
Nota: Il contatore di un ciclo for non deve essere necessariamente un numero. Esso puo' essere un valore di
qualsiasi tipo ordinale, ad esempio un carattere o un tipo enumerativo.
La differenza tra un ciclo while-do e repeat-until e' che il codice del ciclo repeat e' sempre eseguito almeno
una volta. Si puo' facilmente capire perche' guardando il prossimo esempio:
repeat
// use I and J to compute something...
I := I + 1;
J := J + 1;
until (I > 100) or (J > 100);
Se il valore iniziale di I e' maggiore di 100, l'istruzione dentro al ciclo repeat e' eseguita comunque una volta.
Nota: L'altra differenza chiave tra questi due tipi di cicli e' che il ciclo repeat-until ha una condizione inversa.
Il ciclo e' eseguito finche' la condizione non e' soddisfatta. Quando la condizione e' soddisfatta, il ciclo
termina. Questo e' l'opposto che in un ciclo while-do, il quale e' eseguito mentre la condizione e' vera. Per
questa ragione bisogna invertire la condizione nel codice sopra per ottenere un'istruzione simile.
Un esempio di Ciclo
Per esplorare i dettagli dei cicli, si puo' guardare un piccolo esempio Delphi. Il programma Loops sottolinea
la differenza tra un ciclo con un contatore fisso e un ciclo con un contatore quasi casuale. Aprire un progetto
nuovo, piazzare una listbox e due pulsanti sul form principale e dare ai pulsanti un nome appropriato (BtnFor
e BtnWhile) settando la loro proprieta' Name nell'Object Inspector. Si puo' anche la parola Btn dalla
proprieta' Caption (ed eventualmente aggiungere anche il carattere & per attivare la lettera successiva come
un tasto scorciatoia). Di seguito il sommario della descrizione testuale del form d'esempio:
Figura 5.2: Ogni volta che si preme il pulsante for dell'esempio Loops, la listbox e' riempita con
numeri consecutivi.
Adesso si puo' aggiungere codice all'evento OnClick dei due pulsanti. Il primo pulsante ha un semplice ciclo
for per visualizzare una lista di numeri, come si puo' vedere nella Figura 5.2. Prima di eseguire questo ciclo,
il quale aggiunge parecchie stringhe alla proprieta' Items della listbox, bisogna pulire il contenuto della
listbox stessa:
Il codice associato con il secondo pulsante e' leggermente piu' complesso. In questo caso c'e' un ciclo while
basato su un contatore, che e' incrementato casualmente. Per realizzare cio', ho invocato la procedura
Randomize, che resetta il generatore di numeri casuali, e la funzione Random con un intervallo di valori di
100. Il risultato di questa funzione e' un numero compreso tra 0 e 99, scelto in modo casuale. La serie di
numeri casuali controlla quante volte il ciclo while e' eseguito.
Ogni volta che si preme il pulsante While, i numeri sono sempre diversi, siccome dipendono dal generatore
di numeri casuali. La Figura 5.3 mostra il risultato di due distinti click sul pulsante. Da notare che non solo i
numeri generati sono differenti, ma anche il numero di elementi. Vale a dire che il ciclo while e' eseguito un
numero casuale di volte. Se si preme il pulsante While per diverse volte, si vedra' che la listbox avra' di volta
in volta numeri di linee differenti.
Figura 5.3: Il contenuto della listbox dell'esempio Loops cambia ogni volta che viene premuto il
pulsante While. Siccome il contatore del ciclo e' incrementato da un valore casuale, ogni volta
che viene premuto il pulsante, il ciclo verra' eseguito un numero di volte sempre diverso.
Nota: Si puo' alterare il flusso standard dell'esecuzione di un ciclo usando le procedure di sistema Break e
Continue. La prima interrompe il ciclo, la seconda e' usata e' usata per saltare direttamente alla parte di test
del ciclo o all'icremento del contatore, continuando con la successiva iterazione del ciclo (salvo che la
condizione sia zero o il contatore abbia raggiunto il valore limite). Altre due procedure di sistema, Exit e Halt,
permettono di uscire dalla funzione o procedura corrente o terminare il programma.
L'istruzione With
L'ultimo tipo di istruzione Pascal su cui mi soffermo e' l'istruzione With, la quale e' peculiare di questo
linguaggio di programmazione (recentemente e' stata introdotta in Visual Basic) e molto utile nella
programmazione Delphi.
L'istruzione With e' solo per brevita' di scrittura. Quando bisogna riferirsi ad una variabile di tipo record (o ad
un oggetto), invece di ripetere il suo nome ogni volta, si puo' usare l'istruzione with. Per esempio, mentre
presentavo il tipo record ho scritto il seguente codice:
type
Date = record
Year: Integer;
Month: Byte;
Day: Byte;
end;
var
BirthDay: Date;
begin
BirthDay.Year := 1997;
BirthDay.Month := 2;
BirthDay.Day := 14;
Usando l'istruzione with, posso migliorare la parte finale del codice come segue:
begin
with BirthDay do
begin
Year := 1995;
Month := 2;
Day := 14;
end;
Questo approcio puo' essere usato nei programmi Delphi per riferirsi a componenti e altri tipi classe. Ad
esempio, si puo' riscrivere la parte finale dell'ultimo esempio, Loops, usando l'istruzione with per accedere
agli items della listbox:
Quandi si lavora con componenti o in generale con classi, l'struzione with permette di saltare la scrittura di
diverso codice, particolarmente per campi nidificati. Per esempio, si supponga di volor cambiare le proprieta'
Width e Color di un oggetto Pen. Si puo' scrivere in seguente codice:
Form1.Canvas.Pen.Width := 2;
Form1.Canvas.Pen.Color := clRed;
with Form1.Canvas.Pen do
begin
Width := 2;
Color := clRed;
end;
Quando si sta scrivendo codice complesso, l'istruzione with puo' essere efficace e fa risparmiare la
dichiarazione di diverse variabili temporanee ma ha anche un lato negativo. Puo' rendere il codice meno
leggibile, particolarmente mentre si usando diverso oggetti con proprieta' corrispondenti.
Un altro svantaggio e' che usando l'istruzione with possono esserci sottili errori di logica che il compilatore
non e' in grado di rilevare. Ad esempio:
with Button1 do
begin
Width := 200;
Caption := 'New Caption';
Color := clRed;
end;
Questo codice cambia Caption e Width di un pulsante ma influisce anche sulla proprieta' Color della form,
non quella del pulsante! La ragione e' che il componente TButton non ha la proprieta' Color e siccome il
codice e' eseguito dall'oggetto Form (si sta scrivendo un metodo della form) questo oggetto e' disponibile di
default. Se invece si fosse scritto:
Button1.Width := 200;
Button1.Caption := 'New Caption';
Button1.Color := clRed; // error!
il compilatore avrebbe generato un errore. In generale, si puo' dire che siccome l'istruzione with introduce
nuovi identificatori nel contesto corrente, si possono nascondere gli identificatori esestenti, o accedere
erroneamente un altro identificatore nel medesimo contesto (come nella prima versione del codice). Anche
considerando questi svantaggi, suggerisco di usare l'istruzione with, siccome puo' essere molto pratica e a
volte rende il codice piu' leggibile.
Il codice che segue a questa riga probabilmente sara' davvero illeggibile, visto che per ogni proprieta'
definita in questo blocco bisogna pensare a quale componente appartiene, e questo dipende dalle rispettive
proprieta' e dall'ordine dei componenti nell'istruzione with.
Nota: Parlando di leggibilita', il Pascal non ha istruzioni endif o endcase. Se un'istruzione if ha un blocco
begin-end, allora la fine del blocco e' la fine dell'istruzione. L'istruzione case, invece, e' sempre terminata da
un end. Tutti questi end, che spesso si trovano di seguito, possono rendere il codice difficile da seguire.
Solamente seguendo l'indentazione si puo' vedere a quale istruzione un particolare end si riferisce. Un
metodo comune per risolvere questo proble e rendere il codice piu' leggibile, e' di aggiungere un commento
dopo l'istruzione end che indichi il suo ruolo, come in:
if ... then
...
end; // if
Conclusioni
Questo capitolo ha descritto come scrivere istruzioni condizionali e cicli. Invece di scrivere lunghi listati, i
programmi di solito si dividono in routine: procedure o funzioni. Questo e' l'argomento del prossimo capitolo,
il quale introduce anche alcuni elementi avanzati del Pascal.
Un'altra importante idea messa in risalto dal Pascal e' il concetto di routine, fondamentalmente una serie di
istruzioni con un nome unico, le quali possono essere attivate diverse volte usando il loro nome. In questo
modo si evita di ripetere le stesse istruzioni piu' volte, avendo quindi una stessa versione del codice che puo'
essere modificato per tutto il programma. Da questo punto do vista, si puo' pensare ad una routine come un
meccanismo di base di incapsulazione. Tornero' su questo argomento con un esempio dopo aver introdotto
la sintassi per le routine in Pascal.
In pratica, comunque, la differenza tra funzioni e procedure e' molto limitata: si puo' chiamare una funzione
per compiere un certo lavoro ed ignorare il risultato (che puo' essere un codice d'errore opzionale o qualcosa
di simile) oppure si puo' invocare una procedura che passa un risultato tramite un suo parametro (altro
materiale sui parametri per riferimento piu' avanti in questo capitolo).
Ecco le definizioni di una procedura e due versioni della stessa funzione, usando una sintassi leggermente
differente:
procedure Hello;
begin
ShowMessage ('Hello world!');
end;
// or, as an alternative
function Double2 (Value: Integer) : Integer;
begin
Result := Value * 2;
end;
L'uso di Result invece del nome della funzione per assegnare il valore di ritorno, e' diventato abbastanza
popolare e, secondo me, tende a rendere il codice piu' leggibile.
Una volta che queste routine sono state definite, si possono chiamare una o piu' volte. Si chiama una
procedura per eseguire un lavoro, e si chiama una funzione per calcolare un valore:
Nota: Per il momento non ci si deve preoccupare della sintassi delle due procedure sopra, che sono
effettivamente metodi. Semplicemente mettere due pulsanti su un form di Delphi, cliccare due volte su di
essi a design-time e l'IDE di Delphi generera' il giusto codice di supporto: Adesso bisogna solo riempire le
linee tra begin e end. Per compilare il codice sopra bisogna aggiungere anche un componente Edit al form.
Adesso possiamo ritornare sul concetto di incapsulazione che ho introdotto prima. Quando si chiama la
funzione Double, nopn c'e' bisogno di conoscere l'algoritmo usato per implementarla. Se successivamente si
trova un modo migliore di raddoppiare un numero, si puo' facilmente cambiare il codice della funzione, ma il
resto codice rimarra' intatto (sebbene girera' piu' velocemente!). Lo stesso principio puo' essere applicato
alla procedura Hello: Si puo' modificare l'output del programma cambiando il codice di questa procedura e il
metodo Button2Click automaticamente cambiera' il suo effetto. Ecco come si puo' cambiare il codice:
procedure Hello;
begin
MessageDlg ('Hello world!', mtInformation, [mbOK]);
end;
Tip: Quando si chiama una procedura o funzione esistente o un metodo della VCL, bisogna ricordare il
numero ed il tipo dei parametri. L'editor di Delphi aiuta suggerendo la lista di parametri di una funzione o
procedura con hint appena si scrive il nome e si apre la parentesi. Questa funzionalita' e' chiamata Code
Parameters ed e' parte della tecnologia Code Insight.
Passare un parametro per riferimento significa il suo valore non e' copiato nello stack nel parametro formale
della routine (evitando una copia spesso il programma e' piu' veloce). Il programma, invece, punta al valore
originale, anche nel codice della routine. Questo permette alle procedure o funzioni di cambiare il valore di
un parametro. I parametri passati per riferimento sono riconoscibili dalla keyword var.
Nota: Questa tecnica e' disponibile in diversi linguaggi di programmazione. Non e' presente in C, ma e' stata
introdotta in C++, dove si usa il simbolo & (passaggio per riferimento). In Visual Basic qualsiasi parametro
non specificato come ByVal e' passato per riferimento.
In questo caso, il parametro e' usato sia per passare un valore alla procedura sia per ritornare un nuovo
valore al codice chiamante. Quando si scrive:
var
X: Integer;
begin
X := 10;
DoubleTheValue (X);
il valore della variabile X diventa 20, siccome la funzione usa un riferimento alla locazione di memoria
originale della variabile X, cambiando il suo valore iniziale.
Passare parametri per riferimento ha senso per i tipi ordinali, per le stringhe vecchio stile e per record di
dimensioni notevoli. Gli oggetti di Delphi, di fatto, sono invariabilmente passati per valore, siccome gli
oggetti sono riferimenti. Per questa ragione passare un oggetto per riferimento ha poco senso (a parte casi
speciali) , siccome e' come passare un riferimento ad un riferimento.
Le long string di Delphi hanno un comportamento leggermente differente: esse si comportano come
riferimenti, ma se si cambia una delle variabili staringa che puntano alla stessa stringa in memoria, questa e'
copiata prima dell'aggiornamento. Una long string passata come valore si comporta come un riferimento
solamente in termini di occupazione di memoria e velocita' di operazione. Se invece si modifica il valore della
stringa, il valore originale non e' cambiato. Al contrario, se si passa la long string per riferimento, si puo'
cambiare il valore originale.
Nota: Delphi 3 introduce un nuovo tipo di parametro: out. Un parametro out non ha un valore iniziale ed e'
usato solo per ritornare un valore. Questi parametri devono essere usati solo per procedure e funzioni COM,
in generale e' meglio usare i piu' efficienti parametri var. Eccetto per non avere valore iniziale, un parametro
out si comporta come un parametro var.
Parametri Constant
In alternativa ai parametri per riferimento, si possono usare i parametri const. Siccome non si puo'
assegnare un nuovo valore ad un parametro costante nel corpo della routine, il compilatore puo' ottimizzare
il passaggio dei parametri. Il compilatore puo' scegliere un approccio similare ai parametri per riferimento (o
una const reference in termini di C++), ma il comportamento rimane simile ai parametri per valore, siccome
il valore iniziale non puo' essere cambiato dalla routine.
Usando High(A) si puo' conoscere la dimensione dell'array. Da notare anche l'uso del valore di ritorno della
funzione, Result, per memorizzare valori temporanei. Si puo' chiamare questa funzione passandole un array
di espressioni di tipo intero:
Se si ha un array di interi, di qualsiasi dimensione, lo si puo' passare direttamente ad una routine che
richiede un parametro open array o, invece, si puo' chiamare la funzione Slice per passare solo una porzione
dell'array (come indicato dal suo secondo parametro). Ecco un esempio, dove l'array completo e' passato
come parametro:
var
List: array [1..10] of Integer;
X, I: Integer;
begin
// initialize the array
for I := Low (List) to High (List) do
List [I] := I * 2;
// call
X := Sum (List);
Se si cerca di passare solo una porzione dell'array alla funzione Slice, chiamarla in questo modo:
Figura 6.1: L'esempio OpenArr quando il pulsante Partial Slice e' premuto
Nota: Gli open array con tipo in Delphi 4 sono completamente compatibili con gli array dinamici (introdotti in
Delphi 4 e discussi nel capitolo 8). Gli array dinamici usano la stessa sintassi degli open array, con la
differenza che si puo' usare una notazione del tipo array of Integer per dichiarare una variabile, non soltanto
per passare un parametro.
Oltre a questi tipi di open array, Delphi permette di definire open array di tipo variant o senza tipo. Questi
particolari tipi di array hanno un nimero indefinito di valori, i quali possono essere utili per passare
parametri.
Tecnicamente, la definizione di un array di costanti permette di passare un array con un numero indefinito di
elementi di differente tipo ad una routine. Ad esempio, ecco la definizione della funzione Format (si vedra' il
funzionamento di questa funzione nel Capitolo 7, riguardante le stringhe):
Il secondo parametro e' un open array, il quale prende un numero di valori variabile. Di fatto, si puo'
chiamare questa funzione nei seguenti modi:
N := 20;
S := 'Total:';
Label1.Caption := Format ('Total: %d', [N]);
Label2.Caption := Format ('Int: %d, Float: %f', [N, 12.4]);
Label3.Caption := Format ('%s %d', [S, N * 2]);
Notare che si passa un parametro come valore costante, valore di una variabile ed espressione. Dichiarando
una funzione di questo tipo e' semplice, ma come scriverla? Come conoscere i tipi dei parametri? I valori di
un parametro open array di tipo variant sono compatibili con i valori di tipo TVarRec.
Nota: Non confondere il record TVarRec con il record TVarData usato dal typo Variant stesso. Queste due
strutture hanno diversi scopi e non sono compatibili. Anche la lista dei possibili tipi e' differente, siccome
TVarRec puo' contenere tipi di dati di Delphi, mentre TVarData puo' contenere tipi di dati OLE.
type
TVarRec = record
case Byte of
vtInteger: (VInteger: Integer; VType: Byte);
vtBoolean: (VBoolean: Boolean);
vtChar: (VChar: Char);
vtExtended: (VExtended: PExtended);
vtString: (VString: PShortString);
vtPointer: (VPointer: Pointer);
vtPChar: (VPChar: PChar);
vtObject: (VObject: TObject);
vtClass: (VClass: TClass);
vtWideChar: (VWideChar: WideChar);
vtPWideChar: (VPWideChar: PWideChar);
vtAnsiString: (VAnsiString: Pointer);
vtCurrency: (VCurrency: PCurrency);
vtVariant: (VVariant: PVariant);
vtInterface: (VInterface: Pointer);
end;
Ogni possibile record ha il campo VType, anche se non e' facile vederlo al primo momento visto che e'
dichiarato solo una volta, piu' il dato effettivo di dimensione integer (generalmente un riferimento o un
puntatore).
Usando questa informazione si puo' effettivamente scrivere una funzione capace di operare su differenti tipi
di dati. Nella funzione d'esempio SumAll, cerco di sommare valori di differenti tipi, trasformando le stringhe
in interi, i caratteri nel corrispondente valore ordinale e aggiungendo 1 per il valore booleano True. Il codice
e' basato sull'istruzione case, ed e' abbastanza semplice, anche se bisogna dereferenziare i puntatori
abbastanza spesso:
Ho aggiunto questo codice all'esempio OpenArr, il quale chiama la funzione SumAll quando un dato pulsante
e' premuto:
Si puo' vedere l'output di questa chiamata e il form dell'esempio OpenArr, nella Figura 6.2.
Figura 6.2: Il form dell'esempio OpenArr, con il message box visualizzato quando il pulsante
"Untyped" e' premuto.
Convenzioni di chiamata in Delphi
La versione a 32 bit di Delphi ha introdotto un nuovo metodo per passare i parametri, conosciuto come
fastcall: Dove possibile, fino a tre parametri possono essere passati nei registri della CPU, rendendo la
chiamata alla funzione piu' veloce. La convenzione di chiamata fastcall (usata di Default in Delphi 3) e'
indicata dalla keyword register.
Il problema e' che questa e' la convenzione di default e le funzioni che la usano non sono compatibili con
Windows: le funzioni delle API Win32 deveno essere dichiarate usando la convenzione di chiamata stdcall,
un misto tra la convenzione di chiamata originale del Pascal (usata in Win16) e la convenzione cdecl (usata
dal linguaggio C).
Generalmente non ci sono ragioni per non usare la nuova convenzione di chiamata veloce, a meno che non
si deve fare una chiamata esterna a Windows o definire una funzione callback. Si vedra' un esempio di usa
della convenzione stdcall alla fine di questo capitolo. Si puo' trovare un sommario delle convenzioni di
chiamate di Delphi nella guida in linea.
Cos'e' un Metodo?
Se si e' gia' lavorato in Delphi o letto i manuali, probabilmente il termine metodo e' gia' noto. Un metodo e'
un tipo speciale di funzione o procedura che e' relazionata ad un tipo di dato. In Delphi, ogni volta che si
gestisce un evento, bisogna definire un metodo, generalmente una procedura. In generale, comunque, il
termine metodo e' usato per indicare sia funzioni sia procedure relazionate ad una classe.
Si sono gia' visti diversi metodi negli esempi fin qui esaminati. Ecco un metodo vuoto automaticamente
aggiunto da Delphi nel codice sorgente di un form:
Dichiarazioni Forward
Quando bisogna usare un identificatore (di qualsiasi tipo), il compilatore deve avere gia' incontrato qualche
tipo di dichiarazione per conoscere a cosa si riferisce. Per questa ragione, solitamente, bisogna costruire una
dichiarazione completa prima di usare qualsiasi routine. Tuttavia, ci sono casi in cui questo non e' possibile.
Se la procedura A chiama la procedura B e la procedura B chiama la procedura A, quando si comincia a
scrivere i codice, ci troviamo a chiamare una routine della quale il compilatore non ha ancora incontrato
nessuna dichiarazione.
Se si cerca di dichiarare l'esistenza di una procedura o funzione con un certo nome e certi parametri, senza
provvedere l'effettivo codice, si puo' scrivere la procedura o funzione seguita dalla keyword forward:
Piu' tardi, il codice deve fornire una completa dichiarazione della procedura, ma questa puo' essere chiamata
anche prima di essere completamente definita. Ecco un semplice esempio, giusto per dare l'idea:
procedure Hello;
begin
if MessageDlg ('Do you want a double message?',
mtConfirmation, [mbYes, mbNo], 0) = mrYes then
DoubleHello
else
ShowMessage ('Hello');
end;
procedure DoubleHello;
begin
Hello;
Hello;
end;
Questo metodo permette di scrivere codice mutuamente ricorsivo: DoubleHello chiama Hello, ma Hello puo'
comunque chiamare DoubleHello. Certo ci deve essere una condizione per terminare la recursione, per
evitare uno stack overflow. Si puo' trovare questo codice, con leggeri cambiamenti, nell'esempio DoubleH.
Tuttavia una dichiarazione di procedura forward non e' molto comune in Delphi, c'e' un caso similare che e'
molto piu' frequente. Quando si dichiara una procedura o funzione nella sezione di interfaccia di una unit (le
unit verranno discusse nel prossimo capitolo), e' considerata una dichiarazione forward, anche se la keyword
forward non e' presente. In realta' non si puo' scrivere il corpo di una routine nella sezione d'interfaccia di
una unit. Allo stesso tempo, bisogna fornire nella stessa unit la vera implementazione di qualsiasi routine che
e' stata dichiarata.
La stessa cosa avviene per la dichiarazione di un metodo all'interno di una classe che e' automaticamente
generata da Delphi (come quando si aggiunge un evento ad un form o ad i suoi componenti). I gestori
d'evento dichiarati nella classe TForm sono dichiarazioni forward: il codice sara' fornito nella sezione di
implementazione della unit. Ecco uno stralcio del codice sorgente di un precedente esempio, con la
dichiarazione del metodo Button1Click:
type
TForm1 = class(TForm)
ListBox1: TListBox;
Button1: TButton;
procedure Button1Click(Sender: TObject);
end;
Tipi Procdurali
Un altra funzionalita' unica dell'Object Pascal e' la presenza dei tipi procedurali. Questo e' veramente un
argomento avanzato, che solamente un numero ristretto di programmatori Delphi usa regolarmente.
Comunque, siccome discuteremo argomenti simili nel prossimo capitolo (in modo specifico method pointers,
una tecnica pesantemente usata in Delphi), vale la pena di dare un'occhiata. I programmatori inesperti,
possono saltare questa sezione per adesso e ritornarvi successivamente.
In Pascal, c'e' il concetto di tipo procedurale (che e' similare al concetto di functio pointer del linguaggio C).
La dichiarazione di un tipo procedurale indica la lista di parametri ed eventualmente il tipo ritornato nel caso
di una funzione. Ad esempio, si puo' dichiarare la procedura con un parametro intero passato per riferimento
come in:
type
IntProc = procedure (var Num: Integer);
Questo tipo procedurale e' compatibile con qualsiasi routine che ha esattamente gli stessi parametri (o la
stessa function signature, per usare un termine C). Ecco un esempio di una routine compatibile:
Nota: Nelle versioni a 16 bit di Delphi, le routine devono essere dichiarate usando la direttiva far per essere
usate come effettivi valori di tipo procedurale.
I tipi procedurali possono essere usati per due diversi scopi: si possono dichiarare variabili di tipo
procedurale o passare un tipo procedurale (un puntatore a funzione) come parametro ad un'altra routine.
Con i tipi e le dichiarazioni di procedure gia' viste, si puo' scrivere questo codice:
var
IP: IntProc;
X: Integer;
begin
IP := DoubleTheValue;
X := 5;
IP (X);
end;
var
X: Integer;
begin
X := 5;
DoubleTheValue (X);
end;
La prima versione e' chiaramente piu' complessa, quindi perche' usarla? In alcuni casi, poter decidere quale
funzione chiamare e chiamarla realmente piu' tardi puo' essere utile. E' possibile costruire un esempio
complesso che mostri questo approccio. Ad ogni modo, preferisco mostrare un esempio abbastanza semplice
chiamato ProcType. Questo esempio e' piu' complesso di quelli visti finora, per rendere la cosa piu' realistica.
Creare semplicemente un nuovo progetto e mettere due radio button, un pulsante, e due label nel form,
come mostrato in Figura 6.3. Questo esempio e' basato su due procedure. Una procedura e' usata per
raddoppiare il valore del parametro. Questa procedura e' simile a quella del precedente esempio in questa
sezione. La seconda procedura e' usata per triplicare il valore del parametro e quindi chiamata
TripleTheValue:
Entrambe le procedure mostrano quello che succede, per avvisare che sono state chiamate. Questa e' una
semplice tecnica di debug che si puo' usare per testare se o quando una certa porzione di codice e' eseguita,
invece di aggiungere un breakpoint.
Ogni volta che un utente preme il pulsante Apply, una delle due procedure e' eseguita, dipendendo dallo
status dei radio button. Di fatto, quando si hanno due radio button in un form, solo uno dei due puo' esser
selezionato nello stesso momento. Questo codice puo' essere implementato testando il valore dei radio
button nel codice dell'evento OnClick del pulsante Apply. Per dimostrare l'uso dei tipi procedurali, ho invece
usato un approccio piu' lungo ma interessante. Ogni volta che un utente clicca su uno dei due radio button,
una delle due procedure e' memorizzata in una variabile:
Per permettere a tre differenti funzioni di accedere alle variabili IP ed X, bisogna renderle visibili a tutto il
form, cioe' non possono essere dichiarate localmente (all'interno dei metodi). Una soluzione a questo
problema e' di mettere queste variabili all'interno della dichiarazione del form:
type
TForm1 = class(TForm)
...
private
{ Private declarations }
IP: IntProc;
X: Integer;
end;
Si vedra' il significato esatto di questo codice nel prossimo capitolo, ma per il momento, bisogna modificare il
codice generato da Delphi per la classe come indicato sopra e aggiungere la definizione del tipo procedurale
visto in precedenza. Per inizializzare queste due variabili con valori adeguati, si puo' gestire l'evento
OnCreate del form (selezionare questo evento nell'Object Inspector dopo aver attivato il form, o
semplicemente fare doppio-click sul form stesso). Suggerisco di riferirsi al listato per studiare i dettagli di
questo esempio.
Nota: Si puo' vedere un esempio pratico dell'uso di tipi procedurali nel Capitolo 9, nella sezione Una
Funzione Callback di Windows.
Function Overloading
L'idea dell'overloading e' semplice: Il compilatore permette di definire due funzioni o procedure usando lo
stesso nome, a patto che i parametri siano differenti. Controllando i parametri, di fatti, il compilatore puo'
determinare quale delle versioni della routine si vuole chiamare.
Considerare questa serie di funzioni estratte dalla unit Math della VCL:
Quando si chiama Min(10, 20), il compilatore determina facilmente che si sta chiamando la prima funzione
del gruppo, cosi' il valore di ritorno sara' un intero.
Ogni versione della routine deve essere seguita dalla keyword overload.
Le differenze devono essere nel numero o nel tipo dei parametri, o in entrambi. Il tipo di ritorno,
invece, non puo' essere usato per ditinguere tra due routine.
Ecco tre versioni overloaded della procedura ShowMsg che ho aggiunto all'esempio OverDef (un'applicazione
che dimostra parametri di default e overloading):
Le tre funzioni mostrano un message box con una stringa, dopo aver formattato la stringa in modi differenti.
Ecco le tre chiamate del programma:
ShowMsg ('Hello');
ShowMsg ('Total = %d.', [100]);
ShowMsg (10, 'MBytes');
Quello che mi ha sorpreso positivamente e' la tecnologia di Delphi Code Parameters che lavora veramente
bene con i parametri overloaded. Come si apre la parentesi dopo il nome della routine, tutti i parametri
disponibili sono elencati. Come si scrive il parametro, Delphi usa il suo tipo per determinare quali dei
parametri alternativi sono ancora da usare. Nella Figura 6.4 si puo' vedere che appena si scrive una stringa
Delphi mostra solo le versioni compatibili (omettendo le versioni della procedura ShowMsg che hanno un
intero come primo parametro).
Figura 6.4: Le alternative multiple offerte dal Code Parameters per le routine overloaded sono
filtrate a seconda del parametro gia' disponibile.
Il fatto che ogni versione di una routine overloaded deve essere contrassegnata correttamente, implica che
non si puo' fare l'overload di una routine esistente che non e' contrassegnata dalla keyword overload. (Il
messaggio di errore che si ottiene quando si tenta e': '<name>' was not marked with the 'overload'
directive). Comunque, non si puo' fare l'overload di una routine che e' stata originariamente dichiarata in una
unit diversa. Questo e' per motivi di compatibilita' con le precedenti versioni di Delphi, che permettono a unit
differenti di riusare lo stesso nome di routine. Notare, comunque, che questo caso speciale non e' una
funzionalita' extra dell'overloading, ma un'indicazione dei problemi che possono apparire.
Questo codice non esegue sul serio l'overload della funzione MessageDlg. Di fatto se si scrive:
MessageDlg ('Hello');
si avra' un messaggio d'errore indicante che alcuni dei parametri richiesti mancano. Il solo modo di chiamare
la versione locale invece dell'originale funzione della VCL, e' di riferirsi esplicitamente alla unit locale, cioe'
qualcosa di lontano dall'idea di overloading:
OverDefF.MessageDlg ('Hello');
Parametri di Default
Una nuova funzionalita' di Delphi 4 e' che si puo' fornire un valore di default ad un parametro di una
funzione e si puo' chiamare la funzione con o senza questo parametro. Ecco di seguito un esempio. Si puo'
definire la seguento incapsulazione del metodo MessageBox dell'oggetto globale Application, che usa PChar
invece di stringhe, fornendo due parametri di default:
Nella Figura 6.5, si puo' vedere che il Code Parameters di Delphi usa uno stile differente per indicare i
parametri che hanno un valore di default, cosi' si puo' facilmente determinare quali parametri possono
essere omessi.
Figura 6.5: Il Code Parameters di Delphi segna con parentesi quadre i parametri che hanno
valori di default, si possono omettere questi parametri nele chiamate.
Da notare che Delphi non genera nessun codice speciale per supportare i parametri di default e nemmeno
crea copie multiple della routine. I parametri mancanti sono semplicemente aggiunti dal compilatore al
codice chiamante.
C'e' un importante restrizione che influisce sull'uso edi parametri di default: Non si possono "saltare"
parametri. Ad esempio, non si puo' passare il terzo parametro alla funzione dopo aver omesso il secondo:
Questa e' la regola principale per i parametri di default: In una chiamata, si possono solamente omettere
parametri a partire dall'ultimo. In altre parole, se si omette un parametro bisogna omettere anche tutti i
successivi.
I parametri con valori di default devono essere alla fine della lista dei parametri.
I valori di default devono essere costanti. Ovviamente, questo limita i tipi che si possono usare con i
parametri default. Ad esempio, un array dinamico o un tipo interfaccia non possono avere un
parametro default altro che nil, i record non possono assolutamente essere usati.
I parametri default devono essere passati per valore o come costanti. Un parametro per riferimento
(var) non puo' avere un valore di default.
Usando i parametri default e l'overloading allo stesso tempo puo' causare qualche problema, visto che
possono entrare in conflitto. Ad esempio, se si aggiunge al precedente esempio la seguente nuova versione
della procedura ShowMsg:
ShowMsg ('Hello');
e' segnalata dal compilatore come Ambiguous overloaded call to 'ShowMsg'. Da notare che questo errore
viene generato da una linea di codice che compilava correttamente prima della dichiarazione della nuova
funzione overloaded. In pratica, non si ha modo di chiamare la procedura ShowMsg con un parametro
stringa, siccome il compilatore non sa se si vuole chiamare la versione con solo un parametro stringa o
quella con il parametro stringa e il parametro intero con valore di default. Quando il compilatore ha questi
dubbi, si blocca e chiede al programmatore di dichiarare le proprie intenzioni piu' chiaramente.
Conclusioni
Scrivere procedure e funzioni e' un elemento chiave della programmazione, benche' in Delphi si tende a
scivere metodi (procedure e funzioni connesse a classi e oggetti).
Invece di muovere nelle caratteristiche object-oriented, comunque, i prossimi capitoli mostreranno i dettagli
di altre tecniche di programmazione Pascal, cominciando dalle stringhe.
La gestione delle stringhe in Delphi e' abbastanza semplice, ma dietro le quinte la situazione e' abbastanza
complessa. Il Pascal ha un modo tradizionale di gestire le stringhe, Windows ha il proprio, derivato dal
linguaggio C, e le versioni a 32 bit di Delphi includono il potente tipo long string, che e' ora il tipo di default
di Delphi.
Tipi di Stringhe
Nel Turbo Pascal di Borland e nella versione a 16 bit di Delphi, la tipica stringa e' una sequenza di caratteri
con il byte di lunghezza all'inizio, che indica la dimensione della stringa. Siccome la lunghezza e' espressa da
un solo byte, non puo' eccedere i 255 caratteri, un valore davvero basso che crea diversi problemi nella
manipolazione di stringhe. Ogni stringa e' definita con una lunghezza fissa (che per default e' la massima,
255), anche se si possono dichiarare stringhe piu' corte per salvare memoria.
Un tipo stringa e' simile ad un tipo array. Di fatto, una stringa e' praticamente un array di caratteri. Questo e'
dimostrato dal fatto che si puo' accedere ad uno specifico carattere della stringa usando la notazione [].
Per superare i limiti delle stringhe tradizionali del Pascal, le versioni a 32 bit di Delphi supportano le long
string. Attualmente ci sono tre tipi di stringhe:
Il tipo ShortString corrisponde alle stringhe tipiche del Pascal, come descritto sopra. Queste stringhe
hanno un limite di 255 caratteri e corrispondono alle stringhe usate nella versione a 16 bit di Delphi.
Ogni elemento di una ShortString e' di tipo ANSIChar (il tipo standard di carattere).
Il tipo ANSIString corrisponde al nuovo tipo di lunghezza variabile long string. Queste stringhe sono
allocate dinamicamente, sono reference counted, e usano una tecnica copy-on-write. La dimensione
di queste stringhe e' praticamente illimitata (possono contenere fino a 2 miliardi di caratteri!). Anche
queste stringhe sono basate sul tipo ANSIChar.
Il tipo WideString e' simile al tipo ANSIString ma e' basato sul tipo WideChar, che contiene caratteri
Unicode.
Le long string di Delphi sono basate sul meccanismo del reference-counting, che tengono traccia di quante
variabili stringa stanno referenziando la stessa stringa in memoria. Questo reference-counting e' anche usato
per liberare memoria quando una stringa non e' piu' usata, quando il reference-count raggiunge zero.
Se si vuole incrementare la dimensione di una stringa in memoria ma c'e' qualcosa d'altro nella memoria
adiacente, allora la stringa non puo' aumentare nella stessa locazione di memoria, e una completa copia
della stringa deve percio' essere fatta in un'altra locazione. Quando capita questa situazione, il supporto run-
time di Delphi rialloca la stringa in modo completamente trasparente. Semplicemente regolare la dimensione
della stringa con la procedura SetLength procedure, allocando efficacemente la quantita' di memoria
richiesta:
La procedura SetLength esegue la richiesta di memoria, non una vera allocazione di memoria. Essa riserva la
memoria richiesta per un uso futuro, senza realmente usare la memoria. Questa tecnica e' basata su una
caratteristica del sistema operativo Windows ed e' usata da Delphi per tutte le allocazioni dinamiche di
memoria. Ad esempio, quando si richiede un array di notevoli dimensioni, la memoria e' riservata ma non
allocata.
Impostare la lunghezza di una stringa raramente necessario. Il solo caso nel quale si deve allocare memoria
per una long string usando la procedura SetLength e' quando bisogna passare la stringa come un parametro
ad una funzione API (dopo un opportuno typecast), come mostrato piu' avanti.
Str1 := 'Hello';
Str2 := Str1;
Oltre a lavorare sulle stringhe, il programma mostra il suo stato interno in una list box, usando la seguente
funzione StringStatus:
Nella funzione StringStatus e' vitale passare il parametro stringa come parametro costante. Passare questo
parametro come copia causa l'effetto collaterale di avere un riferimento extra alla stringa mentre la funzione
e' eseguita. Al contrario, passare il parametro come riferimento (var) o costante (const) non porta ad un
ulteriore riferimento alla stringa. In questo caso ho usato un parametro const, siccome la funzione in oggetto
non modifica la stringa.
Per ottenere l'indirizzo di memoria della stringa (utile per determinare la sua vera identita' e per vedere
quando due stringhe differenti puntano alla stessa area di memoria), ho semplicemente effettuato un
typecast dal tipo stringa al tipo intero. Le stringhe sono riferimenti, in pratica sono puntatori: Il loro valore
contiene la reale locazione di memoria della stringa.
Per estrarre il reference count, ho basato il codice sul fatto poco conosciuto che la lunghezza e il reference
count sono memorizzati nella stringa, prima del testo reale e prima della posizione a cui punta la variabile
stringa. L'offset (negativo) e' -4 per la lunghezza della stringa (un valore che si puo' recuperare piu'
facilmente usando la funzione Length!) e -8 per il reference count.
Da ricordare che questa informazione interna riguardo l'offset puo' cambiare nelle future versioni di Delphi,
non c'e' nemmeno sicurezza che simili funzionalita' non documentate saranno mantenute in futuro.
Eseguendo questo esempio, si possono ottenere due stringhe con lo stesso contenuto, la stessa locazione di
memoria e un reference count di 2, come mostrato nella parte superiore della list box in Figura 2.1. Adesso
se si cambia il valore di una delle due stringhe (non importa quale), la locazione di memoria della stringa
modificata cambiera'. Questo e' l'effetto della tecnica copy-on-write.
Figura 7.1: L'esempio StrRef mostra lo stato interno di due stringhe, incluso il reference-count
corrente.
Si puo' produrre questo effetto, mostrato nella seconda parte della list box di Figura 2.1, scrivendo il
seguente codice per il gestore dell'evento OnClick del secondo pulsante:
Notare che il codice del metodo BtnChangeClick puo' essere eseguito solamente dopo il metodo
BtnAssignClick. Per far rispettare cio', il programma parte con il secondo pulsante disabilitato (la sua
proprieta' Enabled e' messa a False), il programma abilita il pulsante alla fine del primo metodo. Si puo'
liberamente estendere questo esempio e usare la funzione StringStatus per esplorare il comportamento delle
long string in diverse altre circostanze.
Si puo' trovare questo codice nell'esempio LongStr. Notare che se si scrive questo codice ma non si riesce ad
allocare memoria per la stringa con SetLength, il programma probabilmente andra' in crash. Se si sta usando
un PChar per passare un valore (e non per riceverne uno come nel codice sopra), il codice sara' anche piu'
semplice, visto che non c'e' necessita' di definire una stringa temporanea ed inizializzarla. Il codice seguente
passa la proprieta' Caption di una label come parametro ad una funzione API, semplicemente facendo il
typecasting ad un PChar:
Quando si desidera convertire una WideString ad un tipo compatibile con le funzioni di Windows, bisogna
usare il tipo PWideChar invece di PChar. Le WideString sono spesso usate per la programmazione OLE e
COM.
Dopo aver mostrato il lato positivo, adesso ci si focalizza sulle trappole. Ci sono alcuni problemi che possono
presentarsi quando si converte una long string in un PChar. Essenzialmente, il problema fondamentale e' che
dopo questa conversione, si e' responsabili per la stringa ed il suo contenuto mentre Delphi non puo' essere
di nessun aiuto. Esaminare il seguente piccolo cambiamento al codice mostrato sopra, ButtonClick:
Il programma viene compilato, ma quando si esegue, ecco una sorpresa: La caption del pulsante avra' il
testo del titolo della finestra, senza il testo della costante aggiunto. Il problema e' che quando Windows
scrive sulla stringa (all'interno della chiamata GetWindowText), non setta correttamente la lunghezza della
long string Pascal. Delphi puo' ancora usare questa stringa per l'output e puo' calcolare quando finisce
cercando il carattere di terminazione (null), ma se si aggiungono ulteriori caratteri dopo il null terminator,
saranno igorati completamente.
Come fissare questo problema? La soluzione e' di dire al sistema di convertire la stringa restituita dalla
funzione API GetWindowText in una stringa Pascal. Tuttavia, se si scrive il seguente codice:
S1 := String (S1);
il sistema lo ignorera', siccome convertire un tipo di dato in se stesso e' un'operazione senza senso. Per
ottenere l'opportuna long string, bisogna riconvertire la stringa in un PChar e lasciare che Delphi la converta
ancora in una stringa:
Un alternativa e' resettare la lunghezza della stringa Delphi, usando la lunghezza della stringa PChar,
scrivendo:
Si possono trovare tre versioni di questo codice nell'esempio LongStr. Tuttavia, se serve solo accedere al
titolo di un form, si puo' semplicemente usare la proprieta' Caption dell'oggetto form stesso. Non c'e' bisogno
di scrivere tutto questo codice, che serve solo allo scopo di mostrare i problemi della conversione tra
stringhe. Ci sono casi concreti in cui bisogna chiamare funzioni API e considerare questa complessa
situazione.
Formattare le stringhe
Usando l'operatore piu' (+) e alcune funzioni di conversione (ad esempio IntToStr) si possono effettivamente
costruire stringhe complesse partendo dai valori esistenti. Tuttavia, c'e' un differente modo per formattare
numeri, valute e altre stringhe in una stringa finale. Si puo' usare la potente funzione Format o una delle
funzioni correlate.
La funzione Format richiede come parametri una stringa con il testo di base, diversi marcatori (di solito
preceduti dal simbolo %) e un array di valori, uno per ogni marcatore. Ad esempio, per formattare due
numeri in una stringa si puo' scrivere:
dove n1 e n2 sono due valori interi. Il primo marcatore e' sostituito con il primo valore, il secondo dal
secondo valore e cosi' via. Se il tipo del marcatore (indicato dalla lettera dopo il simbolo %) non corrisponde
al tipo del parametro, viene generato un errore run-time. Non avendo un controllo dei tipi in compilazione e'
il maggior svantaggio nell'usare la funzione Format.
La funzione Format usa un parametro open-array (un parametro, cioe', che puo' avere un numero arbitrario
di valori), che discutero' piu' avanti in questo capitolo. Per il momento, comunque, notare che solo la sintassi
simil-array della lista di valori passata come secondo parametro.
Oltre a %d, si possono usare uno dei diversi marcatori definiti da questa funzione e sommariamente elencati
nella Tabella 7.1. Questi marcatori forniscono un output di default per il tipo corrispondente. Tuttavia, si
possono usare ulteriori specificatori del formato per modificare l'output di dafault. Lo specificatore width, ad
esempio, determina un numero fisso di caratteri in output, mentre lo specificatore di precisione indica il
numero di decimali dopo la virgola. Ad esempio,
converte il numero n1 in una stringa ad otto caratteri, allineata a destra (usare il simbolo meno (-) per
specificare l'allineamento a sinistra) e riempita con spazi.
Tabella 7.1: Specificatori di tipo per la funzione Format
Specificatore di
Descrizione
tipo
d (decimale) Il corrispondente valore intero e' convertito in una stringa di numeri decimali
x (esadecimale) Il corrispondente valore intero e' convertito in una stringa di numeri esedecimali.
Il corrispondente valore puntatore e' convertito in una stringa espressa con numeri
p (puntatore)
esedecimali.
s (stringa) Il corrispondente valore stringa, carattere, o PChar e' copiato nella stringa di oputput.
Il corrispondente valore floating-point e' convertito in una stringa basata sulla notazione
e (esponenziale)
esponenziale.
Il corrispondente valore floating-point e' convertito in una stringa basata sulla notazione
f (floating-point)
floating point.
Il corrispondente valore floating-point e' convertito nella stringa piu' piccola possibile
g (generale)
usando la notazione esponenziale o floating point.
Il corrispondente valore floating-point e' convertito in una stringa usando i separatori di
n (numero)
migliaia.
Il corrispondente valore floating-point e' convertito in una stringa basata sulla notazione in
m (valuta) valuta. La conversione e' basata sui settaggi internazionali di Windows (guardare l'help di
Delphi alla voce Currency and date/time variables).
Il miglior modo di vedere esempi su queste conversioni e' di sperimentare personalmente le stringhe di
formato. Per rendere questo piu' facile ho scritto il programma FmtTest, il quale permette all'utente di
inserire le stringhe di formattazione per interi e floating-point. Come si puo' vedere in Figura 7.2, questo
programma mostra un form suddiviso in due parti. La parte di destra e' per i numeri floating-point.
Ogni parte ha un primo edit box con il valore numerico che si vuole formattare in una stringa. Sotto al primo
edit box c'e' un pulsante per eseguire la formattazione e mostrare il risultato in un message box. Apparrira'
quindi un altro edit box, dove si puo' scrivere una stringa di formattazione. Come alternativa si puo'
semplicemente cliccare su una delle linee del compoennte ListBox per selezionare una stringa di
formattazione predefinita. Ogni volta che si scrive una nuova stringa di formattzione valida, essa e' aggiunta
al ListBox (notare che chiudendo il programma verranno perse questi nuovi elementi).
Il codice semplicemente esegue l'operazione di formattazione usando il testo dell'edit box EditFmtInt e il
valore del controllo EditInt. Se la stringa di formattazione non e' gia' nel List Box, essa viene aggiunta. Se
l'utente invece seleziona un elemento del List Box, il codice sposta questo valore nell'Edit Box:
Conclusioni
Le stringhe sono certamente un tipo di dati molto comune. Anche se si possono usare in molti casi senza
capire completamente come lavorano, questo capitolo ha mostrato l'esatto comportamento delle stringhe,
rendendo disponibile la potenza di questo tipo di dato.
Le stringhe sono gestite in memoria in modo dinamico e particolare, come succede con gli array dinamici.
Questo e' l'argomento del prossimo capitolo.
Prossimo Capitolo: La Memoria
© Copyright Marco Cantù, Wintech Italia Srl 1995-2000
www.marcocantu.it
Marco's Delphi Books
Essential Pascal - Web Site
Nota dell'autore: Questo capitolo parlera' della gestione della memoria, delle varie aree di memoria e
introdurra' gli array dinamici. Provvisoriamente solo quest'ultima parte e' diponibile.
Delphi 4 introduce un'implementazione molto semplice degli array dinamici, sul modello delle long string
appena descritte. Come le long string, gli array dinamici sono allocati dinamicamente e reference counted,
ma non offrono la tecnica copy-on-write. Questo non e' un grosso problema, visto che si puo' deallocare un
array ponendo la sua vriabile a nil.
Ora si puo' semplicemente dichiarare un array senza specificare il numero degli elementi e quindi allocarlo
con una data dimensione usando la procedura SetLength. La stessa procedura puo' anche essere usata per
ridimensionare un array senza perdere il suo contenuto. Ci sono anche altre procedure di gestione delle
stringhe, come la procedura Copy, che si possono usare sugli array.
Di seguito c'e' un frammento di codice, che sottolinea il fato che bisogna dichiarare e allocare memoria per
l'array prima di poterlo usare:
Se si dichiara soltanto il numero di elementi dell'array, l'indice partira' sempre da zero. Gli array generici in
Pascal permettono indici non-interi e limiti inferiori non-zero, due caratteristiche che sono precluse agli array
dinamici. Per sapere lo status di un array dinamico, si possono usare le funzioni Length, High e Low, come
per ogni altro array. Per gli array dinamici, tuttavia, la funzione Low ritorna sempre zero e la funzione High
ritorna sempre la lunghezza meno 1. Questo fatto implica che per un array vuoto High ritorna -1 (che e' uno
strano valore, piu' piccolo del valore ritornato da Low!).
Dopo questa sorta di introduzione, posso mostrare un semplice esempio, chiamato DynArr mostrato in
Figura 8.1. E' davvero molto semplice siccome non c'e' nulla di veramente complesso sugli array dinamici.
Usero' questo esempio anche per mostrare alcuni piccoli errori che i programmatori possono commettere. Il
programma dichiara due array globali e inizializza il primo nel gestore d'evento OnCreate:
var
Array1, Array2: array of Integer;
Questo mette tutti i valori a zero. Questo codice d'inizializzazione rende possibile la lettura e la scrittura dei
valori dell'array immediatamente, senza paura di errori di memoria. (Assumendo che non si tenta di
accedere ad elementi oltre al limite superiore dell'array). Per una migliore inizializzazione, il programma ha
un pulsante che scrive in ogni cella dell'array:
Il pulsante Grow, permette di modificare la dimensione della'rray senza perderne il contenuto. Si puo'
verificare cio' testando il valore ritornato dal pulsante Get dopo aver premuto il pulsante Grow:
Il metodo btnAliasClick compie due altre operazioni. La prima e' un test di uguaglianza sugli array. Questo
non controlla gli elementi reali della struttura, piuttosto l'area di memoria a cui puntano gli array,
controllando se le variabili sono due alias allo stesso array in memoria:
La seconda e' una chiamata alla funzione Copy, la quale non solo sposta i dati da un array all'altro, ma
sostituisce anche il primo array con uno nuovo creato dalla funzione. L'effetto e' che la variabile Array1
adesso punta ad un array di 11 elementi, cosi' che premendo il pulsante Get o il pulsante Set si avra' un
errore di memoria e quindi verra' generata un'accezione (a meno che non si abbia il range-checking messo
ad off, nel qual caso l'errore rimane, ma l'eccezione non e' visualizzata). Il codice del pulsante Fill continua a
lavorare bene anche dopo questo cambiamento, siccome gli elementi da modificare sono determinati usando
i limiti attuali dell'array.
Conclusioni
Questo capitolo temporaneamente copre solo gli array dinamici, certamente un elemento importante per la
gestione della memoria, ma solo una porzione dell'intera gestione. Seguira' altro materiale in futuro.
La struttura della memoria descritta in questo capitolo e' tipica della programmazione Windows, un
argomento che verra' introdotto nel prossimo capitolo.
www.marcocantu.it
Marco's Delphi Books
Essential Pascal - Web Site
Delphi fornisce una completa incapsulazione delle funzioni API di Windows di basso livello usando l'Object
Pascal e la Visual Component Library (VCL), quindi e' raramente necessario costruire un'applicazione
Windows usando semplice codice Pascal e chiamando direttamente le funzione WinAPI. Tuttavia, i
programmatori che devono usare tecniche speciali non supportate dalla VCL, possono ancora farlo in Delphi.
Bisogna usare questo approccio solo per casi davvero speciali, come lo sviluppo di nuovi componenti Delphi
basati su chiamate API insolite. In questo libro, comunque, non se ne discuteranno i dettagli. Al contrario si
guarderanno pochi elementi di interazione tra Delphi e il sistema operativo e alcune tecniche di cui possono
beneficiare i programmatori Delphi.
type
THandle = LongWord;
Il tipo Handle e' implementato come numero, ma non e' usato come tale. In Windows, un handle e' un
riferimento ad una struttura dati interna al sistema. Ad esempio, quando si lavora con una finestra (o un
form di Delphi), il sistema fornisce un handle alla finestra. Il sistema informa che la finestra sulla quale si sta
lavorando e' la numero 142, ad esempio. Da questo punto, l'applicazione puo' chiedere al sistema di operare
sulla finestra numero 142, muovendola, ridimensionandola, iconizzandola e cosi' via. Diverse funzioni API di
Windows, di fatto, vogliono un handle come primo parametro. Questo non riguarda solo le funzioni operanti
sulle finestre, altre funzioni API vogliono come primo parametro un handle GDI, un handle ad un menu, un
handle di istanza, un handle ad un bitmap, o uno degli altri tipi di handle.
In altre parole, un handle e' un codice interno che si puo' usare per puntare ad uno specifico elemento
gestito dal sistema: una finestra, un bitmap, un'icona, un blocco di memoria, un cursore, un font, un menu e
cosi' via. In Delphi, raramente si ha la necessita' di usare un handle direttamente, siccome sono nascosti
all'interno dei form, dei bitmap e degli altri oggetti Delphi. Essi diventano utili quando si cerca di chiamare
una funzione API di Windows non supportata da Delphi.
Per completare questa descrizione, ecco un semplice esempio sull'uso degli handle in Windows. Il
programma WHandle ha un semplice form, contenente solo un pulsante. Nel codice, si risponde all'evento
OnCreate del form e all'evento OnClick del pulsante, come indicato dalla seguente dichiarazione testuale del
form principale:
Siccome FormCreate e' un metodo della classe form, puo' accedere direttamente alle proprieta' e metodi
della stessa classe. Percio' in questa procedura ci si puo' semplicemente riferire direttamente alle proprieta'
Caption e Handle.
Figura 9.1: L'esempio WHandle mostra l'handle del form. Ogni volta che si esegue il programma
si otterra' un valore differente.
Se si esegue questo programma diverse volte generalmente si otterranno differenti valori per l'handle.
Questo valore, di fatto, e' determinato da Windows e rispedito all'applicazione. (Gli handle non sono mai
determinati dal programma ed essi non hanno valori predefiniti, sono determinati dal sistema, il quale
genera nuovi valori ogni volta che viene eseguito un programma).
Quando l'utente preme il pulsante, il programma chiama semplicemente una funzione API di Windows,
SetWindowText, che cambia il testo o il titolo della finestra passata come primo parametro. Per essere piu'
precisi, il primo parametro di questa funzione API e' l'handle della finestra che si vuole modificare:
Questo codice ha lo stesso effetto del precedente gestore d'evento, il quale cambiava il testo della finestra
assegnando un nuovo valore alla proprieta' Caption del form. In questo caso, chiamare una funzione API
non ha molto senso, siccome esiste un modo molto piu' semplice in Delphi. Alcune funzioni API, comunque,
non hanno la corrispondente in Delphi, come si potra' vedere negli esempi piu' avanzati piu' avanti in questo
libro.
Dichiarazioni External
Un altro importante elemento per la programmazione Windows e' rappresentato dalle dichiarazioni external.
Originariamente usate per collegare il codice Pascal a funzioni esterne scritte in linguaggio assembly, le
dichiarazioni external sono usate in Windows per chiamare funzioni da una DLL (dynamic link library). In
Delphi, ci sono parecchie dichiarazioni di questo tipo nella unit Windows:
// forward declaration
function LineTo (DC: HDC; X, Y: Integer): BOOL; stdcall;
Raramente si ha bisogno di scrivere dichiarazioni come quella sopra, visto che sono gia' elencate nella unit
Windows e altre unit di sistema di Delphi. La sola ragione che si puo' avere per scrivere questo tipo di
dichiarazione e' la chiamata di funzioni da una DLL non di sistema, o per chiamare funzioni Windows non
documentate.
Nota: Nella versione a 16 bit di Delphi, la dichiarazione external usa il nome della DLL senza estensione, e e'
seguita dalla direttiva name (come nel codice in alto) o in alternativa dalla direttiva index, seguita dal
numero ordinale della funzione all'interno della DLL. Il cambiamento riflette un cambiamento di sistema nel
modo in cui sono trattate: Benche' la piattaforma Win32 permette l'accesso alle funzioni DLL a partire dal
numero ordinale, Microsoft afferma che non sara' piu' supportato in futuro. Da notare anche che la unit
Windows sostituisce le unit WinProcs e WinTypes della versione a 16 bit di Delphi.
Prima di tutto, cosa e' una funzione callback? L'idea e' che alcune funzioni API compiono una data azione
sopra diversi elementi interni di sistema, come l'insieme delle finestre di un certo tipo. Cosi' una funzione,
chiamata anche una funzione enumerativa, richiede come parametro l'azione da eseguire su ognuno degli
elementi, la quale e' passata come una funzione o procedura compatibile con una certo tipo procedurale.
Windows usa le funzioni callback in altre circostanze, ma lo studio sara' limitato ai casi piu' semplici.
Adesso si consideri la funzione API EnumWindows, che ha il seguente prototipo (copiato dal file di Help
Win32):
BOOL EnumWindows(
WNDENUMPROC lpEnumFunc, // address of callback function
LPARAM lParam // application-defined value
);
Ovviamente, questa e' una definizione in linguaggio C. Si puo' guardare nella unit Windows per recuperare la
corrispondente definzione in Pascal:
function EnumWindows (
lpEnumFunc: TFNWndEnumProc;
lParam: LPARAM): BOOL; stdcall;
Consultando il file di help, si trova che la funzione passata come parametro deve essere del seguente tipo
(ancora in C):
type
EnumWindowsProc = function (Hwnd: THandle;
Param: Pointer): Boolean; stdcall;
Il primo parametro e' l'handle di ogni finestra principale successiva, mentre il secondo e' il valore passato
nella chiamata alla funzione EnumWindows. Effettivamente in Pascal il tipo TFNWndEnumProc non e'
correttamente definita, esso e' semplicemente un puntatore. Questo vuol dire che si deve fornire una
funzione con gli opportuni parametri quindi usarla come puntatore, prendendo l'indirizzo della funzione
invece di invocarla. Sfortunatamente, questo vuol dire che il compilatore non fornira' nessun aiuto in caso di
errore nel tipo di un parametro.
Windows ai programmatori di seguire la convenzione di chiamata stdcall ogni volta che si chiama una
funzione API di Windows o si passa una funzione callback al sistema. Delphi, di default, usa una convenzione
diversa e piu' efficiente, indicata dalla keyword register.
Ecco la definizione di una opportuna funzione compatibile, la quale legge il titolo della finestra lo mette in
una stringa e lo aggiunge ad un ListBox di un dato form:
Il form ha un ListBox che copre quasi tutta l'area, insieme ad un pannello in alto che contiene un pulsante.
Quando il pulsante e' premuto, la funzione API EnumWindows e' invocata e la funzione GetTitle e' passata
come suo parametro:
Ho chiamato la funzione senza memorizzare il valore in una variabile temporanea di tipo procedurale, ma
voglio rendere chiaro quello che succede nell'esempio. L'effetto di questo programma e' davvero
interessante, come si puo' vedere in Figura 9.2. L'esempio Callback mostra un elenco di tutte le finestre
esistenti nel sistema.
Figura 9.2: L'output dell'esempio Callback, con l'elenco delle finestre correnti (visibili e
nascoste).
Un Programma Windows minimale
Per completare la discussione sulla programmazione Windows e sul linguaggio Pascal, voglio mostrare una
semplicissima ma completa applicazione costruita senza usare la VCL. Il programma semplicemente prende
un parametro sulla linea di comando (immagazzinato dal sistema nella variabile globale cmdLine) e quindi
estrae informazioni con le funzioni Pascal ParamCount e ParamStr. La prima di queste funzioni, ritorna il
numero di parametri, la seconda ritorna il parametro in una data posizione.
Sebbene gli utenti raramente specificano i parametri sulla linea di comando in un'interfaccia grafica, il
parametri su linea di comando di Windows sono importanti per il sistema. Ad esempio, una volta definita
un'associazione tra estensione del file e applicazione, si puo' semplicemente eseguire il programma
selezionando un file associato. In pratica, quando si esegue un doppio click su un file, Windows esegue il
programma associato e passa il file selezionato come parametro sulla linea di comando.
Ecco il codice sorgente completo del progetto (un file DPR, non un file PAS):
program Strparam;
uses
Windows;
begin
// show the full string
MessageBox (0, cmdLine,
'StrParam Command Line', MB_OK);
Per fornire un parametro in linea di comando a questo programma, si puo' usare il menu di Delphi Run ->
Parameters. Un'altra tecnica e' di aprire l'Explorer di Windows, localizzare la directory che contiene il file
eseguibile e trascinare il file che si scelto sopra il file eseguibile. L'Explorer fara' partire il programma usando
il nome del file trascinato come parametro in linea di comando. La Figura 2.12 mostra sia Explorer sia
l'output corrispondente.
Figura 9.3: Si puo' fornire un parametro in linea di comando all'esempio StrParam trascinando
un file sopra al file nell'Explorer di Windows.
Conclusioni
In questo capitolo si e' vista un'introduzione alla programmazione Windows a basso livello, si sono visti gli
handle e un programma semplice Windows. Per i normali programmi Windows, generalmente si usa
l'ambiente di programmazione visuale fornito da Delphi e basato sulla VCL. Questo pero' non e' negli scopi di
questo libro che e' un testo sul linguaggio Pascal.
Il prossimo capitolo parla dei variant, un tipo abbastanza "strano" aggiunto di recente al Pascal, introdotto
per un pieno supporto alla tecnologia OLE.
www.marcocantu.it
Marco's Delphi Books
Essential Pascal - Web Site
Per avere un pieno supporto OLE, le versioni a 32 bit di Delphi includono il tipo di dato Variant. Qui voglio
discutere questo tipo di dato da una prospettiva generale. Il tipo Variant, di fatto, ha un effetto esteso
all'intero linguaggio e nella libreria dei componenti di Delphi viene usato per cose non necessariamente
relazionate alla programmazione OLE.
I Variant non hanno Tipo
In generale, si possono usare i Variant per memorizzare qualsiasi tipo di dato e compiere diverse operazioni
e conversioni di tipo. Da notare che questo va contro l'approccio generale del linguaggio Pascal e va contro
le regole di buona programmazione. Un tipo Variant e' controlalto e calcolato a run-time. Il compilatore non
avvisera' di possibili errori nel codice, i quali potranno essere scoperti solo dopo un ciclo pesante di testing.
Nel complesso, si possono considerare le porzioni di codice che usano i variant come codice interpretato,
visto che, come il codice interpretato, le operazioni non possono essere risolte che a run-time. Questo
influenza in particolare la velocita' di esecuzione del codice.
Adesso che ho avvertito dei pericoli nell'uso del tipo Variant, e' tempo di guardare come si possono usare.
Basilarmente, una volta dichiarato una variabile variant come nel seguente codice:
var
V: Variant;
V := 10;
V := 'Hello, World';
V := 45.55;
Una volta che si ha il valore del variant, lo si puo' copiare in tipo compatibile o incompatibile. Se si assegna
un valore ad un tipo non compatibile, Delphi se possibile effettuera' una conversione. Altrimenti verra'
generato un errore a run-time. Di fatto, un variant immagazzina le informazioni sul tipo assieme al dato,
permettendo cosi' diverse operazioni a run-time, queste operazioni possono essere pratiche ma sono lente e
pericolose.
Si consideri il seguente esempio (chiamato VariTest), che e' un'estensione del codice visto sopra. Ci sono tre
editbox su un nuovo form, alcuni pulsanti e il seguente codice nell'evento OnClick del primo pulsante:
Buffo vero? Oltre ad assegnare un variant che una stringa alla proprieta' Text di un componente edit, si puo'
assegnare alla proprieta' Text un variant che contiene un intero o un valore reale. Come si puo' vedere in
Figura 10.1, funziona tutto.
Figure 10.1: L'output dell'esempio VariTest dopo che il pulsante Assign e' stato premuto.
Cosa ancora peggiore, si possono usare i variant per calcolare valori, come si puo' vedere nel codice relativo
al secondo pulsante:
Scrivere questo tipo di codice e' a dir poco rischioso. Se il primo edit box contiene un numero, tutto ok. Se
non contiene un numero e' generata un'eccezione. Di nuovo, si puo' scrivere codice similare, ma senza una
ragione per farlo, non si dovrebbe usare il tipo Variant, invece bisogna cercare di usare il piu' possibile i tipi
di dato tradizionali del Pascal. In Delphi e nella VCL (Visual Component Library), i variant sono usati per il
supporto OLE e per accedere ai campi di database.
I possibili valori del campo VType, corrispondono ai tipi di dato che si possono usare nell'automazione OLE e
sono spesso chiamati tipi OLE o tipi variant. Ecco un elenco (in ordine alfabetico) dei tipi variant disponibili:
varArray
varBoolean
varByRef
varCurrency
varDate
varDispatch
varDouble
varEmpty
varError
varInteger
varNull
varOleStr
varSingle
varSmallint
varString
varTypeMask
varUnknown
varVariant
Si puo' trovare la descrizione di questi tipi nell'argomento Variant nell'help online di Delphi.
Ci sono anche diverse funzioni per operare sui variant che si possono usare per specifiche conversioni di tipo
o per richiedere informazioni riguardo il tipo di un variant (ad esempio la funzione VarType). La maggior
parte di queste funzioni di conversione sono in realta' chiamate automaticamente quando si scrivono
espressioni che usano i variant. Le altre routine di supporto per i variant (vedere l'argomento Variant
Support Routines nel file di help) in realta' operano sui variant array.
Questo programma esegue un ciclo, calcola la velocita' di esecuzione e mostra lo stato in una progress bar.
Qui, e' il primo dei due cicli davvero simili, basati sugli interi e variant:
Vale la pena di guardare il codice della temporizzazione, visto che e' possibile adattarlo ad ogni tipo di test
delle prestazioni. Come si puo' vedere, il programma usa la funzione Now per ottenere l'ora corrente e la
funzione FormatDateTime per scrivere la differenza, richiedendo solo i minuti ("n") e i secondi ("ss") nella
stringa di formato. Come alternativa, si puo' usare la funzione API di Windows GetTickCount, che ritorna con
un'alta precisione in numero di millisecondi trascorsi da quando il sistema operativo e' partito.
In questo esempio la differenza di velocita' e' in realta' cosi grande che si nota anche senza un controllo
preciso. Ad ogni modo, si puo' vedere il risultato in Figura 10.2. I valori reali dipendono dal computer usato
per eseguire il programma, ma la proporzione non cambiera' di molto.
Figura 10.2: Le differenti velocita' di esecuzione dello stesso algoritmo, basato su integer e
variant (i tempi variano a seconda del computer usato), come sono mostrati dall'esempio
VSpeed.
Conclusioni
I Variant sono cosi' diversi dai tipi di dato tradizionali del Pascal, che ho deciso di descriverli separatamente
in questo breve capitolo. Sebbene il loro ruolo sia nella programmazione OLE, essi possono essere pratici per
scrivere programmi "quick and dirty" senza preoccuparsi dei tipi di dato. Come si e' visto questo pero'
penalizza di molto le prestazioni.
Dopo aver discusso delle principali caratteristiche del linguaggio, si prendera' in esame la struttura generale
di un programma e la modularizzazione offerta dalle unit.
www.marcocantu.it
Marco's Delphi Books
Essential Pascal - Web Site
Le applicazioni Delphi fanno un intensivo uso delle unit, o moduli di programma. Le unit, di fatto, sono le
basi della modularizzazione nel linguaggio prima che le classi fossero introdotte. In un'applicazione Delphi,
ogni form ha una corrispondente unit dietro ad esso. Quando si aggiunge un nuovo form ad un progetto
(con la toolbar corrispondente o dal menu File -> New Form) Delphi in realta' aggiunge una nuova unit, che
definsce la classe del nuovo form.
Unit
Se e vero che ogni form e' definito in una unit, non e' invece vero il contrario. Le unit non devono
necessariamente definire un form, esse possono semplicemente definire e rendere disponibili una raccolta di
routine. Selezionando il comando di menu File -> New e l'icona "Unit" nell'Object Repository, si aggiunge
una unit vuota nel progetto corrente. Questa unit contiene il seguente codice, che delimita le sezioni in cui e'
divisa una unit:
unit Unit1;
interface
implementation
end.
Il concetto di una unit e' semplice. Una unit ha un nome univoco che corrisponde al nome del file, una
sezione d'interfaccia che dichiara cosa e' visibile alle altre unit e una sezione d'implementazione con il codice
reale e altre dichiarazioni nascoste. Infine, la unit puo' avere una sezione di inizializzazione (opzionale) con
codice che viene eseguito quando il programma e' caricato in memoria e una sezione di finalizzazione
(opzionale) con codice che viene eseguito quando il programma viene terminato.
La struttura generale di una unit, con tutte le possibili sezioni e' la seguente:
unit unitName;
interface
// exported constants
const
Zero = 0;
// global variables
var
Total: Integer;
implementation
uses
D, E;
initialization
// optional initialization part
finalization
// optional clean-up code
end.
La clausola uses all'inizio della sezione d'interfaccia indica a quali altre unit si vuole accedere dalla sezione
d'interfaccia. Queste includono le unit che definiscono i tipi di dati di cui abbiamo bisogno nella definizione di
altri dati, come i componenti usati nel form che si sta definendo.
La seconda clausola uses, all'inizio della sezione d'implementazione, indica ulteriori unit che servono solo nel
codice di implementazione. Quando bisogna riferirsi ad altre unit nel codice delle routine e metodi, si
possono aggiungere le unit in questa seconda clausola uses invece che nella prima. Tutte le unit alla quale ci
si riferisce devono essere presenti nella directory di progetto o in una directory del "search path" (si puo'
modificare il search path per un progetto nella pagina Directories/Conditionals della finestra di dialogo
Project Options).
I programmatori C++ devono sapere che la clausola uses non corrisponde alla direttiva include. L'effetto di
un'istruzione uses e' di importare solo l'interfaccia, precompilata, delle unit elencate. La porzione
d'implementazione e' considerata solo quando questa unit e' compilata. Le unit alla quale ci si riferisce
possono essere sia in codice sorgente (PAS) che nel formato compilato (DCU), ma il compilatore che ha
generato le unit deve essere della stessa versione.
L'interfaccia di una unit puo' dichiarare numerosi elementi differenti, incluso procedure, funzioni, varabili
globali e tipi. Nelle applicazioni Delphi, i tipi di dato sono probabilmente i piu' usati. Delphi automaticamente
crea una nuova classe in una unit ogni volta che si crea un form. Comunque, la dichiarazione di un form non
e' certamente la sola funzione delle unit in Delphi. Si possono avere ancora le unit tradizionali, con funzioni e
procedure e si possono avere unit con classi che non hanno a che fare con i form o gli elementi visuali.
Unit e Visibilita'
In Pascal, le unit sono la chiave dell'incapsulazione e della visibilita e sono probabilmente anche piu'
importanti delle keyword delle classi, private e public. (Di fatto, come si vedra' nel prossimo capitolo, l'effetto
della keyword private e' collegato alla visibilita' della unit contenente la classe). La visibilita' di un
identificatore (come una variabile, rpocedura, funzione o tipo di dato) e' la porzione di codice nel quale
l'identificatore e' accessibile. La regola di base e' che un identificatore e' significativo solo dentro al blocco in
cui e' dichiarato. Non si puo' usare un identificatore fuori dal suo blocco di visibilita'. Ecco alcuni esempi.
Variabili locali: Se si dichiara una variabile all'interno del blocco che definisce una routine o metodo,
non si potra' usare questa variabile al di fuori di questa procedura o metodo. La visibilita'
dell'identificatore abbraccia l'intera procedura, incluso routine nidificate (a meno che un
identificatore con lo stesso nome copre la definizione del precedente). La memoria per questa
variabile e' allocata sullo stack quando il programma esegue la routine nel quale la variabile e'
definita. Appena la routine termina, la memoria sullo stack e' automaticamente rilasciata.
Variabili globali nascoste: Se si dichiara un identificatore nella sezione d'implementazione di una unit,
non si puo' usare fuori dalla unit, ma si puo' usare in qualsiasi procedura definita dentro la unit. La
memoria per questa variabile e' allocata appena il programma parte e viene mantenuta finche' il
programma termina. Si puo' usare la sezione di inizializzazione della unit per stabilire un determinato
valore iniziale.
Variabili globali: Se si dichiara un identificatore nella sezione d'interfaccia della unit, la sua visibilita'
si estende ad ogni altra unit che usa quella in cui e' dichiarato. Questa variabile usa memoria ed ha
lo stesso ciclo di vita del precedente gruppo, la sola differenza e' nella visibilita'.
Qualsiasi dichiarazione nella sezione d'interfaccia di una unit e' accessibile da ogni punto delprogramma che
include la unit nella clausola uses. Le variabili della classe form sono dichiarate nello stesso modo, cosi' ci si
puo' riferire ad un form (e ai suoi campi, proprieta' e componenti) dal codice di ogni altro form. Ovviamente,
non e' buona prassi dichiarare tutto come globale. A parte gli ovvi problemi di consumo di memoria, usare
variabili globali rende il programma piu' difficile da mantenere e aggiornare. In breve, si deve usare il minor
numero possibile di variabili globali.
In questo caso, si puo' semplicemente usare il nome della unit come prefisso al nome della routine definita
nella unit. Ad esempio, ci si puo' riferire alla procedura ComputeTotal definita in una unit Totals come
Totals.ComputeTotal. Questo non deve accadere spesso, visto che e' una cattiva regola usare nomi uguali
per differenti elementi in un programma.
Tuttavia, si si guarda nella libreria VCL e nella API di Windows, si trovera' che alcune funzioni Delphi hanno
lo stesso nome (ma in genere diversi parametri) di alcune funzioni Windows disponibili in Delphi stesso. Un
esempio e' la procedura Beep.
allora, appena si preme il pulsante si sentira' un breve suono. Se si guarda il codice nella clausola uses:
uses
Windows, Messages, SysUtils, Classes, ...
e si cambia con il seguente (semplicemente spostando la unit SysUtils prima della unit Windows):
uses
SysUtils, Windows, Messages, Classes, ...
Adesso se si prova a ricompilare questo codice si avra' un errore: "Not enough actual parameters." Il
problema e' che la unit Windows definisce un'altra funzione Beep con due parametri. Quello che succede e'
che le definizioni della prima unit (nella clausola uses) possono essere nascoste dalle corrispondenti
definizioni delle unit successive. La soluzione e' abbastanza semplice:
Questo codice compilera' a dispetto dell'ordine delle unit nella clausola uses. Ci sono poche altri conflitti di
nome in Delphi, semplicemente perche' il codice Delphi e' contenuto dai metodi delle classi. Avere due
metodi con lo stesso nome in due differenti classi non crea alcun problema. Il problema esiste solo con le
routine globali.
Unit e Programmi
Un'applicazione Delphi consiste di due tipi di codice sorgente: una o piu' unit e un file di programma. Le unit
possono essere considerate files secondari, i quali sono legati alla parte principale dell'applicazione, il
programma, In teoria, questo e' vero. In pratica, il file di programma e' normalmente generato
automaticamente, ed ha un ruolo secondario. Semplicemente fa partire il programma e visualizza il form
principale. Il codice del file di programma, o progetto (DPR), puo' essere editato manualmente o manipolato
tramite il Project Manager o i settaggi del Project Options relativi al programma o ai form dell'applicazione.
la struttura del file di programma e' normalmente piu' semplice della struttura delle unit. Ecco il codice
sorgente di un file di programma di esempio:
program Project1;
uses
Forms,
Unit1 in ‘Unit1.PAS’ {Form1DateForm};
begin
Application.Initialize;
Application.CreateForm (TForm1, Form1);
Application.Run;
end.
Come si puo' vedere, c'e' solo una clausola uses e il codice principale dell'applicazione, racchiuso dalle
keyword begin end. La clausola uses del programma e' particolarmente importante, siccome e' usata per
gestire la compilazione e il link dell'applicazione.
Conclusioni
At least for the moment, this chapter on the structure of a Pascal application written in Delphi or with one of
the latest versions of Turbo Pascal, is the last of the book. Feel free to email me your comment and
requests.
Almeno per il momento, questo capitolo sulla struttura di un'applicazione Pascal scritta in Delphi o con una
delle ultime versioni del Turbo Pascal, e' l'ultimo del libro. Per commenti e richieste mandare una mail a:
Marco
Se dopo questa inroduzione del linguaggio Pascal si vogliono approfondire gli elemnti della programmazione
orientata agli oggetti in Object Pascal, ci si puo' indirizzare al mio ultimo libro pubblicato Masterig Delphi 5
(Sybex, 1999). Per maggiori informazioni su questo e altri libri piu' avanzati (anche di altri autori) guardate il
mio sito web, www.marcocantu.com/books.
Ritorna all'indice
© Copyright Marco Cantù, Wintech Italia Srl 1995-2000