In C++Builder è definita la classe
AnsiString
, utile per memorizzare sequenze
di caratteri in modo più immediato e flessibile rispetto alle
stringhe previste in C e C++ ANSI, in
quanto, oltre a memorizzare i caratteri, è dotato di numerosi
metodi che ne permettono l'elaborazione. La
stringa C è contenuta nella proprietà data
della classe AnsiString
.
AnsiString
è una classe atipica, in
quanto funziona sia come struttura sia come
classe standard.
Una stringa può essere creata in modo implicito, senza cioè
invocare esplicitamente un costruttore
semplicemente assegnando ad una
variabile di tipo
AnsiString
un
valore espresso da una sequenza di
caratteri compresa tra virgolette doppie.
Esempio
AnsiString nome = "Paolo", cognome="Rossi";
Il carattere n-esimo di una stringa è ottenuto nel seguente modo.
AnsiString nome = "Paolo", cognome="Rossi"; char nc1 = nome[1]; //nc1 vale 'P' char cc2 = cognome[2]; //cc2 = 'o';
Quando una variabile di tipo AnsiString è definita in questo modo i suoi metodi sono accessibili tramite il selettore di campo punto (.)
AnsiString nome = "Paolo", cognome="Rossi"; AnsiString nome_maiuscolo = nome.Uppercase(); //nome_maiuscolo vale "PAOLO" int lunghezza_cognome = cognome.Length(); //lunghezza_cognome vale 5
Nella tabella seguente si elencano alcuni metodi della classe
AnsiString
. Per una documentazione più
completa riferirsi allo Help di C++Builder. Negli esempi
a e b sono variabili di tipo
AnsiString
, c di tipo
char
, n di tipo
int
.
tipo | funzione | descrizione |
---|---|---|
bool | IsEmpty() | true se la stringa è vuota,
false altrimenti |
int | Lenght() | Lunghezza della stringa: n= a.Length() |
int | Pos(const AnsiString& subStr) | posizione di subStr nella stringa: n=a.Pos(b)( |
AnsiString | UpperCase() | Tutto maiuscolo: b=a.UpperCase() |
AnsiString | LowerCase() | Tutto minuscolo: b=a.Lowercase() |
AnsiString | Trim() | Elimina i caratteri vuoti iniziali e finali: b=a.Trim() |
AnsiString | SubString(int index, int count) | Sottostringa: b = a.SubString(3,6) |
AnsiString | Delete(int index, int count) | Cancellazione di caratteri: b = a.Delete(3,2) |
AnsiString | Insert(const AnsiString& str, int index) | Inserimento di sottostringa: b = a.Insert("xxx",2) |
void * | data() | puntatore alla stringa C++: char *c=(char*)a.data() |
Si possono creare oggetti di classe
AnsiString
, individuati da
puntatori, usando i costruttori
espliciti di tale classe in unione con l'operatore
new
.
Esempio
AnsiString *n; n = new AnsiString("Alessandro");
In questo caso, poiché l'oggetto è individuato da un puntatore, il selettore di campo è la freccia a destra ->.
Un oggetto di tipo AnsiString
creato in
questo modo, quando non serve più va
distrutto esplicitamente.
Esempio
AnsiString *n; n = new AnsiString("Alessandro"); AnsiString n_maiuscolo = n->UpperCase(); ShowMessage(*n); ShowMessage(n_maiuscolo); delete n;
Nella tabella seguente si elencano alcuni costruttori della
classe AnsiString
. Per una documentazione
più completa riferirsi allo Help di C++builder.
costruttore | descrizione |
---|---|
AnsiString() | Stringa vuota |
AnsiString(const AnsiString& src) | Copia un'altra stringa AnsiString |
AnsiString(const char* src) | Copia un'altra stringa C++ |
AnsiString(int src) | Copia un numero intero |
AnsiString(double src) | Copia un numero reale |
Più variabili dello stesso tipo
(tutte int
o tutte
char
o tutte
AnsiString
, ecc) possono essere
organizzate in un unico contenitore con un
identificatore collettivo detto
array
. Le singole variabili sono
dette elementi dell'array.
Un array è dichiarato solitamente scrivendo il nome del tipo degli elementi seguito da un identificatore a sua volta seguito da coppie di parentesi quadre in numero corrispondente alla dimensione dell'array. Ogni coppia di parentesi contiene un intero indicante il numero di elementi nella corrispondente dimensione
Una volta definito un array, i suoi elementi sono individuati tramite tanti indici interi quante sono le sue dimensioni: ognuno di questi indici va posto tra parentesi quadre. L'indice del primo elemento è 0 (non 1), per cui in un array di n elementi l'indice può variare da 0 a n-1.
Gli arrays possono essere inizializzati direttamente nel modo mostrato nell'esempio.
int giorni_mese[12] = {31,28,31,30,31,30,31,31,30,31,30,31}; AnsiString settimana[7]={"Lunedì","Martedì","Mercoledì","Giovedì","Venerdì", "Sabato","Domenica"}; ........................... int giorni_gennaio = giorni_mese[0]; AnsiString festa = settimana[6]; if (anno_bisestile) giorni_mese[1] = 29;
Si possono anche dichiarare array aperti
(open array
): in questo caso
l'identificatore è seguito da una coppia di parentesi quadre
senza contenuto. Negli array aperti il dimensionamento avviene
implicitamente in base al contenuto assegnato. Questa modalità
può essere utile per dichiarare stringhe.
Una sequenza di caratteri ASCII è normalmente detta stringa e, nel C++, viene spesso rappresentata da un array di caratteri dimensionato implicitamente. C++Builder rende disponibile al programmatore il tipo AnsiString di uso più immediato e più flessibile.
char nome[] = "Giuseppe"; AnsiString stesso_nome = "Giuseppe";
Esempio di array bidimensionale:
AnsiString musicisti[2][2] = {{"Gioacchino","Rossini"},{"Giuseppe","Verdi"}}; AnsiString nome, cognome; ........................... // nome e cognome assumono i valori "Gioacchino" e "Rossini" nome = musicisti[0][0]; cognome = musicisti[0][1]; ........................... // nome e cognome assumono i valori "Giuseppe" e "Verdi" nome = musicisti[1][0]; cognome = musicisti[1][1];
In generale, un array può essere dichiarato ed inizializzato nel modo mostrato nell'esempio, in cui si compila una tavola pitagorica.
int tavpit[10][10]; for (int i = 0; i < 10; i++ ) for (int j = 0; j < 10; j++ ) tavpit[i][j] = (i+1)*(j+1); /* La prima riga di tavpit, quella di indice i=0, contiene i numeri da 1 a 10. La seconda riga, con indice i=1, contiene i numeri pari da 2 a 20, ecc. La settima riga, con indice i=6, contiene i multipli di 7. */ int sette_per_otto = tavpit[6][7];
L'assegnazione è l'attribuzione di un valore ad una variabile. Variabile e valore devono essere dello stesso tipo.
L'operatore di assegnazione è
=
.
Di norma la sequenza è identificatore = valore ma sono possibili forme contratte di assegnazione.
float base; base = 2.5; AnsiString nome; nome = "Giovanni";
In C++ l'assegnazione di valore ad una variabile può avvenire subito al momento della sua dichiarazione.
float base = 2.5; AnsiString nome = "Giovanni";
In C++ sono possibili assegnazioni concatenate.
int a, b, c; c = b = a = 1; //equivalente a a = 1; b = a; c = b;
Un blocco (o istruzione composta) è una sequenza di istruzioni che vengono eseguite dalla prima all'ultima, a meno che il flusso di esecuzione non venga forzatamente deviato da un'istruzione di controllo di flusso.
Un blocco è delimitato da una coppia di parentesi graffe. Queste parentesi non sono esplicite nelle tastiere italiane. Si possono ottenere dai tasti in cui compaiono le parentesi quadre premendoli contemporaneamente ad AltGr+Maiuscolo o da tastierino numerico con Alt123 e Alt125.
int a, b, c; a = 2; b = 1; if (a>b) { c = a; a = b; b = c; }
Una variabile dichiarata
all'interno di un blocco non è accessibile dall'esterno del
blocco e viene distrutta all'uscita dal blocco. Nell'esempio
precedente, dato che la variabile c
serve
solo all'interno del blocco, sarebbe conveniente scrivere
così:
int a, b; a = 2; b = 1; if (a>b) { int c = a; a = b; b = c; }
Di norma la parentesi di chiusura non va seguita dal punto e virgola che però è necessario al termine della dichiarazione di una classe.
Se il blocco non è il corpo di una funzione ed è composto da un'unica istruzione, le graffe sono facoltative.
int a, b, c; a = 2; b = 1; if (a>b) c = a; else c = b;
I campi sono componenti grafici che permettono operazioni di input/output da parte dell'utente. In generale sono di pronto uso: si prelevano dalla palette e si inseriscono in un contenitore precedentemente approntato.
Alcuni campi di uso comune
E' una opzione di un menu principale (TMainMenu) o di un menù a tendina (TPopupMenu). Si inserisce in uno di questi contenitori tramite un apposito editor di menù.
Una scritta di cui si può controllare il contenuto e la forma.
Una zona in cui è possibile scrivere o emettere un testo di una riga.
Combina insieme TLabel e TEdit.
Un campo in cui è possibile scrivere o stampare un testo articolato su molte righe. In pratica un semplice editor come NotePad.
Un bottone che può essere cliccato per produrre una opportuna risposta.
Una tabella strutturata in caselle su righe e colonne nelle quali possono essere tabulati dati rappresentati da stringhe.
Un rettangolo in cui può essere riprodotta un'immagine preesistente su file.
Un rettangolo in cui opportune istruzioni possono produrre disegni.
Questo componente permette la gestione di una database: lettura e scrittura di records, ricerca, ecc.
Se si prevede che un'istruzione o un blocco di istruzioni possano o debbano essere reiterati, conviene inserirli in una adeguata struttura iterativa.
A questo scopo possono essere usate tre diverse forme:
In un ciclo a controllo finale il blocco di istruzioni da iterare viene eseguito almeno una volta. Infatti il controllo sulla condizione di ripetizione del blocco viene fatto al termine del blocco stesso: il blocco viene ripetuto se il valore logico di controllo risulta vero. Per evitare cicli infiniti è necessario che una reiterazione del blocco cambi il valore del controllo.
Se nel blocco è presente l'istruzione
break
il ciclo viene interrotto immediatamente.
La struttura del ciclo è
do { istruzione1; istruzione2; ...... } while (controllo==true);
Esempio:
AnsiString filosofo[3] = {"Aristotele","Eraclito","Platone"}; int i = 0; do { ShowMessage(filosofo[i]); i++; } while (i<3);
Il ciclo con contatore è una variante del ciclo a controllo iniziale. In esso l'inizializzazione della variabile di controllo, il controllo e l'incremento da attribuire alla variabile di controllo ad ogni iterazione sono definiti nell'intestazione del ciclo, separati da punto e virgola.
Nella versione più semplice ed usuale del ciclo la variabile di controllo è di tipo intero e viene incrementata (o decrementata) di un'unità ad ogni iterazione.
I cicli si possono 'annidare' l'uno dentro l'altro.
Esempio:
int i, j, temp, fine = 100; int numero[100]; for (i = 0; i < fine; i++) numero[i] = random(100); for (i = 0; i < (fine-1); i++) for (j = i+1; j < fine; j++) if (numero[i]<numero[j]) { temp = numero[i]; numero[i] = numero[j]; numero[j] = temp; }
Ci possono essere più variabili di controllo e possono essere dichiarata nell'intestazione in modo da essere definita solo all'interno del ciclo. L'incremento può essere diverso da 1.
Esempio:
int n_pari[100]; for (int i=0, j=2; i < 100; i++, j+=2) n_pari[i] = j;
In C++ la variabile di controllo può essere anche di tipo reale.
Esempio:
float serie[200] for (int i=0, float a=0; a < 10; i++, a += M_PI/6 ) serie[i] = a;
Se nel blocco è presente l'istruzione
break
il ciclo viene interrotto immediatamente.
In un ciclo a controllo iniziale il blocco di istruzioni del ciclo viene eseguito solo se è vero un valore logico controllato preliminarmente all'esecuzione del ciclo. Per evitare cicli infiniti è necessario che ad un certo momento qualche istruzione del ciclo modifichi il valore della condizione di iterazione.
Se nel blocco è presente l'istruzione
break
il ciclo viene interrotto immediatamente.
Le la condizione di controllo è inizialmente falsa il blocco non viene mai eseguito.
La struttura del ciclo è
while (condizione) { ...............; ...............; }
Esempio:
float a = 3f, b = 1.5f; while ( a < 15 ) a += b;
Una classe è la descrizione generale delle caratteristiche e delle modalità di elaborazione comuni a tutta una categoria di unità di informazione.
La dichiarazione di una classe è fondamentalmente composta dal suo identificatore seguito da un blocco contenente le dichiarazioni dei membri della classe che sono di due tipi:
Le proprietà vanno dichiarate scrivendo tipo e identificatore.
I metodi sono funzioni che hanno accesso a tutti i membri della classe. Nella dichiarazione della classe vanno scritti solo i prototipi dei metodi.
Una classe i cui membri siano accessibili dall'esterno deve
essere dichiarata pubblica
(public
).
Allo stesso modo, i membri della classe che si vuole sia accessibili dall'esterno devono essere dichiarati pubblici.
Una classe può ereditare i suoi membri da una classe descritta nello stesso sorgente o contenuta in un file header incluso.
Esempio:
// Dichiarazione della classe Poligono. // La classe Poligono eredita tutti i membri della classe TObject // predefinita nella libreria di default di C++Builder; class Poligono: public TObject { public: int n_lati; //proprietà float lato[100]; //proprietà float perimetro, area; //proprietà Poligono(int nl); //costruttore ~Poligono(); //distruttore; void Perimetro(); //metodo virtual float Area(); //metodo virtuale }; //la classe Triangolo eredita tutti i membri della classe Poligono, //e ne aggiunge altri specifici class Triangolo: public Poligono { public: float baricentro[2]; //proprietà: una coppia di n. reali void Baricentro(); //costruttore };
In C++Builder le dichiarazioni delle classi vanno registrate
nel file di intestazioni (header) della
unità in elaborazione, dotato di estensione
h
, mentre i corpi dei suoi metodi vanno
descritti e registrati nel file di implementazione
della unità, con estensione cpp
.
Files di intestazione e files di implementazione hanno lo stesso
nome che è quello della unità di cui fanno parte.
Ad esempio, dopo aver dichiarato la classe Poligono nel file UnitGeometria.h della unità UnitGeometria, si implementa il metodo Perimetro() nel file UnitGeometria.cpp nel seguente modo.
void Poligono::Perimetro() { perimetro = 0; for (int i=0; i< n_lati; i++) perimetro += lato[i]; }
Una classe può possedere uno o più costruttori.
Una classe può possedere un metodo
distruttore il cui identificatore
coincide con l'identificatore della classe preceduto dal segno
~
(tilde) che nelle tastiere italiane si
ottiene con Alt126.
Da una classe possono essere generati oggetti.
Se una classe è dichiarata astratta
(abstract
) essa può servire solo per
derivare altre classi ma non per produrre oggetti.
Un commento in un sorgente C++ è un testo che non fa parte del codice eseguibile, utile ai programmatori per illustrare le fasi di elaborazione.
Sono possibili due tipi di commento
Un commento in riga è introdotto da una coppia di barre ( // ) e termina alla fine della riga di testo in cui è posto.
Un commento esteso può espandersi su più righe di testo. Il segnale di inizio è barra+asterisco ( /* ); il segnale di fine è asterisco+barra ( */ ).
Un componente di C++Builder è una classe dichiarata e implementata in una delle librerie che lo corredano.
Le librerie di C++Builder predefiniscono classi per molti tipi di componenti.
Molti componenti grafici sono rappresentati da icone nella palette dell'interfaccia grafica di C++Builder, raggruppati in fogli.
Il foglio che contiene i componenti grafici di uso più comune e immediato + quello intestato Standard.
Si possono utilizzare immediatamente questi componenti in un
proprio progetto cliccando prima sull'icona e poi dentro al
contorno di una TForm o di un altro
contenitore precedentemente collocato.
Conviene poi, di norma, settare le proprietà del componente
agendo sui campi dell'ObjectInspector
collocato a sinistra della TForm in
elaborazione.
Durante queste operazioni, C++Builder deriva automaticamente e
silenziosamente dalla classe del componente selezionato la
dichiarazione di una classe figlia, con nome corrispondente a
quello scritto nel campo Name
di
ObjectInspector
e con i valori assegnati
alle proprietà. Da questa classe poi C++Builder deriva un
oggetto.
Il lavoro svolto da C++Builder è visibile nel file header dell'unità in elaborazione.
L'utente può anche svolgere questo lavoro manualmente,
dichiarando le proprie classi figlie dei componenti di
C++Builder, ma deve poi anche provvedere a dichiarare gli oggetti
e produrli esplicitamente con l'operatore
new
I componenti grafici ('visual') di uso più comune in C++Buider si possono dividere in due categorie:
Le librerie del C++ definiscono e rendono disponibili al programmatore moltissimi componenti non grafici.
Nelle librerie di C++Builder sono disponibli molte classi 'non visual', cioè che non producono schemi grafici e non sono quindi prelevabili dalla palette. Dopo essersi accertati di aver incluso nella propria unità il file header dell'unità in cui sono dichiarate, si possono dichiarare loro oggetti. Se ne riportano alcune di uso frequente.
Gli oggetti di classe TStringList sono liste di oggetti AnsiString.
La classe TStringList è dotata di numerosi metodi per aggiungere, togliere, ordinare, ecc. le stringhe nella lista.
TStringList *lista = new TStringList(); lista->Sorted = true; lista->Add("Roma"); lista->Add("Firenze"); lista->Add("Napoli"); //lista contiene ora le tre stringhe, ma in ordine alfabetico for (int i=0; i<lista->Count; i++) ShowMessage(lista->Strings[i])
Gli oggetti di classe TStringList sono utili anche per leggere o scrivere file di testo. Esempio:
TStringList *lettura = new TStringList(); lista->Sorted = true; lettura->LoadFromFile("citta.txt"); //poiché la proprietà Sorted è vera, i dati vengono ordinati lettura->SaveToFile("citta.txt"); //nel file ora i dati sono ordinati
Gli oggetti di classe TBitmap permettono di rappresentare ed elaborare figure. Esempio:
Graphics::TBitmap *bm = new Graphics::TBitmap(); bm->LoadFromFile("figura.bmp"); Canvas->Draw(0,0,bm);
TCanvas fornisce un supporto per l'output grafico.
Non è frequente dover dichiarare oggetti di questa classe in quanto le classi interessate alla grafica, come TFormm TPaintBox, TBitmap sono di dotate di una proprietà Canvas di questo tipo.
La classe TScreen rappresenta lo schermo del monitor.
Non è frequente dover dichiarare oggetti di questa classe in quanto C++Builder crea ed associa automaticamente ad ogni applicazione l'oggetto globale Screen le cui proprietà si conformano allo schermo in uso. Esempio:
Width = Screen->Width/3; Height = Screen->Height/2; Screen->Cursor = crDefault;
Gli oggetti di questa classe sono utili per leggere e scrivere dati su disco. Esempio:
char buffer[101]; TFileStream *file1 = new TFileStream("file_da_leggere",fmOpenRead); file1->ReadBuffer(buffer,100); delete file1; //nell'array buffer ci sono 100 bytes del file aperto; for (int i=0; i<100; i++) buffer[i] = 'x'; file1 = new TFileStream("file_da_leggere",fmOpenWrite); file1->WriteBuffer(buffer,100); //ora i primi 100 bytes del file sono dei bytes 120 ('x') delete file1;
I contenitori sono componenti grafici che possono contenere altri contenitori o campi. Sono caratterizzati da molte proprietà che permettono di controllare dimensioni, colori, caratteri, ecc.
Alcuni contenitori di uso frequente
I contenitori più generali di C++Builder sono quelli di classe TForm, che corrispondono alle 'finestre' di Windows.
In generale ogni progetto C++Builder ha almeno una TForm che funge da interfaccia grafica tra l'applicazione e l'utente.
Una TForm non può essere contenuta in altri contenitori i quali, invece o direttamente o indirettamente sono contenuti in una TForm.
Le TForm sono dotate della proprietà Canvas, un oggetto di classe TCanvas su cui è possibile produrre output grafico.
Un progetto può contenere molte TForm, che vengono generate automaticamente cliccando sull'apposita icona. Per ogni TForm del progetto, C++Builder genera una unità che codifica la classe della nuova TForm, derivata da quella di libreria e che va salvata in un file apposito.
Dentro una TForm viene solitamente collocato il contenitore TMainMenu destinato a sua volta a contenere i campi TMenuItem.
Menù a tendina destinato a sua volta a contenere i campi TMenuItem.
TPanel è un rettangolo che può contenere
una scritta (Caption
) e altri contenitori
o campi.
TGroupBox assomiglia a TPanel, ma è dotato di una intestazione e collega tra loro gli oggetti contenuti.
TRadioGroup può contenere dei selettori grafici per opzioni mutuamente esclusive (TRadioButton).
Ogni classe è in grado di generare oggetti in quanto dotata di appositi metodi detti costruttori.
Ogni costruttore ha come identificatore il nome della classe.
In C++Builder tutte le classi, in quanto discendenti dalla
classe 'capostipite' TObject
ereditano da
questa il costruttore senza parametri NomeClasse() che quindi non
è necessario esplicitare.
Gli altri eventuali costruttori si diversificano tra di loro
per le diverse associazioni di
parametri: vedere gli esempi di
costruttori della classe
AnsiString
.
La costruzione di un oggetto della classe data si effettua con
l'operatore new
seguito dal
costruttore adeguato.
Esempio: per costruire l'oggetto lista di classe
TStringList
:
TStringList *lista; // lista è destinato a puntare ad un oggetto di classe TStringList // ma l'oggetto non esiste ancora. lista = new TStringList(); // Ora l'oggetto esiste da qualche parte in RAM e lista // ne ha l'indirizzo.
Ogni dato (variabile o funzione) del C++ non può essere usato prima di essere stato dichiarato in tipo ed identificatore.
Esempi.
Dichiarazioni di variabili
int m, n; double alfa, beta; bool vero; int *punta_m, *punta_n; double *punta_alfa, *punta_beta; AnsiString stringa; char *stringaC; TStringList *lista; Graphics::TBitmap *bm; MiaClasse *oggetto1;
Dichiarazioni di funzioni
void Repaint(); long Fattoriale(long n); double Sommatoria(double a[], int n);
In generale la visibilità (ingl. 'scope') e la durata di un dato sono limitate al blocco in cui esso viene dichiarato. Se si cerca di fare riferimento al valore di un dato fuori dal blocco in cui esso è stato definito il compilatore emette un messaggio di 'identificatore sconosciuto'.
Variabili e funzioni di massima visibilità vanno dichiarate nel file header di una unità. Per quanto riguarda le funzioni, nel file header va scritto solo il prototipo: il corpo va implementato nel file di implementazione.
Un dato dichiarato in una unità, oltre ad essere visibile all'interno della stessa unità, diventa accessibile a tutte le altre unità in cui la prima viene inclusa.
Se un dato è dichiarato come membro di una classe è visibile per gli altri membri della classe. Assieme al prefisso di classe è visibile all'esterno solo se è dichiarato pubblico.
Una variabile dichiarata nel corpo di una funzione è visibile solo all'interno della funzione e cessa di esistere al termine della funzione.
Una variabile dichiarata in un 'sottoblocco' del corpo di una funzione è visibile solo all'interno del sottoblocco e cessa di esistere al termine dello stesso. Esempio
for (int i=0; i<101; i++) { ShowMessage(IntToStr(i)); } i = 10; // Errore! i è sconosciuta fuori dal blocco del ciclo for
Gli oggetti generati da una classe tramite l'operatore new seguito da un costruttore sono individuati da un puntatore del tipo della classe generatrice.
Al termine della loro utilità, l'occupazione di memoria da parte di questi oggetti può diventare ingombrante per una applicazione: è bene quindi che la memoria venga liberata provvedendo alla distruzione dell'oggetto. Questa operazione è comandata dall'operatore delete seguito dal nome dell'oggetto.
delete attiva il metodo distruttore che l'oggetto eredita dalla classe generatrice e distrugge l'oggetto, non la classe .
Ogni classe deve possedere il distruttore, il cui identificatore coincide con l'identificatore della classe preceduto dal carattere 'tilde' ~ (Alt126).
Quando si dichiara una classe derivandola da un'altra precedentemente definita, la nuova classe eredita anche il distruttore che, di solito non va riscritto. E' importante riscrivere un distruttore specifico di una nuova classe, quando questa al suo interno ha altre classi o variabili generate da una allocazione dinamica di memoria che, prima della distruzione della classe, vanno pure distrutte con l'uso degli operatori delete per le classi e free per le variabili.
Un'espressione è un valore determinato dall'azione di uno o più operatori su uno o più valori o immediati o espressi da variabili o prodotti da funzioni o metodi.
Se un'espressione contiene più operatori, la precedenza nell'esecuzione può essere data da regole generali (come nel caso delle operazioni matematiche) o esplicitata con l'uso di parentesi tonde.
Il tipo di operatori che si possono usare in un'espressione dipende dal tipo degli operandi. In linea di massima gli operandi devono essere dello stesso tipo e un'espressione produce un risultato dello stesso tipo degli operandi. Ma ci sono notevoli eccezioni, trattate nelle descrizioni delle varie categorie di operatori.
Le espressioni matematiche si scrivono in linea secondo le convenzioni usuali in molti linguaggi di programmazione per quanto riguarda la notazione degli operatori matematici, le regole di precedenza e l'uso delle parentesi, solo tonde, eventualmente annidate.
Un etichetta è un identificatore usato per marcare la posizione di un'istruzione. L'etichetta deve terminare con i due punti.
Le etichette sono usate come riferimento per le istruzioni di
goto
.
void Lotteria(int numero) { int r, prove=1; start: r = 1+random(91); if (r==n) goto uscito; ShowMessage(IntToStr(n)+ "non è uscito."); prove++; goto start; uscito: ShowMessage(IntToStr(n)+ " è uscito dopo "+IntToStr(prove)+" prove."); }
Una funzione è un complesso di istruzioni che realizzano una elaborazione autonoma.
Il complesso di istruzioni da eseguire forma un blocco detto corpo della funzione, che va incluso tra graffe.
La funzione è caratterizzata da un tipo ed è individuata da un identificatore.
Una funzione può ricevere informazioni dall'esterno tramite
uno o più parametri o
argomenti, ognuno definito in tipo e
identificatore, eventualmente separati tra loro da virgole,
inclusi in una coppia di parentesi tonde che segue
l'identificatore. Se il tipo della funzione è diverso da
void
la sequenza delle istruzioni eseguite
deve terminare con una istruzione
return
seguita da un valore di tipo
identico a quello della funzione. Esempio:
int SommaInteri(int a, int b) { return a+b; }
Una funzione può contenere più istruzioni return producendo valori diversi nei vari casi.
Se presente, il valore indicato da return è il valore della funzione ed è accessibile all'esterno della stessa.
Se la funzione non richiede parametri, tra le parentesi tonde
si può scrivere void
o, semplicemente,
nulla: ma le tonde sono
obbligatorie in quanto hanno la funzione sintattica di
marcare l'identificatore come identificatore di funzione.
Di norma una funzione è 'invocata' da un'istruzione di un'altra funzione. Se la funzione invocata prevede parametri, la funzione invocante deve assegnare un valore ad ogni parametro. Esempio:
int SommaInteri(int a, int b) { return a+b; } void Somma() { int h = 1, k = 3; ShowMessage(IntToStr(SommaInteri(h,k))); }
Nell'esempio, la funzione Somma() invoca la funzione SommaInteri(). Somma() ha assegnato un valore alle variabili h e k. All'atto della chiamata Somma() assegna i valori di h e k rispettivamente alle variabili locali a e b della funzione SommaInteri(). SommaInteri() assume valore 4. Questo valore è l'argomento della funzione di libreria IntToStr() che trasforma il numero 4 nel carattere tipografico '4' che,a sua volta è l'argomento della funzione di libreria ShowMessage().
Se i parametri sono puntatori, la funzione può modificare i valori puntati: in molte situazioni può essere una soluzione efficace, ma risulta anche più pericolosa, in quanto i suoi effetti risultano meno controllabili. Questa tecnica, in linea di massima, va usata con molta prudenza.
Le funzioni, come le variabili, in generale sono accessibili solo nell'ambito in cui sono definite e solo dopo che sono state definite.
In generale, una funzione viene definita in una unità. Se si vuole che la funzione sia accessibile da tutte le altre funzioni della stessa unità 'u' in cui viene definita e da quelle delle altre unità in cui 'u' viene inclusa, conviene scrivere il prototipo della funzione nel file header dell'unità 'u' e il testo della funzione nel file di implementazione dell'unità 'u'.
Esempio. Nel file header:
TStringList *FattoriPrimi(int n);
Nel file di implementazione:
TStringList *FattoriPrimi(int numero) { TStringList *scomposizione = new TStringList(); scomposizione->Add("Scomposizione di "+ IntToStr(numero)); if (numero<4) { scomposizione->Add(IntToStr(numero)); return scomposizione; } unsigned long esponente=0, dividendo = numero; while ((dividendo & 1)==0) { dividendo >>= 1; esponente++; } if (esponente) { scomposizione->Add("2^"+IntToStr(esponente)); esponente = 0; } if (dividendo>1) { double radiceq = sqrt(numero); unsigned long base = 3; while ((dividendo>1) && (base <= radiceq)) if ((dividendo % base)==0) { dividendo /= base; esponente++; } else { if (esponente) { scomposizione->Add(IntToStr(base)+'^'+IntToStr(esponente)); esponente = 0; } base += 2; } if (esponente) scomposizione->Add(IntToStr(base)+'^'+IntToStr(esponente)); else scomposizione->Add(IntToStr(dividendo)+"^1"); } return scomposizione; }
Le librerie di C++Builder rendono disponibili molte funzioni matematiche e di vario tipo.
tipo | funzione | descrizione |
---|---|---|
bool | InputQuery(const AnsiString ACaption, const AnsiString APrompt, AnsiString &Value) | Richiede l'input di una stringa; vera se l'input è confermato. |
void | ShowMessage(const AnsiString Msg) | Emette un messaggio costrituito da una stringa. |
AnsiString | IntToStr(int Value) | Produce la rappresentazione tipografica di un intero. |
int | StrToInt(const AnsiString S); | Produce il codice binario di un intero dalla sua rappresentazione tipografica decimale. |
AnsiString | FloatToStr(long double Value) | Produce la rappresentazione tipografica di un reale. |
long double | StrToFloat(const AnsiString S) | Produce il codice binario di un reale dalla sua rappresentazione tipografica decimale. |
AnsiString | GetCurrentDir() | Produce il pathname della cartella su cui si sta lavorando |
bool | SetCurrentDir(const AnsiString Dir) | Cambia la cartella su cui si sta lavorando |
Le istruzioni di controllo di flusso modificano l'esecuzione sequenziale delle istruzioni presenti in un blocco.
Queste istruzioni si possono raggruppare in quattro tipologie:
Un'identificatore è una sequenza di caratteri alfanumerici che rappresenta mnemonicamente un dato codificato in memoria.
Il primo carattere deve essere alfabetico.
Tra i caratteri alfabetici è incluso anche il carattere di
sottolineatura _
.
Non sono ammessi caratteri accentati.
Le versioni maiuscola e minuscola della stessa lettera sono considerate diverse.
Gli identificatori non possono coincidere con nessuna delle
parole chiave del C++ (ad esempio int
,
bool
, for
,
break
,
class
,...).
Esempi.
int numero = 1, Numero = 100, _numero = -100, n1 = 1000, n_1 = 1234;
Le istruzioni di interruzione di flusso interrompono l'esecuzione sequenziale delle istruzioni presenti in un blocco.
Queste istruzioni sono
return
break
continue
goto
L'istruzione return
può essere
usata nel corpo di una funzione per
terminarla prima di arrivare all'ultima istruzione.
Nel corpo di una funzione possono essere presenti diverse
istruzioni return
.
L'istruzione break
è usata:
L'istruzione continue
è
usata:
L'istruzione goto
è usata:
Un'istruzione (ingl. 'statement') è la descrizione delle elaborazioni da compiere sui valori immediati o su quelli delle variabili o su quelli prodotti da funzioni.
Le istruzioni sono collocate in un blocco e sono terminate da un punto e virgola.
I tipi di istruzione più importanti sono i seguenti.
Le variabili locali sono dichiarate all'interno di un blocco. Sono dette locali perché la loro visibilità è limitata all'ambito del blocco.
Variabili, anche di diverso tipo in blocchi diversi possono avere lo stesso identificatore senza confliggere.
Le funzioni matematiche predefinite in C++ sono dichiarate nella unità math. Perché siano riconosciute dal compilatore è necessario che all'inizio dell'unità in elaborazione ci sia l'istruzione di inclusione #include <math.h>
Diverse funzioni di variabile reale hanno due versioni a
seconda che l'argomento sia di tipo double
o long double
. La versione long
double si differenzia dall'identificatore della versione
double per l'aggiunta di una 'elle' finale.
Esempio:
double rad2 = sqrt(2); long double alfa = asinl(sqrtl(3/2);
In tabella si riportano i nomi di alcune funzioni di uso più frequente. Per semplicità si omettono le versioni long double. Per una documentazione completa consultare lo Help in linea di C++Builder. Osservare che tutti gli identificatori di questa unità sono in caratteri minuscoli.
tipo | funzione | descrizione |
---|---|---|
long double | M_E | base naturale e (n. di Nepero) |
long double | M_PI | π (pi greca) |
int | abs(int x) | valore assoluto di un intero |
double | fabs(double x) | valore assoluto di un reale |
double | sqrt(double x) | radice quadrata |
double | exp(double x) | esponenziale naturale |
double | log(double x) | logaritmo naturale |
double | log10(double x) | logaritmo decimale |
double | sin(double x) | seno circolare |
double | cos(double x) | coseno circolare |
double | tan(double x) | tangente circolare |
double | asin(double x) | arcoseno circolare |
double | acos(double x) | arcocoseno circolare |
double | atan(double x) | arcotangente circolare |
double | sinh(double x) | seno iperbolico |
double | cosh(double x) | coseno iperbolico |
double | tanh(double x) | tangente iperbolica |
double | ceil(double x) | intero immediatamente superiore o uguale |
double | floor(double x) | intero immediatamente inferiore o uguale |
double | hypot(double x, double y) | ipotenusa dei cateti x e y |
C++Builder fornisce un'ulteriore libreria di funzioni matematiche, la unità Math , che in parte ridefinisce le funzioni dell'unità math, in parte espande il repertorio. Gli identificatori delle funzioni di questa libreria incominciano per maiuscola. Per usare queste funzioni bisogna includere la libreria all'inizio del file in cui di usano, scrivendo #include <Math.hpp>.
Un metodo di una classe è una funzione definita all'interno della dichiarazione di una classe. Questa funzione ha accesso ai valori delle proprietà della classe.
Nella dichiarazione della classe, nel file header di una unità, si scrive solo il prototipo del metodo. L'implementazione del metodo va scritta nel file di implementazione, riportando l'intestazione preceduta dall'identificatore della classe di appartenenza seguito da una coppia di due punti (::) e sviluppando il corpo del metodo. Ad esempio, nel file header si scrive
class Poligono: public TObject { public: int n_lati; //proprietà float lato[100]; //proprietà float perimetro; //proprietà Poligono(int nl); //costruttore void Perimetro(); //metodo };
Nel file di implementazione si sviluppa il corpo del metodo.
void Poligono::Perimetro() { perimetro = 0; for (int i=0; i< n_lati; i++) perimetro += lato[i]; }
Se un metodo di una classe è dichiarato
virtuale (virtual
) le
classi derivate ereditano il prototipo, ma possono implementarlo
in modo diverso dalla classe genitrice.
Proprietà e metodi possono essere dichiarati
statici (static
): in
tal caso essi sono gli stessi per tutte le classi derivate: sono
cioè attributi della classe, non dei singoli membri. Se un membro
è statico, non può essere virtuale e può far riferimento solo ad
altri membri statici.
Un oggetto è una concreta realizzazione in memoria (ingl. 'instance') di tutte le caratteristiche definite nella classe da cui viene generato.
Un oggetto viene generato, cioè codificato in memoria,
assegnando al suo identificatore il
valore generato dall'operatore new
seguito
da uno dei costruttori della
classe.
Un oggetto di una determinata classe è identificato da un puntatore tipizzato sulla stessa classe. Esempio.
TStringList *lista; lista = new TStringList();
Nell'oggetto sono realizzati tutti i membri (proprietà e metodi) della classe.
Il valore di un membro dell'oggetto è accessibile indicando
l'identificatore dell'oggetto seguito da
->
(selettore di campo) e dal nome del
campo (metodo o proprietà).
TStringList *lista; lista = new TStringList(); AnsiString primo = lista->First();
Gli operatori di confronto analizzano l'ordinamento di due
valori e producono un valore di
tipo logico
(true
o
false
).
Quindi il risultato di un'espressione contenente un confronto
può essere eventualmente assegnato solo a variabili di tipo
bool
.
operatore | uso | descrizione |
---|---|---|
== | a == b | Vero se a è uguale a b, falso se a è diverso da b |
!= | a != b | Vero se a è diverso da b, falso se a è uguale a b |
> | a > b | Vero se a è maggiore di b, falso se a è minore o uguale a b |
>= | a >= b | Vero se a è maggiore o uguale a b, falso se a è minore di b |
< | a < b | Vero se a è minore di b, falso se a è maggiore o uguale a b |
<= | a <= b | Vero se a è minore o uguale a b, falso se a è maggiore di b |
Gli operatori digitali (ingl. 'bitwise operators') agiscono sui singoli bit delle codifiche binarie dei valori dei tipi interi.
Gli operandi e il risultato degli operatori digitali devono essere dello stesso tipo, devono cioè avere lo stesso numero di bit, poiché gli operatori operano sulle coppie di bit nella stessa posizione (primo-primo, secondo-secondo, ecc.).
Nella tabella seguente a[i] è lo i-esimo bit di a, b[i] è lo i-esimo bit di b, r[i] è lo i-esimo bit di r.
operatore | nome | uso | descrizione |
---|---|---|---|
& | and | r = a & b | r[i]=1 solo se se a[i]==1 e b[i]==1 |
| | or | r = a | b | r[i]=0 solo se a[i]==0 e b[i]==0 |
^ | xor | r = a ^ b | r[i]=0 solo se a[i]==b[i] |
~ | not | r = ~a | r è il complemento a 2 di a |
>> | slittamento a destra | r = a >> 2 | r è uguale ad a diviso 4 (con eventuale resto) |
<< | slittamento a sinistra | r = a << 2 | r è uguale ad a per 4 |
Due operatori unari sono operatori di 'slittamento' (ingl. 'shift'): spostano la sequenza di bit dell'operando verso destra o verso sinistra di tanti posti quante sono le unità indicate a destra del segno. Non ha senso che lo spostamento superi il numero di bit dell'operando.
Slittare a destra un valore di un posto equivale a dividerlo per 2 e, viceversa, slittare a sinistra un valore di un posto equivale a moltiplicarlo per 2.
Il terzo operatore citato produce il complemento a due dell'operando. Nella notazione binaria di un intero, il complemento a due dell'intero è l'intero (di ugual numero di bit) che sommato a quello dato dà zero: in pratica il suo opposto.
Esempio:
Il decimale 10, codificato in binario su 8 bit, è 00001010 Il suo complemento a due è 11110110 (cioè la notazione binaria, su 8 bit, di -10) Infatti 00001010 + 11110110 = __________ 00000000
Questi operatori, come quelli matematici possono apparire in forma contratta nelle assegnazioni.
Gli operatori logici (o booleani) agiscono su operandi di
tipo logico (bool
) e
producono un valore logico
(true/false
).
Solo l'operatore di negazione è unario. Gli altri agiscono su coppie di operandi.
operatore | nome | uso | descrizione |
---|---|---|---|
! | negazione | r = ! a | se a==true, r=false e viceversa |
&& | congiunzione | r = a && b | r = true solo se a==true e b == true |
|| | disgiunzione | r = a || b | r = false solo se a==false e b == false |
 :
Gli operatori matematici binari, che agiscono sui tipi interi e reali, sono, oltre a quelli usuali di somma (+), sottrazione (-), prodotto (*), divisione (/), l'operatore di modulo (%) che produce il resto della divisione tra gli operandi.
Il C++ prevede inoltre vari operatori unari, che agiscono cioè su un solo argomento.
operatore | uso | descrizione |
---|---|---|
- | -a | Opposto di a |
++ | ++a | Incrementa a di 1 e poi lo valuta |
++ | a++ | Valuta a e poi lo incrementa di 1 |
- - | - -a | Decrementa a di 1 e poi lo valuta |
- - | a- - | Valuta a e poi lo decrementa 1 |
Le forme postfisse degli operatori
++
e
--
sono molto usate nelle
implementazioni dei cicli.
Sono possibili inoltre forme contratte di operazione-assegnazione.
operatore | uso | equivalente a |
---|---|---|
+ = | a + = b | a = a + b |
- = | a - = b | a = a - b |
* = | a * = b | a = a * b |
/ = | a / = b | a = a / b |
% = | a % = b | a = a % b |
In un'espressione matematica possono apparire mescolati i vari tipi numerici interi e reali. Il tipo del risultato è quello del tipo più generale che appare nell'espressione.
Se le stringhe sono oggetti della classe
AnsiString
, i loro valori possono essere
concatenati con l'uso dell'operatore
+
.
Esempio:
AnsiString s = "Giuseppe"+" "+"Verdi"; //s ha valore "Giuseppe Verdi" AnsiString n = "Giuseppe"; AnsiString c = "Verdi"; AnsiString s = n+" "+c; //s ha valore "Giuseppe Verdi"
Una stringa AnsiString
non può essere
concatenata con un valore di tipo numerico: in questi casi
bisogna convertire il valore numerico nella sua rappresentazione
tipografica con l'uso delle funzioni
IntToStr
o
FloatToStr
.
Esempio:
int gm = 31; AnsiString s = "Giorni del mese = "+IntToStr(gm); //s ha valore "Giorni del mese = 31"
Le funzioni e i metodi possono ricevere dall'esterno i valori da assegnare a una o più delle loro variabili locali.
Queste variabili locali che vengono inizializzate dall'esterno sono dette parametri e vanno dichiarate in tipo e nome in una lista, separate da virgole, all'interno della coppia di parentesi tonde che accompagna l'identificatore della funzione nel suo prototipo.
Una proprietà (detta anche variabile di classe) rappresenta un dato informativo comune a tutti gli oggetti derivati da una classe.
Per ogni proprietà vanno specificati
tipo
nome (identificatore)
Ad ogni proprietà può essere assegnato un valore.
I valori delle proprietà di una classe accessibili a tutti i metodi della classe che ne possono cambiare il valore con nuove assegnazioni.
Se una proprietà è dichiarata pubblica
(public
) il valore della proprietà
è accessibile e può essere modificato anche dall'esterno della
classe e viene indicato dal nome della classe seguito dal
selettore di campo seguito dall'identificatore della
proprietà.
I puntatori sono variabili che possono contenere l'indirizzo in memoria di un'altra variabile.
Gli identificatori dei puntatori sono preceduti dal carattere
asterisco *
.
I puntatori si possono dividere in due categorie:
void
;Il tipo void
se attribuito ad una
funzione, indica che la funzione non
assume nessun valore. Se attribuito ad una variabile nella
dichiarazione della stessa indica che la variabile può puntare
indirizzi di variabili di qualunque tipo. Non sarà però possibile
accedere all'informazione puntata, senza l'aiuto di un puntatore
tipizzato.
I puntatori tipizzati sono dichiarati anteponendo all'identificatore il tipo del valore puntato.
E' possibile ottenere il puntatore alla collocazione in
memoria del valore di una variabile anteponendo
all'identificatore della variabile l'operatore
&
.
E', viceversa, possibile ottenere il valore puntato da un
puntatore tipizzato anteponendo un asterisco all'identificatore
del puntatore. Questo ovviamente non è possibile con un puntatore
di tipo void
. Esempi:
int a = 1; int *punta_a = &a; void *generico = punta_a; AnsiString stringa_a = IntToStr(*punta_a);// Giusto. AnsiString sbaglio = IntToStr(*generico); // Errore! generico ha l'indirizzo di a //ma non ha informazioni sul tipo di dato //contenuto in a.
Per evitare l'errore evidenziato nell'esempio precedente, i
puntatori di tipo void
vanno
convertiti in puntatori tipizzati Un
esempio tipico è dato dalla funzione
malloc()
che è di
tipo void *
.
L'istruzione di selezione binaria permette di scegliere tra due diversi successivi blocchi di istruzioni a seconda del valore booleana di controllo.
La struttura dell'istruzione è
if (controllo==true) { } [ else { } ]
Le parentesi quadre indicano che la clausola
else
è facoltativa.
Se controllo è vero, viene eseguito il blocco che segue
if
, altrimenti, se è presente la
clausola else
, viene eseguito il
blocco che segue else
.
Spesso risulta utile la forma contratta della selezione binaria. La struttura è la seguente:
(controllo==true)? istruzione_si: istruzione_no;
Esempio:
bool NPari(int n) { (n % 2 == 0)? return true: return false; }
La funzione NPari() potrebbe essere scritta, più usualmente, così:
bool NPari(int n) { if (n % 2 == 0) return true; else return false; }
Ma sarebbe meglio:
bool NPari(int n) { return (n % 2 == 0); }
o ancora più sinteticamente
bool NPari(int n) { return !(n % 2); }
L'istruzione di selezione multipla è una
generalizzazione dell'istruzione di
selezione binaria e permette di
diramare il flusso di esecuzione in più di due direzioni, a
seconda dei valori assunti da una variabile di controllo,
solitamente di tipo intero, che appare come
argomento della clausola
switch
.
La struttura dell'istruzione è la seguente:
switch(controllo) { case val1: ...............; ...............; break; case val2: ...............; ...............; break; .................. .................. [ default: ................; ................; ] }
Se controllo ha valore val1, viene eseguita la sequenza di istruzioni situata dopo case val1. Questa sequenza, come le successive deve terminare con l'istruzione break, altrimenti vengono eseguite anche le istruzioni del caso seguente.
AnsiString numero; int i = random(4); switch(i) { case 0: numero = "zero"; break; case 1: numero = "uno"; break; case 2: numero = "due"; break; case 3: numero = "tre"; break; }
Un uso articolato di break
permette di
far corrispondere lo stesso blocco di istruzioni a diversi valore
del selettore in switch
.
int i = random(4); switch(i) { case 0: ShowMessage("E' uscito 0."); break; case 1: case 3: ShowMessage("E' uscito un numero dispari."); break; default: ShowMessage("E' uscito un numero pari."); }
La clausola default
è
facoltativa e, se presente, deve essere l'ultima e viene eseguita
se non si è verificata nessuna delle evenienze esplicitamente
considerate.
Solitamente con il termine stringa si intende una sequenza di caratteri tipografici.
In C++ il valore di una stringa è delimitato da una coppia di doppie virgolette.
Per memorizzare direttamente una stringa è necessaria una variabile di tipo corrispondente, ma il C++ non prevede un esplicito tipo stringa. Nel C++Builder il problema è superato dall'uso della classe AnsiString.
Nel C++ standard le stringhe possono essere definite implicitamente ed immediatamente inizializzate come array aperti di caratteri. Esempio:
char stringa[]="Questa è una stringa."; ShowMessage(stringa); ShowMessage(stringa[0]);
Con questa codifica, il carattere che segue l'ultimo carattere della stringa è sempre 0 (non '0') che funge da marcatore di terminazione.
Le stringhe allocate in questo modo sono statiche, non possono cambiare la loro collocazione o la loro lunghezza. Non sono possibili riassegnazioni dirette. Sarebbe cioè sbagliato scrivere:
char stringa[]="Questa è una stringa."; char stringa1 = stringa;
In un caso del genere bisognerebbe ricorrere al seguente costrutto:
char stringa[]="Questa è una stringa."; char stringa1[22]; strcpy(stringa1,stringa);
strcpy(char *dest, const char *src)
è
una funzione che copia caratteri da una locazione (sorgente)
all'altra (destinazione) e bisogna essere ben sicuri che la
destinazione abbia posto sufficiente per tutti i caratteri,
compreso lo 0 terminale, altrimenti sono guai.
In effetti, istruzioni come
char stringa[]="Questa è una stringa."; char stringa1[22];
sono dei modi impliciti di allocare bytes in memoria e gli
identificatori stringa e stringa1 sono dei
puntatori al tipo
char
.
Le due istruzioni potrebbero essere scritte più esplicitamente nel seguente modo:
char *stringa= (char*)malloc(22); strcpy(stringa,"Questa è una stringa."); char *stringa1= (char*)malloc(22); strcpy(stringa1,stringa); free(stringa);
void *malloc(size_t size)
è una
funzione di libreria che 'alloca' in memoria size bytes.
Questo modo di utilizzare la memoria è detto
allocazione dinamica è può risultare utile in
molti altri casi. In effetti, quando si costruisce un oggetto con
l'operatore void *malloc(size_t size)
si
alloca dinamicamente l'oggetto in memoria.
malloc()
è una funzione di tipo
void
: per poter associare il suo valore ad
un puntatore di tipo
char
è necessaria un'operazione di
typecasting.
Quando la memoria allocata con malloc()
non serve più è bene ricordarsi di liberarla con l'invocazione
della funzione 'inversa' void free(void
*block)
.
tipo | intestazione | uso |
---|---|---|
void | *malloc(size_t size) | allocazione dinamica di size bytes |
void | free(void *block) | liberazione della memoria puntata da block |
size_t (int) | strlen(const char *s) | lunghezza della stringa |
char * | strupr(char *s) | tutto maiuscolo |
char * | strlwr(char *s) | tutto minuscolo |
char * | strcpy(char *dest, const char *src) | copia src in dest. |
char * | strcat(char *dest, const char *src) | concatena src con dest |
int | strcmp(const char *s1, const char *s2) | confronto= 0: uguali; -1:s1<s2; 1: s1>s2 |
Per tutte le variabili e le funzioni di C++ va sempre stabilito tipo, cioè l'indicazione della modalità di codificazione in memoria o in altri supporti digitali.
I tipi del C++ si possono suddividere in tre categorie:
Basandosi sui tipi del C++ il programmatore può definire tipi
personalizzati per le proprie specifiche esigenze usando
l'istruzione typedef
.
Basandosi sui tipi fondamentali del C++ il programmatore può definire tipi personalizzati a seconda delle specifiche esigenze della sula applicazione.
Conviene definire nuovi tipi nel file header di una unità.
Uno dei modi più semplici è quello di ridenominare gli stessi tipi fondamentali con identificatori giudicati più sintetici o espressivi. Esempio
// Nel file header typedef unsigned char Byte; typedef unsigned short Word; typedef int CentoInteri[100]; // Tipo array. // Nel file di implementazione si potranno usare direttamente i tipi Byte, Word. CentoInteri Byte b1, b2; Word w1, w2; CentoInteri ccc; b1 = 18; b2 = -25; //Errore! b2 non supporta il segno. b2 = 10000; //Errore! Valore fuori dal dominio. w1 = 12000; //Corretto. w2 = 100000; //Errore! Valore fuori dominio. ccc[0] = 12; //Corretto: ccc è un array con 100 elementi. ccc[100] = 44; //Errore! gli indici di ccc vano da 0 a 99.
Il programmatore può definire organizzazioni complesse di dati eterogenei attinenti ad una stesso soggetto: questi complessi di dati sono detti strutture. Esempio:
// Nel file header typedef struct { AnsiString Cognome, Nome; unsigned char Eta; AnsiString Classe, Scuola; float Media; }Studente; typedef Studente studente[30]; // Nel file di implementazione Classe studente; studente[0].Cognome="Amici"; studente[0].Nome="Franco"; studente[0].Eta = 16; studente[0].Classe = "3 A"; studente[0].Scuola = "G. Garibaldi"; studente[0].Media = 7.8; studente[1].Cognome="Beltrami"; studente[1].Nome="Sara"; studente[1].Eta = 16; studente[1].Classe = "3 A"; studente[1].Scuola = "G. Garibaldi"; studente[1].Media = 8.1; // ecc.
Nell'esempio si è definito il tipo Studente come struttura composta da diversi dati; di seguito si è definito il tipo Classe come array di 30 dati di tipo Studente.
Si è poi definita la variabile studente di tipo Classe: quindi studente è un array che può contenere fino a 30 elementi di tipo Studente.
Di seguito si dono attribuiti valori ai campi relativi al primo studente (elemento di indice 0 dell'array) e al secondo studente (elemento di indice 1 dell'array).
Notare che ogni campo relativo ad uno studente è individuato dall'identificatore dello studente (studente[0], studente[1]...) seguito da un punto e dal nome del campo. Il selettore di campo in questo caso è il punto (non la freccia -> come per le classi) perché studente[0], studente[1] non sono puntatori.
Il tipo di un dato è detto fondamentale quando il dato è elementare, non ulteriormente analizzabile in dati di altro genere.
I tipi fondamentali hanno un dominio di valori limitato.
tipo | descrizione | formato |
---|---|---|
interi | ||
char | 1 byte | 8 bit |
int | intero da 4 bytes | 32 bit |
reali | ||
float | singola precisione: 4 bytes | 32 bit (IEEE 754) |
double | doppia precisione: 8 bytes | 64 bit (IEEE 754) |
altri tipi | ||
boolean | logico | true o false |
void | puntatore | nessun valore |
Il tipo char
è un tipo intero perché in
effetti il suo contenuto è un byte e accetta valori interi.
Tuttavia nelle funzioni di output esso viene rappresentato come
carattere alfabetico, secondo il codice ASCII. Esempio:
char c = 65; ShowMessage(c); //ShowMessage presenta la lettera 'A'
Più usualmente a variabili di tipo char
si assegnano direttamente caratteri alfabetici scritti tra apici.
Esempio:
char c = 'A'; ShowMessage(c); //ShowMessage presenta la lettera 'A' AnsiString parola="parola"; for (int i=1; i<=parola.Length(); i++) { c = parola[i]; ShowMessage(c); }
Alcuni dei tipi fondamentali possono essere 'declinati' in vario modo con l'uso dei seguenti attributi preposti al tipo stesso.
attributo | tipi | descrizione |
---|---|---|
signed | char, int | il bit di ordine maggiore è per il segno (0: pos; 1: neg.) |
unsigned | char, int | tutti i bit per la codifica del valore assoluto |
short | int | intero su 2 bytes |
long | int, double | intero su 8 bytes, reale su 10 bytes |
Sono possibili varie combinazioni di questi attributi.
unsigned short int k; signed char c; long double coeff;
In alcuni casi è possibile la conversione implicita di
tipo (ingl. 'typecasting') cioè la
ricodifica di in valore di un tipo in
quello di un'altro tipo compatibile. Ad esempio, è possibile
assegnare ad una variabile di tipo float
un valore
di tipo int
.
int k = 10; float theta = k;
Se si cerca di assegnare un valore ad una variabile di tipo meno generale, si può incorrere in una perdita di informazione che può produrre errori difficili da rintracciare.
Se si eseguono operazioni su operandi di diverso tipo, purché compatibili, il tipo del risultato è quello dell'operando di tipo più generale.
Spesso è utile forzare una conversione esplicita di tipo (ingl. 'typecasting') anteponendo al nome di una variabile il nome del nuovo tipo compreso tra parentesi tonde.
Esempio:
int a = 10; long int aa = (long int)a; double aaa = (double)a;
Quando una funzione può essere esposta a situazioni
problematiche il programmatore può cercare di prevenire
malfunzionamenti della funzione 'sollevando' un'eccezione usando
l'istruzione throw
.
Nel corpo del metodo sarà inserita l'istruzione
throw
seguita dal
costruttore dell'eccezione.
//------------------------------------ void ControlloNome(AnsiString s) { if (s.IsEmpty()) throw EInvalidOp("Input vuoto."); if ((s[1]<'A') || (s[1]>'Z')) throw EInvalidOp("L'iniziale deve essere maiuscola."); for (int i=2;i <= s.Length(); i++) if ((s[i] < 'a') || (s[i] > 'z')) throw EInvalidOp("Input improprio."); } //------------------------------------ AnsiString LeggeNome(AnsiString n) { AnsiString nome_letto = n; if (!InputQuery("Inserire il nome","Nome?",nome_letto)) return n; try { ControlloNome(nome_letto); } catch (EInvalidOp *e) { ShowMessage(e->Message); return n; } return nome_letto; } //------------------------------------ void PrevenzioneErrori() { AnsiString nome = LeggeNome(""); if (!nome.IsEmpty()) ShowMessage(nome); }
La prima funzione, in ordine logico, è
PrevenzioneErrori() che, per raggiungere il
suo scopo, invoca LeggeNome(). Quest'ultima
funzione, a sua volta, invoca ControlloNome()
per verificare che l'input ricevuto da
LeggeNome() sia appropriato.
ControlloNome() fa diversi controlli e se
qualcuno di questi risulta negativo, immette un messaggio nel
campo Message
dell'oggetto di classe
errore EInvalidOp
. Se viene prodotto
questo messaggio, esso viene emesso dalla funzione
LeggeNome() nella sua clausola
catch
.
La struttura 'acchiappa errori' è la seguente:
try { ..................; ..................; } catch(eccezione1 *e1) { ..................; ..................; } catch(eccezione2 *e2) { ..................; ..................; } ....................... ....................... [ finally { ...................; ...................; } ]
La clausola catch
può anche essere
riempita con tre punti consecutivi per 'acchiappare' qualunque
errore.
C++Builder ha predefinite molte classi di Eccezioni: i loro
identificatori cominciano tutti con E maiuscola (vedere la
documentazione in linea). Da queste classi, se necessario,
l'utente può derivare classi personalizzate. Una classe di
eccezioni di uso frequente, sollevata da molte funzioni di
C++Builder, è EConvertError
. Esempio:
void InputInteroPositivo() { AnsiString result=""; bool errore = false; if (!InputQuery("","Numero?",result)) return; try { int temp = StrToInt(result); if (temp<0) throw(EInvalidOp("")); } catch (EConvertError *eccezione) { errore = true; ShowMessage(eccezione->Message); } catch(...) { errore = true; ShowMessage("Input negativo."); } if (!errore) ShowMessage(result); }
In alcuni casi, se c'è affinità di tipo, è possibile assegnare un valore di un certo tipo ad una variabile di tipo diverso, convertendo il tipo del primo valore in quello della variabile cui va assegnato. Per operare questa conversione si fa precedere il nome del valore da assegnare da una coppia di parentesi tonde contenenti il tipo convertito. Esempi:
int n = 10; double alfa = (double)n; void *punta = &n; AnsiString stringa_n = IntToStr(*(int*)punta);
Un progetto C++Builder è formato da un complesso di risorse solitamente articolato in una o più unità (unit) ognuna delle quali viene redatta singolarmente e singolarmente salvata, solitamente nella stessa cartella con le altre unità del progetto, in un file di estensione *.cpp
Nello sviluppo del progetto lo IDE (Integrated Development Environment) del C++Builder permette di aprire e redigere oguna di queste unità.
In particolare, per ogni unità, l'editor dello IDE presenta due fogli:
Salvando l'unità, si salvano i due file redatti, con ugual nome e diverso suffisso.
I files header così generati, possono rendere disponibili i loro dati in altre unità, tramite l'istruzione per il compilatore #include seguita dal nome del file header da richiamare.
Questo nome può essere compreso tra parentesi uncinate (<nome.h>) o tra doppie virgolette ("nome.h") a seconda dei metodi di ricerca da attivare per l'individuazione del file.
I files header disponibili in C++Builder sono tutti quelli delle librerie standard del C++, caratterizzati da nomi tutti minuscoli e dall'estensione h.
C++Builder rende disponibile per il programmatore molte altre librerie che vengono automaticamente incluse nelle unità del programmatore a seconda dei componenti inseriti nelle sue unità. I nomi di queste librerie hanno iniziale maiuscola ed estensione hpp.
Ad ogni variabile può essere assegnato un valore, cioè la codificazione del dato attualmente rappresentato.
Il valore può essere rappresentato:
Il valore assegnato ad una variabile deve essere dello stesso tipo assegnato all'identificatore della variabile.
Per variabili di tipo intero (char, int
) il valore immediato è normalmente espresso da una sequenza di
cifre decimali.
Per le variabili di tipo reale (float e
double
) il valore immediato è normalmente espresso da
una sequenza di cifre decimali in virgola fissa o in notazione
esponenziale in linea (es. 1.6E-19).
I valori di tipo char
sono o
piccoli interi o singoli caratteri compresi in una coppia di
apici (es. 'a', 'W').
I valori di tipo logico (bool
)
sono solo due: true
e
false
.
Anche agli oggetti di classe
AnsiString
può essere assegnato un
valore immediato con le stesse modalità usate per i tipo
primitivi. Il valore immediato di una stringa è una sequenza di
caratteri alfanumerici racchiusa da doppie virgolette. Es.
AnsiString nome = "Paolo".
Il valore associato all'identificatore di un oggetto è fornito
dall'operatore new
seguito dal nome
di un costruttore che, dopo aver
generato l'oggetto un una certa locazione di memoria, ne passa
l'indirizzo all'identificatore.
Il valore dell'indirizzo di un identificatore non ancora
inizializzato dall'operatore new
è
NULL
.
Una variabile è la memorizzazione codificata di una o più informazioni.
Ogni variabile deve esser dichiarata in un blocco, definendone il tipo e l'identificatore.
La dichiarazione può essere fatta in qualunque punto del blocco, ma, in linea di massima, per chiarezza, è bene che le dichiarazioni siano all'inizio del blocco.
Ad ogni variabile può essere assegnato un valore o nella stessa istruzione in cui viene dichiarata o in una successiva istruzione del blocco.
Dopo questa assegnazione il valore è rappresentato dall'identificatore.
Nel linguaggio C++ sono definite varie tipologie di variabili, distinte dal loro ambito di azione (ingl. 'scope')