AnsiString

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.

AnsiString come struttura

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.

Alcuni metodi della classe AnsiString
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()

AnsiString come classe

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.

Alcuni costruttori della classe AnsiString
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

 


array

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];

 


assegnazione

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;

blocchi

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;

 


campi

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

 


TMenuItem

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ù.

 


TLabel

Una scritta di cui si può controllare il contenuto e la forma.

 


TEdit

Una zona in cui è possibile scrivere o emettere un testo di una riga.

 


TLabeledEdit

Combina insieme TLabel e TEdit.

 


TMemo

Un campo in cui è possibile scrivere o stampare un testo articolato su molte righe. In pratica un semplice editor come NotePad.

 


TButton

Un bottone che può essere cliccato per produrre una opportuna risposta.

 


TStringGrid

Una tabella strutturata in caselle su righe e colonne nelle quali possono essere tabulati dati rappresentati da stringhe.

 


TImage

Un rettangolo in cui può essere riprodotta un'immagine preesistente su file.

 


TPaintBox

Un rettangolo in cui opportune istruzioni possono produrre disegni.

 


TTable

Questo componente permette la gestione di una database: lettura e scrittura di records, ricerca, ecc.

 


cicli

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:


ciclo do (iterazione con controllo finale)

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);

 


ciclo for (iterazione con contatore)

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.

 


ciclo while (iterazione con controllo iniziale

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;

 


classe

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.


commenti

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 ( */ ).


componenti

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.

 


componenti non 'visual'

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.

 


TStringList

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

 


TBitmap

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

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.

 


TScreen

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;

 


TFileStream

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;

 


contenitori

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

 


TForm

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.

 


TMainMenu

Dentro una TForm viene solitamente collocato il contenitore TMainMenu destinato a sua volta a contenere i campi TMenuItem.

 


TPopupMenu

Menù a tendina destinato a sua volta a contenere i campi TMenuItem.

 


TPanel

TPanel è un rettangolo che può contenere una scritta (Caption) e altri contenitori o campi.

 


TGroupBox

TGroupBox assomiglia a TPanel, ma è dotato di una intestazione e collega tra loro gli oggetti contenuti.

 


TRadioGroup

TRadioGroup può contenere dei selettori grafici per opzioni mutuamente esclusive (TRadioButton).

 


costruttori

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.

 


dichiarazioni

Ogni dato (variabile o funzione) del C++ non può essere usato prima di essere stato dichiarato in tipo ed identificatore.

Esempi.

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

 


distruttori

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.

 


espressioni

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.


etichetta (label)

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.");
}

funzioni

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.

 


funzioni di libreria

alcune funzioni di uso frequente
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

 


controllo di flusso

Le istruzioni di controllo di flusso modificano l'esecuzione sequenziale delle istruzioni presenti in un blocco.

Queste istruzioni si possono raggruppare in quattro tipologie:

  1. Istruzioni di diramazione
  2. Istruzioni di iterazione
  3. Istruzioni di interruzione
  4. Istruzioni di prevenzione delle eccezioni

identificatori

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;

interruzione di flusso

Le istruzioni di interruzione di flusso interrompono l'esecuzione sequenziale delle istruzioni presenti in un blocco.

Queste istruzioni sono

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:




istruzioni

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.


variabili locali

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.


funzioni matematiche

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.

funzioni matematiche
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>.


metodi

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.

 


oggetto

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();


operatori di confronto

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.

 

Operatori di confronto
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



operatori digitali

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.

Operatori digitali binari
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.

 


operatori logici

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.

Operatori logici
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

&nbsp:


operatori matematici

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.

Operatori unari
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.

Operatori contratti
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.


operatori su stringhe

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"

 


parametri

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.


proprietà

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à.


puntatori

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:

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 *.

 


selezione binaria

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);
    }

 


selezione multipla

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.


stringhe

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).

principali funzioni sulle stringhe C++
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

 


tipo

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.

 


tipi derivati

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.

 


tipi fondamentali

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.

Tipi fondamentali
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.

Attributi dei tipi fondamentali
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;

 


prevenzione degli errori

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);
}

 


conversione di tipo (typecasting)

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);

 


unità

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.

 


valore

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.


variabili

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')