Elementi di programmazione ad oggetti a. a. 2009/2010 Corso di Laurea Magistrale in Ingegneria Elettronica Docente: Mauro Mazzieri, Dipartimento di Ingegneria Informatica, Gestionale e dell’Automazione Lezione 2 Regole di visibilità e ciclo di vita Visibilità di una variabile Il campo d’azione o spazio di visibilità di una variabile (scope) indica la parte di programma che può accedere alla variabile per leggerla o modificarla La visibilità è limitata al blocco (racchiuso tra parentesi graffe) in cui la variabile è dichiarata Visibilità in C++ Il C++ supporta tre tipi di visibilità: Locale La parte di programma che costituisce la definizione di una funzione definisce un campo di visibilità separato Di namespace La parte di programma non contenuta nella dichiarazione o definizione di una funzione o di una classe Lo scope del namespace più esterno è detto scope globale Di classe Interno alla definizione di una classe Esempio // scope int a1 = int a2 = int b1 = int b2 = globale 3; 4; 9; 11; double distanza(int x1, int y1, int x2, int y2) { // scope locale double dx = x2 - x1; double dy = y2 - y1; return sqrt(dx*dx + dy*dy); } int main() { std::cout << distanza(a1, a2, b1, b2) <<std::endl; system("pause"); return 0; } Esempio: errori e precisazioni double distanza(int x1, double dx = a2 – a1; double dy = b1 - y1; return sqrt(dx*dx + } int y1, int x2, int b1) { // errore: non è accessibile // parametro, non b1 del main dy*dy); int main() { int a1 = 3; int a2 = 4; int b1 = 9; int b2 = 11; std::cout << distanza(a1, a2, b1, b2) <<std::endl; system("pause"); return 0; } Ciclo di vita Il ciclo di vita di una variabile è il periodo compreso tra la sua creazione e la sua eliminazione Nasce quando la variabile viene associata ad una locazione di memoria Muore quando la locazione di memoria ritorna libera e riutilizzabile La vita di una variabile inizia quando viene dichiarata e finisce con la parentesi graffa di chiusura del suo blocco di visibilità Esempio int main() { int a = 5; // a nasce if (a > 3) { int b = 7; // b nasce; a = b * b; a++; } // b muore cout << a; cout << b; // errore: b non è pù visibile } // a muore Ciclo di vita e scope Se una variabile è viva, non vuol dire che sia visibile dalla porzione di codice in esecuzione ad un dato istante Una variabile locale può esistere in più istanze contemporaneamente Tutte le istanze sono vive, ma solo una è visibile Esempio int fattoriale(int n) { int f1 = 1; if (n > 1) f1 = fattoriale(n - 1); return f1 * n; } int main() { int a = 3; cout << fattoriale(4); } Dentro fattoriale, a è viva ma non visibile Vengono successivamente create 3 istanze di f1 (corrispondenti alle chiamate ricorsive di fattoriale) Oggetti e funzioni globali Una funzione dichiarata nel campo d’azione globale è una funzione globale Una variabile dichiarata nel campo d’azione globale è un oggetto globale Il ciclo di vita di un oggetto globale inizia all’avvio del programma e termina alla fine del programma One Definition Rule (ODR): funzioni e oggetti globali possono essere definiti una sola volta Definizioni La definizione di una funzione le fornisce un corpo, racchiuso tra parentesi graffe int calcola(int a, int b); // solo dichiarazione int maggiore(int x, int y) { // definizione if (x > y) return x; return y; } La definizione di un oggetto ha due possibili forme <tipo> <nome>; // senza inizializzazione <tipo> <nome> = <inizializzazione>; Oggetti locali La dichiarazione di una variabile nel campo d’azione locale introduce un oggetto locale Ci sono 3 generi di oggetti locali: Automatici register Vivono dalla chiamata della funzione in cui sono definiti fino al ritorno dalla funzione Oggetti automatici per i quali viene richiesta al compilatore una memorizzazione veloce Statici Risiedono in uno spazio di memoria che rimane riservato per l’intera vita del programma Oggetti automatici Occupano uno spazio di memoria che viene allocato al momento della chiamata della funzione in cui sono dichiarati Se non vengono inizializzati, hanno un contenuto che dipende da cosa era memorizzato in precedenza in quella locazione di memoria Lo spazio di memoria a loro dedicato viene liberato in automatico al termine della funzione Oggetti register Dichiarati con la parola chiave register La dichiarazione suggerisce al compilatore di ottimizzarne l’accesso per uso frequente Nella maggior parte dei compilatori moderni, è inutile e ridondante Oggetti locali statici Dichiarate con la parola chiave static Durano l’intera esecuzione del programma Sono inizializzate la prima volta che l’esecuzione del programma passa attraverso la loro dichiarazione Se non inizializzate esplicitamente, vengono inizializzate a 0 Vengono usate quando il valore di una variabile non deve andar perso alla fine dell’esecuzione di una funzione ma deve poter essere riutilizzabile Esempio int uso_frequente(int a) { static int count; count++; if (count == 1000) std::cout << "Complimenti, lei è il millesimo cliente!" << std::endl; return a * 3; } Ciclo di vita e visibilità delle variabili statiche Una variabile statica definita all’interno di una funzione Ha lo stesso ciclo di vita di una variabile globale Viene distrutta solo quando il programma termina Ha la stessa visibilità di una variabile locale Definizioni di namespace I namespace (compreso il namespace globale) possono contenere altri namespace annidati Le entità definite in un namespace sono membre del namespace I nomi dei membri del namespace sono composti da <nome_namespace>::<nome_membro> :: è l’operatore di scope Ci si può riferire ai membri del namespace globale con ::<nome_membro> Esempi int a1 = 3; int f() { int a = 3; return ::a1 * a; // uso di una variabile del namespace globale } namespace n1 { int a; int g() { // definzione dentro il namespace return 2; } int h(); } int n1::h() { // definizione fuori dal namespace return 3; } Uso dei namespace Generalemente, in un programma si pongono Le dichiarazioni dei membri di un namespace in un file header Le definizioni dei membri in un file di implementazione Si possono utilizzare i mebri di un namespace includendo l’header e utilizzando l’operatore di scope #include <iostream> std::cout << “test1”; Si può rendere visibile un membro di un namespace con la direttiva using #include <iostream> using std::cout; cout << “test2”; Uso dei namespcae (cont.) Si possono rendere visibili tutti i membri di un namespace con a direttiva using namespace: #include <iostream> using namespace std; cout << “test3”; Si può utilizzare un namespace tramite un alias #include <iostream> namespace predefinito = std; predefinito::cout << “test3”;