Wskaźniki i referencje

In C++ it's harder to shoot yourself in the foot, but when you do, you blow off your whole leg.

To laboratorium jest przypomnieniem informacji o wskaźnikach, które powinny zostać przyswojone w poprzednim semestrze na „Językach i Metodach Programowania I”.

Z racji, że nie jest to „regularne” laboratorium, za rozwiązanie zadań nie będą przyznawane plusy/punkty.

Wskaźniki

Wskaźniki są zmiennymi przechowującymi adresy pamięci innych zmiennych. Operatory związane ze wskaźnikami:

  • Operator wyłuskania * - zastosowany do wskaźnika wyłuskuje zmienną znajdującą się pod adresem przechowywanym przez wskaźnik
  • Operator adresu & - zastosowany do zmiennej wybiera adres pamięci pod którym przechowywana jest wartość zmiennej.

Wskaźniki

#include <iostream>
using namespace std;
 
int main(void){
  int liczba = 10;
  int *pointer = &liczba;
  cout << liczba << endl;    // wyświetli 10
  cout << *pointer << endl;  // wyświetli 10
  cout << &liczba << endl;   // wyświetli (rys.) 0xbf8d6d4c
  cout << pointer << endl;   // wyświetli (rys.) 0xbf8d6d4c 
  return 0;
}

Wskaźniki i const

Stosowanie słowa kluczowego const w kontekście wskaźników, może być powodem błędów:

  • const int * ptr = &zmienna; - wskaźnik do stałej wartości. Wskaźnikowi będzie można przypisać inny adres, ale wartość spod adresu &zmienna będzie stała.
  • int * const ptr = &zmienna; - stała będzie wartość wskaźnika. Będzie można zmienić wartość zmiennej, ale nie będzie można przypisać danemu wskaźnikowi innego adresu. Wskaźnikowi takiemu musi zostać przypisana wartość w momencie deklaracji.

Wskaźnik void*

Może przechowywać wartość wskaźnika dowolnego typu. Nie może jednak podlegać dereferencji - wcześniej konieczne jest rzutowanie na typ konkretny.

int zmienna;
int* ptr = &zmienna;
void* vptr = ptr;
 
//NIEPOPRAWNIE
cout << "Wartość zmiennej to: " << *vptr << endl;
 
//Poprawnie:
cout << "Wartość zmiennej to: " << *(int*)vptr << endl;

Wskaźniki do funkcji

Podobnie jak nazwa tablicy jest wskaźnikiem do jej pierwszego elementu, tak nazwa funkcji jest początkowym adresem w pamięci kodu dokonywującego zadanie.

Wskaźniki do funkcji najczęściej przekazywane są jako parametry innych funkcji. Definicja funkcji przyjmującej jako parametr wskaźnik do innej funkcji:

// funkcja foo przyjmuje jako parametr wskaźnik do funkcji zwracającej int
// oraz przyjmującej dwa parametry typu int
 
void foo(int (*fooptr)(int,int)){
  // Wywołanie funkcji przekazanej jako parametr
  cout << "Wynik : " << (*footptr)(10,10) << endl;
}

Arytmetyka wskaźników

Do wskaźników można stosować podstawowe operatory matematyczne:

  • wskaźniki można zwiększać i zmniejszać (operatory: ++ i –)
  • można do nich dodawać, lub odejmować liczby całkowite (operatory + i -)
  • można odejmować od siebie dwa wskaźniki

Należy pamiętać, że inkrementowanie wskaźnika nie zwiększa wartości adresu o 1, ale o rozmiar wskaźnika. Jeśli jest to wskaźnik do typu int, to standardowo o 4 bajty, jeśli double, to o 8 bajtów, etc.

Referencje

Są to aliasy zmiennych. Referencja wskazuje na dokładnie tą sama komórkę pamięci co „oryginalna” zmienna. Referencja musi być zainicjalizowana w momencie deklaracji! Jedyne sensowne zastosowanie referencji, to przekazywanie parametrów do funkcji poprzez referencję. To samo jednak można osiągnąć używając wskaźników…

Referencje

#include <iostream>
using namespace std;
 
int main(void){
  int liczba = 10;
  int &ref = liczba;	     
  cout << liczba << endl;    // wyświetli 10
  cout << ref << endl;       // wyświetli 10
  cout << &liczba << endl;   // wyświetli (rys.) 0xbf8d6d4c
  cout << &ref << endl;      // wyświetli (rys.) 0xbf8d6d4c
  return 0;
}

Ćwiczenia

  1. Napisz 3 funkcje które w swoim ciele będą zmieniały wartość przekazanego do nich parametru typu int. Funkcje mogą na przykład sprawdzać czy dana liczba jest liczbą pierwszą. Przetestuj działanie funkcji. Jaka jest różnica pomiędzy funkcją przyjmującą wskaźnik a funkcja przyjmującą referencję? Napisz następujące wersje funkcji:
    • pobierającą referencję
    • pobierającą wskaźnik
    • pobierająca zmienną
  2. Skompiluj poniższy kod. Co jest źle:
    #include <iostream.h>
    using namespace std;
     
    int * potega(int liczba, int potega){
      int* wynik = &liczba;
      for(int i = 1; i < potega; i++)
        *wynik *= liczba;
      return wynik;
    }
    int main(void){
      cout << *(potega(3,3)) << endl;
    }
  3. Nazwa tablicy jest wskaźnikiem do jej pierwszego elementu. Napisz metodę wyświetlającą tablicę dwuwymiarową za pomocą jednej pętli for, bez używania operatora [] dostępu do elementów tablicy. Deklaracja funkcji powinna wyglądać następująco:
    void showTable(int tab[][5], int rows, int cols);
  4. Sortowanie bąbelkowe. Napisz funkcję implementującą algorytm sortowania bąbelkowego, która jako parametry przyjmuje następujące elementy:
    • int * tablica,
    • int length,
    • int (*porownanie)(int,int) - zaimplementuj dwie wersje funkcji porównanie jedna porównującą malejąco, a drugą rosnąco. Przetestuj działanie programu.
  5. Wskaźniki do funkcji i funkcja sortująca. W bibliotece standardowej istnieje funkcja implementująca algorytm sortowania quicksort. Aby użyć tej funkcji konieczne jest napisanie własnej funkcji porównującej i przekazanie jej do metody sortującej. Przeczytaj rozdział poświęcony tej funkcji: qsort. Napisz strukturę, która będzie zawierać następujące pola:
    • char imie[10];
    • char nazwisko[10];
    • short rok_urodzenia;

Napisz program umożliwiający wypełnienie tablicy kilkoma elementami stworzonej struktury. Następnie wykorzystując funkcje qsort napisz program sortujący tablicę struktur według roku urodzenia lub według nazwiska.

pl/dydaktyka/jimp2/2016/labs/wskazniki.txt · ostatnio zmienione: 2019/06/27 15:50 (edycja zewnętrzna)
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0