1. Programowanie obiektowe

1.4. Deklaracje pól

Pola są właściwą treścią każdego obiektu klasy, to one stanowią jego reprezentację w pamięci operacyjnej. Pod tym względem nie różnią się niczym od znanych ci już pól w strukturach i są po prostu zwykłymi zmiennymi, zgrupowanymi w jedną, kompleksową całość. Jako miejsce na przechowywanie wszelkiego rodzaju danych, pola mają kluczowe znaczenie dla obiektów i dlatego powinny być chronione przez niepowołanym dostępem z zewnątrz. Przyjęło się więc, że w zasadzie wszystkie pola w klasie powinny być prywatne i chronione. Wspominałem o tym już wcześniej, teraz przypominam, tym bardziej że to będzie sprawdzane w Waszych projektach ;>

By jakoś odróżnić pola od zmiennych lokalnych, stosuje się różne konwencje nazywania jednych i drugich. Konwencji jest wiele – jedni za konwencją stosowaną w Qt wszystkie pola w klasie nazywają zaczynając od litery m_, inni zaczynają nazwę pola dużą literą a zmiennej lokalnej małą, jeszcze inni stosują podkreślenie na końcu nazwy... wybrać można co chcecie – ważne by konsekwentnie trzymać się swojego wyboru.

Skoro pola nie są dostępne spoza klasy - to dostęp do danych w nich zawartych musi się odbywać za pomocą dedykowanych metod. Rozwiązanie to ma wiele rozlicznych zalet: pozwala chociażby na tworzenie pól, które można jedynie odczytywać, daje sposobność wykrywania niedozwolonych wartości (np. indeksów przekraczających rozmiary tablic itp.) czy też podejmowania dodatkowych akcji podczas operacji zmiany wartości pola (zmiany stanu obiektu). Takie funkcje zwyczajowo nazywa się setterami / getterami, i też oznacza w jakiś sposób. Mi osobiście najbardziej odpowiada konwencja nazwania funkcji zwracającej tak samo jak brzmi podstawowa nazwa pola, natomiast metody ustawiającej znów nazwą pola, lecz tym razem poprzedzonej przedrostkiem set.

Ponieważ jednak dla większej czytelności przekazu w podręczniku posługuję się polskimi nazwami klas, metod, zmiennych, itp - to przedrostek także będzie polski - zmień.  

Przykład:


class CMoja {
public:
    double wartosc() { return m_wartosc; }
    void zmienWartosc(double _v) { m_wartosc = _v; }
private:
    double m_wartosc;
};


Domyślnie wartości pól nie są inicjowane - podobnie jak zmienne lokalne. Natomiast zawsze istnieje możliwość inicjacji pól - albo w konstruktorze, albo bezpośrednio w deklaracji klasy, analogicznie jak to ma miejsce w przypadku zmiennych. 

Pola statyczne

Omawiane dotychczas pola są ściśle powiązane z konkretnym obiektem, przy czym dla każdego z nich mogą mieć różną wartość. Tak jak to wspominaliśmy we wstępie – pola reprezentują stan obiektu. Jak nie ma obiektu – nie ma i pól. Pamięć na pola jest przyznawana w momencie tworzenia obiektu, i zwalniana w momencie gdy obiekt jest kasowany.

Wszystkie powyższe uwagi są prawdziwe, z jednym wyjątkiem – pól statycznych. W przeciwieństwie do zwykłych pól – pola statyczne nie są powiązane z jakimkolwiek obiektem będącym instancją klasy. Pola statyczne istnieją od momentu uruchomienia programu, i są kasowane dopiero po jego zakończeniu. Ich czas życia jest analogiczny do czasu życia zmiennych globalnych. Tak naprawdę pola statyczne to są zmienne globalne – specyficzne, bo zdefiniowane w przestrzeni nazw jakiejś klasy.

Zasady składniowe ich definiowania i deklarowania są proste – wystarczy w definicji klasy dodać dodatkowe słowo kluczowe static przed polem które ma być statyczne, i następnie dodatkowo zdefiniować gdzieś instancję tego pola. Przy czym owe nieostre gdzieś oznacza poza nagłówkiem (bo inaczej mielibyśmy do czynienia z redefinicją pola przy każdym dołączeniu go do następnej jednostki kompilacji - pliku źródłowego), w dowolnym module włączonym do aplikacji. Zazwyczaj robi się to w pliku implementacji klasy. Przykład tworzenia pola statycznego jest zamieszczony poniżej:


#include "iostream"

using namespace std;

/** Przykładowa klasa wykorzystująca pola statyczne */
class CStatMoja {
public:
  /** metoda drukująca */
  void drukujInfo() { cout << "Pole: " << m_statPole << "\n"; }
  static int m_statPole;
};

/// dodatkowa definicja zmiennej - niezbędna w przypadku pól statycznych
int CStatMoja::m_statPole = 0;

// przykład korzystania
int main(int argc, char **argv) {
    CStatMoja o1, o2;
    // wydrukujmy zawartość pola statycznego korzystając z obu obiektów
    o1.drukujInfo();
    o2.drukujInfo();
    // zmieńmy wartość pola - odwołując się do niego przez nazwę klasy
    CStatMoja::m_statPole = 10;
    // wydrukujmy zawartość pola statycznego korzystając z obu obiektów
    o1.drukujInfo();
    o2.drukujInfo();
    // pole statyczne można zmieniać z dowolnego obiektu - i tak jest
    // to jeden obszar w pamięci, więc jakiekolwiek zmiany będą widoczne
    // zawsze tak samo
    o1.m_statPole = 15;
    o2.drukujInfo();
    cout << CStatMoja::m_statPole << endl;

    return 0;
}

Powyższy przykład ma zdefiniowane pole statyczne w części publicznej klasy. Jednakże nic nie stoi na przeszkodzie by to pole znalazło się w części prywatnej lub chronionej – wtedy stracilibyśmy jedynie możliwość dostępu do niego poprzez nazwę klasy:


#include "iostream"

using namespace std;

/** Przykładowa klasa wykorzystująca pola statyczne */
class CStatMoja {
public:
  /** metoda drukująca */
  void drukujInfo() { cout << "Pole: " << m_statPole << "\n"; }
  /** metoda zmieniająca zawartość pola statycznego */
  void zmienStatPole(int _v) {
    m_statPole = _v;
  }
private:
  static int m_statPole;
};

/// dodatkowa definicja zmiennej - niezbędna w przypadku pól statycznych
int CStatMoja::m_statPole = 0;

// przykład korzystania
int main(int argc, char **argv) {
    CStatMoja o1, o2;
    // wydrukujmy zawartość pola statycznego korzystając z obu obiektów
    o1.drukujInfo();
    o2.drukujInfo();
    // zmieńmy wartość pola - odwołując się do niego przez nazwę klasy
    o1.zmienStatPole(10);
    // wydrukujmy zawartość pola statycznego korzystając z obu obiektów
    o1.drukujInfo();
    o2.drukujInfo();


    return 0;
}

Zasady dostępu do pól statycznych są także takie same jak dla zmiennej globalnej w jakiejś przestrzeni nazw. Pole statyczne istnieje zawsze – więc można się odwołać do niego nawet jeśli nie istnieje instancja obiektu danej klasy. Dodatkowo – można się do takich pól odwoływać bezpośrednio z metod należących do danej klasy – bo wtedy jesteśmy w jej przestrzeni nazw.