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.
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).
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
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;
22: Rectangle::Rectangle(int width, int height):
23: itsWidth(width), // inicjalizacje
24: itsHeight(height)
25: {} // puste ciało konstruktora
26:
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
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++)
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: }
DrawShape(0,0,true)...
DrawShape(40,2)...
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.
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.
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!
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>
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()
33: Rectangle Rect1;
34: cout << "Rect1 szerokosc: " << Rect1.GetWidth() << endl;
35: cout << "Rect1 dlugosc: " << Rect1.GetLength() << endl;
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;
Rect1 szerokosc: 5
Rect1 dlugosc: 10
Podaj szerokosc: 20
Podaj dlugosc: 50
Rect2 szerokosc: 20
Rect2 dlugosc: 50
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 ...
Infesto