Premessa: molto semplicemente si può immaginare la
memoria come un insieme di celle di vari tipi, che rappresentano le
variabili, ed ognuna di queste celle per essere raggiunta deve avere
un indirizzo che rappresenta la sua posizione all'interno della
memoria. Una volta capito questo concetto si può capire che un
puntatore è semplicemente una variabile che contiene l'indirizzo di
un altra variabile. Questo rende i puntatori uno strumento molto
potente, ma anche molto pericoloso, perché se si commettono errori è
possibile modificare aree di memoria che non volevamo toccare. Per
dichiarare un puntatore è buona norma farlo tramite il costrutto
type che abbiamo già visto precedentemente (in un altro articolo). Facciamo un esempio:
type
PuntatoreAdIntero=^integer;
var
Puntatore:
PuntatoreAdIntero;
oppure si può banalmene scrivere
var
Puntatore:^integer;
Nel primo caso ho definito un tipo di variabile con nome
PuntatoreAdIntero che corrisponde a un puntatore ad integer, poi ho
dichiarato una variabile di nome Puntatore e del tipo appena
dichiarato PuntatoreAdIntero. Nel secondo caso ho dichiarato una
variabile di tipo puntatore ad integer. Quindi come si può dedurre
da i due modi di dichiarare un puntatore per dire che si tratta
effettivamente di un puntatore bisogna usare il simbolo ^ prima del
tipo della variabile da puntare.
Ora che abbiamo dichiarato il puntatore vediamo un po di
codice per capire meglio come utilizzare un puntatore.
{
Dichiaro il tipo di dato PuntatoreAdIntero }
type
PuntatoreAdIntero=^integer;
.
.
.
{
Dichiaro le variabili che mi servono }
var
MiaVariabile: integer;
MioPuntatore: PuntatoreAdIntero;
.
.
.
{
Codice vero e proprio che andremo ad analizzare }
MiaVariabile:=60; { Inizializzo la variabile }
MioPuntatore:=@MiaVariabile; { Il puntatore ora lo
punto alla variabile }
writeln('Il valore puntato è: ', MioPuntatore^); {
Stampo il valore puntato da puntatore }
MiaVariabile:=50;
writeln('Il valore puntato è: ', MioPuntatore^); {
Stampo il valore puntato da puntatore }
Per prima cosa inizializzo la variabile di nome
MiaVariabile a 60, dopodichè dico a MioPuntatore di puntare
all'indirizzo di MiaVariabile, dopodiché stampo a video il contenuto
della variabile puntata, il risultato sarà 60. In seconda battuta
modifico il contenuto di MiaVariabile e ristampo nuovamente il dato
contenuto nella variabile puntata, il risultato sarà 50.
I puntatori possono essere utilizzati su qualsiasi tipo
di variabile, anche sulle variabili realizzate da noi attraverso il
type.
Ad esempio se si realizzasse il tipo di dato animale in
questo modo
type
Animale=record
Anni:
integer;
Tipo:
string;
end;
sarebbe possibile dichiarare un puntatore a tale
variabile e le variabili Var1 e Var2 tramite questo codice
var
MioPuntatore:
^Animale;
Var1:
Animale;
Var2:
Animale;
Poi valorizzerei le due variabili
Var1.Anni:=5;
Var1.Tipo:='cane';
Var2.Anni:=6;
Var2.Tipo:='gatto';
E stamperei a video tramite il puntatore i dati delle
due variabili:
Una volta compilato ed eseguito questo programma il
risultato ottenuto sarà:
Ho un cane di anni: 5
Ho un gatto di anni: 6
Non bisogna lasciarsi trarre in inganno dagli esempi
visti fino ad ora, i puntatori sono molto utili e flessibili come
strumenti, soprattutto se si affrontano tematiche come liste ed
alberi.
Written by xinyimanposted in Lazarus 1.0 Ottobre 24, 2011, 11:47:00 pm22168 ViewsRating: 0 (0 Rates)Print
Quante volte è capitato di dirsi, però ora che ho fatto il programma se lo rendessi multilingua potrei distribuirlo in numero maggiore! Bene questo tutorial spiega i passi per rendere il vostro software multilingua in maniera semplice e multipiattaforma. Tutto questo con l'ausilio dei file INi.
Bene per prima cosa necessitiamo di un file ini che chiameremo lingue.ini (nella stessa cartella dell'eseguibile), così strutturato.
[Lingue]
; in questa zona dichiaro le lingue che andrò ad implementare nel mio programma
Numero=2
Lingua1=italiano
Lingua2=inglese
[italiano]
;notare che italiano è tutto minuscolo per essere uguale alla lingua dichiarata sopra nella sezione Lingue
;per non creare problematiche di alcun tipo in sistemi operativi case-sensitive
Lbl_Nome=Nome
Lbl_Cognome=Cognome
[inglese]
Lbl_Nome=Name
Lbl_Cognome=Surname
Come seconda cosa andiamo ad analizzare un po di codice Free Pascal per interagire con tale file INI. Creiamo un nuovo progetto con una form che si chiama Form1 e aggiungete tre componenti, una TComboBox e due TLabel. Ecco come sarà il sorgente
unit Unit1;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls
,INIFiles {libreria per usare i file ini}
;
{ In fase di caricamento delle form inserisco nella combobox le possibili lingue inserite nel file ini}
procedure TForm1.FormCreate(Sender: TObject);
Var
INI:TINIFile;
Numero, i: integer;
begin
INI := TINIFile.Create(Application.Location + 'lingue.ini');
Numero := StrToInt(INI.ReadString('Lingue','Numero',''));
for i:=1 to Numero do
begin
Cmb_Lingue.Items.Add(INI.ReadString('Lingue','Lingua' + IntToStr(i),''));
end;
Ini.Free;
Cmb_Lingue.Text:=Cmb_Lingue.Items.Strings[0];
CambiaLingua();
end;
{ Al cambiamento del contenuto della ComboBox cambio i valori delle label }
procedure TForm1.Cmb_LingueChange(Sender: TObject);
begin
CambiaLingua();
end;
procedure TForm1.CambiaLingua();
var
i: integer;
INI:TINIFile;
Lingua: string;
begin
Lingua:=Cmb_Lingue.Text;
INI := TINIFile.Create(Application.Location + 'lingue.ini');
for i:=0 to Form1.ComponentCount-1 do
begin
Form1.Controls[i].Caption:=INI.ReadString(Lingua,Form1.Components[i].Name,'');
end;
Ini.Free;
Cmb_Lingue.Text:=Lingua;
end;
end.
Il codice è abbastanza semplice, è facile dedurne il funzionamento compilandolo. Se avete soluzioni migliori o alternative (ma sempre il più possibile multipiattaforma) mandatemeli tramite messaggio, io provvederò a pubblicarli.
Per funzioni/procedure ricorsive si intendono
funzioni/procedure che al loro interno richiamano loro stesse. Questo
tipo di logica che viene chiamata logica ricorsiva permette un codice
più pulito e più facilmente leggibile. Prendiamo in considerazione
l'esempio principe per capire la ricorsione: il fattoriale.
{La
funzione che permette la fatturazione di un numero}
function
Fatt(numero: integer): longint;
begin
if
numero=1 then
Fatt:=numero
else
Fatt:=(numero)*(Fatt(numero-1));
end;
Come si può vedere la funzione in questione si chiama
Fatt e prende come parametro un numero intero. Ma se andiamo ad
analizzare il codice possiamo trovare la seguente riga come anomala:
Fatt:=(numero)*(Fatt(numero-1)); perché
ritorniamo come valore la moltiplicazione del parametro per il
risultato di una chiamata a se stessa. Cioè all'interno della
funzione Fatt esiste una chiamata alla funzione Fatt. Questo modo di
usare le funzioni/procedure è molto pulito e facile da interpretare,
ma ad esempio rispetto ad un ciclo all'interno di una funzione occupa
più spazio in memoria. Per creare delle buone funzioni/procedure
ricorsive è necessario che le stesse non entrino in un ciclo
infinito, per ciclo infinito si intende che non si verifica mai la
condizione per uscire dal ciclo. Per esempio immaginiamo di aver
dichiarato come variabile globale, quindi visibile anche all'interno
di funzioni, un vettore lungo 50 celle di nome esempioric, e vogliamo
sapere quante celle sono valorizzate con il numero 20; dovremmo
scrivere la seguente funzione non ricorsiva:
function
Conteggia20(): integer;
var
i:
integer;
cont:
integer;
begin
cont:=0;
for
i:=0 to 49 do
begin
if
(esempioric[i]=20) then
begin
cont:=cont+1;
end;
end;
Conteggia20:=cont;
end;
Così facendo abbiamo una funzione che cicla fino a fine
vettore e ogni qual volta incontra un valore 20 all'interno del
vettore incrementa di uno la variabile cont, che corrisponde al
risultato che vogliamo ottenere. Ma questo non è l'unico modo per
scrivere questa funzione, perché con la ricorsione possiamo
ottenere:
function
Conteggia20Ric(indice: integer): integer;
begin
if
indice>=50 then
Conteggia20Ric:=0
else
begin
if (esempioric[indice]=20) then
Conteggia20Ric:=1+Conteggia20Ric(indice+1)
else
Conteggia20Ric:=Conteggia20Ric(indice+1);
end;
end;
Come si può evincere dall'esempio non esiste un ciclo
all'interno della funzione stessa, ma semplicemente la funzione
analizza il valore della prima cella e poi se non si trova all'ultima
cella controlla tramite una chiamata a se stessa (e qui entra in
gioco la ricorsione) il valore della cella successiva. C'è da notare
anche una altra cosa che la seconda funzione scritta (quella
ricorsiva) ha un parametro, che corrisponde alla cella del vettore
che deve analizzare, quindi quando dal programma principale
richiameremo la funzione bisogna passargli come parametro il valore
0.
Per libreria si intende una raccolta di procedure e
funzioni. L'uso delle librerie è molto frequente perché permette di
suddividere la scrittura del codice da uno a più file, in modo da
rendere il codice più leggibile, meglio strutturato e
riutilizzabile. Per realizzare una nuova libreria basta andare su
FILE → NUOVA UNIT dopodichè comparirà questo codice:
unit
Unit2;
{$mode
objfpc}{$H+}
interface
uses
Classes, SysUtils;
implementation
end.
Dove unit è una parola riservata che identifica
l'inizio della nostra libreria e Unit2 rappresenta il nome della
nostra libreria. Nel nostro caso dobbiamo cambiare Unit2 con il nome
che vogliamo dare alla nostra ipotetica libreria, ipotizziamo
FunzioniRiciclabili, si noti che quando si va a salvare la libreria
bisogna dargli lo stesso nome della unit.
Dopodichè esiste un commento che ignoriamo per andare a
vedere la parola riservata interface, dove all'interno di questo
segmento di codice possiamo dichiarare costanti, variabili, funzioni
e procedure visibili dall'esterno della libreria stessa.
Segue poi la parola riservata uses dove all'interno di
questo segmento di codice dobbiamo inserire le librerie che ci
servono per poter compilare le nostre funzioni e procedure.
Infine esiste il blocco implementation end. che
contengono l'implementazione del codice, ovvero dove dobbiamo
scrivere le procedure e le funzioni per intero.
Vediamo ora un esempio pratico, questa librerie che
segue contiene una sola funzione di nome
ContieneCaratteriNonConcessi.
unit
FunzioniRiciclabili;
{$mode
objfpc}{$H+}
interface
function
ContieneCaratteriNonConcessi(MiaStringa: string): integer;
implementation
uses
Crt;
{Mi dice se nella stringa ci sono caratteri
tipo il punto e virgola che non sono ammessi}
function
ContieneCaratteriNonConcessi(MiaStringa: string): integer;
var
i: integer;
lung: integer;
Esci: integer;
begin
lung:=Length(MiaStringa);
i:=1;
Esci:=0;
while ((i<lung) AND (Esci=0)) do
begin
if MiaStringa[i]=';' then
begin
Esci:=1;
end;
i:=i+1;
end;
ContieneCaratteriNonConcessi:=Esci;
end;
end.
Come si può evincere da questo esempio il nome della
libreria è FunzioniRiciclabili e l'unica funzione contenuta al suo
interno lavora sulle stringhe, ovvero in base ad una stringa passata
per parametro che se non contiene il carattere ; allora restituisce
il valore numerico 0, altrimenti restituirebbe 1.
Ora che abbiamo scritto la nostra libreria è importante
riuscire a poterla usare nelle altre unit che compongono il
programma. Per fare ciò bisogna immettere nella sezione uses della
unit in cui vogliamo usare la funzione appena scritta il nome della
libreria appena creata, nel nostro caso sarebbe FunzioniRiciclabili,
e ora possiamo usare la funzione ContieneCaratteriNonConcessi
liberamente.
Fino ad ora abbiamo visto solo programmi che
permettevano il mantenimento di dati in memoria solo fino al termine
dell'esecuzione dello stesso. Mantenere i dati memorizzato oltre
l'esecuzione del programma è possibile tramite l'uso dei file. Per
capire come funzionano analizziamo il seguente esempio.
procedure
LavoriamoConIFile();
var
F: system.Text;
Riga: string;
begin
AssignFile (F,'Test.txt');
rewrite (f);
writeln (f,'Uso dei file1!');
CloseFile (f);
AssignFile (F,'Test.txt');
append (f);
writeln (f,'Uso dei file2!');
CloseFile (f);
AssignFile (F,'Test.txt');
reset
(f);
while
not eof(F) do
begin
readln(f,Riga);
writeln(Riga);
end;
CloseFile (f);
writeln('FINITO!');
end;
Abbiamo dichiarato due variabili, la variabile F che
contiene lo stream al file e la variabile Riga che conterrà i dati
letti dal file. In questo modo:
var
F: system.Text;
Riga: string;
Dopodiché abbiamo detto al programma che vogliamo
aprire un canale di comunicazione con un file che in questo caso si
chiama Test.txt e che il canale in questione si chiama F, e lo
abbiamo fatto in questo modo:
AssignFile (F,'Test.txt');
Però al momento non abbiamo ancora aperto fisicamente
un canale di comunicazione con il file, cosa che facciamo in questa
maniera:
rewrite (f);
Il rewrite non è il solo modo di aprire il canale verso
il file. Ci sono tre modi di farlo e sono:
rewrite: che apre il file in scrittura, se non c'è lo
crea e se esiste con del contenuto ne cancella il contenuto.
reset: che apre il file in sola lettura, e se il file
non esiste restituisce un errore
append: apre il file in scrittura ma senza cancellarne
il contenuto, aggiungendo al fondo del contenuto già esistente.
In seguito abbiamo scritto all'interno del file la
stringa 'Uso dei file1!', in questo modo:
writeln (f,'Uso dei file1!');
al passo successivo abbiamo chiuso in canale il sola
scrittura sul file in questo modo:
CloseFile (f);
Bene così facendo abbiamo creato un file di nome
Test.txt se ancora non esisteva, se invece già esisteva allora lo
abbiamo svuotato. Dopodiché abbiamo inserito la stringa 'Uso dei
file1 !'. Ma il programma non è ancora terminato, e vediamo come
prosegue:
AssignFile (F,'Test.txt');
append (f);
writeln (f,'Uso dei file2!');
CloseFile (f);
Grazie a quello appena imparato possiamo analizzare il
blocco per intero, che apre il canale al file senza svuotarlo (grazie
al comando append) e inserisce in coda la stringa 'Uso dei file2!',
in seguito chiude il canale al file.
Ma il programma ancora non è terminato, analizziamo il
pezzo successivo:
AssignFile (F,'Test.txt');
reset
(f);
while
not eof(F) do
begin
readln(f,Riga);
writeln(Riga);
end;
CloseFile (f);
writeln('FINITO!');
In questo ultimo blocco che andiamo ad analizzare
possiamo subito notare che grazie al comando reset apriamo il canale
al file in sola lettura. Dopodiché entriamo in un ciclo da cui si
esce solo quando si è a fine file. Ogni volta che eseguo una
interazione (ovvero un giro del codice contenuto nel ciclo) leggo una
riga dal file con il comando readln passandogli come parametri il
canale f e una stringa che conterrà la riga stessa e poi la stampo
grazie al comando writeln. Una volta che sono arrivato a fine file
stampo a video la stringa 'FINITO!'.
Da tenere ben presente è la dicitura EOF che significa End Of File e che serve ad identificare la fine del file.
Questo blog non rappresenta una testata giornalistica poiché viene
aggiornato senza alcuna periodicità. Non può pertanto considerarsi un
prodotto editoriale ai sensi della legge n. 62/2001.
Questo sito utilizza cookie, anche di terze parti, per offriti servizi in linea con le tue preferenze. Chiudendo questo banner, scorrendo questa pagina, cliccando su un link o proseguendo la navigazione in altra maniera, acconsenti all’uso dei cookie.