Wskaźniki

Wskaźnik (pointer) to adres, który określa jednoznacznie pozycję danych w pamięci komputera. Każda zmienna zajmuje określone miejsce w pamięci i jest umiejscowiona w pamięci pod pewnym adresem. Tak jak adres na kopercie listu wskazuje jednoznacznie lokalizację adresata tak wskaźnik pokazuje miejsce w pamięci, gdzie możemy odnaleźć wskazywaną zmienną. Adres zmiennej uzyskujemy za pomocą operatora referencji '&'.

Adres zmiennej i operator &

Operator referencji & zwraca adres zmiennej. Jest to operator jednoargumentowy, tzn. działa na jedną wartość stojącą z prawej strony, np.: &x wyznacza adres zmiennej a.

wsk1.c
#include<stdio.h>
 
int main()
{
   int a = 42;
   printf("adres zmiennej a %p\n", &a);
}

Przykładowy wynik działania programu:

adres zmiennej a 0x7fff8e6a47e4

Adres jest dodatnią liczbą całkowitą, którą wygodnie jest przedstawiać w systemie szesnastkowym. Jednak zazwyczaj nie ma potrzeby prezentowania tej wartości. Możemy taki adres przypisać do zmiennej wskaźnikowej i korzystać z tej zmiennej gry potrzebujemy dostępu do wskazywanego obszaru pamięci.

Deklaracja zmiennej wskaźnikowej

Zmienna wskaźnikowa to zmienna, która przechowuje adres do zmiennej pewnego typu. W poniższym przykładzie znajduje przykład który zawiera deklarację zmiennej wskaźnikowej w, która będzie przechowywała adres zmiennej typu int. W instrukcji w = &a następuje przypisanie wartości &a do zmiennej w.

wsk2.c
#include<stdio.h>
 
int main()
{
   int a = 42;
   int *w;
 
   w = &a;
 
   printf("w = %p\n", w);
   printf("adres zmiennej a %p\n", &a);
}

Przykładowy wynik działania programu:

w = 0x7fff0c2c954c
adres zmiennej a 0x7fff0c2c954c

W podobny sposób deklaruje się zmienne wskaźnikowe, które mogą przechowywać adresy zmiennych dowolnego typu a nawet adresy innych zmiennych wskaźnikowych.

Przykłady deklaracji zmiennych wskaźnikowych:

int *a;       // wskaźnik zmiennej typu int
float *b;     // wskaźnik zmiennej typu float
char *c       // wskaźnik zmiennej typu char
int *d[10];   // tablica 10-cio elementowa wskaźników typu int
float **e;    // wskaźnik zmiennej typu float* (wskaźnik do wskaźnika)

Dostęp do adresu - operator *

Operator dereferencji * służy do wydobycia wskazanej przez wskaźnik wartości. To również operator jednoargumentowy a instrukcja *x zwraca wartość wskazywaną, przez adres zawarty w zmiennej x, tzn. zmienna x musi być zmienną wskaźnikową i zawiera poprawny adres pewnej innej zmiennej. Zwróć uwagę, że symbol * używany jest również jako operator mnożenia x * y , jednak mnożenie jest operacją na dwóch argumentach, zaś operator dereferencji działa zawsze na jedną wartość. Operator dereferencji * udostępnia wartość z danego adresu, dzięki czemu możemy nie tylko odczytać wartość wskazywanej zmiennej ale także zmodyfikować jej wartość. Przykład:

wsk3.c
#include<stdio.h>
 
int main()
{
   int a = 42;
   int *w;
 
   w = &a;
 
   printf("a = %d\n", a);
   printf("*w = %d\n", *w);
 
   *w = 13;
 
   printf("a = %d\n", a);
   printf("*w = %d\n", *w);
}

Wynik działania programu:

a = 42
*w = 42
a = 13
*w = 13

Uwaga: uważaj na to aby zmienna wskaźnikowa zawsze zawierała poprawny adres. Sprawdź, co się stanie jeżeli w powyższym przykładzie usuniemy instrukcję w = &a. Brak przypisania poprawnego adresu w zmiennej w doprowadzi do katastrofy w momencie wykonania instrukcji *w = 13.

Wskaźnik argumentem funkcji

Jednym z najważniejszych zastosowań wskaźników jest ich wykorzystanie w argumentach funkcji. Adres zmiennej przekazany w argumencie funkcji pozwala tej funkcji zmodyfikować wartość wskazywanej zmiennej, tzn. funkcja jest w stanie podstawić nową wartość do wskazywanej zmiennej. Poniższy przykład prezentuje definicję funkcji zwieksz, która przyjmuje w argumencie wskaźnik zmiennej (int * a) a następnie odnosząc się przez dany adres zwiększa wartość o 1.

wsk4.c
#include<stdio.h>
 
void zwieksz(int *a)
{
   *a = *a + 1;
}
 
int main()
{
   int a = 1;
 
   zwieksz( &a );
 
   printf("a = %d\n", a);
}

Wynik działania programu:

a = 2

W taki sam sposób działa funkcja scanf(„%d”, &x), która w drugim argumencie MUSI mieć adres zmiennej x do której podstawi wartość wczytaną z terminala.

Napisz funkcję zamień(), która zamienia wartości 2 zmiennych podanych w argumentach.
Przetestuj działanie funkcji w programie, który wczyta 2 liczby do zmienneych a nastepnie zamieni ich wartości korzystając z funkcji zamień()

Zaimplementuj funkcję o nazwie pierwiastki, która wyznacza miejsca zerowe równania kwadratowego. Parabola jest określona przez trzy wartości rzeczywiste a, b i c równaniem

\[ f(x) = ax^2 + bx + c \]

Parabola może posiadać dwa miejsca zerowe, jedno miejsce zerowe lub może nie posiadać miejsc zerowych. Zadana funkcja parabola zwraca informację o liczbie miejsc zerowych (0, 1 lub 2) oraz dwie wartości rzeczywiste x1 oraz x2 stanowiące miejsca zerowe.
Argumenty funkcji pierwiastki: liczby rzeczywiste a, b, c definiujące równanie kwadratowe oraz dwa adresy (wskaźniki) x1 oraz x2, pod które zostaną wstawione wartości obu miejsc zerowych. W przypadku braku miejsc zerowych, zmienne wskazywane przez wskaźniki nie są modyfikowane.
Wartość zwracana funkcji: liczba całkowita (0, 1 lub 2) określająca liczbę miejsc zerowych równania kwadratowego

Napisz program, który pobierze od użytkownika 3 liczby rzeczywiste a, b oraz c a następnie, korzystając z funkcji pierwiastk() wyznaczy miejsca zerowe równania kwadratowego zdefiniowanego podanymi współczynnikami. Wynikiem działania programu jest komunikat informujący o liczbie miejsc zerowych oraz wartości tych miejsc zerowych.

Przykład działania programu

Podaj wsp. paraboli a, b i c :
1 1 1
Brak miejsc zerowych
Podaj wsp. paraboli a, b i c :
1 -4 4
Jedno miejsce zerowe: 2.000000
Podaj wsp. paraboli a, b i c :
1 0 -4
Dwa miejsca zerowe: x1=2.000000, x2=-2.000000

Zadanie - Rzut ukośny

Napisz funkcję o nazwie rzut(), która wyznacza położenie $(x(t), y(t))$ oraz prędkość $v_y(t)$ w chwili $t$ obiektu rzuconego z prędkością początkową $v_0$ pod kątem $\alpha$. Funkcja powinna spełniać poniższą specyfikację.

Argumenty funkcji: funkcja posiada 5 argumentów:

  1. wartość $v_0$ określająca prędkość obiektu w chwili $t=0$
  2. wartość $\alpha$ określająca kąt w radianach pod jakim rzucono obiekt
  3. wartość czasu $t$
  4. adres zmiennej x, do której funkcja wstawi wartość położenia obiektu $x(t)$ w chwili $t$
  5. adres zmiennej y, do której funkcja wstawi wartość wysokości obiektu $y(t)$ w chwili $t$

Wartość zwracana: funkcja zwraca wartość prędkości chwilowej w kierunku pionowym $v_y(t)$ w chwili $t$.

Położenie $(x(t), y(t))$ obiektu po czasie $t$ dane jest wzorem $$ x(t) = v_0 t \cos{\alpha} $$ $$ y(t) = v_0 t \sin{\alpha} - \frac{gt^2}{2}$$

Prędkość chwilowa w kierunku pionowym $v_y(t)$ po czasie $t$ wynosi $$ v_y(t) = v_0 \sin{\alpha} - g t$$

gdzie $g$ oznacza przyspieszenie ziemskie. Przyjmij $g=9.8 \frac{m}{s}$.

Napisz program, który wykorzysta funkcję rzut() do wypisania położenia oraz prędkości rzuconego obiektu w kolejnych momentach rzutu ukośnego. Po uruchomieniu program pobiera od użytkownika następujące dane: prędkość początkową $v_0$, kąt rzutu $\alpha$ oraz wielkość kroku czasowego $\Delta t$. Następnie program wypisuje w kolejnych liniach położenie obiektu oraz prędkość w kolejnych odstępach czasu $\Delta t$, począwszy od momentu startu $t=0$ aż do momentu upadku przedmiotu, tzn. dopóki $y(t) > 0$.

Przykładowy wynik działania programu:

predkosc poczatkowa: 42
kat rzutu: 0.8
krok czasu: 0.5
t      x      y      v
0.00   0.00   0.00  30.13 
0.50  14.63  13.84  25.23 
1.00  29.26  25.23  20.33 
1.50  43.89  34.17  15.43 
2.00  58.52  40.66  10.53 
2.50  73.15  44.70   5.63 
3.00  87.79  46.29   0.73 
3.50 102.42  45.43  -4.17 
4.00 117.05  42.12  -9.07 
4.50 131.68  36.36 -13.97 
5.00 146.31  28.14 -18.87 
5.50 160.94  17.48 -23.77 
6.00 175.57   4.37 -28.67

Rozwiązanie (plik źródłowy) umieść w Moodle pod adresem https://moodle.umk.pl/WFAIIS/mod/assign/view.php?id=7533