7.2 Zasięg i widoczność zmiennych

Zasięg (ang. scope) deklaracji to ta część programu, gdzie deklaracja jest aktywna, czyli nazwa deklarowana może być użyta i odnosi się do tej właśnie deklaracji. Zakres widoczności (ang. visibility) natomiast to ten fragment (fragmenty), w których nazwa (identyfikator) może być użyta bez żadnej kwalifikacji (nazwą klasy, przestrzeni nazw itd.). Zakres widoczności zadeklarowanej zmiennej może być węższy niż zasięg deklaracji na skutek przesłonięcia (patrz dalej).

Zmienne zadeklarowane poza wszystkimi funkcjami mają zasięg od miejsca deklaracji do końca pliku (a właściwie jednostki translacji), w szczególności obejmuje on wszystke funkcje zdefiniowane w tym module po wystąpieniu deklaracji. Zasięg może być rozszerzony na inne jednostki translacji jeśli nazwa jest w nich zadeklarowana ze specyfikatorem extern. Mówimy wtedy, że takie zmienne są eksportowane (patrz rozdział o zmiennych zewnętrznych ). Zmienne (ogólnie: nazwy) zadeklarowane poza wszystkimi funkcjami nazywamy zmiennymi globalnymi. W odróżnieniu od zmiennych lokalnych, definiowanych wewnątrz funkcji, one automatycznie inicjowane zerami odpowiedniego typu (dotyczy to również wskaźników). Ich czas życia to okres od początku do końca działania programu.

Zmienne lokalne są deklarowane wewnątrz funkcji, a ogólniej bloku ograniczonego nawiasami klamrowymi. Ich zasięg obejmuje część programu od miejsca deklaracji do końca tego bloku. Parametry funkcji można uważać za zmienne lokalne zdefiniowane na samym początku funkcji i inicjowane w trakcie wykonania wartościami argumentów wywołania. Czasem życia niestatycznej zmiennej lokalnej jest czas od napotkania jej definicji przez przepływ sterowania do jego wyjścia z funkcji, a ogólniej z najwęższego bloku, w którym ta definicja wystąpiła: w tym momencie zmienne są z pamięci usuwane. W szczególności oznacza to, że kiedy przepływ sterowania powraca do tej samej funkcji (bloku), zmienne lokalne są tworzone na nowo i nie „pamiętają” swoich wcześniejszych wartości. Takimi zmiennymi są też zmienne deklarowane w części inicjalizacyjnej pętli for (część w nawiasie okrągłym przed pierwszym średnikiem) jak i zmienne deklarowane w części warunkowej instrukcji if, while, for, switch: ich zasięgiem jest ciało danej instrukcji (więcej na ten temat powiemy później).

Zmienne globalne mogą być przesłonięte, jeśli wewnątrz funkcji (lub bloku) zadeklarujemy inną zmienną o tej samej nazwie (choć niekoniecznie tym samym typie). Wówczas nazwa tej zmiennej w ciele funkcji odnosi się do zmiennej lokalnej. Zmienna globalna istnieje, ale jest w zakresie funkcji (bloku) niewidoczna. Nie znaczy to, że nie mamy do niej dostępu. Do przesłoniętej zmiennej globalnej możemy odwołać się poprzez operator zasięgu ' ::' („czterokropek”). Jeśli na przykład przesłonięta została zmienna o nazwie k, to do globalnej zmiennej o tej nazwie odwołać się można poprzez nazwę kwalifikowaną ::k.

Inicjalizację, operator zasięgu i widoczność zmiennych globalnych ilustruje poniższy program; demonstruje też deklarowanie zmiennych wewnątrz bloku: jeśli wewnątrz bloku zasłonimy zmienną lokalną, to ta lokalna zmienna staje się w tym bloku całkowicie niewidoczna, podczas gdy zmienna globalna o tej samej nazwie jest widoczna w dalszym ciągu, jeśli użyjemy operatora zasięgu.


P34: przesl.cpp     Widoczność i przesłanianie zmiennych

      1.  #include <iostream>
      2.  using namespace std;
      3.  
      4.  int k;                                 
      5.  
      6.  int main() {
      7.      cout << "  k: " <<   k << endl;    
      8.      cout << "::k: " << ::k << endl;
      9.  
     10.      int k = 10;                        
     11.  
     12.      cout << "  k: " <<   k << endl;
     13.      cout << "::k: " << ::k << endl;
     14.  
     15.      ::k = 1;                           
     16.  
     17.      cout << "  k: " <<   k << endl;
     18.      cout << "::k: " << ::k << endl;
     19.  
     20.      {                                  
     21.          int k = 77;
     22.          cout << "W bloku:"     << endl;
     23.          cout << "  k: " <<   k << endl;
     24.          cout << "::k: " << ::k << endl;
     25.      }
     26.  
     27.      cout << "Po bloku:" << endl;
     28.      cout << "  k: " <<   k << endl;
     29.      cout << "::k: " << ::k << endl;
     30.  }

Zmienna k zadeklarowana w linii  jest zmienną globalną (i jako taka jest zainicjowana zerem). Jest zatem widoczna i w funkcji main. W linii drukujemy wartość k — ponieważ k nie zostało przesłonięte inną zmienną, w tym miejscu programu zarówno k, jak i  ::k odnoszą się do tej samej zmiennej — zmiennej globalnej.

W linii  wprowadzamy lokalną dla funkcji main zmienną k. Przesłania ona globalne k; od tej pory użycie w funkcji main nazwy k oznacza odniesienie się do zmiennej lokalnej. Jednak zmienna globalna jest wciąż dostępna: trzeba tylko odnosić się do niej za pomocą pełnej nazwy kwalifikowanej operatorem zakresu, a więc ::k. W linii zmieniamy wartość zmiennej globalnej — wartość lokalnej zmiennej k pozostaje oczywiście niezmieniona.

W linii otwieramy blok wewnętrzny zanurzony w bloku, jakim jest ciało funkcji main. Wprowadzona w tym bloku zmienna k całkowicie przesłania lokalną dla main zmienną k. Zauważmy tu na marginesie, że takie przesłonięcie byłoby nielegalne w Javie. Wewnątrz tego zagnieżdżonego bloku mamy dostęp wyłącznie do k z tego bloku i  k globalnego. Natomiast po wyjściu z tego bloku, zdefiniowana w nim zmienna jest usuwana i znów staje się widoczna zmienna k zdefiniowana w linii . Możemy się o tym przekonać patrząc na wynik programu

      k: 0
    ::k: 0
      k: 10
    ::k: 0
      k: 10
    ::k: 1
    W bloku:
      k: 77
    ::k: 1
    Po bloku:
      k: 10
    ::k: 1

T.R. Werner, 21 lutego 2016; 20:17