Mar 14

Livello | Definizione | Specificatore | Periodo di Vita | Visibilità |
esterno | variabile | static | globale | file sorgente in cui compare |
| variabile | extern | globale | nel resto del file sorgente |
| funzione o | static | globale | file sorgente in cui compare |
| prototipo | | | |
| funzione o | extern | globale | nel resto del file sorgente |
| prototipo | | | |
interno | variabile | static | globale | blocco in cui compare |
| variabile | auto | locale | blocco in cui compare |

Mar 8

Per definire un tipo di dato occorre fornire precise indicazioni sulla tipologia dei suoi elementi e sulle operazioni che è possibile compiere su di essi.

In C++ i puntatori rappresentano un tipo di dato che contiene gli indirizzi dei vari elementi di un programma; questi indirizzi, indipendentemente dall’oggetto puntato, occuperanno sempre la stessa quantità di memoria, a seconda del sistema utilizzato.

Una variabile di tipo puntatore viene definita in modo formale con la seguente notazione:

tipo_oggetto_puntato * nome del puntatore

dove l’operatore * è chiamato operatore di indirizzamento indiretto.

Nel caso di una lista di definizioni dello stesso tipo bisogna sapere che, pur indicando una sola volta il tipo di puntatore che si vuole dichiarare, l’operatore di indirizzamento indiretto deve precedere ciascun identificatore che si vuole utilizzare come puntatore.

Se ad esempio si scrive:
long *plg1, plg2; <--- definizione errata

la variabile plg1 verrà definita come puntatore ad un long, mentre la variabile plg2 sarà considerata essa stessa una variabile di tipo long e non un puntatore. Invece, per definire due puntatori, si scriverà:
long *plg1, *plg2; <--- definizione corretta

E' evidente che con la definizione non si alloca solo un'area di memoria che conterrà un indirizzo, ma si marchia anche la variabile puntatore col tipo di dato che essa dovrà puntare: da questo momento in poi tutti i dati puntati da questo puntatore saranno interpretati come dati appartenenti al tipo associato al puntatore stesso.

Si capisce anche che, visto che un puntatore contiene sempre l’indirizzo di un’altro oggetto, sarà indispensabile inizializzarlo con un indirizzo valido, assegnandogli l’indirizzo di una variabile precedentemente definita o il valore di un altro puntatore dello stesso tipo.

Per esempio:
long *plg;
long lg=5;
plg = ≶ <--- al puntatore plg viene assegnato l’indirizzo di lg
long * plg2;
plg2=plg; <--- al puntatore plg2 viene assegnato il valore di plg, cioè l’indirizzo di lg

Mar 6

Per gestire opportunamente i controlli, necessari per garantire la coerenza tra i vari moduli che compongono un programma articolato su più file, i prototipi delle funzioni prevedono due specificatori di classe (extern e static) che indicano al compilatore dove deve andare a cercare il corpo della funzione al momento di costruire il programma oggetto.

In pratica:
- la parola chiave extern (che èp di default), posta davanti al prototipo, indica al compilatore che la funzione ha interesse globale e che quindi la sua definizione può trovarsi in uno qualsiasi dei file sorgente che compongono il programma.
In questo caso bisogna fare attenzione alla definizione delle funzioni, per evitare che involontariamente il compilatore si trovi di fronte allo sviluppo di due funzioni identiche poste su file diversi, dando origine ad un errore di doppia definizione.

- la parola chiave static indica al compilatore che la funzione riveste un interesse locale e quindi può essere richiamata solo dal file sorgente in cui compare anche il suo sviluppo completo.
Si evitano così tutti i problemi di conflitto, ma il compilatore richiede la presenza della definizione della funzione per potere procedere alla compilazione del programma.

Se ad esempio, si prevede un programma che dichiara due funzioni Funz1 e Funz2:
- Funz1 è definita in un altro file;
- Funz2 ha interesse locale;
- Funz2 richiama una funzione Funz3 non ancora sviluppata;

il programma potrebbe assumere la seguente struttura:

#include …
extern int Funz1 (int, int); <--- prototipo di Funz1
static float Funz2 (int, float, float); <--- prototipo di Funz2

main()
{ int a,b,c; <--- programma principale
float d,e;
…………
chiamata di Funz1(a,b)
………….
chiamata di Funz2(c,d,e)
………….
}
double Funz3 (int, int); <— prototipo di Funz3

int Funz2 (int c, float d, float e) <— definizione della Funz2
{ int f, g;
………
chiamata di Funz3(f,g)
………
}

Mar 5

Siamo giunti alla trattazione dell’ultimo tipo di trasformazione, ovvero la rotazione.

Questo punto richiede una grande attenzione perchè è necessario introdurre della matematica. Prima di tutto bisogna sapere che un oggetto 3d può ruotare indistintamente su ciascuno dei tre assi cartesiani (x, y, z); ogni rotazione complessa può essere interpretata come una composizione di rotazioni.

Bisogna perciò operare una suddivisione in 3 casi:
- rotazione sull’asse X;
- rotazione sull’asse y;
- rotazione sull’asse z.

Per fortuna, la matematica ci dà 3 matrici differenti per ciascun caso. Tutto ciò che si deve fare è moltiplicare ogni vertice locale dell’oggetto 3d per la matrice di rotazione desiderata, e quindi l’oggetto 3d ruoterà secondo l’angolo voluto.
Dato che a noi interessa la praticità, salteremo tutti i passaggi matematici, arrivando subito al punto. Per prima cosa, i passi necessari da compiere per risolvere ciascun caso.

angolo = angolo di rotazione
(x, y, z) = vecchia coordinata del vertice
(x1, y1, z1) = nuova coordinata del vertice

a) Rotazione sull’asse X
x1 = x
y1 = y * cos (angolo) + z * sin (angolo)
z1 = y * (-sin(angolo)) + z * cos (angolo)

b) Rotazione sull’asse Y
x1 = x * cos (angolo) + z * (-sin (angolo))
y1 = y
z1 = x * sin (angolo) + z * cos (angolo)

c) Rotazione sull’asse Z
x1 = x * cos (angolo) + y * sin (angolo)
y1 = x * (-sin(angolo)) + y * cos (angolo)
z1 = z

Vedremo poi come implementare il codice.

Mar 3

Dopo avere trattato la dichiarazione delle variabili a livello esterno, sulla differenza che intercorre tra variabili locali e variabili globali, passiamo ora a parlare delle variabili dichiarate a livello interno. In questo caso il problema è molto più semplice, poichè il discorso è ristretto alle variabili che devono essere utilizzate solo all’interno del modulo che si sta sviluppando.

Per le variabili dichiarate all’interno delle funzioni che compongono il blocco (variabili locali) la classe di memorizzazione standard è la classe automatica o semplicemente auto, che consente di creare una variabile con visibilità di blocco e periodo di vita ristretto al tempo di esecuzione dello stesso blocco. In questo caso, se nella definizione non viene specificato un valore di inizializzazione, la variabile stessa assumerà un valore indefinito.

Quando invece si fa precedere la definizione di una variabile locale dallo specificatore di memorizzazione static, la variabile, pur mantenendo il livello di visibilità locale, assume un periodo di vita globale e quindi mantiene il proprio valore anche quando il controllo dell’esecuzione del programma esce dal blocco.

Contrariamente a quanto avviene per le variabili automatiche, quando nella definizione di variabili static non viene specificato il valore di inizializzazione, questa viene effettuata in automatico col valore zero. Bisogna comunque tenere presente che in ogni caso l’inizializzazione di una variabile static avviene una sola volta, perchè se nel corso dell’esecuzione si dovesse passare nuovamente per quel punto la variabile deve mantenere il valore precedente.

Facciamo un esempio:
for (int i=0; i<3; i++)
{ int=0;
static int y=0;
cout< x++;
y++;
}

visualizzerà le seguenti tre coppie di valori:

0 0
0 1
0 2

Mar 1

Un’altra trasformazione utile in molti casi è la scalatura, tramite la quale è possibile ingrandire o rimpicciolire la dimensione dell’oggetto 3D.

Per eseguire questa operazione è necessario moltiplicare ogni componente (x, y, z) di ciascun vertice locale per un valore definito col termine “valore di scala”, che determina quale sarà la dimensione finale dell’oggetto 3D.

Per la realizzazione di questa funzione utilizziamo qualche caratteristica saliente della struttura oggetto:
void scalatura_oggetto (oggetto_ptr oggetto, float valore_di_scala)
{
int indice;
for (indice=0; indice<oggetto->num_vertici; indice++)
{
oggetto->local_vert[indice].x *= valore_di_scala;
oggetto->local_vert[indice].y *= valore_di_scala;
oggetto->local_vert[indice].z *= valore_di_scala;
}
}

Una piccola nota: nulla vieta di poter scalare l’oggetto 3D in maniera non omogenea, utilizzando tre diversi parametri di scalatura per ciascuna componente (x, y, z) di ogni vertice; per semplicità in questo caso i tre parametri sono ovviamente tutti uguali.

Nel prossimo articolo tratteremo un’altra fondamentale trasformazione applicabile su un oggetto 3D, ovvero la rotazione.