2. Elementi di Programmazione
Le Macro di Excel
Ambiente di Sviluppo del VBA
La Sintassi del VBA
Matlab in sintesi (estrema!)
Matlab Excel Linker
MS Visual Studio 6.0
La Sintassi del C/C++
Integriamo VB e C/C++: le DLL
4. Il compilatore (cenni)
Nel gergo informatico il termine compilazione
codice sorgente di un programma nel cosiddetto
nell’approppriato codice binario che permette
microprocessore sulle istruzioni da compiere
procedure contenute nel programma stesso.
Un compilatore in sostanza è un traduttore; il codice macchina prodotto dal
compilatore dipende in maniera specifica dal tipo di microprocessore con
cui è equipaggiato il computer su cui dovrà girare il programma.
indica la traduzione del
linguaggio macchina cioè
di istruire uno specifico
per l’elaborazione delle
Ecco perché programmi compilati per girare su un personal computer non
possono essere trasferiti direttamente su una macchina di diversa architettura.
In realtà il codice prodotto dal compilatore non è direttamente eseguibile
dal computer, esso viene salvato in un file con estensione .obj in attesa di
essere processato da un altro programma, il cosiddetto linker.
5. Il linker (cenni)
In generale un programma C è composto in generale di più moduli a
ciascuno dei quali corrisponde un file sorgente (con estensione .c o
.cpp) o un file di tipo include (con estensione .h).
La compilazione di questi file produce altrettanti file con estensione
.obj. Si tratta di file in codice macchina ma che, tuttavia, non possono
ancora essere eseguiti.
Infatti quando scrivete del codice in un modulo potete richiamare delle
funzioni la cui implementazione si trova in un file sorgente diverso da
quello in cui state lavorando.
Questo accade regolarmente quando programmiamo in VBA solo che in
quel caso tutta la faccenda è gestita per noi dall’interprete VBA che ci
risparmia un bel po’ di scocciature.
In questo caso invece quando il compilatore trova una chiamata ad una
generica funzione non sa ancora dove sia la funzione chiamata perché il
compilatore esamina un file sorgente alla volta.
6. Il linker (cenni)
In realtà le domande a cui dobbiamo fornire
una risposta sono due:
come fa il compilatore a riconoscere che una
generica stringa contenuta nel codice sorgente
è una chiamata ad una funzione?
una volta risolto il problema precedente, come
può gestire la traduzione del codice non avendo
a disposizione ancora il codice della funzione
chiamata?
7. Il linker (cenni)
Per quanto riguarda il primo problema è necessario indicare
esplicitamente al compilatore quali sono le funzioni che troverà
all’interno del codice sorgente in esame.
In maniera del tutto analoga alla dichiarazione delle variabili
occorre cioè dichiarare anche le funzioni.
La dichiarazione delle funzioni avviene inserendo all’interno del
codice sorgente che le richiama una riga contenente
l’intestazione della funzione seguita dal punto e virgola.
La dichiarazione di una funzione è chiamata anche prototipo
della funzione stessa.
8. (Specificatori di formato)
Anticipiamo alcune informazioni relative alla
funzione printf
Specificatore di formato Descrizione
%c
%d
%E
%f
%lf
%s
visualizza un singolo carattere
visualizza un numero intero
notazione scientifica
visualizza un numero reale in singola precisione
visualizza un numero reale in doppia precisione
visualizza una stringa di caratteri
9. Il preprocessore
Alcune istruzioni sono “speciali” in quanto non si traducono
direttamente in istruzioni eseguibili bensì in istruzioni che
devono essere eseguite dal compilatore prima di effettuare la
traduzione del codice in linguaggio macchina.
Queste istruzioni sono dette direttive del preprocessore e sono
individuate dal simbolo # posto all’inizio della riga.
L’istruzione #include è una di queste.
Una direttiva al compilatore non termina con il punto e virgola
come le altre istruzioni ma semplicemente con un carattere di “a
capo”.
10. Il preprocessore
Ma che cosa sono con esattezza le direttive del preprocessore?
Per rispondere a questa domanda occorre prima spiegare che cos’è il
preprocessore.
Quando si lavora con un programma scritto in C/C++, il preprocessore è il
primo programma ad essere invocato durante la fase di compilazione.
Il preprocessore è un sottosistema del compilatore che possiede un
proprio analizzatore sintattico delle righe di programma al fine di
compiere le seguenti operazioni:
inserimento di altri file nel file sorgente (elaborazione della direttiva
#include);
macro-sostituzione (elaborazione della direttiva #define);
controllo condizionale del codice sorgente (elaborazione della direttiva
#if e di quelle ad essa associate).
11. Il preprocessore
La direttiva #include
La direttiva #include indica al precompilatore di inserire il contenuto del file
specificato nel file sorgente al posto della direttiva stessa. La direttiva può avere
due forme:
#include <nomefile>
#include “nomefile”
Nel primo caso il preprocessore ricercherà il file da includere nel percorso
predefinito per i file di tipo include del linguaggio C/C++. In questi file si trovano le
dichiarazioni, le definizioni e le costanti utilizzate dalle funzioni standard del C.
Tipicamente la directory predefinita viene impostata come variabile di ambiente
durante la procedura di installazione di Visual Studio.
Se si utilizza la direttiva #include nella seconda forma, il preprocessore cerca il
file anzitutto nella stessa directory del file sorgente. Se il file non viene trovato, il
preprocessore cerca il file nella directory standard.
12. Il preprocessore
La direttiva #include
Il file che viene inserito è detto file header e possiede normalmente
l’estensione .h.
Un file header semplifica la programmazione perché tradizionalmente
contiene definizioni di strutture, prototipi di funzioni e tutte le
dichiarazioni di dati utilizzate da più file sorgente.
I file header agevolano la stesura iniziale, le modifiche e la
manutenzione del programma.
13. Il preprocessore
La direttiva #define
La direttiva #define viene utilizzata per la sostituzione di testo e per
creare macro simili a funzioni. Si può pensare a questa istruzione come
ad una sempice operazione di ricerca e sostituzione di testo assai simile
a quanto potrebbe essere realizzato con un programma di elaborazione
testi.
Nella sua forma più semplice, l’istruzione #define è seguita da un
nome di macro e dal testo da sostituire. Questo testo si chiama corpo
della macro. Solitamente un nome di macro utilizzato per la definizione di
una costante viene scritto in caratteri tutti maiuscoli, si tratta solo di una
convenzione che, per quanto largamente seguita, non è imposta dalle
caratteristiche di alcun compilatore C. Vediamo un esempio
#define PIGRECO 3.141592653589
(notate l’assenza del punto e virgola alla fine della riga)
Quando viene eseguito il preprocessore, questo sostituirà tutte le
occorrenze della stringa PIGRECO con il numero 3.141592653589.
14. Il preprocessore
Le direttive condizionali
Talvolta, nello sviluppo di un programma, si vuole che parte del codice
venga ignorata dal compilatore.
Ad esempio si possono avere istruzioni di stampa che servono solo
durante la fase di test.
In questo caso si vuole che queste istruzioni vengano compilate quando si
è pronti per il collaudo ma non si vuole che il codice di test appaia nella
versione definitiva del programma destinata all’utente finale.
Un altro caso nel quale si vuole che il compilatore ignori parte del codice è
rappresentato da un programma destinato ad essere eseguito su
computer diversi quando esista un sottoinsieme di codice strettamente
legato alla macchina utilizzata.
Anche in questo caso l’uso di direttive condizionali permette di saltare
condizionalmente parti del codice durante il processo di compilazione.
15. Il preprocessore
Le direttive condizionali sono analoghe alle istruzioni del Visual Basic if, else if e
end if ma vengono elaborate dal preprocessore del C, la sintassi è la seguente
#if condizione
<blocco di codice 1>
#else
<blocco di codice 2>
#endif
<blocco di codice 1> e <blocco di codice 2> sono blocchi di un
numero qualsiasi di istruzioni C/C++ e possono contenere a loro volta altre
direttive al compilatore.
Se la condizione della direttiva #if risulta vera, viene compilato il <blocco di
codice 1> mentre il <blocco di codice 2> viene ignorato, l’opposto
accade se la condizione risulta falsa.
Sottolineamo ancora che in questo caso il flusso condizionale non determina
quale parte del codice verrà eseguito ma quale parte del codice sarà compilata.
16. La gestione della memoria (cenni)
La memoria di un computer può essere immaginata come una sequenza
di locazioni ciascuna delle quali composta da 8 bit (1 Byte).
Una locazione è caratterizzata da due proprietà:
il suo contenuto
il suo indirizzo
Il contenuto di una locazione di memoria è il valore del Byte (che può
essere la codifica di un carattere oppure una parte di una variabile).
L’ indirizzo invece è un identificatore univoco che permette di riferirsi a
quella specifica locazione di memoria.
Gli indirizzi di memoria verranno indicati in notazione esadecimale.
Ricordiamo che un numero in notazione esadecimale è espresso in base
16 utilizzando le cifre arabe da 0 a 9 e le lettere A, B, C, D, E ed F che
corrispondono rispettivamente ai numeri 10, 11, 12, 13, 14 e 15.
17. La gestione della memoria (cenni)
Nel sistema operativo Windows, ogni processo riceve uno spazio di
indirizzamento lineare di 4 GByte totalmente privato.
Questo significa che un’applicazione ha accesso a tutti gli indirizzi di
memoria nell’intervallo da 0x00000000 a 0xFFFFFFFF in uno spazio
virtuale.
Cerchiamo di chiarire meglio questo concetto; supponiamo di dichiarare una
variabile a di tipo intero ed immaginiamo che a questa variabile venga
assegnato l’indirizzo di memoria 0x00FA5524 dello spazio di indirizzamento
del nostro processo. Supponiamo ora che un’altra applicazione crei una
seconda variabile b di tipo intero e la memorizzi all’indirizzo 0x00FA5524
del suo spazio di indirizzamento. Le due variabili sono in qualche modo
collegate? No, perché lo spazio di indirizzamento del primo processo non è
il alcun modo correlato con lo spazio di indirizzamento del secondo. In altre
parole l’indirizzo 0x00FA5524 ha un valore relativo.
L’identificatore di un indirizzo nello spazio di indirizzamento di un dato
processo non ha nulla a che vedere con la sua locazione fisica in memoria.
La gestione degli spazi di indirizzamento è ovviamente a carico del sistema
operativo che tiene traccia di dove sono memorizzati effettivamente i dati.
18. La gestione della memoria (cenni)
La richiesta al sistema operativo di una locazione per memorizzare
una variabile è detta dichiarazione o allocazione di variabile.
Il risultato di questa operazione è l’assegnamento da parte del sistema
operativo di un indirizzo nello spazio di indirizzamento del vostro
processo al quale corrisponde la variabile dichiarata.
Una delle differenze maggiori fra la programmazione in Visual Basic e quella in
C/C++ è sicuramente la gestione della memoria. Infatti come vedremo più
avanti il programmatore C/C++ non può permettersi di ignorare del tutto il
funzionamento della memoria in quanto l’allocazione di memoria per le
strutture dati, i vettori e gli oggetti è delegata al programmatore che ne effettua
la gestione tramite apposite istruzioni che gli permettono di manipolare gli
indirizzi di memoria delle variabili. Questo è sicuramente uno degli aspetti più
complessi della programmazione in C/C++.
19. Elementi di Programmazione
Le Macro di Excel
Ambiente di Sviluppo del VBA
La Sintassi del VBA
Matlab in sintesi (estrema!)
Matlab Excel Linker
MS Visual Studio 6.0
La Sintassi del C/C++
Integriamo VB e C/C++: le DLL
21. Principali tipi di dato in C
Nome
Tipo Equivalente in Visual Basic
char
short
int
long
float
double
void
Tipo
carattere
intero corto
intero
intero lungo
numero decimale
numero decimale in doppia precisione
assenza di valore
non definito
Integer
Long
Long
Single
Double
non definito
L’occupazione di memoria di una variabile di uno di questi tipi dipende dalla
macchina su cui si compila il programma. Si possono per esempio avere interi
a 16 bit o a 32 bit; questo determina una differenza nei range di valori che le
variabili possono assumere.
Il tipo char ha sempre la stessa implementazione, una variabile di questo tipo
occupa sempre 8 bit.
Un discorso a parte merita il tipo void. Si tratta di un tipo speciale in quanto
non ha valore, viene utilizzato esclusivamente per specificare che non esiste
un valore. Il tipo void si utilizza normalmente come tipo di ritorno di una
funzione per indicare che quella funzione non restituisce alcun valore o come
unico elemento della lista di parametri di una funzione che non ha argomenti.
22. Dichiarazione ed Inizializzazione delle Variabili
La sintassi per la dichiarazione delle variabili è
TipoDato NomeVariabile [= Valore]
Alcuni esempi di dichiarazione sono i seguenti:
int a = 0;
float b;
char c = ‘a’;
double d;
23. Dichiarazione ed Inizializzazione delle Variabili
Un errore che viene spesso fatto agli inizi è quello di dimenticarsi di
inizializzare una variabile; questo può portare a comportamenti
difficilmente prevedibili dal momento che fino a quando una variabile
non viene inizializzata, essa contiene un valore non significativo.
Quando si programma in Visual Basic questo problema non si pone
perché l’interprete VBA si prende cura di inizializzare a zero le variabili.
Inoltre occorre ricordare che il C, a differenza del Visual Basic, è un
linguaggio case sensitive cioè sensibile alla differenza fra lettere
maiuscole e minuscole per cui le seguenti istruzioni
int VariabileDiProva;
int variabilediprova;
si riferiscono a due variabili diverse.
24. Dichiarazione ed Inizializzazione delle Variabili
Notate che in C non esiste il tipo stringa; per memorizzare
sequenze di caratteri occorre dichiarare una variabile di tipo
vettoriale.
Supponiamo ad esempio di voler definire una variabile che
permetta di contenere stringhe composte al massimo da 255
caratteri, la sintassi da seguire è
char Stringa[255];
la sintassi VBA equivalente è
Dim Stringa As String*255
Entrambi queste dichiarazioni allocano spazio in memoria per
255 caratteri.
25. Dichiarazione ed Inizializzazione delle Variabili
Un altro modo per dichiarare una stringa è il seguente
char Stringa[] = “Salve Mondo!”;
che è del tutto equivalente a
char Stringa[13] = “Salve Mondo!”;
Detto in altri termini, quando si inizializza un char[] con una stringa senza
specificarne la dimensione, il compilatore conta automaticamente il numero di
caratteri della stringa e imposta la dimensione del char[] alla lunghezza della
stringa più uno.
Lo spazio in più serve per contenere il terminatore di fine stringa rappresentato
dalla sequenza “0”. In C/C++ ogni sequenza di caratteri è sempre seguita dal
carattere di escape “0”. Per esempio la stringa “Hello!” viene memorizzata in
un vettore di 7 caratteri in cui v[0] = ‘H’, v[1] = ‘e’, v[2] = ‘l’, v[3]
= ‘l’, v[4] = ‘o’, v[5] = ‘!’ e v[6] = ‘0’.
Riprenderemo l’argomento dei vettori più avanti, osserviamo tuttavia fin da subito che in C/C++
l’indicizzazione di un generico array parte sempre da 0 (zero).
26. Operatori
Gli operatori aritmetici +, -, *, / sono operatori binari, ovvero hanno
due operandi; la moltiplicazione e la divisione hanno la precedenza sulla
somma e sottrazione.
Un altro operatore aritmetico è il modulo %; l’espressione a % b
restituisce il resto della divisione di a per b ed è applicabile solamente a
variabili di tipo intero.
Gli operatori ++ e -- sono operatori unari (agiscono cioè su un singolo
operando), sono denominati rispettivamente operatore di incremento e di
decremento. Essi producono l’incremento o il decremento di una unità
della variabile a cui sono applicati.
L’origine del nome C++ proviene proprio da questo operatore. Il linguaggio di
programmazione C venne sviluppato come evoluzione del linguaggio B prodotto dai
Bell Laboratories, poiché il C++ nacque inizialmente come miglioramento del
linguaggio C ecco spiegato il motivo dello strano nome. Resta invece ancora avvolto
nel mistero il motivo per cui non sia stato chiamato D.
27. Operatori
E’ necessario sottolineare che le espressioni ++n e n++ producono risultati diversi: in
entrambi i casi il contenuto della variabile n è incrementato ma, mentre nel primo caso
n viene incrementato prima di utilizzarne il valore, nel secondo prima si incrementa n e
poi se ne utilizza il valore.
Un esempio chiarirà meglio il funzionamento di questi operatori, considerate le
seguenti espressioni
n = 5;
x = ++n;
la prima istruzione assegna ad n il valore 5; la seconda istruzione prima incrementa di
una unità il valore di n (che quindi diventa 6) e dopo lo assegna ad x che quindi
assume il valore 6. Invece se avessimo scritto
n = 5;
x = n++;
il risultato sarebbe stato diverso. Infatti in questo caso prima viene assegnato il valore
corrente di n (che ricordiamo è 5) alla variabile x e solo dopo si incrementa n di una
unità.
28. Operatori
Come risulta chiaro dagli esempi sin qui presentati anche in C il principale operatore di
assegnamento è l’operatore =. Esistono tuttavia altri operatori che permettono di
esprimere in forma più compatta espressioni del tipo
i = i + 3;
nelle quali la variabile sul lato sinistro viene ripetuta sul lato destro. L’espressione appena
vista in C può essere scritta come
i += 3;
In un certo senso questa notazione, oltre ad essere più compatta, permette anche di
evitare la fastidiosa sensazione di scrivere una relazione matematicamente assurda
come i = i + 3. Analogamente le espressioni
i = 2*i;
i = i – 3;
i = i/3;
possono essere riscritte come
i *= 2;
i -= 3;
i /= 3;
29. Operatori
Gli operatori relazionali vengono utilizzati per confrontare fra
loro le variabili, essi sono: <, <=, >, >=, == (test di
uguaglianza) e != (diverso da).
Questi operatori vengono spesso utilizzati assieme agli
operatori logici && (And) e || (Or). Le espressioni connesse da
&& e || vengono valutate a partire da sinistra e la valutazione si
ferma non appena si determina la verità o la falsità dell’intera
espressione.
E’ molto importante capire esattamente cosa accade quando effettuiamo un
confronto con questi operatori, il C infatti non possiede un tipo di dato analogo
al boolean del Visual Basic (Il C++ e il C# si!).
Un operatore relazione non produce quindi un risultato Vero o Falso bensì
restituisce 0 (zero) nel caso in cui il confronto non sia soddisfatto oppure un
valore diverso da zero nel caso opposto.
L’operatore unario di negazione ! converte un operando non nullo in un 0
(zero) ed un operando nullo in un 1 (uno).
31. Espressioni condizionali
L’istruzione di controllo if-else introduce nel programma una struttura
decisionale in cui il flusso del programma può suddividersi in funzione del
risultato fornito solitamente da un operatore relazionale su uno o più operandi. La
sintassi è la seguente
if(espressione)
{
<istruzioni da eseguire se l’espressione ritorna un valore diverso da 0>
}
else
{
<istruzioni da eseguire se l’espressione ritorna un valore uguale a 0>
}
Un’istruzione può essere un semplice comando (nel qual caso le parentesi graffe
non sono necessarie anche se migliorano in ogni caso la leggibilità del codice)
oppure un insieme di comandi. espressione rappresenta una qualunque
espressione valida.
32. Espressioni condizionali
Analogamente a quanto abbiamo visto nel caso del Visual Basic esiste anche la possibilità
di definire scelte multiple utilizzando l’istruzione else if, un esempio di utilizzo è il
seguente
if(espressione1)
{
<istruzioni 1>
}
else if (espressione2)
{
<istruzioni 2>
}
else if (espressione3)
{
<istruzioni 3>
}
else
{
<istruzioni 4>
}
Ricordando quanto abbiamo appena
detto a proposito degli operatori
relazionali, il risultato della condizione if
è il seguente:
se il risultato di espressione è
diverso da zero, allora la condizione
è considerata soddisfatta e verrà
eseguito il blocco di istruzioni
immediatamente dopo il comando if;
altrimenti se espressione vale
esattamente 0, la condizione è
considerata non soddisfatta e le
istruzioni eseguite saranno quelle
dopo l’istruzione else.
Qualora l’istruzione else sia assente il
programma segue il normale flusso di
istruzioni poste dopo il blocco if.
33. Espressioni condizionali
Quando è necessario variare il flusso del programma in base al risultato del confronto fra il
valore di una variabile e uno o più dati costanti, al posto di una serie di if-else if si può
utilizzare un costrutto più sintetico: il costrutto switch la cui sintassi è:
switch (espressione)
{
case <costante 1> :
{
<istruzioni 1>
[break;]
}
case <costante 2> :
{
<istruzioni 2>
[break;]
}
[default :
{
<istruzioni>
}]
}
Quando nel codice è presente un blocco switch il
flusso del programma prosegue a partire dalle istruzioni
associate al valore di espressione qualora questo sia
contemplato in una clausola case; altrimenti vengono
eseguite le istruzioni associate alla clausola default
(che è opzionale).
Notate che il flusso del programma prosegue a partire
dalla clausola case soddisfatta eseguendo quindi anche
le istruzioni collegate alle altre eventuali clausole case
successive.
Sotto questo punto di vista il comportamento di
questa istruzione non è analogo ad una sequenza di
if-else.
Se si desidera bloccare l’esecuzione ad un solo
sottoinsieme di istruzioni è sufficiente terminare il blocco
con il comando break. Il costrutto switch con
l’istruzione break al termine di ogni blocco è del tutto
analogo ad un blocco select case del VBA.
34. Cicli For
Com’è noto il ciclo for permette di definire un’insieme di istruzioni che devono
essere eseguite un numero predefinito di volte. La sintassi utilizzata utilizzata dal
C non è molto diversa da quella impiegata da Visual Basic, tuttavia esistono
alcune differenze che è necessario sottolineare. La sintassi C è la seguente
for(<espressione 1> ; <espressione 2> ; <espressione 3)
{
<istruzioni>
}
dove
<espressione 1> viene valutata una sola volta e specifica l’inizializzazione del
blocco for;
<espressione 2> rappresenta la condizione di terminazione e viene pertanto
valutata all’inizio di ogni nuovo ciclo. Il programma esegue le istruzioni del blocco solo
se questa espressione assume valore diverso da 0 (zero);
<espressione 3> viene valutata ad ogni ciclo dopo l’esecuzione del blocco di
istruzioni;
35. Cicli For
Esempio
Supponiamo di alimentare un vettore composto da n elementi,
l’istruzione C è la seguente
for(i = 0; i < n; i++)
{
vettore[i] = 0.0;
}
Se vogliamo inizializzare solo gli elementi pari, possiamo scrivere
for(i = 0; i < n; i+=2)
{
vettore[i] = 0.0;
}
36. Cicli Do-While
Il ciclo do-while è simile alla versione Visual Basic,
le istruzioni C
E’ chiaro che in questo caso, il valore
do
{
<istruzione>
}while (<espressione>)
corrispondono al ciclo VBA
Do
<istruzioni>
Loop While (<espressione>)
di <espressione> deve dipendere dai
calcoli eseguiti all’interno del blocco
do-while
Esempio: calcolo dell’errore di
approssimazione durante la ricerca
dello zero di una funzione.
Importante: la condizione di
uscita deve essere SICURA!
Es. aggiungere sempre un
contatore
e
prevedere
l’uscita dal ciclo quando il
contatore ha superato un
numero
massimo
di
iterazioni!
38. Array
In C la dichiarazione di un array avviene secondo la sintassi
TipoDato
NomeArray[Dimensione]
Gli elementi di un vettore di dimensione n sono sempre riferiti con gli
indici che vanno da 0 ad n-1, pertanto il primo elemento di un generico
vettore v è sempre l’elemento v[0]. Il riferimento al generico elemento
avviene indicando l’indice corrispondente fra parentesi quadre.
Di seguito riportiamo alcuni esempi di dichiarazione di array in C:
int
nVector[10];
/* dichiara un vettore di interi composto da
elementi */
Float fVector[100]; /* dichiara un vettore di numeri reali in singola
precisione di dimensione 100 */
double dMatrix[10][10]; /* dichiara una matrice di numeri reali in doppia
precisione composta da 10 righe e 10 colonne */
10
39. Array
Anche nel caso dei vettori di tipo numerico la dimensione può essere
fissata automaticamente dal compilatore qualora al vettore vengano
assegnati dei valori come nell’esempio seguente
float fVector[] = {0.1,0.2,0.3,0.4,0.5};
Il vettore fVector viene automaticamente dimensionato a 5.
La dimensione non può essere cambiata successivamente.
Gli array così dichiarati sono del tutto analoghi agli array statici del
Visual Basic.
41. Funzioni
Il formato di una funzione in C/C++ è il seguente
TipoDiRitorno NomeFunzione ([argomenti])
{
<corpo della funzione>
}
Il codice corrispondente in Visual Basic sarebbe
[Public
|
Private]
Function
([argomenti]) [As TipoDiRitorno]
<corpo della funzione>
End Function
NomeFunzione
43. Puntatori
Per poter capire che cosa è e a cosa serve un
puntatore, è indispensabile conoscere almeno a
grandi linee l’organizzazione della memoria di un
calcolatore. La parte di memoria in cui possono
risiedere le variabili è suddivisa in due parti:
lo Stack
lo Heap
Non entreremo ulteriormente nei dettagli strutturali di questa
suddivisione ma ci limiteremo a descrivere a cosa servono queste due
aree di memoria. Il compilatore memorizza tre cose nello stack:
le variabili locali,
gli argomenti passati a funzioni chiamate,
gli indirizzi di ritorno.
44. Puntatori
Quando vengono utilizzate variabili allocate nello stack, l'applicazione non è in
grado di deallocare la memoria quando la variabile non serve più. Quest'ultima
osservazione può rappresentare un problema quando si manipolano grosse
quantità di informazioni in quanto potrebbe succedere che lo stack si riempia e
quando si tenta di allocare altra memoria si verifichi il cosìdetto stack overflow.
Quando si effettua una chiamata ad una funzione, il processore inserisce
automaticamente nello stack un indirizzo di ritorno che specifica l’indirizzo di
memoria nel programma principale al quale occorre far ritornare il flusso del
programma una volta che l’esecuzione della funzione sia terminata.
Per permettere al programmatore di utilizzare la memoria in maniera
"intelligente" è possibile allora utilizzare la memoria heap, detta anche memoria
dinamica. Il termine dinamica sta proprio ad indicare che è data la possibilità al
programmatore di allocare e deallocare la memoria a suo piacimento. Questa,
tuttavia, è un'operazione molto delicata che, se compiuta in modo errato può
portare ad errori spesso difficili da trovare che si verificano in fase di esecuzione.
La manipolazione dello heap avviene tramite i puntatori.
45. Puntatori
Un puntatore è una variabile il cui contenuto è
l’indirizzo di una locazione di memoria.
Potete pensare alla memoria di un computer come
ad un grande schedario; immaginate una schiera di
piccole cassette ciascuna delle quali rechi
un’etichetta con un numero che ne identifica in
maniera univoca la posizione all’interno dello
schedario:
la cassetta rappresenta la locazione o cella di memoria
il numero scritto sull’etichetta rappresenta l’indirizzo di
quella locazione
Il contenuto della cassetta rappresenta il valore
memorizzato in quella determinata cella
46. Puntatori
le variabili di tipo puntatore vengono dichiarate usanto
l’operatore * come segue
int *pVar = 0;
Questa istruzione crea un puntatore che viene inizializzato
all’indirizzo di memoria 0x00000000
nello spazio di
indirizzamento del processo. Gli indirizzi di memoria verranno
indicati utilizzando la notazione esadecimale.
per conoscere l'indirizzo di una variabile, è sufficiente far
precedere al nome della variabile l'operatore &. Pertanto per
assegnare l'indirizzo della variabile x a pVar è sufficiente
scrivere
pVar = &x;
47. Puntatori
L’operatore * ha anche un altro significato quando viene utilizzato fuori
da un’istruzione di dichiarazione. Applicandolo ad una variabile
puntatore, esso consente di accedere al valore presente all’indirizzo di
memoria contenuto in questa variabile.
Notate che i puntatori sono dotati di tipo, questo significa che
dobbiamo dire al compilatore qual è il tipo di variabile cui punteremo.
Ad esempio volendo utilizzare l’indirizzo di una variabile in doppia
precisione dovremmo dichiarare un puntatore del tipo
double *pVariabileDoppiaPrecisione;
è bene fare attenzione che questa notazione può indurre in errore. Si
potrebbe pensare, infatti, che dopo questa dichiarazione alla variabile
*pVariabileDoppiaPrecisione venga assegnato uno spazio in
memoria uguale a quello di una variabile in doppia precisione. In realtà lo
spazio assegnato alle variabili di tipo puntatore è sempre lo stesso e pari
a quello di una variabile di tipo long in quanto devono in ogni caso
contenere un indirizzo di memoria.
51. Puntatori e vettori
I puntatori e gli array sono argomenti strettamente correlati. Infatti, in C/C++, il nome di un
array è anche il puntatore al suo primo elemento. L’identificatore di un vettore coincide
quindi con un puntatore avente lo stesso nome. Date le dichiarazioni:
#define SIZE 20
float fVector[SIZE];
float* pfVector;
Il nome dell'array fVector è una costante il cui valore è l'indirizzo del primo elemento
dell'array di 20 float. L'istruzione seguente assegna l'indirizzo del primo elemento
dell'array alla variabile puntatore pfVector:
pfVector = fVector;
Quest’istruzione è del tutto equivalente a:
pfVector = &fVector[0];
52. Puntatori e vettori
Tramite i puntatori però possiamo costruire degli
array dinamici sfruttando al meglio la memoria del
computer. Che cosa ci serve per definire un vettore
dinamico? Semplificando al massimo il problema
sono sufficienti due cose:
1.
2.
una procedura che richieda al sistema operativo di
riservare un certo quantitativo di memoria all’interno dello
spazio di indirizzamento del processo;
un modo per memorizzare l’indirizzo di memoria iniziale
del blocco così riservato.
53. Puntatori e vettori
Il secondo punto è facilmente risolvibile con una variabile di tipo puntatore, per quanto
riguarda il primo possiamo ricorrere all’operatore new. new prende come argomento un
tipo di dati astratto e restituisce il puntatore all’area di memoria riservata per un oggetto
di quel tipo. Nell’esempio seguente vediamo come allocare un vettore di double con un
numero variabile di elementi
/* dichiarazione di un puntatore a double */
double* Vettore
/* allocazione di memoria */
Vettore = new double[NumeroElementi];
/* deallocazione della memoria */
delete [] Vettore;
Attenzione!!!
Attenzione!!!
Una volta utilizzato l’array è necessario rilasciare la memoria allocata tramite l’operatore
Se dimenticate di usare l’operatore delete andrete incontro al problema dei
Se dimenticate di usare puntatore all’oggetto da distruggere
delete. Quest’ultimo prende memory leak (perdite di ill’operatoreUna situazione del genere al problema dei
come argomento memoria). delete andrete incontro capita quando
memory leak (perdite di memoria). Una situazione del genere capita quando
avete un puntatore restituito da new). Ci sono due versioni
(che necessariamente deve essere in memoria delle variabili che non sono state deallocate eeche non sono
avete in memoria delle variabili che non sono state deallocate che non sono
più referenziate. Il Visual Basic, e altri linguaggi tipo Java, dispongono di una
più referenziate. Il Visual Basic, e altri linguaggi tipo Java,
di questo operatore, una per oggetti singoli, l’altra per ideallocazione automatica di dispongono di una
gestione automatica per la vettori
queste variabili.
delete px;
delete [] pv;
gestione automatica per la deallocazione automatica di queste variabili.
Quando invece si utilizza un linguaggio come il C/C++ è il programmatore che
Quando invece si utilizza un linguaggio come il C/C++ è il programmatore che
ha la responsabilità di ripulire la memoria per cui come regola generale
ha la responsabilità di ripulire la memoria per cui come regola generale
ricordate sempre di utilizzare una delete per ogni vettore o matrice che
ricordate sempre di utilizzare una delete per ogni vettore o matrice che
create con una new.
create con una new.
54. La Sintassi del C/C++
Programmazione Orientata agli Oggetti in C++
55. Quando Java non basta ++
I concetti della Programmazione Orientata agli Oggetti
vengono implementati in C++ in modo strettamente simile a
Java (che di fatto, sintatticamente, deriva dal C++);
L’utilizzo del C/C++ è tuttavia preferibile in tutte quelle
circostanze in cui sia indispensabile disporre di programmi ad
alta efficienza in termini di tempi di calcolo;
Questo è tipicamente il caso delle applicazioni di calcolo
numerico, in cui il tempo impiegato per effettuare
un’operazione in virgola mobile diventa un elemento
discriminatore per la scelta del linguaggio.
56. Classi
Una classe si dichiara mediante la parola chiave class seguita
dal nome della classe, quindi mettendo tutte le dichiarazioni di
proprietà e metodi fra le parentesi graffe. I membri possono
essere dichiarati public, private o protected
class TitoloFinanziario
{
public:
TitoloFinanziario();
~TitoloFinanziario();
char*
void
...
get_Descrizione();
put_Descrizione(char* psDesc);
private:
char*
char*
...
};
Descrizione;
Codice;
57. Ereditarietà
Il modo in cui il C++ implementa il
funzionamento
dell’ereditarietà
passa
attraverso il concetto di classe derivata come
in Java;
Una classe derivata di un’altra classe (detta
classe base) è una classe che ha tutte le
caratteristiche della classe base più altre
caratteristiche supplementari rivolte ad
aumentarne o specializzarne le funzionalità
rivolte all’esterno.
60. Elementi di Programmazione
Le Macro di Excel
Ambiente di Sviluppo del VBA
La Sintassi del VBA
Matlab in sintesi (estrema!)
Matlab Excel Linker
MS Visual Studio 6.0
La Sintassi del C/C++
Integriamo VB e C/C++: le DLL
61. Che cos’è una DLL
DLL è l'abbreviazione di Dynamic Link Library (Libreria a Collegamento
Dinamico).
Le DLL sono delle raccolte di funzioni che possono essere chiamate da
più programmi e solitamente contengono codice di utilità generale che
viene messo a disposizione di più applicazioni. Ciò permette di non
inserire codice duplicato in applicazioni differenti.
Tecnicamente parlando una DLL è un file compilato che contiene una
serie di funzioni che possono essere utilizzate da qualsiasi altro
processo server.
Essendo compilate, sono completamente indipendenti dal linguaggio in
cui sono state scritte. Questo permette, tanto per fare un esempio, ad un
programmatore Visual Basic di sfruttare Librerie scritte in C/C++ o
viceversa.
62. Che cos’è una DLL
Perché una DLL renda disponibili le proprie funzioni
ad altri programmi è necessario che essa renda
disponibili gli indirizzi virtuali relativi delle funzioni
stesse (RVA, Relative Virtual Address).
Infatti quando un processo carica una libreria a
collegamento dinamico questa viene caricata ad un certo
indirizzo dello spazio di indirizzamento del processo che
l’ha richiesta (questo indirizzo si chiama base address
della DLL).
Pertanto affinché un’applicazione possa utilizzare una
funzione contenuta nella DLL deve sommare all’indirizzo di
base della DLL stessa il RVA della funzione desiderata
presente all’interno della DLL.
63. DLL
Dal menu File selezionate New, nella finestra di dialogo scegliete la scheda
Projects e successivamente la voce “Win32 Dynamic Link Library”,
denominate il progetto MyFirstDLL.
Nella finestra di dialogo successiva selezionate “a simple DLL project” .
Aprite il file MyFirstDLL.cpp, il codice è quello riportato nel riquadro seguente
// MyFirstDLL.cpp : Defines the entry point for the DLL application.
//
#include "stdafx.h"
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
return TRUE;
}
65. DLL
Non entreremo nel dettaglio di questa
funzione, ci limitiamo a descrivere i due
termini che precedono la definizione della
funzione stessa: BOOL e APIENTRY detti
anche modificatori.
BOOL è una typedef a int ed è definita da
Windows nel file header <windows.h>.
APIENTRY è definito come typedef a WINAPI
che, a sua volta, è una typedef a _stdcall.
66. DLL
_stdcall è una cosiddetta calling convention (convenzione di
chiamata). In poche parole, questa convenzione specifica che la
funzione chiamata ha il compito, alla fine della sua esecuzione, di
rimuovere dallo stack tutti gli argomenti che erano stati copiati nel
momento della chiamata alla funzione stessa.
La convenzione di default è la cosiddetta _cdecl. Secondo questa
convenzione la responsabilità della pulizia dello stack è delegata al
programma chiamante.
Questa è la convenzione standard seguita dal C/C++, tuttavia
ogniqualvolta vogliamo costruire una DLL destinata ad essere
utilizzata con procedure scritte in altri linguaggi dobbiamo
utilizzare la convenzione _stdcall. In particolare Visual
Basic richiede esplicitamente questa convenzione per ogni
DLL utilizzata.
67. DLL
Ponete il cursore subito dopo la fine della funzione
DLLMain() e digitate il seguente codice:
int APIENTRY FunzioneEsportata()
{
return 42;
}
Questa sarà la nosta funzione di prova che richiameremo da
un’applicazione VBA. Manca solo un ultimo dettaglio e saremo
pronti a compilare la nostra prima DLL.
68. DLL – Definition File
Per esportare una funzione contenuta in una DLL verso un’applicazione che non
sia scritta in linguaggio C/C++ è necessario aggiungere al progetto un file con
estensione .def (definition file).
Purtroppo la realizzazione di un file di questo tipo non è supportata
automaticamente da Visual Studio per cui occorre inserire manualmente un file di
tipo testo che deve essere salvato con lo stesso nome della libreria e con
estensione .def.
La struttura generale di un definition file è la seguente
; <NomeDLL>.def : Declares the module parameters for the DLL.
LIBRARY
DESCRIPTION
"<NomeDLL>"
'<NomeDLL> Windows Dynamic Link Library'
EXPORTS
PrimaFunzione
SecondaFunzione
....
....
@1;
@2;
69. DLL – Definition File
La prima linea del file .def inizia con un punto e virgola il che denota un
commento. Nella riga successiva troviamo la parola chiave LIBRARY, la stringa
che segue è il nome della DLL. DESCRIPTION inserisce una stringa di
descrizione all’interno della DLL. La sezione successiva, che inizia con la parola
chiave EXPORTS, è la più importante. Qui sono contenuti i nomi delle funzioni
che desiderate esportare, questa sezione indica inoltre al linker che tali funzioni
devono essere esportate esattamente con i nomi indicati. Nel nostro caso il
file .def è il seguente
; MyFirstDLL.def : Declares the module parameters for the DLL.
LIBRARY
DESCRIPTION
"MyFirstDLL"
'MyFirstDLL Windows Dynamic Link Library'
EXPORTS
; Explicit exports can go here
FunzioneEsportata
@1;
70. DLL – Definition File
Se cercate exporting function in MSDN troverete delle informazioni che
vi diranno che potete anche esportare funzioni con il modifier
_declspec(dllexport). Tuttavia questa informazione è sbagliata
se (come nel nostro caso) state progettando di chiamare le funzioni
della vostra DLL da un linguaggio diverso dal C++ (inoltre il
programma C++ che la usa dovrebbe anche essere compilato con lo
stesso compilatore che ha compilato la DLL!!).
Perché? La risposta è la conseguenza di una cosa chiamata name
mangling.
Il name mangling è un processo seguito dal compilatore per facilitare la
gestione delle funzioni “overloaded”. Sfortunatamente anche se nel
vostro programma non avete nessuna funzione “overloaded” il
compilatore continuerà ad applicare il name mangling.
71. DLL – Dichiarazione di procedure
Per dichiarare una procedura DLL in un’applicazione
scritta in VBA è necessario porre un’istruzione di tipo
Declare nella sezione generale di un form o di un
modulo di codice.
Le due modalità non sono equivalenti.
Infatti se ponete un’istruzione Declare nella sezione
dichiarativa di un form, la visibilità della procedura sarà
limitata al codice contenuto nel form.
Se invece ponete l’istruzione Declare nella sezione
dichiarativa di un modulo di codice, le procedure dichiarate
saranno visibili a tutta l’applicazione.
72. DLL – Dichiarazione di procedure
Come sappiamo il Visual Basic a differenza del C distingue le
Obbligatoria.
che una DLL
risorsa
procedure la inIndicada dichiarare. Laeproposizione codice
Funzioni o unaSubroutine a seconda che queste
contiene
routine
Lib è
obbligatoria ino meno un valore.
tutte le dichiarazioni.
restituiscano
Obbligatoria. Qualsiasi nome di routine valido. Si noti
che i punti di ingresso delle DLL distinguono tra
Se la vostra funzione C restituiscemaiuscole e minuscoledichiaratela come
un valore
Function seguendo la sintassi seguente
Declare Function <NomeFunzione> Lib “<NomeLibreria>” [Alias
"nomealias"] ([Lista Argomenti]) As TipoRestituito
Obbligatoria. Nome della DLL o della risorsa codice che
contiene la routine dichiarata. In generale dovete
indicare anche il percorso completo della libreria ad
esempio: “c:windowssystemmylib.dll”. La specifica
del percorso può essere evitata ricorrendo alle variabili
di ambiente.
Se invece la funzione C è di tipo void, in VBA dichiarate una
procedura di tipo Sub scrivendo
Declare Sub <NomeFunzione> Lib
"nomealias"] ([Lista Argomenti])
“<NomeLibreria>”
[Alias
73. DLL – La risposta finale
Aprite Excel e ponete un pulsante di comando sul foglio di lavoro,
salvate il file in una cartella a vostro piacimento. Nella stessa cartella in
cui avete salvato la cartella Excel, copiate il file MyFirstDLL.dll.
Attivate l’Editor del Visual Basic ed inserite un modulo nel progetto.
All’interno del modulo digitate la seguente istruzione
Declare
Function
FunzioneEsportata
”<Path>MyFirstDLL.dll” () As Long
Lib
Ricordate di inserire il percorso di ricerca della DLL. All’evento click
del pulsante associate semplicemente l’istruzione
MsgBox “La risposta al mistero dell’universo, della vita e
tutto quanto è : “ & FunzioneEsportata()
Tornate nel foglio di lavoro e fate click sul pulsante, se tutto va bene
dovreste veder comparire una MessageBox con il numero 42 in output.
74. DLL
Passaggio di argomenti per riferimento e per valore
La modalità predefinita di passaggio dei parametri del Visual
Basic è quella per riferimento.
Tuttavia quando utilizziamo una procedura contenuta in una
DLL dobbiamo sapere con precisione come sono dichiarati
gli argomenti nella procedura stessa.
Per quanto riguarda i tipi numerici possiamo dire che se nella
funzione in C compaiono argomenti dichiarati come puntatori
allora questi vengono passati dal Visual Basic per
riferimento.
Invece se gli argomenti sono variabili di tipo numerico,
queste vengono passate da VBA per valore.
75. DLL
Passaggio di argomenti per riferimento e per valore
Le stringhe di caratteri vengono invece passate dal
Visual Basic sempre per riferimento, ma vanno
sempre fatte precedere dallo specificatore ByVal.
Questa bizzarra caratteristica è dovuta al fatto che
l’utilizzo della parola chiave ByVal anteposta alla
dichiarazione di una variabile di tipo stringa ha l’effetto di
trasformare la stringa dal formato VBA al formato del C.
Al termine della stringa viene aggiunto il terminatore “0”.
Ricordiamo infine che una variabile passata per
riferimento può venir modificata dalla funzione a cui
viene passata.
76. DLL
Passaggio di argomenti per riferimento e per valore
Il passaggio di un array numerico viene
realizzato passando per riferimento il primo
elemento dell’array stesso.
Poiché gli elementi di un array numerico
vengono allocati in celle di memoria
adiacenti, sarà sufficiente dichiarare in C un
argomento di tipo puntatore al dato
appropriato per poter accedere al contenuto
dell’array stesso.