Wszystkie składowe klasy są widoczne wewnątrz klasy. Oznacza to, że mają do nich dostęp funkcje (metody) zadeklarowane jako funkcje składowe danej klasy. Mogą jednak mieć różny poziom dostępności z zewnątrz, a więc z funkcji które nie sa skłądowymi danej klasy: poziom dostępności jest określany jednym ze słów kluczowych: public, private, lub protected.. W odróżnieniu od Javy, nie stawia się go jednak przed każdą ze składowych osobno, a definicję klasy dzieli się na tzw. sekcje: każda sekcja rozpoczyna się od jednego z tych słów kluczowych z następującym po nim dwukropkiem. Sekcja rozciąga się do końca definicji klasy lub do rozpoczęcia innej sekcji, na przykład:
1. class Klasa { 2. int s1; 3. public: 4. int s2; 5. double d2; 6. private: 7. double s3; 8. void fun3(int,double); 9. public: 10. int s4; 11. char c4; 12. };Zauważmy, że pole s1 zostało zdefiniowane przed pojawieniem się jakiegokolwiek specyfikatora poziomu dostępności. Przyjmuje się wtedy dostępność domyślną, według następującej zasady:
Tak więc w powyższym przykładzie występują cztery sekcje: pierwsz i trzecia private a druga i czwarta public. Tak więc prywatne są pola s1, s3 i funkcja (metoda) fun3, natomiast publiczne s2, d2, s4 i c4. Jak widzimy, w definicji klasy (struktury) może być wiele sekcji publicznych czy prywatnych.
Pod względem dostępności składowe dzielą się zatem na:
Zauważmy, że ograniczenie dostępności dotyczy nazw, a nie na przykład obszarów pamięci zajmowanych przez składowe prywatne.
1. #include <iostream> 2. using namespace std; 3. 4. class Pozdro { 5. int k1; ➊ 6. public: 7. enum Kraj { PL, DE, FR }; 8. int k2; ➋ 9. void fun(Kraj kraj) { 10. switch (kraj) { 11. case PL: 12. cout << "Dzien dobry\n"; k1 = 1; break; 13. case DE: 14. cout << "Guten Tag\n"; k1 = 2; break; 15. case FR: 16. cout << "Bonjour\n"; k1 = 3; break; 17. } 18. } 19. }; 20. 21. int main() { 22. Pozdro dd; ➌ 23. 24. dd.fun(Pozdro::DE); ➍ 25. 26. int *pk1 = &dd.k2 - 1; ➎ 27. 28. cout << "sizeof(dd) = " << sizeof(dd) << endl; ➏ 29. cout << "dd.k1 = " << *pk1 << endl; ➐ 30. }
Definiujemy tu klasę Pozdro. Pole k1 jest prywatne (➊), pole k2 (➋), definicja wyliczenia Kraj i metoda (funkcja) fun są publiczne. W programie głównym tworzymy obiekt dd tej klasy (➌). Zauważmy, że składnia jest taka jak przy tworzeniu zwykłej zmiennej typu int — nazwa typu i nazwa wprowadzanej zmiennej.
W linii ➍ wywołujemy na jego rzecz metodę fun kwalifikując nazwę funkcji nazwą obiektu — bardziej szczegółowo omówimy tę kwestię poniżej. Argument jest typu Kraj, ale ten typ (wyliczeniowy) został zdefiniowany w klasie Pozdro; jego nazwa nie jest widoczna poza zasięgiem klasy. Nie możemy więc użyć po prostu nazwy DE oznaczającej jeden z elementów wyliczenia; musimy poprzedzić ją nazwą klasy Pozdro i operatorem zasięgu ' ::' (czyli kwalifikować). Piszemy zatem Pozdro::DE, aby poinformować kompilator, że chodzi nam o nazwę DE z przestrzeni nazw (w tym przypadku klasy) Pozdro.
Metoda fun wywołana z argumentem Pozdro::DE wpisała do prywatnej składowej k1 obiektu na rzecz którego została wywołana wartość 2. Oczywiście mogła to zrobić, gdyż wszystkie metody (funkcje) klasy „widzą” wszystkie nazwy zdefiniowane w zasięgu klasy, niezależnie od tego, czy są one publiczne czy nie (a samą funkcję mogliśmy wywołać, bo jest ona publiczna). Ale jak wewnątrz funkcji main przekonać się, że rzeczywiście wartość składowej dd.k1 wynosi 2, skoro ta składowa jest prywatna i użycie nazwy k1 poza klasą spowoduje błąd? W linii ➏ drukujemy rozmiar obiektu dd:
Guten Tag sizeof(dd) = 8 dd.k1 = 2Wynosi on 8 bajtów, co przekonuje nas, że rzeczywiście obiekt ten zawiera dwie składowe typu int i nic więcej (zakładamy, że uszeregowane są w kolejności odpowiadającej kolejności ich deklaracji w definicji klasy). W linii ➎ pobieramy zatem adres składowej k2, do której mamy dostęp, bo jest publiczna (i wystarczy odnieść się do niej poprzez nazwę obiektu). Od niego odejmujemy 1, co zgodnie z arytmetyką wskaźników powinno dać nam adres poprzedniej składowej, czyli prywatnej składowej k1 (zmienna wskaźnikowa pk1). Wydrukowanie wartości zmiennej spod tego adresu (➐) daje 2, co przekonuje nas, że jest to istotnie ta prywatna składowa, o którą nam chodziło.
Jak więc widać, to nie sama składowa jest chroniona za pomocą kwalifikatora private; chroniona jest nazwa składowej. Jeśli potrafimy „dobrać się” do tej składowej bez użycia jej nazwy, to język na to pozwala. Wprowadzenie podziału na składowe publiczne i prywatne jest raczej elementem wspierającym tworzenie przejrzystszego i mniej podatnego na błędy kodu, niż sposobem na crackerów. Sztuczne omijanie zabezpieczeń, jakie ten podział daje prowadzi do kodu niezrozumiałego, niebezpiecznego i niemożliwego do pielęgnacji.
Dostępność wszystkich składowych klasy przez jej składowe funkcje
(statyczne, niestatyczne, konstruktory, destruktor) dotyczy
klasy, a nie konkretnych obiektów. Na przykład w poniższym programie
1. #include <iostream>
2. using namespace std;
3.
4. class Vector {
5. double x, y, z;
6. public:
7. void set(double xx, double yy, double zz) {
8. x = xx;
9. y = yy;
10. z = zz;
11. }
12. double dot_product(const Vector& w) {
13. return x*w.x + y*w.y + z*w.z;
14. }
15. };
16.
17. int main() {
18. Vector w1, w2;
19. w1.set(1, 1, 2);
20. w2.set(1,-1, 2);
21. cout << "w1*w2 = " << w1.dot_product(w2) << endl; ➊
22. }
funkcja dot_product (iloczyn skalarny), jako składowa klasy, ma dostęp do prywatnych składowych x, y, z zarówno obiektu w1, na rzecz którego została wywołana (i do których odnosi się poprzez ich niekwalifikowane nazwy), jak i do składowych obiektu w2, przesłanego przez referencję jako argument wywołania (➊). Świadczy o tym wydruk programu:
w1*w2 = 4
T.R. Werner, 21 lutego 2016; 20:17