środa, 26 marca 2008

Wskaźniki na... "coś"

Zdarza się, że potrzebujemy w jednym obiekcie przechować dane więcej niż tylko jednego konkretnego typu. Np. chcielibyśmy otrzymać stos czy jakąś listę, na której znalazłyby się liczby zmiennoprzecinkowe, stałoprzecinkowe, łańcuchy znaków albo nawet obiekty dowolnej klasy. I co wtedy można zrobić? Teoretycznie można by wykorzystać unię - ale jest to dobre tylko dla niewielkich obiektów. Lepszym wyjściem byłoby użycie jakiegoś wskaźnika (przecież wskaźnik na dowolny typ na jednej platformie zajmuje zawsze tyle samo miejsca, dla 32-bit: 4B). No to świetnie, idea już jest, ale zaraz.. jak takie coś można właściwie zrobić? Jaki typ ma być tego wskaźnika?

Wskaźnik musi być zadeklarowany tak:
void* Wartosc;

No świetnie, ale jeszcze chyba jednego brakuje... przecież tak naprawdę nie wiemy, co (jaki typ) będzie wskazywane przez ten nasz nowy wskaźnik... może tam być dosłownie wszystko. Dlatego rozszerzymy sobie tą deklarację na klasę:

class TElement {
public:
void* Wartosc;
short int Typ;
};


Typ będzie nam potrzebny do tego, by potem uzyskiwać odpowiednie dane.
Na koniec musimy jeszcze oczywiście wprowadzić dane:

int* Calkowita;
double* ZmiennoPrzecinkowa;
TElement Tablica[2];

Calkowita = new int;
*Calkowita = 5;
Tablica[0].Wartosc = Calkowita;
Tablica[0].Typ = 1; // int
ZmiennoPrzecinkowa = new double;
*ZmiennoPrzecinkowa = 3.445;
Tablica[1].Wartosc = ZmiennoPrzecinkowa;
Tablica[1].Typ = 2; // double


Oczywiście, nie byłoby to nam do niczego potrzebne, gdybyśmy jeszcze nie wiedzieli, jak potem to wykorzystać. A i to wcale nie jest znowu takie trudne; ważne jest rzutowanie naszego wskaźnika na odpowiedni wskaźnik... Może przykład to rozjaśni:

switch (Tablica[0].Typ) {
case 1:
Calkowita = static_cast<int*> (Tablica[0].Wartosc);
cout << *Calkowita;
break;
case 2:
ZmiennoPrzecinkowa = static_cast<double*> (Tablica[0].Wartosc);
cout << *ZmiennoPrzecinkowa;
break;
}


Prawda, że proste?
Naturalnie, powyższą strukturę można wykorzystać dla dowolnych typów danych, włączając w to pojemniki STL'a, klasy, wskaźniki na obiekty, itd... Ile typów, tyle możliwości w skrócie.

sobota, 15 marca 2008

Wskaźniki na funkcje

Zdarza się czasem, że potrzebujemy wykorzystać kilka funkcji w naszym programie. Jednym ze sposobów jest zaimplementowanie wielu różnych funkcji przy pomocy instrukcji sterujących (if, switch). Jednak wtedy rozmiar kodu może się drastycznie zwiększyć, tudzież czytelność kodu może zmaleć. Co wtedy robić? Z pomocą przychodzą wskaźniki. Z początku bardzo nie lubiłem wskaźników w C++, jednak pomału przyzwyczajam się do nich, mało tego, uważam, że są bardzo całkiem proste w użyciu! Tak, tak, dobrze przeczytałeś. Nie są trudne, ważne, by załapać ideę wskaźników, a potem to już pójdzie po maśle ;)

No ale wróćmy do tematu. Wskaźnik na funkcję. Jak można się domyślić, jest to po prostu liczba, która wskazuje adres w pamięci, gdzie zaczyna się nasza szukana funkcja. No dobrze, ale jak to wykorzystać? Są dwa sposoby. Pierwszy polega na napisaniu dodatkowej funkcji. Jeśli nasza funkcja posiada jeden argument (zakładamy, że argumenty i wartości zwracane przez funkcję są typu double - naturalnie, może to być dowolny typ):

double funkcja(double (*wskaznik_na_funkcje)(double), double x)
{
double wynik;
wynik = (*wskaznik_na_funkcje) (x);
return (wynik);
}


Początek deklaracji nie powinien dziwić - typ zwracany przez naszą funkcję pomocniczą i jej nazwa. Pierwszym parametrem jest wskaźnik na naszą funkcję, wraz z listą parametrów, jakie potrzebuje ta funkcja (w naszym wypadku jest to jeden parametr). Pierwsze double to oczywiście wartość zwracana przez funkcję. Drugim parametrem jest wartość liczbowa, która będzie potem parametrem wskazywanej funkcji.
W ciele funkcji deklarujemy sobie dodatkową zmienną, w której przechowamy wynik. I najważniejsze w tym wszystkim: wynik = (*wskaznik_na_funkcje) (x);. Te nawiasy są jak najbardziej potrzebne, w przeciwnym wypadku kompilator będzie to interpretować jako wskaźnik na liczbę. I teraz najważniejsze - wykorzystanie:

double a, b;
a = funkcja(sin, 1.74);
b = funkcja(cos, 1.74);
cout << a << "\n";
cout << b << "\n";



Innym sposobem jest wykorzystanie bezpośrednie tego wskaźnika, a mianowicie: deklarujemy najpierw zmienną wskaźnikową wraz z parametrami:

double (*func)(double);

A następnie musimy przypisać temu wskaźnikowi adres naszej funkcji:

func = sin;
a = func(1.74);
func = cos;
b = func(1.74);


Wykorzystanie, jak widać, jest również proste.


Można także skorzystać z tablicy wskaźników na funkcje.
Deklaracja tablicy wygląda tak:

double (*funkcje[4])(double);

Chyba nie trzeba tego tłumaczyć... Następnie przydzielamy odpowiednie wartości, np:

funkcje[0] = sin;
funkcje[1] = cos;
funkcje[2] = tan;
funkcje[3] = ctg;


(funkcję ctg zdefiniowałem jako: pow(tan(x), -1.0))

I na koniec wykorzystanie:

for (int i = 0; i < 4; i ++)
cout << funkcje[i](3.1415 / 4.0) << "\n";


Prawda, że ładniej to wygląda? ;)