Vizibilitatea numelor

Domenii

În C++, entitățile denumite, precum variabilele, funcțiile și tipurile compuse, trebuie declarate înainte de a fi folosite. Locul în care se face declarația influențează vizibilitatea lor:

O entitate declarată în afara oricărui bloc are domeniu global, adică identificatorul său este valid oriunde în program. În schimb, o entitate declarată într-un bloc, de exemplu într-o funcție sau într-o instrucțiune de decizie, are domeniu de bloc și este vizibil doar în interiorul acelui bloc în care a fost declarată, nu și în exteriorul acestuia.

Variabilele cu domeniu de bloc sunt cunoscute ca variabile locale.

De exemplu, o variabilă declarată în corpul unei funcții este o variabilă locală care rămâne până la sfârșitul funcției (adică până la acolada } care închide definiția funcției), dar nu și în afara ei:

1
2
3
4
5
6
7
8
9
10
11
12
13
int foo;        // variabila globala

int o_functie ()
{
  int bar;      // variabila locala
  bar = 0;
}

int alta_functie ()
{
  foo = 1;  // corect: foo este o variabila globala
  bar = 2;  // eronat: bar nu este vizibila din aceasta functie
}

Într-un domeniu, un nume poate reprezenta numai o entitate. De exemplu, nu pot exista două variabile cu același nume în același domeniu:

1
2
3
4
5
6
7
int o_functie ()
{
  int x;
  x = 0;
  double x;   // eronat: numele exista deja in acest domeniu
  x = 0.0;
}

Vizibilitatea unei entități cu domeniu de bloc se extinde până la sfârșitul blocului, inclusiv în blocurile incluse. Cu toate acestea, un bloc interior (inclus) poate reutiliza un nume existent în blocul exterior care îl include pentru a se referi la o altă entitate, deoarece este un alt bloc; în asemenea cazuri, identificatorul se va referi la o entitate diferită numai în blocul interior; în cel exterior, entitatea aceasta nu este vizibilă, iar identificatorul se va referi din nou la entitatea originală. De exemplu:

// inner block scopes
#include <iostream>
using namespace std;

int main () {
  int x = 10;
  int y = 20;
  {
    int x;   // corect, domeniul interior
    x = 50;  // seteaza valoarea lui x interior
    y = 50;  // seteaza valoarea lui y (exterior)
    cout << "bloc interior:\n";
    cout << "x: " << x << '\n';
    cout << "y: " << y << '\n';
  }
  cout << "bloc exterior:\n";
  cout << "x: " << x << '\n';
  cout << "y: " << y << '\n';
  return 0;
}
bloc interior:
x: 50
y: 50
bloc exterior:
x: 10
y: 50

Să observăm că y este vizibil în blocul interior și, deci, accesarea lui y rerpezintă accesarea variabilei exterioare.

Variabilele declarate la începutul unui bloc, precum parametrii funcțiilor și variabilele declarate în bucle și decizii (cele declarate într-un for sau if) aparțin blocului respectiv.

Spații de nume

Într-un anumit domeniu poate exista o wingură entitate cu un anumit nume. Aceasta devine o problemă numai rareori, deoarece blocurile tind sa fie relativ scurte și denumirile au un anume scop, precum contor de variabilă, argument, etc...

Pentru denumirile globale avem o probabilitate mai mare de apariție a coliziunilor, mai ales dacă luăm în considerare ca bibliotecile pot să definească mai multe funcții, tipuri și variabile care să nu fie locale, iar unele dintre ele chiar generice.

Spațiile de nume ne permit să grupăm entități care ar putea avea domeniu global în domenii mai restrânse, acordându-le domeniu de namespace. Acest mecanism permite organizarea elementelor programelor în domenii logice diferite referite de nume.

Sintaxa pentru declarearea unui spațiu de nume (namespace) este:


namespace identificator
{
  entitati_denumite
}


unde identificator este orice identificator valid și entitati_denumite reprezintă un set de variabile, tipuri și funcții incluse în spațiile de nume. De exemplu:

1
2
3
4
namespace spatiul_meu
{
  int a, b;
}

În acest caz, variabilele a și b sunt variabile obișnuite declarate în interiorul spațiului de nume spatiul_meu.

Aceste variabile pot fi accesate normal din spațiul lor, cu ajutorul identificatorilor (a respectiv b); dar, pentru a putea fi accesate din afara spațiului spatiul_meu ele trebuie să fie precedate de spațiu și operatorul de domeniu ::. De exemplu, pentru a accesa variabilele anterioare din afara lui spatiul_meu ar trebui calificate astfel:

1
2
spatiul_meu::a
spatiul_meu::b

Spațiile de nume sunt utile mai ales pentru a evita coliziunile de nume. De exemplu:

// spatii de nume
#include <iostream>
using namespace std;

namespace foo
{
  int valoare() { return 5; }
}

namespace bar
{
  const double pi = 3.1416;
  double valoare() { return 2*pi; }
}

int main () {
  cout << foo::valoare() << '\n';
  cout << bar::valoare() << '\n';
  cout << bar::pi << '\n';
  return 0;
}
5
6.2832
3.1416

În acest caz, avem două funcții cu aceeași denumire: valoare. Una este definită în spațiul de nume foo, iar cealaltă în bar. Datorită spațiilor de nume nu se produc erori de redefinire. Să observăm că pi este accesat normal (necalificat) în interiorul spațiului de nume bar (doar ca pi), în timp ce în interiorul lui main trebuie să fie calificat ca bar::pi.

Putem avea spații de nume împărțite: două segmente de cod diferite pot fi declarate în același spațiu de nume:

1
2
3
namespace foo { int a; }
namespace bar { int b; }
namespace foo { int c; }

Această secvență declară trei variabile: a și c sunt în spațiul de nume foo, în timp ce b este în spațiul de nume bar. Spațiile de nume pot chiar să se extindă în mai multe unități (adică în mai multe fișiere de cod sursă).

using

Cuvântul cheie using adaugă un nume în regiunea declarativă curentă (de exemplu, un bloc), nemaifiind nevoie să calificăm acel nume. De exemplu:

// using
#include <iostream>
using namespace std;

namespace unu
{
  int x = 5;
  int y = 10;
}

namespace doi
{
  double x = 3.1416;
  double y = 2.7183;
}

int main () {
  using unu::x;
  using doi::y;
  cout << x << '\n';
  cout << y << '\n';
  cout << unu::y << '\n';
  cout << doi::x << '\n';
  return 0;
}
5
2.7183
10
3.1416

Să observăm că în main variabila x (fără calificarea numelui) se referă la unu::x, în timp ce y se referă la doi::y, așa cum precizează declarațiile using. Variabilele unu::y și doi::x pot fi accesate, dar trebuie calificate.

Cuvântul cheie using poate fi folosit și ca o directivă pentru a include un întreg spațiu de nume:

// using
#include <iostream>
using namespace std;

namespace unu
{
  int x = 5;
  int y = 10;
}

namespace doi
{
  double x = 3.1416;
  double y = 2.7183;
}

int main () {
  using namespace unu;
  cout << x << '\n';
  cout << y << '\n';
  cout << doi::x << '\n';
  cout << doi::y << '\n';
  return 0;
}
5
10
3.1416
2.7183

În acest caz, în care declarăm includerea spațiului de nume unu, folosirea directă a lui x și y, fără calificarea identificatorilor, va face căutarea în spațiul de nume unu.

using și using namespace sunt valide numai în același bloc în care sunt precizate sau în întregul fișier sursă dacă sunt folosite la nivel global. De exemplu, este posibil să folosim mai întâi obiecte ale unui spațiu de nume, apoi pe cele din altul dacă împărțim codul în blocuri diferite:

// exemplu using namespace
#include <iostream>
using namespace std;

namespace unu
{
  int x = 5;
}

namespace doi
{
  double x = 3.1416;
}

int main () {
  {
    using namespace unu;
    cout << x << '\n';
  }
  {
    using namespace doi;
    cout << x << '\n';
  }
  return 0;
}
5
3.1416

Alias-uri pentru spații de nume

Putem crea nume noi pentru spațiile de nume existente, folosind următoarea sintaxă:

namespace noul_nume = nume_curent;

Spațiul de nume std

Toate entitățile (variabile, tipuri, constante și funcții) din biblioteca standard C++ sunt declarate în spațiul de nume std. De fapt, cele mai multe exemple din acest tutorial, includ următoarea linie:

1
using namespace std;

Aceasta conferă vizibilitate directă tuturor numelor din spațiul de nume std, în codul sursă. Așa procedăm și în acest tutorial pentru a-l face mai ușor de înțeles și pentru ca exemplele să fie mai scurte, dar mulți programatori preferă să califice fiecare element din biblioteca standard folosit în programe. De exemplu, în loc de:

1
cout << "Hello world!";

putem întâlni:

1
std::cout << "Hello world!";

Fie că elementele din spațiul de nume std sunt introduse cu declarații using, fie că sunt calificate la fiecare folosire, comportamentul sau eficiența programului nu se schimbă în nici un fel. Ține mai mult de stilul preferat, deși se preferă calificarea explicită în cazul proiectelor care folosesc mai multe biblioteci.

Clase de stocare

Memoria pentru variabilele cu domeniu global sau domeniu de spațiu de nume se alocă pe toată durata programului. Numim aceasta ca fiind alocare statică și ea este opusă alocării de variabile locale (cele declarate în interiorul unui bloc). Cea din urmă este cunoscută ca alocare automatică. Memoria alocată pentru variabilele locale este disponibilă numai pe durata blocului în care au fost declarate; după aceea, același spațiu de memorie poate fi folosit pentru o variabilă locală a altei funcții sau folosită altfel.

Dar mai există o diferență majoră între variabilele alocate static și variabilele alocate automatic:
- Variabilele cu alocare statică (precum variabilele globale) care nu sunt inițializate explicit se inițializează automat cu zero.
- Variabilele cu alocare automatică (precum variabilele locale) care nu sunt inițializate explicit rămân neinițializate și, de aceea, au valori nedeterminate.

De exemplu:
// alocare statică vs alcoare automatică
#include <iostream>
using namespace std;

int x;

int main ()
{
  int y;
  cout << x << '\n';
  cout << y << '\n';
  return 0;
}
0
4285838

Ieșirea efectivă poate varia, dar numai valoarea lui x va fi sigur zero. y poate conține, de fapt, orice valoare (inclusiv zero).
Index
Index