punkt01






Scott Meyers
Język C++ bardziej efektywny




Scott Meyers
Język C++ bardziej efektywny
 
Punkt 1
Rozróżniaj wskaźniki i odniesienia
 
 
Wskaźniki i odniesienia wyglądają całkiem odmiennie (do wskaźników stosują się operatory * oraz .>, do odniesień zaś
kropka), ale wydaje się, że spełniają podobną rolę. Zarówno wskaźniki, jak odniesienia pozwalają pośrednio odnosić się do innych obiektów. Czym zatem należy kierować się przy podejmowaniu decyzji, aby użyć jednego z nich zamiast drugiego?
Po pierwsze zauważmy, że nie ma czegoś takiego, jak odniesienie puste. Odniesienie musi zawsze odnosić się do jakiegoś obiektu. Tak więc, jeżeli masz zmienną, która służy do tego, aby poprzez nią odnosić się do drugiego obiektu, ale może się zdarzyć, że nie będzie takiego obiektu, to musisz uczynić tę zmienną wskaźnikiem, ponieważ wówczas możesz nadać jej wartość zero. Z drugiej strony, jeżeli zmienna musi zawsze odnosić się do pewnego obiektu, to znaczy jeśli projektując program nie dopuszczamy takiej możliwości, iż zmienna przyjmowałaby wartość zero, to przypuszczalnie zmienną należy zdefiniować jako odniesienie.
"Ale zaczekaj - zapytasz - co będzie w takiej sytuacji:"
 


char *wz = 0; // niech wskaźnik równa się zero
char& oz = *wz; // niech odniesienie odnosi się do wyłuskanego




// wskaźnika pustego
 






No cóż, to jest rzeczywiście prawdziwe diabelstwo: wynik tego zapisu jest niezdefiniowany (kompilatory mogą dać zupełnie dowolne wyniki). Ludzi, którzy w ten sposób programują, powinno się unikać dopóty, dopóki nie zaniechają takich praktyk. Jeżeli musisz się obawiać, że coś podobnego może znaleźć się w Twoim oprogramowaniu, to prawdopodobnie najlepiej będzie w ogóle nie stosować w nim odniesień. Albo należy pozyskać do współpracy lepszych programistów! Dlatego nie będziemy brać pod uwagę takiej możliwości, że w programie pojawi się odniesienie puste.
Ponieważ odniesienie musi odnosić się do obiektu, zatem język C++ żąda, aby odniesienie było zainicjowane:
 


string& on; // błąd! odniesienie musi być zainicjowane
string n("xyzzy");
string& on = n; // w porzÄ…dku, on odnosi siÄ™ do n
 


Nie ma takich ograniczeń w stosunku do wskaźników:


string *wn; // wskaźnik nie zainicjowany, poprawne, chociaż niebezpieczne
 


Brak czegoś takiego jak odniesienie puste powoduje, że używanie odniesień może być wydajniejsze niż wskaźników. Wynika to stąd, iż nie ma potrzeby sprawdzania poprawności odniesienia przed jego zastosowaniem:
 


void drukujDouble(const double& od)
{ cout « od; // nie trzeba sprawdzać od, ponieważ




// musi odnosić się do double






}
Z drugiej strony powinno się sprawdzać , czy wskaźnik nie jest pusty: void
 


drukujDouble(const double *wd)
{
( if (wd) { // sprawdź, czy to nie jest wskaźnik pusty
cout « *wd
}
}


Druga ważna różnica między wskaźnikami, a odniesieniami polega na tym, że można ponownie przypisać wskaźniki do różnych obiektów, natomiast odniesienie zawsze odnosi się do tego obiektu, do którego było zainicjowane:
 
string n1("kot");
string n2("płot");string& on = n1; // on odnosi się do napisu n1
string *wn = &n1; // wn wskazuje na napis n1
on = n2; // on nadal odnosi się do n1, ale teraz wartością n1
// jest "płot"
wn = &n2; // teraz wn wskazuje na n2;




// wartość n1 nie zmieniła się




W zasadzie wskaźnika powinno się używać zawsze wtedy, kiedy trzeba się liczyć z tym, że nie będzie się do czego odnieść (wówczas można wskaźnikowi nadać wartość zero), albo też kiedy trzeba umożliwić odwoływanie się w różnych sytuacjach do różnych rzeczy (wówczas można zmieniać ukierunkowanie wskaźników). Odniesienia zaś można używać wtedy, kiedy wiadomo, że zawsze będzie istnieć obiekt, do którego będziemy się odwoływać, a także wiadomo, iż jeśli już odwołamy się do tego obiektu, to do niczego innego nie będziemy chcieli się odwoływać.
Istnieje jeszcze jedna sytuacja, w której powinno się używać odniesienia: występuje ona wtedy, kiedy implementuje się pewne operatory. Najczęstszym tego przykładem jest operator[] . Operator ten zazwyczaj wymaga przekazywania czegoś, co może być użyte jako obiekt docelowy przypisania; np.:
 
vector<int> v(10); // utwórz wektor typu int o rozmiarze 10;








// vector jest nazwÄ… wzorca w bibliotece
// standardowej C++ (zob. punkt 35)








v[5] = 10; // obiektem docelowym tego przypisania jest
// wartość przekazywana przez operator[]
 
Jeżeli operator[] przekazywałby wskaźnik, to ostatnia instrukcja musiałaby wyglądać w ten sposób:
*v[5] = 10;
 
To zaś wygląda tak, jakby v był wektorem wskaźników, co nie jest prawdą. Dlatego prawie zawsze będziemy chcieli, aby operator[] przekazywał odniesienie.
Tak więc wybieramy odniesienia wtedy, kiedy wiemy, że mamy coś, do czego chcemy się odnieść, kiedy nigdy nie będziemy chcieli odnosić się do czegoś innego oraz kiedy ze składniowych wymagań implementowanych przez nas operatorów wynika, że używanie wskaźników jest niepożądane. We wszelkich innych sytuacjach stawiamy na wskaźniki.



Wyszukiwarka

Podobne podstrony:
punkt02
punkt06
punkt08
punkt04
punkt09

więcej podobnych podstron