Wiem, że referencje to cukier składniowy, więc kod jest łatwiejszy do odczytania i napisania.
Ale jakie są różnice?
int &x = *(int*)0;
na gcc. Odwołanie może rzeczywiście wskazywać na NULL.
Wiem, że referencje to cukier składniowy, więc kod jest łatwiejszy do odczytania i napisania.
Ale jakie są różnice?
int &x = *(int*)0;
na gcc. Odwołanie może rzeczywiście wskazywać na NULL.
Odpowiedzi:
Wskaźnik można przypisać ponownie:
int x = 5;
int y = 6;
int *p;
p = &x;
p = &y;
*p = 10;
assert(x == 5);
assert(y == 10);
Odwołanie nie może i musi zostać przypisane przy inicjalizacji:
int x = 5;
int y = 6;
int &r = x;
Wskaźnik ma swój własny adres i rozmiar pamięci na stosie (4 bajty na x86), podczas gdy odwołanie dzieli ten sam adres pamięci (z oryginalną zmienną), ale zajmuje również trochę miejsca na stosie. Ponieważ odwołanie ma ten sam adres, co sama zmienna oryginalna, można bezpiecznie traktować odwołanie jako inną nazwę dla tej samej zmiennej. Uwaga: Co może wskazywać wskaźnik na stosie lub stosie. To samo dotyczy Moim twierdzeniem w tym stwierdzeniu nie jest to, że wskaźnik musi wskazywać na stos. Wskaźnik to po prostu zmienna, która przechowuje adres pamięci. Ta zmienna znajduje się na stosie. Ponieważ odwołanie ma własną przestrzeń na stosie, a ponieważ adres jest taki sam jak zmienna, do której się odwołuje. Więcej o stosie vs kupie. Oznacza to, że istnieje prawdziwy adres odwołania, którego kompilator nie powie.
int x = 0;
int &r = x;
int *p = &x;
int *p2 = &r;
assert(p == p2);
Możesz mieć wskaźniki do wskaźników do wskaźników oferujących dodatkowy poziom pośrednictwa. Podczas gdy referencje oferują tylko jeden poziom pośredni.
int x = 0;
int y = 0;
int *p = &x;
int *q = &y;
int **pp = &p;
pp = &q;//*pp = q
**pp = 4;
assert(y == 4);
assert(x == 0);
Wskaźnik można przypisać nullptr
bezpośrednio, a odniesienia nie. Jeśli spróbujesz wystarczająco mocno i wiesz, jak to zrobić, możesz podać adres referencji nullptr
. Podobnie, jeśli spróbujesz wystarczająco mocno, możesz mieć odniesienie do wskaźnika, a następnie to odniesienie może zawierać nullptr
.
int *p = nullptr;
int &r = nullptr; <--- compiling error
int &r = *p; <--- likely no compiling error, especially if the nullptr is hidden behind a function call, yet it refers to a non-existent int at address 0
Wskaźniki mogą iterować po tablicy; możesz użyć, ++
aby przejść do następnego elementu, na który wskazuje wskaźnik, i + 4
przejść do piątego elementu. Nie ma znaczenia, jaki rozmiar ma obiekt, na który wskazuje wskaźnik.
*
Aby uzyskać dostęp do miejsca w pamięci, do którego wskazuje, należy oddzielić wskaźnik , natomiast odniesienia można użyć bezpośrednio. Wskaźnik do klasy / struct używa, ->
aby uzyskać dostęp do jej członków, podczas gdy referencja używa .
.
Referencje nie mogą być umieszczane w tablicy, podczas gdy wskaźniki mogą być (wspomniane przez użytkownika @litb)
Odnośniki Const mogą być powiązane z tymczasowymi. Wskaźniki nie mogą (nie bez pośrednictwa):
const int &x = int(12); //legal C++
int *y = &int(12); //illegal to dereference a temporary.
Dzięki temu const&
korzystanie z list argumentów jest bezpieczniejsze.
Odniesienia może być traktowany jako stały wskaźnik (nie mylić ze wskaźnikiem do wartości stałej!) Z automatycznym zadnie, czyli będzie stosować kompilatora *
operatora dla Ciebie.
Wszystkie odwołania muszą być inicjowane wartością inną niż null, w przeciwnym razie kompilacja się nie powiedzie. Nie jest możliwe uzyskanie adresu referencji - operator adresu zwróci adres referencyjnej wartości - nie jest też możliwe wykonanie arytmetyki na referencjach.
Programiści C mogą nie lubić odniesień do C ++, ponieważ nie będzie już oczywiste, kiedy nastąpi pośrednictwo lub jeśli argument zostanie przekazany przez wartość lub wskaźnik bez patrzenia na podpisy funkcji.
Programiści C ++ mogą nie lubić używania wskaźników, ponieważ są uważani za niebezpieczne - chociaż referencje nie są tak naprawdę bezpieczniejsze niż stałe wskaźniki, z wyjątkiem najbardziej trywialnych przypadków - nie mają wygody automatycznej pośredniczenia i mają inną konotację semantyczną.
Rozważ następujące oświadczenie z C ++ FAQ :
Mimo że odwołanie jest często implementowane przy użyciu adresu w podstawowym języku asemblera, nie należy uważać odniesienia za zabawnie wyglądający wskaźnik do obiektu. Odwołanie to obiekt. Nie jest wskaźnikiem do obiektu ani kopią obiektu. To jest przedmiot.
Ale jeśli referencja naprawdę była obiektem, to jak mogłyby istnieć wiszące referencje? W językach niezarządzanych odniesienia nie mogą być „bezpieczniejsze” niż wskaźniki - na ogół po prostu nie ma sposobu na wiarygodne aliasy wartości ponad granicami zakresu!
Pochodzące z tła C referencje C ++ mogą wyglądać trochę głupio, ale nadal należy ich używać zamiast wskaźników, tam gdzie to możliwe: automatyczna pośrednia jest wygodna, a referencje stają się szczególnie przydatne, gdy mamy do czynienia z RAII - ale nie ze względu na postrzegane bezpieczeństwo zaletą, ale raczej dlatego, że sprawiają, że pisanie kodu idiomatycznego jest mniej niewygodne.
RAII jest jedną z głównych koncepcji C ++, ale współdziała w sposób niebanalny z kopiowaniem semantyki. Przekazywanie obiektów przez odniesienie pozwala uniknąć tych problemów, ponieważ nie jest wymagane kopiowanie. Jeśli referencje nie byłyby obecne w języku, należy zamiast tego użyć wskaźników, które są bardziej kłopotliwe w użyciu, naruszając w ten sposób zasadę projektowania języka, zgodnie z którą najlepsze praktyki powinny być łatwiejsze niż alternatywy.
Jeśli chcesz być naprawdę pedantyczny, możesz zrobić jedną rzecz z referencją, której nie możesz zrobić za pomocą wskaźnika: wydłużyć czas życia obiektu tymczasowego. W C ++, jeśli powiążesz stałe odniesienie z obiektem tymczasowym, czas życia tego obiektu staje się czasem życia odwołania.
std::string s1 = "123";
std::string s2 = "456";
std::string s3_copy = s1 + s2;
const std::string& s3_reference = s1 + s2;
W tym przykładzie s3_copy kopiuje obiekt tymczasowy będący wynikiem konkatenacji. Natomiast s3_reference w istocie staje się obiektem tymczasowym. To tak naprawdę odniesienie do obiektu tymczasowego, który ma teraz taki sam okres istnienia jak odniesienie.
Jeśli spróbujesz tego bez const
niego, kompilacja nie powiedzie się. Nie możesz powiązać nieistniejącego odwołania z obiektem tymczasowym, ani nie możesz wziąć jego adresu w tej sprawie.
const &
powiązania i tylko wtedy, gdy odwołanie wykracza poza zakres, wywoływany jest destruktor rzeczywistego typu referencyjnego (w porównaniu z typem referencyjnym, który może być bazą). Ponieważ jest to odniesienie, nie będzie między nimi krojenia.
Animal x = fast ? getHare() : getTortoise()
następnie x
zmierzy się klasyczny problem krojenia, gdy Animal& x = ...
będzie działać prawidłowo.
Oprócz cukru syntaktycznego odniesieniem jest const
wskaźnik ( nie wskaźnik do a const
). Musisz zadeklarować, do czego się odnosi, kiedy deklarujesz zmienną referencyjną, i nie możesz jej później zmienić.
Aktualizacja: teraz, gdy o tym myślę, jest ważna różnica.
Cel wskaźnika stałej może zostać zastąpiony przez pobranie jego adresu i użycie stałej rzutowania.
Celu odniesienia nie można zastąpić w żaden sposób bez UB.
Powinno to pozwolić kompilatorowi na lepszą optymalizację referencji.
T* const
z innym składniowym cukrem (co zdarza się, aby wyeliminować wiele * i & z twojego kodu).
int i; int const *pci = &i; /* implicit conv to const int* */ int *pi = const_cast<int*>(pci);
jest OK.
W przeciwieństwie do popularnej opinii, możliwe jest, aby mieć referencję NULL.
int * p = NULL;
int & r = *p;
r = 1; // crash! (if you're lucky)
To prawda, o wiele trudniej jest zrobić z referencją - ale jeśli sobie z tym poradzisz, oderwiesz włosy, próbując je znaleźć. Referencje nie są z natury bezpieczne w C ++!
Technicznie jest to nieprawidłowe odwołanie , a nie odwołanie zerowe. C ++ nie obsługuje pojęć zerowych jako koncepcji, którą można znaleźć w innych językach. Istnieją również inne rodzaje nieprawidłowych odniesień. Wszelkie nieprawidłowe odwołania wywołują spektrum niezdefiniowanych zachowań , tak jak zrobiłby to niewłaściwy wskaźnik.
Rzeczywisty błąd polega na dereferencji wskaźnika NULL przed przypisaniem do odwołania. Ale nie znam żadnych kompilatorów, które generowałyby błędy w tym stanie - błąd rozprzestrzenia się do punktu w dalszej części kodu. Właśnie dlatego ten problem jest tak podstępny. Przez większość czasu, jeśli odrzucisz wskaźnik NULL, rozbijasz się w tym miejscu i nie trzeba dużo debugowania, aby go zrozumieć.
Mój przykład powyżej jest krótki i wymyślony. Oto bardziej realistyczny przykład.
class MyClass
{
...
virtual void DoSomething(int,int,int,int,int);
};
void Foo(const MyClass & bar)
{
...
bar.DoSomething(i1,i2,i3,i4,i5); // crash occurs here due to memory access violation - obvious why?
}
MyClass * GetInstance()
{
if (somecondition)
return NULL;
...
}
MyClass * p = GetInstance();
Foo(*p);
Chciałbym powtórzyć, że jedynym sposobem na uzyskanie odwołania zerowego jest użycie zniekształconego kodu, a gdy już go znajdziesz, zachowanie jest niezdefiniowane. Sprawdzanie, czy istnieje odwołanie zerowe, nigdy nie ma sensu; na przykład możesz spróbować, if(&bar==NULL)...
ale kompilator może zoptymalizować nieistniejącą instrukcję! Prawidłowe odwołanie nigdy nie może mieć wartości NULL, więc z punktu widzenia kompilatora porównanie jest zawsze fałszywe i można dowolnie usunąć if
klauzulę jako martwy kod - jest to istota nieokreślonego zachowania.
Właściwym sposobem na uniknięcie problemów jest uniknięcie dereferencji wskaźnika NULL w celu utworzenia odwołania. Oto zautomatyzowany sposób na osiągnięcie tego.
template<typename T>
T& deref(T* p)
{
if (p == NULL)
throw std::invalid_argument(std::string("NULL reference"));
return *p;
}
MyClass * p = GetInstance();
Foo(deref(p));
Aby zobaczyć starsze spojrzenie na ten problem od kogoś z lepszymi umiejętnościami pisania, zobacz Null References od Jima Hyslopa i Herb Suttera.
Inny przykład niebezpieczeństw związanych z dereferencją wskaźnika zerowego znajduje się w sekcji Ujawnianie niezdefiniowanego zachowania podczas próby przeniesienia kodu na inną platformę przez Raymonda Chena.
Zapomniałeś najważniejszej części:
dostęp członków za pomocą wskaźników używa ->
dostępu członków za pomocą odwołań.
foo.bar
jest wyraźnie lepszy od foo->bar
tego samego, co vi jest wyraźnie lepszy od Emacsa :-)
->
zapewnia odniesienia do wskaźników, tak jak w przypadku samego wskaźnika.
.
i ->
ma to coś wspólnego z vi i emacsem :)
.
jest lepsze niż używanie ->
, ale podobnie jak vi vs emacs, jest całkowicie subiektywne i nie możesz niczego udowodnić
Referencje są bardzo podobne do wskaźników, ale zostały specjalnie opracowane, aby pomóc w optymalizacji kompilatorów.
Jako przykład:
void maybeModify(int& x); // may modify x in some way
void hurtTheCompilersOptimizer(short size, int array[])
{
// This function is designed to do something particularly troublesome
// for optimizers. It will constantly call maybeModify on array[0] while
// adding array[1] to array[2]..array[size-1]. There's no real reason to
// do this, other than to demonstrate the power of references.
for (int i = 2; i < (int)size; i++) {
maybeModify(array[0]);
array[i] += array[1];
}
}
Kompilator optymalizujący może zdać sobie sprawę, że uzyskujemy dostęp do [0] i [1] całkiem sporego grona. Chciałbym zoptymalizować algorytm, aby:
void hurtTheCompilersOptimizer(short size, int array[])
{
// Do the same thing as above, but instead of accessing array[1]
// all the time, access it once and store the result in a register,
// which is much faster to do arithmetic with.
register int a0 = a[0];
register int a1 = a[1]; // access a[1] once
for (int i = 2; i < (int)size; i++) {
maybeModify(a0); // Give maybeModify a reference to a register
array[i] += a1; // Use the saved register value over and over
}
a[0] = a0; // Store the modified a[0] back into the array
}
Aby dokonać takiej optymalizacji, musi udowodnić, że nic nie może zmienić tablicy [1] podczas połączenia. Jest to raczej łatwe do zrobienia. i nigdy nie jest mniejsze niż 2, więc tablica [i] nigdy nie może odnosić się do tablicy [1]. możeModify () otrzymuje a0 jako odniesienie (tablica aliasingu [0]). Ponieważ nie ma arytmetyki „referencyjnej”, kompilator musi tylko udowodnić, że być może Modify nigdy nie otrzymuje adresu x, i udowodnił, że nic nie zmienia tablicy [1].
Musi również udowodnić, że nie ma możliwości, aby przyszłe połączenie mogło odczytać / napisać [0], podczas gdy mamy tymczasową kopię rejestru w a0. Jest to często trywialne do udowodnienia, ponieważ w wielu przypadkach jest oczywiste, że odwołanie nigdy nie jest przechowywane w stałej strukturze, takiej jak instancja klasy.
Teraz zrób to samo ze wskaźnikami
void maybeModify(int* x); // May modify x in some way
void hurtTheCompilersOptimizer(short size, int array[])
{
// Same operation, only now with pointers, making the
// optimization trickier.
for (int i = 2; i < (int)size; i++) {
maybeModify(&(array[0]));
array[i] += array[1];
}
}
Zachowanie jest takie samo; dopiero teraz o wiele trudniej jest udowodnić, że być może Modify nigdy nie modyfikuje tablicy [1], ponieważ już jej nadaliśmy wskaźnik; kot wyszedł z torby. Teraz musi wykonać znacznie trudniejszy dowód: statyczną analizę możeModify, aby udowodnić, że nigdy nie zapisuje do & x + 1. Musi także udowodnić, że nigdy nie oszczędza wskaźnika, który może odnosić się do tablicy [0], która jest po prostu jak trudne.
Nowoczesne kompilatory są coraz lepsze w analizie statycznej, ale zawsze miło jest im pomóc i korzystać z referencji.
Oczywiście, pomijając takie sprytne optymalizacje, kompilatory rzeczywiście zamienią odniesienia w wskaźniki w razie potrzeby.
EDYCJA: Pięć lat po opublikowaniu tej odpowiedzi znalazłem faktyczną różnicę techniczną, w której odniesienia są inne niż tylko inny sposób patrzenia na tę samą koncepcję adresowania. Odnośniki mogą modyfikować żywotność obiektów tymczasowych w sposób, którego nie potrafią wskaźniki.
F createF(int argument);
void extending()
{
const F& ref = createF(5);
std::cout << ref.getArgument() << std::endl;
};
Zwykle obiekty tymczasowe, takie jak ten utworzony przez wywołanie, createF(5)
są niszczone na końcu wyrażenia. Jednak poprzez powiązanie tego obiektu z odwołaniem, ref
C ++ wydłuży czas życia tego obiektu tymczasowego, dopóki nie ref
wyjdzie poza zakres.
maybeModify
nie przyjmuje adresu niczego związanego z, x
jest znacznie łatwiejsze niż udowodnienie, że nie występuje wiązka arytmetyki wskaźnika.
void maybeModify(int& x) { 1[&x]++; }
, co omawiają pozostałe komentarze
W rzeczywistości odwołanie nie jest tak naprawdę wskaźnikiem.
Kompilator przechowuje „odniesienia” do zmiennych, kojarząc nazwę z adresem pamięci; to jego zadaniem jest tłumaczenie dowolnej nazwy zmiennej na adres pamięci podczas kompilacji.
Podczas tworzenia odwołania informujesz kompilator, że przypisujesz inną nazwę do zmiennej wskaźnika; dlatego referencje nie mogą „wskazywać na zero”, ponieważ zmienna nie może być i nie może być.
Wskaźniki są zmiennymi; zawierają adres innej zmiennej lub mogą być zerowe. Ważne jest to, że wskaźnik ma wartość, podczas gdy odwołanie ma tylko zmienną, do której się odwołuje.
Teraz wyjaśnienie prawdziwego kodu:
int a = 0;
int& b = a;
Tutaj nie tworzysz innej zmiennej, która wskazuje a
; właśnie dodajesz inną nazwę do zawartości pamięci o wartości a
. Pamięć ta ma teraz dwa nazwiska, a
a b
, a to może być adresowane przy użyciu nazwy.
void increment(int& n)
{
n = n + 1;
}
int a;
increment(a);
Podczas wywoływania funkcji kompilator zwykle generuje przestrzenie pamięci dla argumentów, które mają zostać skopiowane. Podpis funkcji definiuje spacje, które należy utworzyć, i podaje nazwę, która powinna być użyta dla tych spacji. Zadeklarowanie parametru jako odwołania po prostu mówi kompilatorowi, aby używał wejściowej przestrzeni pamięci zmiennej zamiast przydzielać nową przestrzeń pamięci podczas wywołania metody. Może wydawać się dziwne stwierdzenie, że twoja funkcja będzie bezpośrednio manipulować zmienną zadeklarowaną w zakresie wywołującym, ale pamiętaj, że podczas wykonywania skompilowanego kodu nie ma już więcej zakresu; jest po prostu płaska pamięć, a kod funkcji może manipulować dowolnymi zmiennymi.
Teraz mogą występować przypadki, w których kompilator może nie być w stanie poznać odwołania podczas kompilacji, na przykład podczas korzystania ze zmiennej zewnętrznej. Odwołanie może, ale nie musi być zaimplementowane jako wskaźnik w podstawowym kodzie. Ale w przykładach, które ci podałem, najprawdopodobniej nie zostanie zaimplementowany ze wskaźnikiem.
Referencja nigdy nie może być NULL
.
void Foo::bar() { virtual_baz(); }
segfaults. Jeśli nie wiesz, że odwołania mogą mieć wartość NULL, nie możesz prześledzić wartości NULL z powrotem do jej początku.
int &r=*p;
to niezdefiniowane zachowanie. W tym momencie nie masz „odniesienia do NULL”, masz program, którego nie można już w ogóle uzasadnić .
Chociaż zarówno odniesienia, jak i wskaźniki służą do pośredniego dostępu do innej wartości, istnieją dwie ważne różnice między odniesieniami i wskaźnikami. Po pierwsze, odwołanie zawsze odnosi się do obiektu: Błędem jest zdefiniowanie odwołania bez jego inicjalizacji. Zachowanie przypisania jest drugą ważną różnicą: przypisanie do odwołania zmienia obiekt, z którym jest powiązane; nie wiąże ponownie odwołania do innego obiektu. Po zainicjowaniu odwołanie zawsze odnosi się do tego samego obiektu podstawowego.
Rozważ te dwa fragmenty programu. W pierwszym przypisujemy jeden wskaźnik do drugiego:
int ival = 1024, ival2 = 2048;
int *pi = &ival, *pi2 = &ival2;
pi = pi2; // pi now points to ival2
Po przypisaniu ival obiekt adresowany przez pi pozostaje niezmieniony. Przypisanie zmienia wartość pi, powodując, że wskazuje inny obiekt. Rozważmy teraz podobny program, który przypisuje dwa odwołania:
int &ri = ival, &ri2 = ival2;
ri = ri2; // assigns ival2 to ival
To przypisanie zmienia wartość ival, wartość, do której odwołuje się ri, a nie sama referencja. Po przypisaniu dwa odniesienia nadal odnoszą się do ich oryginalnych obiektów, a ich wartość jest teraz taka sama.
Istnieje semantyczna różnica, która może wydawać się ezoteryczna, jeśli nie znasz języków komputerowych w sposób abstrakcyjny, a nawet akademicki.
Na najwyższym poziomie referencje polegają na tym, że są to przezroczyste „aliasy”. Twój komputer może użyć adresu, aby działały, ale nie powinieneś się tym przejmować: powinieneś myśleć o nich jako o „innej nazwie” dla istniejącego obiektu, a składnia to odzwierciedla. Są bardziej rygorystyczne niż wskaźniki, więc twój kompilator może bardziej niezawodnie ostrzec cię, gdy masz zamiar utworzyć wiszące odniesienie, niż kiedy masz zamiar stworzyć wiszący wskaźnik.
Poza tym istnieją oczywiście praktyczne różnice między wskaźnikami i odniesieniami. Składnia do ich użycia jest oczywiście inna i nie można „ponownie umieszczać” odniesień, mieć odniesień do nicości ani wskaźników do odniesień.
Odwołanie jest aliasem innej zmiennej, podczas gdy wskaźnik przechowuje adres pamięci zmiennej. Odwołania są zwykle używane jako parametry funkcji, dzięki czemu przekazywanym obiektem nie jest kopia, ale sam obiekt.
void fun(int &a, int &b); // A common usage of references.
int a = 0;
int &b = a; // b is an alias for a. Not so common to use.
Nie ma znaczenia, ile miejsca to zajmie, ponieważ w rzeczywistości nie widać żadnego efektu ubocznego (bez wykonania kodu) zajmowanego miejsca.
Z drugiej strony, jedną z głównych różnic między referencjami i wskaźnikami jest to, że tymczasowe przypisania do stałych odwołań działają, dopóki stałe odniesienie nie wyjdzie poza zakres.
Na przykład:
class scope_test
{
public:
~scope_test() { printf("scope_test done!\n"); }
};
...
{
const scope_test &test= scope_test();
printf("in scope\n");
}
wydrukuje:
in scope
scope_test done!
Jest to mechanizm językowy, który umożliwia działanie ScopeGuard.
Jest to oparte na samouczku . To, co jest napisane, wyjaśnia:
>>> The address that locates a variable within memory is
what we call a reference to that variable. (5th paragraph at page 63)
>>> The variable that stores the reference to another
variable is what we call a pointer. (3rd paragraph at page 64)
Po prostu o tym pamiętać,
>>> reference stands for memory location
>>> pointer is a reference container (Maybe because we will use it for
several times, it is better to remember that reference.)
Co więcej, ponieważ możemy odwoływać się do niemal każdego samouczka dotyczącego wskaźnika, wskaźnik jest obiektem obsługiwanym przez arytmetykę wskaźnika, która upodabnia wskaźnik do tablicy.
Spójrz na następujące oświadczenie:
int Tom(0);
int & alias_Tom = Tom;
alias_Tom
można rozumieć jako alias of a variable
(inny z typedef
, który jest alias of a type
) Tom
. Można również zapomnieć o terminologii takiego stwierdzenia, aby utworzyć odniesienie do Tom
.
nullptr
? Czy przeczytałeś już jakąkolwiek inną część tego wątku lub ...?
Odwołanie nie jest inną nazwą nadaną pamięci. Jest niezmiennym wskaźnikiem, który jest automatycznie usuwany z odniesienia przy użyciu. Zasadniczo sprowadza się do:
int& j = i;
Staje się wewnętrznie
int* const j = &i;
const
wskaźnika. Ta elastyczność nie dowodzi, że istnieje różnica między odniesieniem a wskaźnikiem.
Co to jest odwołanie w C ++? Niektóre konkretne wystąpienia typu, który nie jest typem obiektu .
Co to jest wskaźnik w C ++? Niektóre konkretne wystąpienie typu, które jest typem obiektu .
Z definicji typu obiektu ISO C ++ :
Obiekt typu jest (ewentualnie cv -qualified) typ, który nie jest typem funkcji, a nie rodzaj odniesienia, a nie cv nieważne.
Warto wiedzieć, że typ obiektu jest najwyższą kategorią wszechświata typów w C ++. Odniesienie jest również kategorią najwyższego poziomu. Ale wskaźnik nie jest.
Wskaźniki i odniesienia są wymienione razem w kontekście typu związku . Wynika to zasadniczo z natury składni deklaratora odziedziczonej z (i rozszerzonej) C, która nie ma odwołań. (Poza tym istnieje więcej niż jeden rodzaj deklaratora odniesień od C ++ 11, podczas gdy wskaźniki są nadal „unityped”: &
+ &&
vs *
..) Tak więc napisanie języka specyficznego dla „rozszerzenia” o podobnym stylu C w tym kontekście jest dość rozsądne . (Będę nadal twierdzą, że składnia declarators odpadów syntaktyczna wyrazistość dużo , sprawia, że zarówno użytkownicy człowieka i implementacje frustrujące. W ten sposób, wszystkie z nich nie są kwalifikacje, aby być wbudowane ww nowym projekcie językowym. Jest to jednak zupełnie inny temat dotyczący projektowania PL).
W przeciwnym razie nie ma znaczenia, że wskaźniki można zakwalifikować jako określone rodzaje typów razem z odnośnikami. Po prostu mają zbyt mało wspólnych właściwości poza podobieństwem składni, więc w większości przypadków nie ma potrzeby ich łączenia.
Zwróć uwagę, że powyższe stwierdzenia wymieniają tylko typy „wskaźników” i „referencji”. Istnieją pewne pytania dotyczące ich instancji (np. Zmienne). Pojawia się też zbyt wiele nieporozumień.
Różnice kategorii najwyższego poziomu mogą już ujawnić wiele konkretnych różnic niezwiązanych bezpośrednio ze wskaźnikami:
cv
kwalifikatory najwyższego poziomu . Referencje nie mogą.Kilka dodatkowych zasad specjalnych dotyczących referencji:
&&
parametrów (jako „referencje przekazywania”) oparte na zwijaniu referencji podczas odejmowania parametrów szablonu pozwalają na „idealne przekazywanie” parametrów.std::initializer_list
podobnym zasadom przedłużania czasu życia odniesienia. To kolejna puszka robaków.Wiem, że referencje to cukier składniowy, więc kod jest łatwiejszy do odczytania i napisania.
Technicznie jest to po prostu błędne. Odnośniki nie są składniowym cukrem żadnych innych funkcji w C ++, ponieważ nie można ich dokładnie zastąpić innymi cechami bez różnic semantycznych.
(Podobnie wyrażenia lambda nie są cukrem syntaktycznym żadnych innych funkcji w C ++, ponieważ nie można go dokładnie symulować za pomocą „nieokreślonych” właściwości, takich jak kolejność deklaracji przechwyconych zmiennych , co może być ważne, ponieważ kolejność inicjalizacji takich zmiennych może być znaczący.)
C ++ ma tylko kilka rodzajów cukrów składniowych w tym ścisłym znaczeniu. Jednym z przykładów jest (odziedziczony z C) wbudowany (nie przeciążony) operator []
, który jest dokładnie zdefiniowany, mając dokładnie takie same właściwości semantyczne określonych form kombinacji w porównaniu z wbudowanym operatorem jedno- *
i dwójkowym+
.
Zarówno wskaźnik, jak i odwołanie wykorzystują tę samą ilość pamięci.
Powyższe stwierdzenie jest po prostu błędne. Aby uniknąć takich nieporozumień, spójrz zamiast tego na reguły ISO C ++:
... Obiekt zajmuje region przechowywania w okresie budowy, przez cały okres jego życia i w okresie zniszczenia. ...
Od [dcl.ref] / 4 :
Nie jest określone, czy odniesienie wymaga przechowywania.
Uwaga: są to właściwości semantyczne .
Nawet jeśli wskaźniki nie są wystarczająco wykwalifikowane, aby połączyć je z odwołaniami w sensie projektu języka, wciąż istnieją pewne argumenty, które sprawiają, że dyskusyjne jest dokonywanie wyboru między nimi w innych kontekstach, na przykład przy dokonywaniu wyborów typów parametrów.
Ale to nie jest cała historia. Mam na myśli, że jest więcej rzeczy niż wskaźników niż referencji, które należy wziąć pod uwagę.
Jeśli nie musisz trzymać się zbyt szczegółowych wyborów, w większości przypadków odpowiedź jest krótka: nie musisz używać wskaźników, więc nie musisz . Wskaźniki są zwykle wystarczająco złe, ponieważ implikują zbyt wiele rzeczy, których się nie spodziewasz, i będą opierać się na zbyt wielu domniemanych założeniach podważających łatwość konserwacji i (nawet) przenośność kodu. Niepotrzebne poleganie na wskaźnikach jest zdecydowanie złym stylem i należy go unikać w sensie współczesnego C ++. Ponownie rozważ swój cel, a w końcu okaże się, że wskaźnik jest cechą ostatnich rodzajów w większości przypadków.
&
typ odniesienia jako pierwszy typ parametru. (I zwykle powinien być const
kwalifikowany.)&&
typ odniesienia jako pierwszy typ parametru. (I zwykle nie powinno być żadnych kwalifikacji.)operator=
jako specjalne funkcje składowe wymaga typów referencyjnych podobnych do 1. parametru konstruktorów kopiuj / przenieś.++
wymaga manekina int
.unique_ptr
i shared_ptr
(lub nawet domowych, jeśli potrzebujesz, aby były nieprzezroczyste ), zamiast surowych wskaźników.std::optional
zamiast surowych wskaźników.observer_ptr
w Library Fundamental TS.Jedynych wyjątków nie można obejść w bieżącym języku:
operator new
. (Jednak cv - void*
jest nadal zupełnie inny i bezpieczniejszy w porównaniu ze zwykłymi wskaźnikami obiektów, ponieważ wyklucza nieoczekiwaną arytmetykę wskaźników, chyba że polegasz na jakimś niezgodnym rozszerzeniu na void*
podobnych GNU.)Tak więc w praktyce odpowiedź jest tak oczywista: w razie wątpliwości należy unikać wskazówek . Wskaźników musisz używać tylko wtedy, gdy istnieją bardzo wyraźne powody, dla których nic innego nie jest bardziej odpowiednie. Z wyjątkiem kilku wyjątkowych przypadków wspomnianych powyżej, takie wybory prawie zawsze nie są wyłącznie specyficzne dla C ++ (ale prawdopodobnie będą specyficzne dla implementacji języka). Takimi instancjami mogą być:
Jeśli zobaczysz pytanie za pomocą wyników wyszukiwania Google (nie dotyczy C ++) , prawdopodobnie jest to niewłaściwe miejsce.
Zawarte w C ++ jest dość „dziwne”, gdyż nie jest w zasadzie pierwszej klasy: będą one traktowane jak obiekty lub funkcje skierowania do tak nie mają szans na poparcie niektórych operacji pierwszej klasy jak jest lewy argument z operator dostępu do elementu niezależnie od rodzaju obiektu, do którego się odnosi. Inne języki mogą, ale nie muszą, mieć podobne ograniczenia dotyczące swoich odniesień.
Odwołania w C ++ prawdopodobnie nie zachowają znaczenia w różnych językach. Na przykład odwołania w ogóle nie implikują właściwości o wartościach pustych dla wartości takich jak w C ++, więc takie założenia mogą nie działać w niektórych innych językach (i można łatwo znaleźć kontrprzykłady, np. Java, C #, ...).
Wciąż mogą istnieć pewne wspólne właściwości wśród odniesień w różnych językach programowania, ale zostawmy to na inne pytania w SO.
(Uwaga dodatkowa: pytanie może być znaczące wcześniej niż w przypadku języków „podobnych do C”, takich jak ALGOL 68 vs. PL / I. )
Odwołanie do wskaźnika jest możliwe w C ++, ale odwrotność nie jest możliwa, oznacza to, że wskaźnik do odniesienia nie jest możliwy. Odwołanie do wskaźnika zapewnia czystszą składnię do modyfikowania wskaźnika. Spójrz na ten przykład:
#include<iostream>
using namespace std;
void swap(char * &str1, char * &str2)
{
char *temp = str1;
str1 = str2;
str2 = temp;
}
int main()
{
char *str1 = "Hi";
char *str2 = "Hello";
swap(str1, str2);
cout<<"str1 is "<<str1<<endl;
cout<<"str2 is "<<str2<<endl;
return 0;
}
Rozważ wersję C powyższego programu. W C musisz użyć wskaźnika do wskaźnika (wielokrotna pośrednia), co prowadzi do zamieszania, a program może wyglądać na skomplikowany.
#include<stdio.h>
/* Swaps strings by swapping pointers */
void swap1(char **str1_ptr, char **str2_ptr)
{
char *temp = *str1_ptr;
*str1_ptr = *str2_ptr;
*str2_ptr = temp;
}
int main()
{
char *str1 = "Hi";
char *str2 = "Hello";
swap1(&str1, &str2);
printf("str1 is %s, str2 is %s", str1, str2);
return 0;
}
Odwiedź następujące informacje, aby uzyskać więcej informacji na temat odniesienia do wskaźnika:
Jak powiedziałem, wskaźnik do odwołania nie jest możliwy. Wypróbuj następujący program:
#include <iostream>
using namespace std;
int main()
{
int x = 10;
int *ptr = &x;
int &*ptr1 = ptr;
}
Używam referencji, chyba że potrzebuję jednego z tych:
Wskaźniki zerowe mogą być użyte jako wartość wartownika, często tani sposób, aby uniknąć przeciążenia funkcji lub użycia bool.
Możesz wykonywać arytmetykę na wskaźniku. Na przykład,p += offset;
&r + offset
gdzie r
zadeklarowano jako odniesienie
Jest jedna zasadnicza różnica między wskaźnikami a referencjami, o których nikomu nie wspominałem: referencje włączają semantykę przekazywania przez argumenty w argumentach funkcji. Wskaźniki, choć na początku nie są widoczne, nie zapewniają: zapewniają jedynie semantykę wartości według wartości. Zostało to bardzo dobrze opisane w tym artykule .
Pozdrawiam i rzej
Ryzykując dodanie do zamieszania, chcę wprowadzić jakieś dane wejściowe, jestem pewien, że zależy to głównie od sposobu, w jaki kompilator implementuje referencje, ale w przypadku gcc pomysł, że referencja może wskazywać tylko zmienną na stosie nie jest właściwie poprawne, weź to na przykład:
#include <iostream>
int main(int argc, char** argv) {
// Create a string on the heap
std::string *str_ptr = new std::string("THIS IS A STRING");
// Dereference the string on the heap, and assign it to the reference
std::string &str_ref = *str_ptr;
// Not even a compiler warning! At least with gcc
// Now lets try to print it's value!
std::cout << str_ref << std::endl;
// It works! Now lets print and compare actual memory addresses
std::cout << str_ptr << " : " << &str_ref << std::endl;
// Exactly the same, now remember to free the memory on the heap
delete str_ptr;
}
Który daje to:
THIS IS A STRING
0xbb2070 : 0xbb2070
Jeśli zauważysz, że nawet adresy pamięci są dokładnie takie same, co oznacza, że odwołanie pomyślnie wskazuje zmienną na stercie! Teraz, jeśli naprawdę chcesz się zakręcić, działa to również:
int main(int argc, char** argv) {
// In the actual new declaration let immediately de-reference and assign it to the reference
std::string &str_ref = *(new std::string("THIS IS A STRING"));
// Once again, it works! (at least in gcc)
std::cout << str_ref;
// Once again it prints fine, however we have no pointer to the heap allocation, right? So how do we free the space we just ignorantly created?
delete &str_ref;
/*And, it works, because we are taking the memory address that the reference is
storing, and deleting it, which is all a pointer is doing, just we have to specify
the address with '&' whereas a pointer does that implicitly, this is sort of like
calling delete &(*str_ptr); (which also compiles and runs fine).*/
}
Który daje to:
THIS IS A STRING
Dlatego odniesienie JEST wskaźnikiem pod maską, oba przechowują tylko adres pamięci, na który adres wskazuje, nie ma znaczenia, jak myślisz, co by się stało, gdybym nazwał std :: cout << str_ref; PO wywołaniu delete & str_ref? Cóż, oczywiście kompiluje się dobrze, ale powoduje błąd segmentacji w czasie wykonywania, ponieważ nie wskazuje już poprawnej zmiennej, w zasadzie mamy uszkodzone odwołanie, które wciąż istnieje (dopóki nie wykracza poza zakres), ale jest bezużyteczne.
Innymi słowy, odniesienie jest niczym innym jak wskaźnikiem, którego mechanika wskaźnika jest oderwana, co czyni go bezpieczniejszym i łatwiejszym w użyciu (bez przypadkowej matematyki wskaźnika, bez mieszania „.” I „->” itd.), Zakładając, że nie próbuj bzdur takich jak moje przykłady powyżej;)
Teraz, niezależnie od tego, jak kompilator obsługuje odniesienia, zawsze będzie miał pod maską jakiś wskaźnik, ponieważ odniesienie musi odnosić się do określonej zmiennej pod określonym adresem pamięci, aby działało zgodnie z oczekiwaniami, nie można tego obejść (stąd termin „odniesienie”).
Jedyną ważną zasadą, o której należy pamiętać w przypadku odniesień, jest to, że muszą być zdefiniowane w momencie deklaracji (z wyjątkiem odwołania w nagłówku, w takim przypadku należy je zdefiniować w konstruktorze, po tym, jak obiekt w nim zawarty jest skonstruowane, jest za późno, aby to zdefiniować).
Pamiętaj, moje powyższe przykłady są po prostu przykładami pokazującymi, czym jest referencja, nigdy nie chciałbyś używać referencji w ten sposób! W celu prawidłowego użycia referencji jest już mnóstwo odpowiedzi, które trafiły w sedno
Inną różnicą jest to, że możesz mieć wskaźniki do typu pustki (i oznacza to wskaźnik do czegokolwiek), ale odwołania do pustki są zabronione.
int a;
void * p = &a; // ok
void & p = a; // forbidden
Nie mogę powiedzieć, że jestem naprawdę zadowolony z tej szczególnej różnicy. Wolałbym, żeby to było dozwolone z odniesieniem znaczeniowym do czegokolwiek z adresem, a poza tym takie samo zachowanie dla odniesień. Umożliwiłoby to zdefiniowanie niektórych odpowiedników funkcji biblioteki C, takich jak memcpy, przy użyciu referencji.
Odwołanie będące parametrem funkcji wstawionej może być obsługiwane inaczej niż wskaźnik.
void increment(int *ptrint) { (*ptrint)++; }
void increment(int &refint) { refint++; }
void incptrtest()
{
int testptr=0;
increment(&testptr);
}
void increftest()
{
int testref=0;
increment(testref);
}
Wiele kompilatorów podczas wstawiania wersji wskaźnikowej faktycznie wymusza zapis do pamięci (adres przyjmujemy jawnie). Pozostawiają jednak referencję w rejestrze, który jest bardziej optymalny.
Oczywiście dla funkcji, które nie są wstawione, wskaźnik i referencja generują ten sam kod i zawsze lepiej jest przekazywać wartości wewnętrzne przez wartość niż przez referencję, jeśli nie są one modyfikowane i zwracane przez funkcję.
Innym interesującym zastosowaniem odwołań jest dostarczenie domyślnego argumentu typu zdefiniowanego przez użytkownika:
class UDT
{
public:
UDT() : val_d(33) {};
UDT(int val) : val_d(val) {};
virtual ~UDT() {};
private:
int val_d;
};
class UDT_Derived : public UDT
{
public:
UDT_Derived() : UDT() {};
virtual ~UDT_Derived() {};
};
class Behavior
{
public:
Behavior(
const UDT &udt = UDT()
) {};
};
int main()
{
Behavior b; // take default
UDT u(88);
Behavior c(u);
UDT_Derived ud;
Behavior d(ud);
return 1;
}
Domyślny smak używa aspektu „powiązania stałej z tymczasowym” odwołania.
Ten program może pomóc w zrozumieniu odpowiedzi na pytanie. Jest to prosty program odwołania „j” i wskaźnika „ptr” wskazującego na zmienną „x”.
#include<iostream>
using namespace std;
int main()
{
int *ptr=0, x=9; // pointer and variable declaration
ptr=&x; // pointer to variable "x"
int & j=x; // reference declaration; reference to variable "x"
cout << "x=" << x << endl;
cout << "&x=" << &x << endl;
cout << "j=" << j << endl;
cout << "&j=" << &j << endl;
cout << "*ptr=" << *ptr << endl;
cout << "ptr=" << ptr << endl;
cout << "&ptr=" << &ptr << endl;
getch();
}
Uruchom program i spójrz na wynik, a zrozumiesz.
Poświęć 10 minut i obejrzyj ten film: https://www.youtube.com/watch?v=rlJrrGV0iOg
Mam wrażenie, że jest jeszcze jedna kwestia, która nie została tutaj omówiona.
W przeciwieństwie do wskaźników, referencje są składniowo równoważne do obiektu, do którego się odnoszą, tj. Każda operacja, która może być zastosowana do obiektu, działa dla odwołania i przy użyciu dokładnie tej samej składni (wyjątkiem jest oczywiście inicjalizacja).
Chociaż może się to wydawać powierzchowne, uważam, że ta właściwość jest kluczowa dla wielu funkcji C ++, na przykład:
Szablony . Ponieważ parametry szablonu są typu kaczego, ważne są właściwości składniowe typu, dlatego często ten sam szablon może być używany zarówno z, jak T
i T&
.
(lub std::reference_wrapper<T>
który nadal polega na niejawnej obsadzie T&
)
Szablony, które obejmują oba T&
i T&&
są jeszcze bardziej powszechne.
Lwowskie . Rozważmy zdanie str[0] = 'X';
Bez referencji, działałoby to tylko dla c-string ( char* str
). Zwrócenie znaku przez odwołanie pozwala klasom zdefiniowanym przez użytkownika mieć tę samą notację.
Kopiuj konstruktory . Składniowo sensowne jest przekazywanie obiektów do konstruktorów, a nie wskaźników do obiektów. Ale po prostu nie ma sposobu, aby konstruktor kopiował obiekt według wartości - spowodowałoby to rekurencyjne wywołanie tego samego konstruktora kopii. Pozostawia to odniesienia jako jedyną opcję tutaj.
Przeciążenie operatora . Za pomocą odniesień możliwe jest wprowadzenie pośrednictwa do wywołania operatora - powiedzmy, operator+(const T& a, const T& b)
przy zachowaniu tej samej notacji infiksowej. Działa to również w przypadku zwykłych funkcji przeciążonych.
Punkty te zapewniają znaczną część C ++ i standardowej biblioteki, więc jest to dość ważna właściwość referencji.
Istnieje bardzo ważna nietechniczna różnica między wskaźnikami i referencjami: Argument przekazany do funkcji przez wskaźnik jest znacznie bardziej widoczny niż argument przekazany do funkcji przez odwołanie inne niż const. Na przykład:
void fn1(std::string s);
void fn2(const std::string& s);
void fn3(std::string& s);
void fn4(std::string* s);
void bar() {
std::string x;
fn1(x); // Cannot modify x
fn2(x); // Cannot modify x (without const_cast)
fn3(x); // CAN modify x!
fn4(&x); // Can modify x (but is obvious about it)
}
Po powrocie do C wywołanie, które wygląda jak, fn(x)
może zostać przekazane tylko przez wartość, więc zdecydowanie nie można go zmodyfikować x
; aby zmodyfikować argument, musisz przekazać wskaźnik fn(&x)
. Więc jeśli argument nie był poprzedzony przez &
wiesz, że nie zostanie zmodyfikowany. (Odwrotność, &
czyli zmodyfikowana, nie była prawdziwa, ponieważ czasami trzeba było przekazywać duże struktury tylko do odczytu za pomocą const
wskaźnika).
Niektórzy twierdzą, że jest to tak przydatna funkcja podczas odczytywania kodu, że parametry wskaźnika powinny zawsze być używane do modyfikowania parametrów, a nie do const
odnośników, nawet jeśli funkcja nigdy nie oczekuje nullptr
. Oznacza to, że ci ludzie twierdzą, że podpisy funkcji jak fn3()
wyżej nie powinny być dozwolone. Wytyczne Google w stylu C ++ są tego przykładem.
Wskaźnik można zainicjować na 0, a odwołanie nie. W rzeczywistości odwołanie musi również odnosić się do obiektu, ale wskaźnikiem może być wskaźnik zerowy:
int* p = 0;
Ale nie możemy mieć int& p = 0;
, a także int& p=5 ;
.
W rzeczywistości, aby zrobić to poprawnie, musimy najpierw zadeklarować i zdefiniować obiekt, a następnie możemy zrobić odwołanie do tego obiektu, aby poprawna implementacja poprzedniego kodu była następująca:
Int x = 0;
Int y = 5;
Int& p = x;
Int& p1 = y;
Inną ważną kwestią jest to, że możemy dokonać deklaracji wskaźnika bez inicjalizacji, jednak nie można tego zrobić w przypadku odwołania, które musi zawsze odwoływać się do zmiennej lub obiektu. Jednak takie użycie wskaźnika jest ryzykowne, więc ogólnie sprawdzamy, czy wskaźnik rzeczywiście wskazuje na coś, czy nie. W przypadku odniesienia taka kontrola nie jest konieczna, ponieważ wiemy już, że odniesienie do obiektu podczas deklaracji jest obowiązkowe.
Kolejna różnica polega na tym, że wskaźnik może wskazywać na inny obiekt, jednak odwołanie zawsze odnosi się do tego samego obiektu, weźmy ten przykład:
Int a = 6, b = 5;
Int& rf = a;
Cout << rf << endl; // The result we will get is 6, because rf is referencing to the value of a.
rf = b;
cout << a << endl; // The result will be 5 because the value of b now will be stored into the address of a so the former value of a will be erased
Kolejny punkt: gdy mamy szablon taki jak szablon STL, taki rodzaj szablonu klasy zawsze zwraca odwołanie, a nie wskaźnik, aby ułatwić czytanie lub przypisywanie nowej wartości za pomocą operatora []:
Std ::vector<int>v(10); // Initialize a vector with 10 elements
V[5] = 5; // Writing the value 5 into the 6 element of our vector, so if the returned type of operator [] was a pointer and not a reference we should write this *v[5]=5, by making a reference we overwrite the element by using the assignment "="
const int& i = 0
.
Różnica polega na tym, że niestała zmienna wskaźnika (nie mylić ze wskaźnikiem na stałą) może zostać zmieniona w pewnym momencie podczas wykonywania programu, wymaga użycia semantyki wskaźnika (&, *) operatorów, podczas gdy odniesienia można ustawić podczas inicjalizacji tylko (dlatego możesz ustawić je tylko na liście inicjalizacyjnej konstruktora, ale jakoś inaczej) i użyć zwykłej wartości uzyskującej dostęp do semantyki. Zasadniczo wprowadzono odniesienia, aby umożliwić obsługę przeciążania operatorów, jak czytałem w bardzo starej książce. Jak ktoś stwierdził w tym wątku - wskaźnik można ustawić na 0 lub dowolną inną wartość. 0 (NULL, nullptr) oznacza, że wskaźnik jest inicjowany z niczym. Błędem jest wyrejestrowanie wskaźnika zerowego. Ale tak naprawdę wskaźnik może zawierać wartość, która nie wskazuje na poprawną lokalizację pamięci. Z kolei referencje starają się nie pozwolić użytkownikowi na zainicjowanie odwołania do czegoś, do czego nie można się odwoływać z uwagi na fakt, że zawsze podaje się dla niego wartość poprawnego typu. Chociaż istnieje wiele sposobów na zainicjowanie zmiennej referencyjnej w niewłaściwym miejscu w pamięci - lepiej nie zagłębiać się w szczegóły. Na poziomie maszyny zarówno wskaźnik, jak i odniesienie działają równomiernie - za pomocą wskaźników. Powiedzmy, że w podstawowych odniesieniach są cukier syntaktyczny. Odwołania do wartości są różne od tego - są to naturalnie obiekty stosu / stosu. Chociaż istnieje wiele sposobów na zainicjowanie zmiennej referencyjnej w niewłaściwym miejscu w pamięci - lepiej nie zagłębiać się w szczegóły. Na poziomie maszyny zarówno wskaźnik, jak i odniesienie działają równomiernie - za pomocą wskaźników. Powiedzmy, że w podstawowych odniesieniach są cukier syntaktyczny. Odwołania do wartości są różne od tego - są to naturalnie obiekty stosu / stosu. Chociaż istnieje wiele sposobów na zainicjowanie zmiennej referencyjnej w niewłaściwym miejscu w pamięci - lepiej nie zagłębiać się w szczegóły. Na poziomie maszyny zarówno wskaźnik, jak i odniesienie działają równomiernie - za pomocą wskaźników. Powiedzmy, że w podstawowych odniesieniach są cukier syntaktyczny. Odwołania do wartości są różne od tego - są to naturalnie obiekty stosu / stosu.
w prostych słowach możemy powiedzieć, że odwołanie jest alternatywną nazwą zmiennej, podczas gdy wskaźnik jest zmienną, która przechowuje adres innej zmiennej. na przykład
int a = 20;
int &r = a;
r = 40; /* now the value of a is changed to 40 */
int b =20;
int *ptr;
ptr = &b; /*assigns address of b to ptr not the value */