wskaźnik na zmienną typu int.
int * wsk;
wskaźnik na wskaźnik typu int.
int ** wsk2;
Z powyższej definicji wynika, że wsk2 możesz pokazać na wskaźnik wsk. Ale uwaga: nie pokazujesz na to, na co pokazuje wsk. Pokazujesz na sam wskaźnik.
wsk2 = &wsk;
Czyli zmienna wskaźnikowa wsk2 przechowuje... adres wskaźnika.
Aby dostać się do wskaźnika zapisujesz:
*wsk2;
Aby dostać się do zmiennej, na którą pokazuje wskaźnik wsk, zapisujesz:
**wsk2;
Taki wskaźnik na wskaźnik ma zastosowania przeważnie przy tablicach i nie ma nic wspólnego z referencją.
Spróbujmy stworzyć dynamicznie dwuwymiarową talicę o nazwie tablica.
[int] [int] [int]
[int] [int] [int]
[int] [int] [int]
Zauważ, że są tutaj 3 tablice 3-elementowe. Dokładniej: 3 wiersze. Jeśli rozumiesz wskaźniki, to stwórzmy jeden taki wiersz za pomocą wskaźnika:
int * wiersz1 = new int[ 3 ];
[int] [int] [int]
Ale zauważ, że tablica tablica ma 2 wymiary. W pierwszym wybierasz wiersz, w drugim kolumnę. Tak więc nasza zmienna tablica jest w rzeczywistości: wskaźnikiem na tablice 3-elementowe. Wykorzystując wiedzę, którą podałem Ci wcześniej napiszmy wskaźnik na tablicę wiersz1.
int ** tablica;
Dobrze, mamy wskaźnik, którym możemy pokazać na tablicę, czyli inaczej: na zmienną typu: int *.
Skoro mamy wskaźnik na int *, to zróbmy tablicę zmiennych typu int*. Można? Oczywiście! Wcześniej mieliśmy wskaźnik na int i zrobiliśmy tablicę int'ów. Teraz też zadziała.
tablica= new int*[ 3 ];
Wygląda super, prawda? :-) Zmienna tablica jest w tym momencie taką tablicą:
[int*] [int*] [int*]
Ale hej! Mieliśmy zrobić tablicę 3 x 3 z intów tak jak wyżej! Co to jest?
Spokojnie, już kończymy.
Przypatrz się dokładnie powyższej tablicy. Są w niej wskaźniki. A z każdego wskaźnika możesz utworzyć tablicę (new).
Do poszczególnych wskaźników odnosimy się jak w normalnej tablicy:
tablica[ 0 ];
tablica[ 1 ];
tablica[ 2 ];
No to robimy tablice 3-elementowe.
tablica[ 0 ] = new int[ 3 ];
tablica[ 1 ] = new int[ 3 ];
tablica[ 2 ] = new int[ 3 ];
Oto co otrzymaliśmy (obróciłem tablicę dla czytelności):
[int*] --> [int] [int] [int]
[int*] --> [int] [int] [int]
[int*] --> [int] [int] [int]
Mamy tablicę trzech wskaźników, do których odnosimy się: tablica[ ... ] przy czym każdy wskaźnik jest tablicą int. I teraz chcemy się odnieść do pierwszego elementu w pierwszym wierszu. W takim wypadku bierzemy pierwszy wskaźnik i pierwszy element z tablicy. W kodzie:
tablica[ 0 ][ 0 ];
Ale pamiętajmy, że nazwa tablicy jest wskaźnikiem na pierwszy element, dlatego poniższy zapis oznacza dokładnie to samo:
**tablica;
można też tak:
(*tablica)[ 0 ];
albo tak:
*(tablica[ 0 ]);
Jak chcesz być śmieszkiem, to możesz zamienić 0 i tablica miejscami :-D
*( 0[ tablica ] );
albo
0[ (*tablica) ];
Jeśli myślisz, że żartuję, to nie. Jestem śmiertelnie poważny!
( tab[ 5 ] oznacza to samo co 5[ tab ] ) C++ <3
Wyjaśnienie dlaczego to działa podaję w komentarzu, bo już mi tu forum krzyczy, że limit znaków :D
Oczywiście NIKT tak się nie odnosi do tablic, ale jeśli dobrze zrozumiałeś temat, to powinieneś wiedzieć dlaczego tak się da.
To był tylko przykład zastosowania wskaźnika na wskaźnik. Są oczywiście inne przykłady. Nie chcę tutaj wyjeżdżać z obiektowością, bo pewnie jej nie znasz, ale napiszę dla potomnych.
Czasami możemy chcieć, aby klasa przechowywała wskaźnik na wskaźnik innej klasy, aby niejako "podglądać" na co aktualnie on pokazuje. Wskaźnikiem bazowym można pokazywać na cokolwiek, a my mając wskaźnik na ten wskaźnik możemy zawsze wiedzieć gdzie aktualnie "patrzy" klasa :-)
Jeśli masz jeszcze jakieś pytania i wątpliwości, to pisz. Różnica pomiędzy referencjami a wskaźnikami to bardzo ciekawy temat, ale boję się, że mnie zaraz forum ograniczy w ilości znaków ;-)
Pozdrawiam.
#EDIT:
Aaaa zaryzykuję. Różnice między referencją a wskaźnikiem, lecimy :-)
1* Każda referencja musi być zainicjalizowana. Przykład referencji do zmiennej:
int zmienna = 5;
int & referencja = zmienna;
Od teraz nazwa "referencja" jest inną nazwą na zmienną "zmienna". Możesz to sprawdzić wypisując adres obydwu zmiennych (będzie taki sam):
cout << &zmienna << endl << &referencja << endl;
Wnioski nasuwają się same. Referencja jest na stałe przypisana do jednej zmiennej. Nie można jej "przestawić". W funkcjach ma szczególne zastosowanie i to zastosowanie doskonale znasz.
2* Nie można stworzyć referencji do referencji w taki sposób:
int && ref2 = ref1; // błąd!
Ale można w taki:
int zmienna = 5;
int & ref1 = zmienna;
int & ref2 = ref1;
3* Wskaźniki przechowują adres zmiennej, tak więc sam wskaźnik też jest zmienną i zajmuje pamięć. Jerzy Grębosz w swojej książce Symfonia C++ pisał, że najczęściej wskaźnik jest rozmiaru int, czyli zajmuje 4 bajty, ale to zależy od kompilatora.
4* Wskaźnik można przestawiać na dowolną zmienną, która jest jego typu, a przy pomocy rzutowania można pokazać na praktycznie dowolne miejsce w pamięci wskaźnikiem dowolnego typu (wskaźnikiem na bool pokazać ostream cout'a. Tak, to jest możliwe :-) Nie wierzysz? Proszę:
bool * wsk = reinterpret_cast<bool*>(&cout);
(zamiast tego reinterpret_cast<bool*> można napisać po prostu (bool*), ale tak jest bardziej profesjonalnie, o ile w przypadku pokazywania wskaźnikiem bool na cout'a można w ogóle mówić o "profesjonalności" XD)
Oczywiście na litość anielską NIGDY tego nie rób. To już podchodzi pod szarlataństwo i tacy programiści trafiają na stertę ;-)
Są również wskaźniki typu void i one z założenia mogą pokazywać na wszystko bez żadnego rzutowania, ale żeby posłużyć się tą wartością, na którą pokazują trzeba już rzutować. Przykład:
int a = 5;
void * wsk = &a;
cout << *(int*)wsk << endl;
cout << *reinterpret_cast<int*>(wsk) << endl;
Obydwie opcje są poprawne. Druga bardziej profesjonalna, choć i tak wiem którą wybierzesz...
5* Można stworzyć wskaźnik na dowolny typ. A że wskaźnik również jest typem, to wskaźnik na wskaźnik jest jak najbardziej prawidłowy. Można stworzyć wskaźnik na typ enum, a... nawet na cout'a. Zróbmy to! :-D
ostream * wskaznik = &cout;
*wskaznik << "Moj wlasny cout!!!" << endl;
Gdybyś użył w taki sposób powyższego wskaźnika bool, to... cóż... najwyżej wysadzisz komputer :-)
Dostrzegasz to piękno C++ teraz?
6* Można zrobić wskaźnik na funkcję. Przykład:
void funkcja()
{
cout << "FUNKCJA!" << endl;
}
int main()
{
void( *wsk )() = funkcja;
wsk(); // wywołanie
}
Referencję do funkcji również można utworzyć:
void( &wsk )() = funkcja;
Można sprawdzić wypisując adres funkcji i referencji. Tak... funkcja ma adres &funkcja ;-)
7* Ale to nie koniec zabawy! Możesz stworzyć rzecz jasna referencję na cout'a i posługiwać się własną nazwą strumienia:
ostream & ref = cout;
ref << "Woooho" << endl;
8* I na koniec jeszcze coś fajowego. Referencja na wskaźnik! Nie jest ją łatwo stworzyć, a wiedza taka nie jest dostępna w żadnej książce. To wiedza zakazana, więc ćśśśś i do dzieła!
int * wsk;
auto & ref = wsk;
cout << &wsk << endl << &ref << endl;
Syntax C++ nie pozwala "normalnie" stworzyć referencji do wskaźnika, ale można to obejść stosując automatyczny dobór typów. Przykład wyżej pokazuje, że obydwie nazwy są tym samym miejscem w pamięci.
Zdaję sobie sprawę, że mogłem Ci zrobić mielonkę z mózgu, ale nie obawiaj się. Nie wszystko musisz zrozumieć. Kiedyś tu wrócisz i się będziesz śmiał.
Możesz tak dalej eksperymentować. Troszkę wkroczyliśmy w szarlataństwo, ale to niiic. Baw się dalej, a jak mi się coś przypomni, to tu dopiszę. Trzymaj się ;-)