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.
#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…
#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
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ą
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;
}
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);
Sortowanie bąbelkowe. Napisz funkcję implementującą algorytm sortowania bąbelkowego, która jako parametry przyjmuje następujące elementy:
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.