2009-06-04 12 views
5

Bardzo podstawowe pytanie, ale mimo to dobrze byłoby usłyszeć od guru C++.Jakie są różnice między definicjami parametrów jako (typ i nazwa) i (typ * nazwa)?

Istnieją dwa dość podobne sposoby deklarowania parametrów odsyłaczy w C++.

1) Używanie "gwiazdkę":

void DoOne(std::wstring* iData); 

2) za pomocą "znaku handlowego":

void DoTwo(std::wstring& iData); 

Jakie są implikacje każdej metody? Czy w każdym razie są jakieś błędy?

Premia # 1: Jaki byłby formalny sposób wywoływania metody w # 1 i # 2? Czy oba są nazywane "referencjami"?

Premia # 2: std :: wstring jest używana celowo. Jakie byłyby konsekwencje w stosunku do standardowych klas bibliotecznych w każdym przypadku?

+1

Wiele powiedziano o argumentach odniesienia/wskaźnikowych. Zobacz też: http://stackoverflow.com/questions/57483/difference-between-pointer-variable-and-reference-variable-in-c. – xtofl

Odpowiedz

5

# 1 używa parametru wskaźnika ("przekazywanie wskaźnika do"), # 2 używa parametru odniesienia ("przekazywanie przez odniesienie"). Są one bardzo podobne, ale należy pamiętać, że kod wywołujący wygląda inaczej w dwóch przypadkach:

std::wstring s; 

DoOne(&s); // pass a pointer to s 
DoTwo(s); // pass s by reference 

Niektórzy ludzie wolą nr 1, stosując konwencję, że przechodząc przez wskaźnik oznacza, że ​​funkcja może zmienić wartość s (nawet choć każda z tych funkcji mogła). Inne osoby (włączając mnie) wolą # 2, ponieważ przekazywanie przez odniesienie nie pozwala na przekazanie wartości NULL.

Jest inna ważna różnica przy przekazywaniu przez wskaźnik lub odniesienie const. Zmienna tymczasowe mogą być przekazywane tylko do const parametru referencyjnego:

void ByConstPointer(const std::wstring&); 
void ByConstReference(const std::wstring*); 

void test() 
{ 
    ByConstPointer(&std::wstring(L"Hello")); // error: cannot take address of temporary 
    ByConstReference(std::wstring(L"Hello")); // fine 
} 
+1

+1 dla zmiennej tymczasowej! –

1

Pisząc przykłady, wymyśliłem własną odpowiedź. Coś innego niż poniżej?

Wynik każdego z nich jest dość podobny: odniesienie do obiektu w pamięci kończy się zakresem metody. Wydaje się, że nie ma wymagań ścisłej pamięci dla żadnego z nich. Obiekt może znajdować się na stosie lub w stercie.

W przypadku stosu każdej z metod byłoby nazwać tak:

{ 
    std::wstring data; 
    DoOne(&data); 
    DoTwo(data); 
} 

Jednak jeśli chodzi o stercie, drugie podejście wymagałoby, że obiekt musi istnieć przed wywołaniem metody. Jeśli obiekt nie istnieje, wywołujący wywoła wyjątek, a nie wywołujący.

{ 
    std::wstring* pData = new std::wstring(); 
    DoOne(pData); 
    DoTwo(*pData); 
} 

W powyższym przykładzie, jeśli out-of-memory występuje stan i pData kończy się NULL, katastrofa by się stało przed DoTwo, ale Doone byłoby przełknąć NULL i może upaść jakiś czas później.

+0

+1 Nie wiele do dodania :) – ralphtheninja

+0

Jest! Jak każda metoda jest nazywana formalnie? –

+0

DoOne to pass-by-reference, DoTwo to pass-by-pointer. Odniesienia nie mogą mieć wartości NULL, są wymuszane przez kompilator. Więc twoje zdanie "Jeśli obiekt nie istnieje, wywołujący spowodowałby wyjątek" jest błędne. Ponadto new nie może zwrócić wartości NULL. Jeśli się nie powiedzie, zgłasza wyjątek. Więc nie musisz się martwić. Przekazywanie według referencji jest preferowane w większości przypadków. – rlbond

0

Nie nazwałbym siebie gitarą w C++ (z wyjątkiem mojego CV), ale powiedziałbym; chyba że istnieje możliwość użycia parametru jako wskaźnika (tzn. funkcja chce sprawdzić wartość zerową), zawsze używaj odwołania.

Dotyczy to także funkcji zwracających obiekty, a zwracanie wskaźnika w jakiś sposób mówi użytkownikowi klasy, że może mieć wartość null.

0

W DoOne, iData może mieć przypisaną wartość NULL. Jeśli użyjesz tego po wywołaniu DoOne, aplikacja ulegnie awarii.

Coś

void DoOne(std::wstring* iData) 
{ 
    //Use iData 
    delete iData; 
    iData = NULL; 
} 

i

{ 
    std::wstring* pData = new std::wstring(); 
    DoOne(pData); 
    pData->someFunction(); //Crash 
} 
+0

To spowodowałoby awarię, ponieważ obiekt został usunięty, nie dlatego, że wskaźnik był przypisany do wartości NULL. –

0

Twoja odpowiedź jest całkowicie błędne, kiedy mówisz:

wynik każdego z nich jest dość podobny: odniesienie do obiekt w pamięć kończy się zasięgiem zakresu metody .Wydaje się, że nie ma żadnych ścisłych wymagań dotyczących pamięci dla żadnego z nich.

connsider:

void f(int * p1) { 
    int ** p2 = & p1; 
} 

tutaj P1 ma określone wymagania "pamięci" - musi istnieć i musi być w stanie podjąć jego adres. Porównaj to z

void f(int & r) } 
    int * p = & r; 
} 

tutaj r nie ma własnego, jest jedynie referencją. Whem Biorę jego adres Biorę adres rzeczy, której dotyczy r.

Twoje uwagi dotyczące wskaźnika NULL również są błędne. Usunięcie wskaźnika NULL powoduje niezdefiniowane zachowanie - może to, ale nie musi, spowodować awarię.

+0

Czy chcesz powiedzieć, że w C++ nie istnieje coś takiego jak "Przekaż pointeru"? Chociaż masz rację, myślę, że pytanie dotyczy tego, kiedy __ użyć wskaźnika lub odniesienia. – xtofl

+0

Nie, nie chcę tego mówić. –

0

Jeśli napisać funkcję, która pobiera zmienną przez wskaźnik, będzie najprawdopodobniej musiał sprawdzić, czy wskaźnik jest poprawny (np nie NULL) , w przeciwnym razie ryzykujesz awarię programu.

+0

"Ty ryzykujesz awarię programu". Standardowe funkcje, takie jak strcpy, nie sprawdzają wartości pustej. Uważają, że to ich rozmówca ryzykuje awarię (przekazując nieprawidłową wartość), a nie ich. –

2

Zasada numer jeden dla tego: Jeśli NULL jest poprawną wartością parametru funkcji w kontekście funkcji, należy przekazać ją jako wskaźnik, w przeciwnym razie przekazać jako odniesienie.

Uzasadnienie, jeśli nie może (nie powinien!) Kiedykolwiek być NULL, to nie poddawaj się kłopotowi sprawdzającemu NULL.

Powiązane problemy