• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

[C++] - Wskaźniki, kiedy ich używać?

Object Storage Arubacloud
+2 głosów
6,185 wizyt
pytanie zadane 13 sierpnia 2016 w C i C++ przez xCodezaur Bywalec (2,850 p.)

Witam,

Kiedy to zacząłem się uczyć programowania w języku C++ (od stycznia tego roku) na odcinkach Mirosława Zelenta nadeszła pora na odcinek #10 o wskaźnikach. To wtedy straciłem ten ciąg do nauki języka C++, odkładałem to na kolejne dni, aż po dzień dzisiejszy. [Tak wiem, zrobiłem duży błąd]

Dzisiaj postanowiłem wrócić do nauki, czytam o wskaźnikach, przejrzewam dużo poradników lecz nadal nie wiem o nich wszystkiego. I tutaj z prośbą do was, o wytłumaczenie moich dwóch pytań.

 

1. Kiedy ich używać? - Jedni mówią, aby ciągle, aby się przyzwyczaić, drudzy, aby przy większych projektach, trzeci pewnie jeszcze inaczej. I stąd ta moją niechęć do wskaźników, nie mam pojęcia kiedy ich używać. Czy tylko przy tablicach, i funkcjach (przekazywanie oryginałów). Czy też przy zwykłych, standardowych zmiennych. Mam nadzieję, że mi to wytłumaczycie raz na zawsze smiley

 

2. Jak poruszać się po tablicach? - W odcinkach Zelenta była to inkrementacja tablicy, lecz dużo osób się z tym nie zgadza i poleca inkrementację wskaźnika wskazującego na dany element tablicy. Jak myślicie wy?

 

Bardziej zależy mi na odpowiedzi do pierwszego pytania, więc jeśli już macie się rozpisywać to proszę tam wink

Z góry dziękuję.

5 odpowiedzi

+3 głosów
odpowiedź 13 sierpnia 2016 przez niezalogowany
wybrane 13 sierpnia 2016 przez xCodezaur
 
Najlepsza
Wskaźników nie używa się jakoś bardzo dużo, nie zastępuję się nimi zwykłych zmiennych. Wskaźników więcej się używa zwykle w większych projektach, jak się pisze obiektowo. Wtedy wskaźniki się często przydają ;) Jak piszesz jakieś małe programiki, jeden plik, nieobiektowo, to wskaźników się aż tyle nie używa. Ale to nie jest tak, że albo wskaźniki ciągle używasz, albo wcale. To jest po prostu zwykłe narzędzie, które się używa do różnych rzeczy (nie tylko do wskazywania na jakąś zmienną). Z czasem się przyzwyczaisz i będziesz wiedział, kiedy takiego wskaźnika użyć, a kiedy możesz się obyć bez niego ;)
komentarz 13 sierpnia 2016 przez xCodezaur Bywalec (2,850 p.)

Dziękuje za odpowiedź, teraz nawet bardziej rozumiem po co one są. Aby zaoszczędzić czas wykonywania operacji w większych projektach, gdzie może on być dużo większy, a w małych są to nie widocznie różnice czasowe. Czekam z niecierpliwością na to przyzwyczajenie laugh

komentarz 13 sierpnia 2016 przez niezalogowany
Właśnie o to w przyzwyczajeniu chodzi, że trochę trzeba poczekać, a i tak nie wiesz, kiedy już się przyzwyczaisz ;)

Miłego kodowania życzę! :)
komentarz 13 sierpnia 2016 przez xCodezaur Bywalec (2,850 p.)
Dziękuję ;)
+10 głosów
odpowiedź 13 sierpnia 2016 przez MetRiko Nałogowiec (37,110 p.)
edycja 13 sierpnia 2016 przez MetRiko

Zacznijmy od tego czym tak na prawdę jest wskaźnik, a więc jest to typ zmiennej przechowujący adres innej zmiennej konkretnego typu.. tak więc wskaźnik to nie cały obiekt, a jedynie czysty adres.

Kiedy ich używać? 
Nie ma konkretnego zakazu nieużywania wskaźników, ale nie ma też obowiązku tworzenia ich w każdym możliwym miejscu.. mówiąc najprościej to programista decyduje, czy chce w kodzie gdzieś wykorzystać wskaźniki, czy też nie.. Jednak wybór przeważnie nie jest kwestią gustu. Aby dobrze wykorzystywać wskaźniki należy najpierw znać ich właściwości.. oto kilka z nich:

1. Wskaźnik jest "lekki":
Skoro wskaźnik przechowuje tylko adres, a nie cały obiekt, przenoszenie obiektów przy ich pomocy jest znacznie szybsze niż przy używaniu kopii (wyjątek stanowią najbardziej podstawowe typy tj. float, int, char, bool itd.)

2. Automatyczna "aktualizacja" obiektu:
Wyobraźmy sobie taką sytuację.. mamy kamerę ustawioną na konkretny cel, jeżeli chcemy, by kamera cały czas miała pozycję gracza mamy dwie możliwości:
a) Cały czas przy aktualizacji pętli głównej gry przesyłać do kamery pozycję gracza (aktualizować pozycję ręcznie).
b) Przechowywać konkretny wskaźnik z pozycją gracza w obiekcie [kamera].. przez co kamera zawsze będzie wskazywać na gracza, chyba że zmienimy adres wskaźnika (automatyczna aktualizacja pozycji).

3. Przechowywanie oryginalnej zmiennej:
Skoro wskaźnik odwołuje się do obiektu poprzez jego adres oznacza to, że modyfikowanie obiektu przy pomocy wskaźnika wpłynie bezpośrednio na ten obiekt.. wskaźnik nie przechowuje kopii, a oryginał.
Prosty przykład:
int a=10;
int b=a; //przesyłanie wartości przez kopię
int *ptr_a=&a;
*ptr_a=30;
b=20;

Modyfikowanie zmiennej b, w żaden sposób nie wpłynie na wartość zmiennej a, możliwa jest natomiast zmiana wartości zmiennej a przy pomocy wskaźnika.. Na końcu programu okaże się, że a=30 i b=20.

4. Szybkie przesyłanie obiektów:
Jeżeli miałeś już do czynienia w funkcjami to powinieneś wiedzieć, że zapis:
int funkcja(int a, int b)
{
    return a+b;
}

Oznacza, że do funkcji (jej argumenty) zostaną przesłane kopie oryginalnych zmiennych. Co prawda w tym wypadku nie jest to kosztowna operacja, ale co gdyby zamiast typu int, mielibyśmy do czynienia z rozbudowanym obiektem przechowującym ponad 40 różnych zmiennych? Wtedy stworzenie kopi zajęło by 40*2 razy dłużej niż przypisanie jakiejś wartości do typu int, ponieważ wszystkie wartości ze wszystkich zmiennych musiałby być przekopiowane. Aby rozwiązać ten problem możemy posłużyć się albo referencją:
obiekt funkcja(obiekt &a, obiekt &b)
{
    return a+b;
}
która sprawi, że argumenty w tej funkcji nie będą już kopią, a oryginałem (nie myl ze wskaźnikami), albo możemy przesłać argumenty w postaci wskaźników:
obiekt funkcja(obiekt *a, obiekt *b)
{
    return *a+*b; //oczywiście, musimy posłużyć się operatorem '*' aby zsumować obiekty, na które pokazują wskaźniki, a nie same adresy obiektów.
}
Wydaje się, że przesyłanie przez referencję jest ładniejsze w kodzie.. jest jednak kluczowa różnica.. do funkcji z referencjami nie możemy przesłać pustego adresu, natomiast przy wskaźnikach jest to już możliwe..
Prosty przykład w postaci całego kodu: http://cpp.sh/97m2f

5. Polimorfizm / dziedziczenie:
Zgaduję, że skoro jesteś dopiero na etapie wskaźników nie wiesz czym dokładnie jest polimorfizm.. dlatego nie będę się rozpisywał.. Postaram się wyjaśnić tylko niezbędne minimum na podstawie przykładu.. Powiedzmy, że mamy w grze kilka, rodzaju przeciwników.. np. Wilk, Niedźwiedź, Golem i Smok.. Teraz rodzi się pytanie.. jak przechować te wszystkie (różnego typu) obiekty w jednej tablicy.. (przypominam, że w jednej tablicy możemy przechowywać tylko jeden typ obiektu) w C++ jest jednak pewien myk, który umożliwi na przechowywanie wszystkich przeciwników, każdego typu.. wystarczy ustawić, że Wilk, Niedźwiedź, Golem i Smok są również Przeciwnikiem (Dokładnie mówiąc to Wilk dziedziczyłby z klasy Przeciwnik.. Niedźwiedź, Golem i Smok również), W takim przypadku mając tablicę ze wskaźnikami typu [Przeciwnik], moglibyśmy przechowywać w niej wszystkie obiekty typu [Przeciwnik] (to, że były by one też wilkiem, czy smokiem.. w tym wypadku nas nie interesuje, ważne że byłyby one również typu [Przeciwnik], czyli takiego jaki przechowujemy w tablicy).

Jak poruszać się po tablicach?
Najpierw należy wiedzieć co tak na prawdę oznacza używanie operatora '[...]'.
Prosty przykład (lubię przykłady ^^):
int Tab[10];
Tab[4]=5;

Zapis Tab[4]=5; jest tak na prawdę równoważny zapisowi:
*(Tab+4)=5;
Co tu się dokładnie dzieje?
Nazwa tablicy oczywiście jest jednocześnie adresem jej pierwszego elementu.. oznacza to, że Tab+4 to tak na prawdę adres jej piątego elementu:
Tab -> Tab+1 ->Tab+2 -> Tab+3 -> Tab+4
[0]    [1]     [2]      [3]      [4]

Używając gwiazdki informujemy kompilator, że chcemy odwołać się do wartości na którą wskazuje (w tym przypadku) adres Tab+4.
Ale działa to też w drugą stronę.. i to jest właśnie to, o co pytasz, ale to już najlepiej wyjaśnić na przykładzie prostego programu: http://cpp.sh/44h7

2
komentarz 14 sierpnia 2016 przez QizmoPL Stary wyjadacz (11,440 p.)
Najlepsze wytlumaczenie z tych wszystkich
1
komentarz 14 sierpnia 2016 przez xCodezaur Bywalec (2,850 p.)

Bardzo dziękuję za wytłumaczenie na najwyższym poziomie. Teraz już prawie wszystko się poukładało w mojej głowie, prawie, ponieważ jeszcze nie rozumiem małej części, ale myślę, że pracując z tym, dopiero to wszystko zrozumiem. Więc kiedy przyjdzie potrzeba, będę wracać do Twojej odpowiedzi smiley

Jest to najlepsza odpowiedź, nie mogę jej dać, ponieważ zaznaczyłem już inną, ale myślę, ze nie odpowiada się na tym forum, aby ją dostać, tylko aby pomóc ;]

 

komentarz 14 sierpnia 2016 przez MetRiko Nałogowiec (37,110 p.)
Cieszę się, że prawie dwie godziny pracy nie poszły na marne : )
Poza tym myślę, że jest to wystarczająco uniwersalne wyjaśnienie, które można wysyłać w ramach odpowiedzi do podobnych postów, gdy znów ktoś będzie miał problem ze wskaźnikami.. Zawsze to lepsze niż ciągłe pisanie tego samego tylko innymi słowami.
Tak więc.. ja również dziękuję za twój post.. bo bez niego ta wypowiedź by nie powstała x)
komentarz 14 sierpnia 2016 przez xCodezaur Bywalec (2,850 p.)
Naprawdę, nie spodziewałem się, że tyle czasu to pisałeś. Naprawdę bardzo, bardzo dziękuję, że aż tyle czasu mi poświęciłeś : )
komentarz 28 kwietnia 2020 przez Szymczak_7 Obywatel (1,860 p.)

@MetRiko, Myślę że pisząc ten post pomogłeś wielu programistom, w żadnym kursie nie widziałem żeby ktoś to tak dobrze wytłumaczył.

komentarz 9 czerwca 2020 przez MetRiko Nałogowiec (37,110 p.)
@Szymczak_7 <3
Aż dziwne, że tyle lat minęło, a ta odpowiedź dalej cieszy się dużym zainteresowaniem.
Cieszę się, że mogłem pomóc ^^
+2 głosów
odpowiedź 13 sierpnia 2016 przez jpacanowski VIP (101,940 p.)
edycja 13 sierpnia 2016 przez jpacanowski

Załóżmy, że chcesz jedną funkcją zmodyfikować 3 zmienne naraz. Nie możesz użyć rozkazu return 3 razy, ponieważ funkcja może zwrócić tylko jedną wartość.

Albo chcesz mieć w swoim programie tablicę, której rozmiar ma być nadawany podczas działania programu.

Wskaźniki również dają tobie dostęp do bardziej złożonych struktur danych.

Wszystko masz tutaj:
http://www.p-programowanie.pl/cpp/wskazniki/
http://xion.org.pl/files/texts/mgt/html/1_8.html

Ja wskaźniki podczas mojej nauki załapałem od razu, a to chyba dzięki znajomości asemblera.

komentarz 13 sierpnia 2016 przez xCodezaur Bywalec (2,850 p.)
Jeszcze nie bardzo rozumiem o co chodzi ze strukturami danych, ale dzięki wielkie za pierwszy przykład. Faktycznie, nie można zwrócić return 3 razy, więc na pewno przy tym pomogą wskaźniki.
+2 głosów
odpowiedź 13 sierpnia 2016 przez Ehlert Ekspert (212,670 p.)

Wskaźniki to zawsze ciężki temat dla początkujących i generalnie nigdy nie wiadomo kiedy ich używać.

  1. Wytłumaczę Ci to na podstawie gry... Piszesz sobie gierkę. Rysujesz mapę. Mapa  to takie różne kafelki. I powiedzmy ładujesz sobie do pamięci ten kafelek i teraz co. Za każdym razem bedziesz go przekazywać do metod w formie argumentu? Nie. Lepiej przesłać adres tego obiektu i już.

    Przykład numer dwa... Masz duży obiekt i zawierający się w nim mały obiekt. Mały obiekt stwierdził że wystąpił jakiś event i chce wywołać metodę z dużego obiektu. Wtedy przesyłasz do małego obiektu adres tego dużego.
  2. Żeby poruszać się bezpiecznie po tablicy wskaźnikiem musisz mieć jej rozmiar. Wtedy cały czas kontrolujesz czy nie wyszedłeś za zakres jej pamięci.
komentarz 13 sierpnia 2016 przez xCodezaur Bywalec (2,850 p.)

Trochę bardziej skomplikowane, ale dzięki za wytłumaczenie. Nie jestem na etapie metod, obiektów i rysowania map, ale twoja wypowiedź trochę mnie uczy w przyszłość. Dzięki wielkie za poświęcenie czasu na odpowiedź wink

+2 głosów
odpowiedź 13 sierpnia 2016 przez criss Mędrzec (172,590 p.)

Po pierwsze przy dynamicznej alokacji - to oczywiste. Chociaż od c++11 masz do tego specjalne sharde_ptr i unique_ptr

Po drugie - polimorfizm, ale to pewnie wiesz

class Base {};
class A : Base {};
class B : Base {};


A a;
B b;
Base *ptr = &a;
ptr = &b;

Kolejna świetna możliwość jaką oferuje wskaźnik, to że może wskazywać na dowolny obiekt danego typu w pamięci. Więc załóżmy, że jakaś klasa chce tylko korzystać z jakiegoś obiektu (ale nie posiadać go) i daje nam do dyspozycji metode, za pomocą której przekażemy obiekt którego adres zostanie pobrany do wskaźnika i nasza klasa będzie od teraz korzystać z tego konkretnego obiektu.
Załóżmy, że mamy jakąś tam klase Game ze wskaźnikiem Renderer* od którgo zależy w jaki sposób nasza gra jest renderowana.

class Game
{ 
public:
   Renderer *m_ptrRenderer;

   void setRenderer(Renderer& _r) { m_ptrRenderer = &_r; }
   void update() { m_ptrRenderer->render(); }
};

Za pomocą setRenderer możemy sobie ustawić dowolny obiekt Renderer kiedy tylko nam się podoba, a Game odrazu zacznie z niego korzystać. Musimy mieć tylko pewność, że obiekt cały czas w pamięci będzie, ale nie obchodzi nas gdzie on jest. To jest potęga wskaźników.

poleca inkrementację wskaźnika wskazującego na dany element tablicy.

Ostatecznie wychodzi na to samo. Kompletnie bez sensu się o to kłócić. Wygodniej jest korzystać z operatora [], więc tak robie. operator [] zwraca
*(przeakazy_wskaznik + przekazana_wartosc), więc nie ma znaczenia czy napiszesz tab[1] czy *(tab + 1). Z tym, że to pierwsze jest wygodniejsze. 

komentarz 13 sierpnia 2016 przez MetRiko Nałogowiec (37,110 p.)

Dodam od siebie, że poruszanie się po tablicy przy używaniu inkrementacji wskaźnika jest szybsze od używania zwykłego operatora '[...]'.

int Tab[10]={1,2,3,4,5,6,7,8,9};
int *Wsk=Tab;


for(int i=0; i<10; i++)
    std::cout<<Wsk[i]<<" "; //W tym miejscu program za każdym razem musi wykonywać operację dodawania *(Wsk+i), 
                            //która jest wolniejsza od czystej inkrementacji.. 
                            //W niektórych przypadkach ta różnica w czasie może być znacząca

for(int i=0; i<10; i++)
{
    std::cout<<*Wsk<<" ";
    ++Wsk;
}


 

komentarz 13 sierpnia 2016 przez criss Mędrzec (172,590 p.)
Sprawdziłem i faktycznie. W jednym przypadku różnica była ponad dwukrotna, a w innych bardzo niewielka. Z tym, że to i tak są wielkości rzędu ok ~0.001s dla 100000 (100 tys) elementowej tablicy..
komentarz 14 sierpnia 2016 przez xCodezaur Bywalec (2,850 p.)
Nie jestem jeszcze na tym etapie, polimorfizm, klasy i obiekty, ale na pewno coś wyciągnąłem z twojej odpowiedzi, dziękuję ;)
komentarz 14 sierpnia 2016 przez xCodezaur Bywalec (2,850 p.)
Wrócę tu, jak się tego nauczę, i wtedy zrozumiem twoją odpowiedź, myślę w 100% ;]
komentarz 14 sierpnia 2016 przez criss Mędrzec (172,590 p.)
Kiedyś skojarzysz, jak będziesz czegoś takiego potrzebował :P

Podobne pytania

+1 głos
2 odpowiedzi 605 wizyt
pytanie zadane 5 października 2019 w PHP przez Przybysz Nowicjusz (130 p.)
0 głosów
1 odpowiedź 676 wizyt
pytanie zadane 30 kwietnia 2020 w C i C++ przez Eriss69 Gaduła (4,470 p.)
0 głosów
1 odpowiedź 276 wizyt
pytanie zadane 12 marca 2021 w C i C++ przez grzechur18 Nowicjusz (150 p.)

92,576 zapytań

141,426 odpowiedzi

319,652 komentarzy

61,961 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Kolejna edycja największej imprezy hakerskiej w Polsce, czyli Mega Sekurak Hacking Party odbędzie się już 20 maja 2024r. Z tej okazji mamy dla Was kod: pasjamshp - jeżeli wpiszecie go w koszyku, to wówczas otrzymacie 40% zniżki na bilet w wersji standard!

Więcej informacji na temat imprezy znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...