8. Wskaźniki - majsylw/Introduction-to-programming-in-C GitHub Wiki
Pamięć komputera podzielona jest na komórki. Każda taka komórka pamięci ma swój unikatowy adres (będący dużą liczbą calkowitą). Deklaracja zmiennej w programie powoduje przydzielenie odpowiedniej pamięci. W programie posługujemy się nazwą zmiennej, która zastępuje adres pamięci - jest aliasem na dany adres, czyli jak to sobie wcześniej nazwaliśmy nazwą pewnego pudełeczka, będącego tym miejscem w pamięci. Wskaźnik (ang. pointer) to natomiast specjalny rodzaj zmiennej, w której zapisany jest owy adres w pamięci komputera. Oznacza to, że wskaźnik wskazuje miejsce, gdzie zapisana jest jakaś informacja (np. zmienna typu liczbowego czy struktura).
By stworzyć wskaźnik do zmiennej i móc się nim posługiwać, należy przypisać mu odpowiednią wartość - adres obiektu, na jaki chcielibyśmy, aby wskazywał. Aby poznać ten adres możemy odpytać zmienną za pomocą operatora & (operatora pobrania adresu). Ten i inne nowe operatory zebrano w poniższej tabeli:
Operator | Opis | Użycie |
---|---|---|
* |
weź wartość x | *x |
* |
deklaracja wskaźnika do wartości | int *x; |
& |
weź adres | &x |
int wiek = 18; /*tworzy zmienną
i przypisuje je wartość 18*/
int * wskWiek = &wiek;/*tworzy
wskaźnik i przypisuje mu adres
zmiennej wiek, ustawia wskaźnik za pomocą operatora referencji*/
printf("Wiek: %d.\n", wiek); // wyświetlony napis to "Wiek: 19."
printf("Wiek: %d.\n", *wskWiek); // wyświetlony napis to "Wiek: 19."; wiek został wyłuskany za pomocą operatora dereferencji
Należy mieć na uwadze, że gwiazdkę łączymy ze zmienną, nie z typem.
int * a,b,c; // utworzyliśmy wskaźnik na zmienną a oraz zmienne b i c typu int
int *a,*b,*c; // teraz utworzyliśmy 3 wskaźniki
W przypadku tablic, nazwa tablicy jest wskaźnikiem na jej pierwszy element (czy teraz już wiesz, czemu w funkcji scanf()
w przypadku tablic operator referencji nie jest potrzebny przy jej wywoływaniu?). Na Twoją uwagę zasługuje także arytmetyka dla zmiennych wskaźnikowych - mając zmienną tab
będącą wskaźnikiem na pierwszy element tablicy możemy uzyskać także wskaźnik na element drugi posiłkując się wyrażeniem tab + 1
(o ile owa tablica posiada odpowiednią liczbę elementów).
// Autor: Karol Tarnowski
#include <stdio.h>
int main(){
int tab[5] = {10, 20, 30, 40, 50};
//wypisanie początkowego elementu tablicy
printf("tab[0] = %d\n", tab[0]);
//wypisanie adresu tablicy (jej początku)
printf("tab = %d\n", tab);
//wypisanie początkowego elementu tablicy
printf("*tab = %d\n", *tab);
//wypisanie elementu tablicy o indeksie 2
printf("tab[2] = %d\n", tab[2]);
printf("*(tab+2) = %d\n", *(tab+2));
//wypisanie adresu elementu tablicy o indeksie 2
printf("tab+2 = %d\n", tab+2);
return 0;
}
Mając styczność z tablicami można się zastanowić, czy nie dałoby się mieć tablic, których rozmiar dostosowuje się do naszych potrzeb a nie jest na stałe zaszyty w kodzie programu. Chcąc pomieścić więcej danych możemy po prostu zwiększyć rozmiar tablicy - ale gdy do przechowania będzie mniej elementów okaże się, że marnujemy pamięć. Język C umożliwia dzięki wskaźnikom i dynamicznej alokacji pamięci tworzenie tablic takiej wielkości, jakiej akurat potrzebujemy.
Podstawową funkcją do rezerwacji pamięci jest funkcja malloc()
. Jest to niezbyt skomplikowana funkcja - podając jej rozmiar (w bajtach) potrzebnej pamięci, dostajemy wskaźnik do zaalokowanego obszaru. Jeśli dany obszar pamięci nie będzie już nam więcej potrzebny powinniśmy go zwolnić, aby system operacyjny mógł go przydzielić innym potrzebującym procesom. Do zwolnienia obszaru pamięci używamy funkcji free()
, która przyjmuje tylko jeden argument - wskaźnik, który otrzymaliśmy w wyniku działania funkcji malloc()
. Pomiędzy wywołaniom tych funkcji możemy na naszej tablicy wykonać wszystkie pożądane przez nas operacje. Poniższy kod może stanowić tego przykład.
// Autor: Karol Tarnowski
#include <stdio.h>
#include <stdlib.h>
int main(){
int i, n;
float *tab;
printf("Program wczytuje n liczb rzeczywistych\
i zapisuje je w tablicy.\n");
printf("Podaj n: ");
scanf("%d",&n);
tab = malloc(n*sizeof(*tab));
for(i=0; i<n; i++){
printf("Podaj liczbe: ");
scanf("%f",&tab[i]);
//scanf("%f",tab+i); // alternatywnie można także w ten sposób
}
for(i=0; i<n; i++){
printf("%d\t%f\n",i,tab[i]);
//printf("%d\t%f\n",i,*(tab+i)); // ta linijka działa tak samo jak powyższa
}
free(tab);
return 0;
}
W powyższym wywołaniu funkcja malloc()
alokuje pamięć na n
liczb typu float (o wielkości tego parametru decyduje użytkownik programu wpisując odpowiednią liczbę), następnie wskaźnik tab jest ustawiany na pozyskaną pamięć. Funkcja malloc()
pobiera jeden argument - liczbę bajtów, która ma zaalokować. Liczbę bajtów dla danego typu zmiennej można odczytać posiłkując się operatorem sizeof()
. Pamięć pozyskana funkcją malloc()
należy na koniec działania programu zwolnić posługując się funkcją free()
. Funkcje malloc()
i free()
znajdują się w bibliotece stdlib
.