Skip to content

Latest commit

 

History

History
277 lines (200 loc) · 8.42 KB

07-pointers.md

File metadata and controls

277 lines (200 loc) · 8.42 KB

Podstawy C++

Wskaźniki

Coders School

Wskaźniki - analogia

Poza referencjami istnieją także wskaźniki. Wskaźniki działają podobnie jak referencje.

Wyobraźmy sobie, że planujemy wycieczkę na Majorkę. Wsiadamy do samolotu i lecimy. Na miejscu okazuje się, ze zapomnieliśmy jaki jest adres hotelu :( W celu znalezienia go musimy zadzwonić do biura podróży, poczekać na obsługę, wytłumaczyć całą zawiłą historię, aż w końcu po długim czasie otrzymujemy adres naszego hotelu. Proces zdobycia tych informacji był dla nas czasochłonny.

Wyobraźmy sobie jednak, że uprzednio zapisaliśmy sobie w telefonie adres naszego hotelu. Aby przypomnieć sobie, gdzie on się znajdował wystarczy, że sprawdzimy telefon i już wiemy. Proces ten zajął nam dużo mniej czasu.


Wskaźniki w C++

Podobnie jest w C++. Wskaźniki służą do wskazywania miejsca w pamięci, gdzie znajduje się pożądany przez nas obiekt.

Procesor nie musi odpytywać każdorazowo magistrale pamięci, gdzie znajduje się podana zmienna, tylko od razu wie, jaki jest jej adres (unikamy pośredników jak telefon do biura obsługi).

Ponadto jeżeli funkcja przyjmuje wskaźnik, nie musi ona kopiować całej zawartości obiektu, co jest czasochłonne. Można dużo szybciej wskazać gdzie ten obiekt już istnieje.


Jak przekazać element przez wskaźnik?

void foo (int* num) {
    std::cout << *num;  // good
    *num += 2;          // good
}

Gdy chcemy mieć pewność, że nikt nie zmodyfikuje nam wartości (chcemy ją przekazać tylko do odczytu) dodajemy const.

void bar (int const* num) {
    std::cout << *num;  // good
    *num += 2;          // compilation error, num is a pointer to const
}

Wywołanie funkcji to:

  int num = 5;
  foo(&num);
  bar(&num);

Gdzie dać const?

Co to jest?

const int * ptr;

Wskaźnik na stałą (const int).

int const * ptr;

Również wskaźnik na stałą (const int = int const).

int * const ptr;

Stały wskaźnik na zmienną (int).


Stałe wskaźniki a wskaźniki na stałe

int const * const ptr;
const int * const ptr;

Stały wskaźnik na stałą (int const = const int).

Jest to częste pytanie z rozmów kwalifikacyjnych. Aby stały był wskaźnik, const musi być za gwiazdką.


Różnice

Wskaźnik na stałą

const int * ptr = new int{42};
*ptr = 43;     // compilation error: assignment of read-only location ‘* ptr’
ptr = nullptr; // ok
  • Nie możemy zmodyfikować obiektu wskazywanego przez wskaźnik
    • Odwołania z * nie mogą modyfikować obiektu
  • Możemy zmodyfikować sam wskaźnik, np. aby wskazywał na inny obiekt
    • Odwołania bez * mogą modyfikować wskaźnik

Różnice

Stały wskaźnik

int * const ptr = new int{42};
*ptr = 43;     // ok
ptr = nullptr; // compilation error: assignment of read-only variable ‘ptr’
  • Możemy zmodyfikować obiekt wskazywany przez wskaźnik
    • Odwołania z * mogą modyfikować obiekt
  • Nie możemy zmodyfikować samego wskaźnika, np. aby wskazywał na inny obiekt
    • Odwołania bez * nie mogą modyfikować wskaźnika

Stały wskaźnik na stałą

const int * const ptr = new int{42};
*ptr = 43;     // compilation error: assignment of read-only location ‘* ptr’
ptr = nullptr; // compilation error: assignment of read-only variable ‘ptr’
  • Nie możemy zmodyfikować obiektu wskazywanego przez wskaźnik
    • Odwołania z * nie mogą modyfikować obiektu
  • Nie możemy zmodyfikować samego wskaźnika, np. aby wskazywał na inny obiekt
    • Odwołania bez * nie mogą modyfikować wskaźnika

Zadanie

Zaimplementuj funkcje foo() i bar().

foo() powinno zmodyfikować wartość przekazaną przez wskaźnik na 10, a bar() na 20.

Czy foo() lub bar() mogą przyjąć wskaźnik na stałą lub stały wskaźnik? Pobierz zadanie

#include <iostream>

// TODO: Implement foo() and bar()
// foo() should modify value under passed pointer to 10
// bar() should modify value under passed pointer to 20
// Can we have a pointer to const or a const pointer?
int main() {
    int number = 5;
    int* pointer = &number;
    std::cout << number << '\n';
    foo(&number);
    std::cout << number << '\n';
    bar(pointer);
    std::cout << number << '\n';

    return 0;
}

Różnice między wskaźnikiem i referencją

Odwołania

  • Do referencji odwołujemy się tak samo jak do zwykłego obiektu - za pomocą nazwy
  • Aby uzyskać element wskazywany przez wskaźnik musimy dodać * przed nazwą wskaźnika

Przekazywanie do funkcji jako argument

  • Zwykłej zmiennej (wtedy tworzona jest kopia tej zmiennej) lub referencji (kopia nie jest tworzona) - podajemy funkcji nazwę zmiennej/referencji
  • Wskaźnika, a mamy do dyspozycji zwykłą zmienną - musimy dodać & przed nazwą zmiennej

Oznaczenia

  • Symbol * (operator dereferencji) oznacza dostęp do obiektu wskazywanego
  • Jeżeli nie damy * przy wskaźniku dostaniemy adres obiektu wskazywanego
  • Symbol & oznacza pobranie adresu naszej zmiennej
  • Powyższe ma sens, ponieważ wskaźnik wskazuje miejsce w pamięci (adres wskazywanego obiektu)

Różnice w kodzie

void copy(int a) { a += 2; }
void ref(int& a) { a += 2; }
void ptr(int* a) { *a += 2; }

void example() {
    int c = 10;
    int& r = c;
    int* p = &c;  // typically int* p = new int{10};
    copy(c);
    copy(r);
    copy(*p);
    ref(c);
    ref(r);
    ref(*p);
    ptr(&c);
    ptr(&r);
    ptr(p);
}

Co oznacza * w kodzie?

int a = 5 * 4;      // jako operacja arytmetyczna - mnożenie
int* b = &a;        // przy typie - wskaźnik na ten typ
int *c = &a;        // przy typie - wskaźnik na ten typ
std::cout << *b;    // przy zmiennej wskaźnikowej - dostęp do obiektu
int fun(int* wsk);  // w argumencie funkcji - przekazanie wskaźnika (adresu)

Co oznacza & w kodzie?

int a = 5 & 4;      // jako operacja arytmetyczna - iloczyn bitowy
int& b = a;         // przy typie - referencja na ten typ
int &c = a;         // przy typie - referencja na ten typ
std::cout << &a;    // przy zmiennej - adres tej zmiennej w pamięci
int fun(int& ref);  // w argumencie funkcji - przekazanie referencji

Ważna zasada

Jeśli nie ma absolutnej potrzeby, to nie używamy wskaźników w ogóle.