Blog literacki, portal erotyczny - seks i humor nie z tej ziemi
LEKCJA 29: FUNKCJE I OVERLOADING.
________________________________________________________________
W trakcie tej lekcji dowiesz się, jak jeszcze w C++ można
wykorzystywać funkcje.
________________________________________________________________
w C++ jedna funkcja może być definiowana wielokrotnie a każda z
wersji funkcji może być przystosowana do obsługi innego typu
argumentów. C++ wybiera tę właściwą wersję funkcji
automatycznie.
JEDNA NAZWA FUNKCJI - WIELE ZASTOSOWAŃ.
Overloading funkcji bywa czasem w podręcznikach dzielony na
odrębne zagadnienia:
* funkcja może tolerować różną liczbę argumentów (co dało się
spokojnie realizować również w klasycznym C - vide printf());
* funkcja może tolerować różne typy argumentów;
* funkcja może realizować różne operacje dla różnych
Wyobraźmy sobie, że mamy funkcję wydrukuj(), która potrafi
wysłać na ekran otrzymany znak:
void wydrukuj(char znak)
{
cout << znak;
}
Tak zdefiniowaną funkcję możemy wywołać w programie w
następujący sposób:
wydrukuj('Z');
Czasem jednak wygodniej byłoby, gdyby nasza funkcja była
bardziej elastyczna i pozwalała na wykonanie szerszego zakresu
operacji, np.:
wydrukuj('Z');
wydrukuj(75); // 75 to kod ASCII znaku, zamiast znaku bezpośr.
wydrukuj("Wiecej niz znak - tekst");
W klasycznym języku C wymaga to zdefiniowania nowej funkcji,
natomiast w C++ to, że funkcja wydrukuj() została już
zdefiniowana w niczym nie przeszkadza. Poniżej definjujemy taką
funkcję.
...
class KLASA
{
public:
void wydrukuj(char znak);
void wydrukuj(int kod_ASCII);
void wydrukuj(char *string); //wskaźnik do lancucha
}
Łańcuch znaków jest widziany jako jednowymiarowa tablica
zawierająca dane typu znakowego, czyli w taki sposób:
char TABLICA[9] ={ "123456789" };
Definice powinny mieć następującą postać:
void KLASA::wydrukuj(char znak) {cout << znak;};
void KLASA::wydrukuj(int kodASCII) {cout << (char) kodASCII;};
void KLASA::wydrukuj(char *string) {cout << string;};
Zapis:
cout << (char) kodASCII;
oznacza forsowanie typu - zamień typ int na typ char -
przyporządkowanie kodowi ASCII - znaku. Wywołanie tej funkcji w
programie może spowodować różne działanie, w zależności od typu
i ilości argumentów, z którym(i) funkcja zostaje wywołana.
Wywołania funkcji mogą wyglądać np. tak:
KLASA Obiekt1, Obiekt2;
main() {
...
Obiekt1.wydrukuj('A'); //Wydrukuje się litera A
Obiekt1.wydrukuj(99); //Wydrukuje się litera c
Obiekt2.wydrukuj("napis"); //Wydrukuje się napis.
...
}
Taki sposób postępowania umożliwia funkcjom większą elastyczność
i pozwala operować bez konfliktów na różnych rodzajach danych.
Język C posiada funkcje służące do kopiowania łańcuchów
znakowych: strcpy() i strncpy(). Funkcja biblioteczna strncpy()
przerywa proces kopiowania po zakończeniu łańcucha żródłowego,
bądź po skopiowaniu zadanej ilości znaków. Dzięki mechanizmowi
overloadingu możemy utworzyć naszą własną funkcję
kopiuj_string(), która zależnie od sytuacji zadziała jak
strcpy(), bądź tak jak strncpy().
[P104.CPP]
# include
/* dwa porototypy - dwie wersje funkcji kopiuj_string() */
/* source: destination: len: */
void kopiuj_string(char*, const char*); //Dwa argumenty
void kopiuj_string(char*, const char*, int); //a tu trzy
static char Piggie[20], Kermit[32];
main()
{
kopiuj_string(Piggie, "Panna Piggie");
kopiuj_string(Kermit, "Kermit - to protokul transmisji", 6);
cout << Kermit << " oraz " << Piggie;
return 0;
}
void kopiuj_string(char *destin, const char *source)
{
while((*destin++ = *source++) != '\0') /* instr. pusta */ ;
}
void kopiuj_string(char *destin, const char *source, int len)
{
while (len && (*destin++ = *source++) != '\0') --len;
while (len--) *destin++ = '\0';
}
[S] Source- Destination.
________________________________________________________________
source - tu: źródłowy łańcuch znaków. Ogólnie - źródło. Typowy
skrót src.
destin - tu: łańcuch przeznaczenia. Ogólnie destination -
miejsce przeznaczenia. Typowy skrót dest, dst, destin.
len - tu: długość.
________________________________________________________________
O FUNKCJACH WPLECIONYCH - TYPU inline.
Czsami zależy nam na przyspieszeniu działania programu
obiektowego (zwykle kosztem zwiększenia długości pliku). Jeśli w
źródłowym tekście programu następuje wywołanie funkcji typu
inline, to kompilator wstawia w to miejsce całe ciało funkcji
(funkcje typu inline nie mają bezpośredniego ani wyłącznego
odniesienia do obiektowego stylu programowania). Dla przykładu,
jeśli nadalibyśmy naszej funkcji wydrukuj() status funkcji
inline, to fragment programu:
obiekt.wydrukuj(65); //Kod ASCII
zostałby zastąpiony wstawionym w to miejsce ciałem funkcji
wydrukuj():
....
cout << (char) 65;
....
Jest to skuteczna metoda przyspieszenia działania programów.
Jeśli chcemy zastosować technikę funkcji inline w stosunku do
metod należących do danej klasy, powinniśmy użyć słowa
kluczowego "inline" w definicjach funkcji. Zwróć uwgę, że w
samej definicji klasy słowo inline NIE POJAWIA SIĘ:
[P105.CPP]
# include
class Klasa
{
public:
void wydrukuj(char* tekst);
void wydrukuj(char Znak);
void wydrukuj(int KodASCII);
};
inline void Klasa::wydrukuj(char* tekst)
{
cout << tekst;
}
inline void Klasa::wydrukuj(char Znak)
{
cout << Znak;
}
inline void Klasa::wydrukuj(int KodASCII)
{
cout << (char) KodASCII;
}
void main()
{
Klasa Obiekt;
cout << "Obiekt wyprowadza dane: " << '\n';
Obiekt.wydrukuj(65);
Obiekt.wydrukuj('B');
Obiekt.wydrukuj("C i juz");
}
Wszystkie wersje funkcji wydrukuj() otrzymały status inline.
Oznacza to, że funkcje te nie będą w programie wywoływane lecz
całe ciała funkcji zostaną wstawione do programu w miejsca
wywołań. Jest to mechanizm podobny do wstawiania do programu
makrorozkazów z tą różnicą, że w przypadku funkcji inline C++
przeprowadza dodatkowo sprawdzenie zgodności typów argumentów
(ang. type checking). W naszym przypadku kompilator C++ wstawi
do programu ciało funkcji tyle razy, ile razy funkcja powinna
zostać wywoływana. Zastosowanie funkcji inline jest opłacalne,
jeżeli ciało funkcji jest stosunkowo krótkie.
[!!!] A CZY NIE MOŻNA WEWNĄTRZ KLASY ?
________________________________________________________________
Można. Jeśli umieścimy pełną definicję funkcji wewnątrz
definicji klasy, to taka funkcja staje się AUTOMATYCZNIE funkcją
typu inline.
________________________________________________________________
Status inline możemy nadać wszystkim trzem wersjom funkcji
wydrukuj() umieszczając definicje funkcji bezpośrednio wewnątrz
definicji klasy:
class Klasa
{
public:
inline void wydrukuj(char* a) { cout << a; }
inline void wydrukuj(char z) { cout << z; }
inline void wydrukuj(int kod) { cout << (char) kod; }
};
W większości przypadków daje to efekt pozytywny. Jeśli
definiujemy funkcje wewnątrz klasy, są to zwykle funkcje o
krótkim ciele.
OVERLOADING KONSTRUKTORÓW.
W C++ możemy poddać overloadingowi także konstruktory.
UWAGA: destruktorów nie można poddać overloadingowi.
Overloading konstruktorów nie wyróżnia się niczym specjalnym.
Wyobraźmy sobie, że tworzymy obiekt klasy Klasa o nazwie Obiekt.
Jeśli chcemy, by konstruktor przy zakładaniu Obiektu przekazał
mu łańcuch znaków "zzzz", możemy to zrobić na dwa sposoby. Raz
polecimy konstruktorowi przekazać do obiektu łańcuch znaków
"zzzz", a za drugim razem polecimy przekazać do obiektu
czterokrotnie znak 'z':
Obiekt("zzzz"); /* albo */ Obiekt('z', 4);
Jeśli w programie zadeklarujemy obiekt danej klasy, spowoduje to
automatyczne wywołanie konstruktora z parametrem podanym w
momencie deklaracji obiektu.
class Klasa
{
public:
Klasa(char*);
Klasa(char, int);
};
Wersje konstruktora Klasa::Klasa() powinniśmy zdefiniować tak:
Klasa::Klasa(char *tekst) { cout << tekst; }
Klasa::Klasa(char Znak, ile = 4);
{
for(int i = 1; i < ile; i++)
cout << Znak;
}
Dodajmy jeszcze jeden kontruktor domyślny. Konstruktory domyślne
działają według zasady, którą w naturalnym języku dałoby się
przekazać mniej więcej tak: "dopóki nie zdecydowano inaczej...".
Dopóki nie zdecydowano inaczej - obiekt otrzyma znak 'x'.
class Klasa
{
public:
Klasa();
Klasa(char*);
Klasa(char, int);
};
...
Klasa::Klasa(void)
{
cout << 'x';
}
Praktyczne zastosowanie w programie będzie wyglądać tak:
[P106.CPP]
# include
class Klasa
{
public:
Klasa();
Klasa(char*);
Klasa(char, int);
};
Klasa::Klasa(void)
{
cout << 'x';
}
Klasa::Klasa(char *tekst)
{
cout << tekst;
}
Klasa::Klasa(char Znak, int ile = 4)
{
for(int i = 0; i < ile; i++) cout << Znak;
}
static char *p = "\nJestem Obiekt.";
void main()
{
Klasa Obiekt1; //Konstr. domyślny
Klasa Obiekt2('A'); // ile - domyslnie == 4
Klasa Obiekt3('B', 3);
Klasa Obiekt4(p);
}