Esercizio - I tipi di scope in C++ (secondo appello 2021-22, teoria)
In questa pagina 3
Domanda aperta 2 del secondo appello 2021-22 (11 febbraio 2022) del corso di Laboratorio di Programmazione, Ing. Informatica UniPD. Teoria: C++ - scope, namespace e librerielo scope è la regione di testo in cui un nome è valido, dal punto di dichiarazione alla fine dello scope; i tipi di scope sono globale (di file), di classe, locale (blocco o lista argomenti di una funzione), di statement (for) e di namespace; funzioni dentro funzioni sono illegali, le classi locali da evitare; i namespace raggruppano nomi senza creare un tipo e si accedono col nome qualificato namespace::membro oppure con using (la using directive va evitata negli header); un header file è un insieme di dichiarazioni che chi lo include può usare (<> per i file di sistema, "" per quelli del progetto), dev'essere protetto dalle include guard (#ifndef/#define/#endif) per non includere due volte le stesse definizioni; il linker unisce i file oggetto con le librerie, statiche (copiate in ogni eseguibile, self-contained) o dinamiche (una sola copia condivisa, .so su Linux, .dll su Windows, ricompilabili senza toccare gli eseguibili).C++ - scope, namespace e librerie →. Una domanda simile, con la differenza fra scope e durata, è nel terzo appello 2022-23: Esercizio - Scope e durata della memorizzazione (terzo appello 2022-23, teoria).
Testo. Definire il concetto di scope in C++. Elencare e descrivere tutti i tipi di scope presenti in C++. Riportare il codice C++ con un esempio di scope per ognuno dei tipi elencati.
Definizione
Scope. Regione del testo di un programma. Ogni nome è dichiarato in uno scope ed è valido (in scope) dal punto della dichiarazione fino alla fine di quello scope. L'obiettivo è rendere i nomi locali ("Locality is good"), per evitare interferenze (clash): per questo i nomi sono tanto più descrittivi quanto più è grande il loro scope.
I tipi di scope
| tipo | regione | caratteristiche |
|---|---|---|
| globale (di file) | fuori da ogni altro scope | funzioni, tipi e costanti; le variabili globali sono visibili e modificabili ovunque nel file: da usare con attenzione (nessun incapsulamento, debug difficile) |
| di classe | il testo dentro una classe | i membri sono visibili in tutta la classe (anche quelli dichiarati dopo); fuori si accede con oggetto.membro o Classe::membro |
| locale | dentro un blocco { } o nella lista degli argomenti di una funzione |
dal punto di dichiarazione alla }; i blocchi si annidano |
| di statement | dentro l'istruzione, per esempio nell'intestazione di un for |
la variabile del for non esiste dopo il ciclo |
| di namespace | uno scope con nome, dentro lo scope globale o un altro namespace | raggruppa classi, funzioni, dati e tipi senza creare un tipo; si raggiunge con namespace::nome |
Scope annidati: uno scope dentro un altro. Sono leciti blocchi in blocchi, funzioni dentro classi (il caso più frequente e utile), classi dentro classi (raro), classi in funzioni (da evitare); non sono legali le funzioni dentro funzioni. Un nome interno nasconde uno esterno uguale (shadowing), raggiungibile con :: se è globale.
Un esempio per ogni tipo
#include <iostream>
int global_counter = 100; // scope GLOBALE (di file): visibile ovunque sotto
namespace Geometria { // scope di NAMESPACE: uno scope con nome
constexpr double pi = 3.14159;
double area_cerchio(double r) { return pi * r * r; }
}
class Contatore { // scope di CLASSE: il testo dentro la classe
public:
void incrementa() { ++valore; } // valore e' visibile qui, anche se dichiarato dopo
int leggi() const { return valore; }
private:
int valore = 0;
};
int somma(int a, int b) // a e b: scope LOCALE (lista degli argomenti)
{
int risultato = a + b; // scope locale: dal punto di dichiarazione alla }
return risultato;
}
int main()
{
int x = 5; // locale a main
{ // un blocco crea uno scope annidato
int x_interno = x * 2; // esiste solo qui dentro
std::cout << "blocco: x_interno = " << x_interno << '\n';
}
// std::cout << x_interno; // errore: x_interno non e' piu' in scope
for (int i = 0; i < 3; ++i) // scope di STATEMENT: i vive solo nel for
global_counter += i;
// std::cout << i; // errore: i non esiste fuori dal for
Contatore c;
c.incrementa();
c.incrementa();
std::cout << "somma = " << somma(2, 3) << '\n';
std::cout << "global_counter = " << global_counter << '\n';
std::cout << "contatore = " << c.leggi() << '\n';
std::cout << "area = " << Geometria::area_cerchio(1.0) << '\n'; // nome qualificato
return 0;
}blocco: x_interno = 10
somma = 5
global_counter = 103
contatore = 2
area = 3.14159global_counter: scope globale; ilforla modifica di : .namespace Geometria: scope di namespace; la funzione si chiama conGeometria::area_cerchio(1.0).class Contatore: scope di classe;valoreè privato ma visibile dentroincrementa()eleggi().somma(int a, int b):a,b,risultatohanno scope locale (parametri e corpo della funzione).- il blocco
{ int x_interno = ... }inmainè uno scope locale annidato:x_internoesiste solo lì;// std::cout << x_interno;non compilerebbe. for (int i = 0; ...): scope di statement;inon è più visibile dopo il ciclo.
Un esempio di shadowing: nel programma seguente la x interna nasconde le altre due e ::x raggiunge la globale.
#include <iostream>
int x = 1; // globale
int main()
{
std::cout << "x globale = " << x << '\n';
int x = 2; // locale: NASCONDE la globale (shadowing)
std::cout << "x locale = " << x << '\n';
{
int x = 3; // ancora piu' interno: nasconde quella di main
std::cout << "x interna = " << x << '\n';
std::cout << "x globale con :: = " << ::x << '\n'; // operatore di scope senza nome = globale
}
std::cout << "di nuovo x locale = " << x << '\n';
return 0;
}x globale = 1
x locale = 2
x interna = 3
x globale con :: = 1
di nuovo x locale = 2Versione ripasso
- Scope = regione di testo in cui un nome è valido: dalla dichiarazione alla fine dello scope. Scopo: nomi locali, niente clash; più grande lo scope, più descrittivo il nome. Rimando: C++ - scope, namespace e librerielo scope è la regione di testo in cui un nome è valido, dal punto di dichiarazione alla fine dello scope; i tipi di scope sono globale (di file), di classe, locale (blocco o lista argomenti di una funzione), di statement (for) e di namespace; funzioni dentro funzioni sono illegali, le classi locali da evitare; i namespace raggruppano nomi senza creare un tipo e si accedono col nome qualificato namespace::membro oppure con using (la using directive va evitata negli header); un header file è un insieme di dichiarazioni che chi lo include può usare (<> per i file di sistema, "" per quelli del progetto), dev'essere protetto dalle include guard (#ifndef/#define/#endif) per non includere due volte le stesse definizioni; il linker unisce i file oggetto con le librerie, statiche (copiate in ogni eseguibile, self-contained) o dinamiche (una sola copia condivisa, .so su Linux, .dll su Windows, ricompilabili senza toccare gli eseguibili).C++ - scope, namespace e librerie →.
| tipo | dove | esempio |
|---|---|---|
| globale | fuori da tutto | int global_counter = 100; |
| classe | dentro la classe | valore privato di Contatore, visibile in incrementa() |
| locale | blocco {} o argomenti di funzione |
a, b, risultato in somma; { int x_interno; } |
| statement | nell'istruzione | for (int i = 0; ...): i muore col ciclo |
| namespace | scope con nome | namespace Geometria { ... } Geometria::area_cerchio(1.0) |
- Annidamenti: funzioni in classi (utile), blocchi (inevitabili), classi in classi (raro), classi locali (da evitare), funzioni in funzioni: illegali. Shadowing: il nome interno nasconde l'esterno;
::x= globale. - Programma:
global_counter100 103 con ilfor();Geometria::area_cerchio(1.0) = 3.14159;somma(2, 3) = 5;Contatoreincrementato due volte 2; shadowing:xglobale 1, locale 2, interna 3,::x= 1. - Errori tipici: usare
x_internooifuori dal loro scope; confondere scope e durata (Esercizio - Scope e durata della memorizzazione (terzo appello 2022-23, teoria)); dimenticare che le variabili globali sono accessibili da ogni funzione; definire una funzione dentro un'altra.