Wykład 4
Uzupełnienie
Ukrywanie informacji: private, protected a może jednak public. Funkcje zaprzyjaźnione, this

Ponieważ w niedługim czasie będziemy chcieli przejść do tematów związanych z dziedziczeniem, dlatego musimy uzupełnić posiadane informacje. Do tej pory świadomie częsci z nich nie podawałem aby nie wprowadzać niepotrzebnego zamieszania wśród i tak wielu nowych pojęć. Nie sposób jednak żyć dalej w nieświadomosci, dlatego zaczynajmy...


Ukrywanie informacji: private, protected a może jednak public

Na pierwszym wykładzie mówiłem, że klasy to sposób na połączenie w jedną logiczną całość danych i funkcji na nich działających. Wspomniałem także, że pisząc klasę decydujemy, które jej własności będą dostępne dla jej użytkownika a które pozostną naszą, projektanta, wewnętrzną sprawą. Do tej pory jednak z zawartością klasy mogliśmy robić co tylko chcieliśmy. Owszem, pisaliśmy medtody umożliwiające nam dostęp do pól lecz nie były one niezbędne; równie dobrze mogliśmy odwoływać się do nich bezpośrednio. Jeśli stworzyliśmy na przykład klasę jak na listingu 4.1


#include <stdio.h>

class ulamek
{
public:
  int l,m;
  int GetL();
  int GetM();
  void SetL(int t);
  void SetM(int t);
  void Wypisz();
};

int ulamek::GetL()
{
  return l;
}

int ulamek::GetM()
{
  return m;
}

void ulamek::SetL(int t)
{
  l=t;
}

void ulamek::SetM(int t)
{
  m=t;
}

void ulamek::Wypisz()
{
  printf("Licznik: %d, mianownik %d\n",l,m);
}
Listing 4.1
to do składowej l mogliśmy odwołać się na dwa różne sposoby sposoby (listing 4.2)

int main()
{
  ulamek u1,u2;
  
  /* postępujemy grzecznie - uzywamy metod klasy */
  u1.Wypisz();
  u1.SetL(3);
  u1.SetM(7);
  u1.Wypisz();
  
  /* grzebiemy bezposrednio w klasie */
  u2.Wypisz();
  u2.l=3;
  u2.m=7;
  u2.Wypisz();
  
  return 0;
}
Listing 4.2
Nie po to zamykamy coś w klasie aby każdy mógł robić z nią co tylko zapragnie. W celu sterowania dostępem do składowych klasy wprowadzono trzy etykiety określające kto, kiedy i do czego ma dostęp.

public

Etykieta public to jedyna z tego grona, którą używaliśmy. Oznacza ona, że składniki klasy występujące po niej aż do napotkania kolejnej etykiety dostępne są tak samo łatwo dla każdego we wnętrzu klasy, jak i na zewnątrz.

private

Etykieta private oznacza natomiast, że składniki występujące po niej są dostępne tylko z wnętrza klasy. W przypadku pól oznacza to, że mogą na nich działać tylko funkcje składowe tej samej klasy.

protected

Składniki występujące po etykiecie protected zachowują się jak private, dodatkowo są jednak dostępne dla klas pochodnych.

Listing 4.3 pokazuje zastosowanie tych etykiet

#include <stdio.h>

class klasa1
{
public:
  int x;
private:
  int y;
protected:
  int z;
};

class klasa2
{
  int x;
};

int main()
{
  klasa1 k1;
  klasa2 k2;
  
  k1.x=1;
  //k1.y=2; // <= nie mozna bo private
  //k1.z=3; // <= nie mozna bo protected
  //k2.x=4; // <= nie mozna bo domyslnie private
  return 0;
}
Listing 4.3


Funkcje zaprzyjaźnione

Funkcja zaprzyjaźniona z pewną klasą to funkcja, która mimo, że nie jest składnikiem klasy, ma dostęp do wszystkich jest składników. Aby uczynić jakąś funkcję funkcją zaprzyjaźnioną z klasą wystarczy w tejże klasie umieścić deklarację tej funkcji poprzedzoną słowem friend. Oto prosty przykład (listing 4.4)


#include <stdio.h>

class klasa2;

class klasa1
{
private:
  int x;
  int y;
public:
  klasa1();
  void wypisz();
  friend void zamien(klasa1 & k1, klasa2 & k2);
};

class klasa2
{
private:
  int x;
  int y;
  friend void zamien(klasa1 & k1, klasa2 & k2);
public:
  klasa2();
  void wypisz();
};

klasa1::klasa1()
{
  x=3;
  y=7;
}

void klasa1::wypisz()
{
  printf("klasa1: x=%d, y=%d\n",x,y);
}

klasa2::klasa2()
{
  x=5;
  y=9;
}

void klasa2::wypisz()
{
  printf("klasa2: x=%d, y=%d\n",x,y);
}

void zamien(klasa1 & k1, klasa2 & k2)
{
  int tx,ty;
  
  tx=k1.x;
  ty=k1.y;
  
  k1.x=k2.x;
  k1.y=k2.y;
  
  k2.x=tx;
  k2.y=ty;
}

int main()
{
  klasa1 k1;
  klasa2 k2;
  
  k1.wypisz();
  k2.wypisz();
  
  zamien(k1,k2);
  
  k1.wypisz();
  k2.wypisz();
  
  return 0;
}
Listing 4.4
zaś wynik jego działania przedstawiam poniżej

klasa1: x=3, y=7
klasa2: x=5, y=9
klasa1: x=5, y=9
klasa2: x=3, y=7
Output 4.1 Efekt działania programu z listingu 4.4
Uwagi:

#include <stdio.h>

class klasa1;

class klasa2
{
private:
  int x;
  int y;
public:
  klasa2();
  void wypisz();
  void zamien(klasa1 & k1);
};

class klasa1
{
private:
  int x;
  int y;
  friend void klasa2::zamien(klasa1 & k1);
  // deklaracja przyjaźni; w tym miejscu musimy już
  // wiedzieć nie tyle, że klasa2 istnieja, ale
  // jak wygląda; zatem wcześniej potrzebna jest
  // nam jej definicja
public:
  klasa1();
  void wypisz();
};

klasa1::klasa1()
{
  x=3;
  y=7;
}

void klasa1::wypisz()
{
  printf("klasa1: x=%d, y=%d\n",x,y);
}

klasa2::klasa2()
{
  x=5;
  y=9;
}

void klasa2::wypisz()
{
  printf("klasa2: x=%d, y=%d\n",x,y);
}

void klasa2::zamien(klasa1 & k1)
{
  int tx,ty;
  
  tx=k1.x;
  ty=k1.y;
  
  k1.x=x;
  k1.y=y;
  
  x=tx;
  y=ty;
}

int main()
{
  klasa1 k1;
  klasa2 k2;
  
  k1.wypisz();
  k2.wypisz();
  
  k2.zamien(k1);
  
  k1.wypisz();
  k2.wypisz();
  
  return 0;
}
Listing 4.5

klasa1: x=3, y=7
klasa2: x=5, y=9
klasa1: x=5, y=9
klasa2: x=3, y=7
Output 4.2 Efekt działania programu z listingu 4.5


this

Może się zdarzyć, że pisząc program z pewnych powodów w metodzie pewnej klasy będziemy chcieli użyć wskaźnik, który będzie pokazywał na obiekt tejże klasy, dla którego wywołaliśmy metodę. Wskaźnik ten nazywa się this. Pokazuje on na egzemplarz obiektu, dla którego wywołaliśmy metodę. Przykład użycia przedstawia listing 4.x


Nie udało mi się wymyślić żadnego sensownego przykład, więc go nie ma :)))
Listing 4.x
Zwróćmy uwagę, że funkcja zaprzyjaźniona nie jest składnikiem klasy, zatem nie posiada ona wskaźnika this.