R10-06.DOC

(224 KB) Pobierz
Szablon dla tlumaczy

Rozdział 10.
Funkcje zaawansowane

W rozdziale 5., „Funkcje”, poznałeś podstawy pracy z funkcjami. Teraz, gdy wiesz także, jak działają wskaźniki i referencje, możesz zgłębić zagadnienia dotyczące funkcji.

Z tego rozdziału dowiesz się, w jaki sposób:

·         przeciążać funkcje składowe,

·         przeciążać operatory,

·         pisać funkcje, mając na celu tworzenie klas z dynamicznie alokowanymi zmiennymi.

Przeciążone funkcje składowe

Z rozdziału 5. dowiedziałeś się jak implementować polimorfizm funkcji, czyli ich przeciążanie, przez tworzenie dwóch lub więcej funkcji o tych samych nazwach, lecz innych parametrach. Funkcje składowe klas mogą być przeciążane w dokładnie ten sam sposób.

Klasa Rectangle (prostokąt), zademonstrowana na listingu 10.1, posiada dwie funkcje DrawShape() (rysuj kształt). Pierwsza z nich, nie posiadająca parametrów, rysuje prostokąt na podstawie bieżących wartości składowych danego egzemplarza klasy. Druga funkcja otrzymuje dwie wartości (szerokość i długość) i rysuje na ich podstawie prostokąt, ignorując bieżące wartości zmiennych składowych.

Listing 10.1. Przeciążone funkcje składowe

  0:  //Listing 10.1 Przeciążanie funkcji składowych klasy

  1: 

  2:  #include <iostream>

  3: 

  4:  // Deklaracja klasy Rectangle

  5:  class Rectangle

  6:  {

  7:  public:

  8:      // konstruktory

  9:      Rectangle(int width, int height);

10:      ~Rectangle(){}

11: 

12:      // przeciążona funkcja składowa klasy

13:      void DrawShape() const;

14:      void DrawShape(int aWidth, int aHeight) const;

15: 

16:  private:

17:      int itsWidth;

18:      int itsHeight;

19:  };

20: 

21:  // implementacja konstruktora

22:  Rectangle::Rectangle(int width, int height)

23:  {

24:      itsWidth = width;

25:      itsHeight = height;

26:  }

27: 

28: 

29:  // Przeciążona funkcja DrawShape - nie ma parametrów

30:  // Rysuje kształt w oparciu o bieżące wartości zmiennych składowych

31:  void Rectangle::DrawShape() const

32:  {

33:      DrawShape( itsWidth, itsHeight);

34:  }

35: 

36: 

37:  // Przeciążona funkcja DrawShape - z dwoma parametrami

38:  // Rysuje kształt w oparciu o podane wartości

39:  void Rectangle::DrawShape(int width, int height) const

40:  {

41:      for (int i = 0; i<height; i++)

42:      {

43:          for (int j = 0; j< width; j++)

44:          {

45:              std::cout << "*";

46:          }

47:          std::cout << "\n";

48:      }

49:  }

50: 

51:  // Główna funkcja demonstrująca przeciążone funkcje

52:  int main()

53:  {

54:      // inicjalizujemy prostokąt 30 na 5

55:      Rectangle theRect(30,5);

56:      std::cout << "DrawShape(): \n";

57:      theRect.DrawShape();

58:      std::cout << "\nDrawShape(40,2): \n";

59:      theRect.DrawShape(40,2);

60:      return 0;

61:  }

 

Wynik

DrawShape():

******************************

******************************

******************************

******************************

******************************

 

DrawShape(40,2):

****************************************

****************************************

Analiza

Listing 10.1 prezentuje okrojoną wersję programu, zamieszczonego w podsumowaniu wiadomości po rozdziale 7. Aby zaoszczędzić miejsce, z programu usunięto sprawdzanie niepoprawnych wartości, a także niektóre z akcesorów. Główny program został sprowadzony do dużo prostszej postaci, w której nie ma już menu.

Najważniejszy kod znajduje się w liniach 13. i 14., gdzie przeciążona została funkcja DrawShape(). Implementacja tych przeciążonych funkcji składowych znajduje się w liniach od 29. do 49. Zwróć uwagę, że funkcja w wersji bez parametrów po prostu wywołuje funkcję z parametrami, przekazując jej bieżące zmienne składowe. Postaraj się nigdy nie powtarzać tego samego kodu w kilkudwóch funkcjach, może to spowodować wiele problemów z zachowaniem ich w zgodności w trakcie wprowadzaniu poprawek go w synchronizacji(może stać się to przyczyną błędów).

Główna funkcja tworzy w liniach od 51. do 61. obiekt prostokąta, po czym wywołuje funkcję DrawShape(), najpierw bez parametrów, a potem z dwoma parametrami typu int.

Kompilator na podstawie ilości i typu podanych parametrów wybiera metodę. Można sobie wyobrazić także trzecią przeciążoną funkcję o nazwie DrawShape(), która otrzymywałaby jeden wymiar oraz wartość wyliczeniową, określającą, czy jest on wysokością czy szerokością (wybór należałby do użytkownika).

Użycie wartości domyślnych

Podobnie, jak w przypadku funkcji składowych klasy, funkcje globalne również mogą mieć jedną lub więcej wartości domyślnych. W przypadku deklaracji wartości domyślnych w funkcjach składowych stosujemy takie same reguły, jak w funkcjach globalnych, co ilustruje listing 10.2.

Listing 10.2. Użycie wartości domyślnych

  0:  //Listing 10.2 Domyślne wartości w funkcjach składowych

  1: 

  2:  #include <iostream>

  3: 

  4:  using namespace std;

  5: 

  6:  // Deklaracja klasy Rectangle

  7:  class Rectangle

  8:  {

  9:  public:

10:      // konstruktory

11:      Rectangle(int width, int height);

12:      ~Rectangle(){}

13:      void DrawShape(int aWidth, int aHeight,

14:          bool UseCurrentVals = false) const;

15: 

16:  private:

17:      int itsWidth;

18:      int itsHeight;

19:  };

20: 

21:  // implementacja konstruktora

22:  Rectangle::Rectangle(int width, int height):

23:  itsWidth(width),       // inicjalizacje

24:  itsHeight(height)

25:  {}                     // puste ciało konstruktora

26: 

27: 

28:  // dla trzeciego parametru jest używana domyślna wartość

29:  void Rectangle::DrawShape(

30:  int width,

31:  int height,

32:  bool UseCurrentValue

33:  ) const

34:  {

35:      int printWidth;

36:      int printHeight;

37: 

38:      if (UseCurrentValue == true)

39:      {

40:          printWidth = itsWidth;       // używa bieżących wartości klasy

41:          printHeight = itsHeight;

42:      }

43:      else

44:      {

45:          printWidth = width;         // używa wartości z parametrów

46:          printHeight = height;

47:      }

48: 

49: 

50:      for (int i = 0; i<printHeight; i++)

51:      {

52:          for (int j = 0; j< printWidth; j++)

53:          {

54:              cout << "*";

55:          }

56:          cout << "\n";

57:      }

58:  }

59: 

60:  // Główna funkcja demonstrująca przeciążone funkcje

61:  int main()

62:  {

63:      // inicjalizujemy prostokąt 30 na 5

64:      Rectangle theRect(30,5);

65:      cout << "DrawShape(0,0,true)...\n";

66:      theRect.DrawShape(0,0,true);

67:      cout <<"DrawShape(40,2)...\n";

68:      theRect.DrawShape(40,2);

69:      return 0;

70:  }

 

Wynik

DrawShape(0,0,true)...

******************************

******************************

******************************

******************************

******************************

DrawShape(40,2)...

****************************************

****************************************

Analiza

Listing 10.2 zastępuje przeciążone funkcje DrawShape() pojedynczą funkcją z domyślnym parametrem. Ta funkcja, zadeklarowana w linii 13., posiada trzy parametry. Dwa pierwsze, aWidth (szerokość) i aHeight (wysokość) są typu int, zaś trzeci, UseCurrentVals (użyj bieżących wartości), jestwartością logiczną zmienną typu bool o domyślnej wartości false.

Implementacja tej nieco udziwnionej funkcji rozpoczyna się w linii 28. Sprawdzany jest w niej trzeci parametr, UseCurrentValues. Jeśli ma on wartość true, wtedy do ustawienia lokalnych zmiennych printWidth (wypisywana szerokość) i printHeight (wypisywana wysokość) są używane zmienne składowe klasy, itsWidth oraz itsHeight.

Jeśli parametr UseCurrentValues ma wartość false, podaną przez użytkownika, lub ustawioną domyślnie, wtedy zmiennym printWidth i printHeight są przypisywane wartości dwóch pierwszych argumentów funkcji.

Zwróć uwagę, że gdy parametr UseCurrentValues ma wartość true, wartości dwóch pierwszych parametrów są całkowicie ignorowane.

Wybór pomiędzy wartościami domyślnymi a przeciążaniem funkcji

Listingi 10.1 i 10.2 dają ten sam wynik, lecz przeciążone funkcje z listingu 10.1 są łatwiejsze do zrozumienia i wygodniejsze w użyciu. Poza tym, gdy jest potrzebna trzecia wersja — na przykład, gdy użytkownik chce dostarczyć szerokości albo wysokości osobno — można łatwo stworzyć kolejną przeciążoną funkcję. Z drugiej strony, w miarę dodawania kolejnych wersji, wartości domyślne mogą szybko stać się zbyt skomplikowane.

W jaki sposób podjąć decyzję, czy użyć przeciążania funkcji, czy wartości domyślnych? Oto ogólna reguła:

Przeciążania funkcji używaj, gdy:

·         nie istnieje sensowna wartość domyślna,

·         używasz różnych algorytmów,

·         chcesz korzystać z różnych rodzajów parametrów funkcji.

Konstruktor domyślny

Jak mówiliśmy w rozdziale 6., „Programowanie zorientowane obiektowo”, jeśli nie zadeklarujesz konstruktora klasy jawnie, zostanie dla niej stworzony konstruktor domyślny, który nie ma żadnych parametrów i nic nie robi. Możesz jednak stworzyć własny konstruktor domyślny, który także nie posiada parametrów, ale odpowiednio „przygotowuje” obiekt do działania.

Taki konstruktor także jest nazywany konstruktorem „domyślnym”, bo zgodnie z konwencją, jest nim konstruktor nie posiadający parametrów. Może to budzić wątpliwości, ale zwykle jasno wynika z kontekstu danego miejsca w programie.

Zwróć uwagę, że gdy stworzysz jakikolwiek konstruktor, kompilator nie dostarcza już konstruktora domyślnego. Gdy potrzebujesz konstruktora nie posiadającego parametrów i stworzysz jakikolwiek inny konstruktor, musisz stworzyć także konstruktor domyślny!

Przeciążanie konstruktorów

Przeznaczeniem konstruktora jest przygotowanie obiektu; na przykład, celem konstruktora Rectangle jest stworzenie poprawnego obiektu prostokąta. Przed wykonaniem konstruktora nie istnieje żaden prostokąt, a jedynie miejsce w pamięci. Gdy konstruktor kończy działanie, w pamięci istnieje kompletny, gotowy do użycia obiekt prostokąta.

Konstruktory, tak jak wszystkie inne funkcje składowe, mogą być przeciążane. Możliwość przeciążania ich jest bardzo przydatna.

Na przykład: możesz mieć obiekt prostokąta posiadający dwa konstruktory. Pierwszy z nich otrzymuje szerokość oraz długość i tworzy prostokąt o podanych rozmiarach. Drugi nie ma żadnych parametrów i tworzy prostokąt o rozmiarach domyślnych. Ten pomysł wykorzystano na listingu 10.3.

Listing 10.3. Przeciążanie konstruktora

  0:  // Listing 10.3

  1:  // Przeciążanie konstruktorów

  2: 

  3:  #include <iostream>

  4:  using namespace std;

  5: 

  6:  class Rectangle

  7:  {

  8:  public:

  9:      Rectangle();

10:      Rectangle(int width, int length);

11:      ~Rectangle() {}

12:      int GetWidth() const { return itsWidth; }

13:      int GetLength() const { return itsLength; }

14:  private:

15:      int itsWidth;

16:      int itsLength;

17:  };

18: 

19:  Rectangle::Rectangle()

20:  {

21:      itsWidth = 5;

22:      itsLength = 10;

23:  }

24: 

25:  Rectangle::Rectangle (int width, int length)

26:  {

27:      itsWidth = width;

28:      itsLength = length;

29:  }

30: 

31:  int main()

32:  {

33:      Rectangle Rect1;

34:      cout << "Rect1 szerokosc: " << Rect1.GetWidth() << endl;

35:      cout << "Rect1 dlugosc: " << Rect1.GetLength() << endl;

36: 

37:      int aWidth, aLength;

38:      cout << "Podaj szerokosc: ";

39:      cin >> aWidth;

40:      cout << "\nPodaj dlugosc: ";

41:      cin >> aLength;

42: 

43:      Rectangle Rect2(aWidth, aLength);

44:      cout << "\nRect2 szerokosc: " << Rect2.GetWidth() << endl;

45:      cout << "Rect2 dlugosc: " << Rect2.GetLength() << endl;

46:      return 0;

47:  }

 

Wynik

Rect1 szerokosc: 5

Rect1 dlugosc: 10

Podaj szerokosc: 20

 

Podaj dlugosc: 50

 

Rect2 szerokosc: 20

Rect2 dlugosc: 50

Analiza

Klasa Rectangle jest zadeklarowana w liniach od 6. do 17. Posiada dwa konstruktory: „domyślny” konstruktor w linii 9. oraz drugi konstruktor w linii 10., przyjmujący dwie liczby całkowite.

W linii 33. za pomocą domyślnego konstruktora ...

Zgłoś jeśli naruszono regulamin