14.3 Pola klasy

Pola klasy definiują dane, z jakich składać się będzie każdy obiekt klasy. Danymi tymi mogą być wielkości dowolnego typu wbudowanego lub zdefiniowanego w programie przez samego programistę lub autora biblioteki, z której program korzysta. Niestatyczne pole klasy nie może być typu, który dana klasa właśnie definiuje (z powodów o których mówiliśmy przy omawianiu struktur), natomiast może mieć typ wskaźnika do obiektu tejże klasy.

Deklaracje pól mają postać definicji zmiennych, ale zwykle nie zawierają inicjalizacji:

       class Klasa {
           int    k1, k2;
           double x,  y;
           // ...
       };
jest prawidłowe, ale
       class Klasa {
           int    k1, k2 = 1;
           double x = 1.5 , y = 3.5; // NIE
           // ...
       };
do niedawna powodowało błąd kompilacji, gdyż inicjowanie w definicji pola było niedozwolone (z pewnymi wyjątkami, o których za chwilę). Obecnie taka inicjalizacja jest dozwolona. Zadeklarowanie pola (niestatycznego) oznacza, że w każdym obiekcie definiowanej klasy będzie utworzona zmienna o nazwie i typie określonym w deklaracji. Sama definicja klasy nie powoduje utworzenia żadnych obiektów.

Pola można deklarować wewnątrz klasy w dowolnej kolejności i miejscu — przed lub po metodach; ich zakresem jest cała klasa, a nie tylko fragment następujący leksykalnie po definicji. Kolejność definicji pól ma jednak znaczenie podczas inicjowania i niszczenia (destrukcji) — pola są inicjowane w kolejności takiej, w jakiej były zadeklarowane, a usuwane w kolejności odwrotnej.

Szczególny rodzaj pól to pola statyczne. Deklaruje się je ze specyfikatorem static. Oznacza on, że będzie utworzona tylko jedna zmienna o tej nazwie i że nazwa ta będzie leżeć w zasięgu klasy. Zmienna ta jest niezależna od obiektów (zmiennych) klasy; można z niej korzystać nawet, jeśli żaden obiekt tej klasy nie został utworzony.

Sama deklaracja pola statycznego w klasie nie tworzy jednak tej zmiennej (bo właśnie jest tylko deklaracją, a nie definicją)! Zmienne odpowiadające polom niestatycznym będą tworzone podczas tworzenia obiektów klasy. Kiedy w takim razie będzie utworzona zmienna statyczna klasy? Utworzyć ją, czyli zdefiniować, trzeba na zewnątrz klasy. Ponieważ jej nazwa należy do zasięgu klasy, poza klasą trzeba się do tej zmiennej odnieść poprzez jej nazwę kwalifikowaną, za pomocą operatora zasięgu klasy, ' ::'. W definicji poza klasą nie powtarzamy już specyfikatora static. Definiując zmienną statyczną klasy można (choć nie trzeba) ją zainicjować. Na przykład:

      1.      class D {
      2.          static int k1;  // deklaracje
      3.          static int k2;
      4.      };
      5.      int D::k1;      // definicja; inicjowanie zerem
      6.      int D::k2 = 7;  // definicja z inicjowaniem
W liniach 2 i 3, w definicji klasy, deklarujemy k1k2 jako statyczne składowe klasy  D. Natomiast poza definicją klasy, w liniach 5 i 6, zmienne te definiujemy, czyli tworzymy, przydzielając im pamięć. Jeśli opuściliśmy inicjator, składowe statyczne inicjowane wartością 0 (zero) odpowiedniego typu.

Wyjątkowo, składowe statyczne mogą być definiowane i inicjalizowane bezpośrednio w punkcie deklaracji, wewnątrz klasy, ale tylko jeśli są ustalone (const) i typu całkowitego

   class D {
       static const int k1, k2 = 2;
       // ...
   };
   const int D::k1 = 1;
Tu k2 jest tworzone i inicjalizowane wewnątrz definicji klasy  D, natomiast k1 tylko deklarowane —  definicja z ostatniej linii jest zatem konieczna, i to wraz z inicjalizacją, bo stałe, jak pamiętamy, muszą być inicjalizowane w momencie ich definiowania.

Zmienne statyczne są tworzone (i inicjowane) po załadowaniu programu do pamięci, ale przed rozpoczęciem wykonywania funkcji main (jak zmienne globalne).

Do zmiennych statycznych klasy można się odwoływać w programie poprzez ich pełną nazwę kwalifikowaną (D::k1) lub tak jak do normalnej składowej, czyli poprzez nazwę dowolnego obiektu tej klasy i operator wyboru składowej (kropka lub „strzałka”, w zależności od tego, czy nazwa była nazwą obiektu czy wskaźnika do niego). Oczywiście, ponieważ istnieje tylko jeden egzemplarz zmiennej statycznej, nie ma wtedy znaczenia, którego obiektu użyjemy.

Ponieważ składowa statyczna istnieje w jednym egzemplarzu i nie wchodzi fizycznie w skład obiektów klasy, więc składową taką może być obiekt tejże klasy. Na przykład klasa (struktura) Punkt z poniższego programu


P103: statskl.cpp     Pola statyczne

      1.  #include <iostream>
      2.  #include <cmath>    // sqrt
      3.  using namespace std;
      4.  
      5.  struct Punkt {
      6.      double x, y;
      7.      static Punkt srodek;  
      8.  };
      9.  Punkt Punkt::srodek;      
     10.  
     11.  void ustal_srodek(double,double);
     12.  double odl_od_srodka(const Punkt&);
     13.  
     14.  int main() {
     15.      Punkt P = {3, 4};     
     16.      cout << "Punkt P = (" << P.x << "," << P.y << ")\n";
     17.  
     18.      ustal_srodek(0,0);
     19.      cout << "Odl. P-srodek: " << odl_od_srodka(P) << endl;
     20.  
     21.      ustal_srodek(9,-4);
     22.      cout << "Odl. P-srodek: " << odl_od_srodka(P) << endl;
     23.  }
     24.  
     25.  void ustal_srodek(double xx,double yy) {
     26.      Punkt::srodek.x = xx; 
     27.      Punkt::srodek.y = yy; 
     28.      cout << "Srodek w p-cie (" << xx << "," << yy << ")\n";
     29.  }
     30.  
     31.  double odl_od_srodka(const Punkt& p) {
     32.      return
     33.          sqrt((p.x-Punkt::srodek.x)*(p.x-Punkt::srodek.x) +
     34.               (p.y-Punkt::srodek.y)*(p.y-Punkt::srodek.y));
     35.  }

zawiera niestatyczne pola xy (współrzędne punktu, zależne od obiektu) i jako pole statyczne obiekt srodek tejże klasy Punkt —  może on na przykład reprezentować aktualny punkt odniesienia, względem którego mierzymy odległości innych punktów. W linii  definiujemy tę składową statyczną, zadeklarowaną w definicji klasy (). Zauważmy, że nie powtarzamy tu specyfikatora static.

W programie głównym tworzymy obiekt p klasy Punkt () i nadajemy wartości jego składowym xy. Ta składnia inicjalizacji jest tu prawidłowa, bo struktura co prawda nie jest czystą C-strukturą, ale jest agregatem, o którym będzie mowa dalej.

Za pomocą funkcji ustal_srodek ustalamy punkt odniesienia na (0, 0). Zauważmy, że w treści tej funkcji, niebędącej składową klasy, odwołujemy się do statycznej składowej srodek klasy Punkt, a zatem używamy jej pełnej, kwalifikowanej nazwy z „czterokropkiem” — linie . Składowa ta sama jest obiektem klasy Punkt, więc ma (niestatyczne) składowe xy.

Funkcja odl_od_srodka oblicza odległość między dowolnym punktem przekazanym jej przez referencję a aktualnym punktem odniesienia, który jest opisywany statyczną składową klasy

    Punkt P = (3,4)
    Srodek w p-cie (0,0)
    Odl. P-srodek: 5
    Srodek w p-cie (9,-4)
    Odl. P-srodek: 10

Pola statyczne stosuje się często, gdy potrzebne są przeróżnego rodzaju liczniki obiektów, parametry wspólne dla wszystkich obiektów klasy (na przykład cena, data wykonania programu, aktualny kurs dolara, deskryptor pliku), flagi, za pomocą których obiekty „porozumiewają się” między sobą itd.

T.R. Werner, 23 lutego 2019; 23:59