Wskaźniki

Transkrypt

Wskaźniki
Wskaźniki
 nie są konieczne, ale dają językowi siłę i
elastyczność
 są języki w których nie używa się wskaźników
 typ wskaźnikowy – typ pochodny:
typ nw;
/* definicja zmiennej nw typu typ */
typ *w_nw; /* definicja wskaźnika w_nw do typu typ */
 wskaźnik – zmienna która zawiera adres innej
zmiennej
 wskaźnik niesie dwojakiego rodzaju informację:
o adres obiektu, który wskazuje (gdzie)
o rodzaj obiektu, który wskazuje (co)
 rola operatora odwołania pośredniego
(wyłuskiwania)
Główne obszary zastosowanie wskaźników
 w funkcjach, które mogą zmieniać wartość
wskazywanych argumentów
 usprawnienie pracy z tablicami
 dostęp do specjalnych komórek pamięci
 rezerwacja obszarów pamięci (dynamiczna alokacja
pamięci)
Funkcje zmieniające wartość wskazywanych argumentów
 zwykłe przekazanie argumentów – przez wartość
 przekazujemy kopię zmiennej; zmiany dokonane
wewnątrz funkcji nie przenoszą się na oryginał
 inne rozwiązanie – zmienne globalne; problem –
niejawne (nieoczywiste) powiązania między danymi
 najlepsze rozwiązanie – użycie wskaźników
 zasada: parametr aktualny – adres obiektu;
parametr formalny – wskaźnik; zmiany – poprzez
operator odwołania pośredniego
Tablice a wskaźniki
 bardzo bliski związek między tablicami a
wskaźnikami
 każda operacja wykonana przez indeksowanie
tablicy można wykonać przez działanie na
wskaźnikach (wersja wskaźnikowa zwykle szybsza)
int a[10];
int *pa;
/* rezerwacja tablicy o 10 elem. typu int */
/* pa – wskaźnik do pokazywania int
*/
pa
a[0]
a[1]
a[2]
a[9]
Po przypisaniu:
pa=&a[0];
/* lub pa=a */
pa
a[0]
a[1]
a[2]
a[9]
 kluczowy punkt: jeżeli pa wskazuje na pewien element tablicy,
to pa+1 wskazuje na następny element tablicy (bez względu
na rodzaj tablicy)
pa pa+1 pa+2
a[0]
a[1]
a[2]
a[9]
 pa wskazuje na a[0]  pa+k wskazuje na a[k]
 *pa == a[0]  *(pa+k) == a[k]
Wniosek: wyrażenie postaci ”tablica i indeks” jest
równoważne wyrażeniu postaci: ”wskaźnik i
przesunięcie”
 a – stała, adres początku tablicy; dlatego a[k] == *(a+k)
 różnica pomiędzy wskaźnikiem a nazwą tablicy – a to nie jest
l-wartość; wyrażenia a++ niedozwolone
#include <iostream>
using namespace std;
int main()
{
int a[5]={1, 2, 3, 4, 5};
int w1, w2, w3, s, k, *pa;
/* metoda tablica + indeks */
for (s=0, k=0; k<5; k++)
s += a[k];
w1=s;
/* metoda nazwa tablicy + przesuniecie */
for (s=0, k=0; k<5; k++)
s += *(a+k);
w2=s;
/* metoda wskaźnik + przesuniecie */
pa=a;
for (s=0, k=0; k<5; k++)
s += *pa++;
w3=s;
cout<<”w1= << w1 << ” w2=” << w2
<<” w3=” << w3 << endl;
}
w1=15 w2=15 w3=15
Arytmetyka wskaźników
 dodawanie/odejmowanie liczb całkowitych do/od wskaźnika
o wskaźnik + liczba całkowita k  przesuwa wskaźnik o k
pozycji wzdłuż tablicy
o wskaźnik - liczba całkowita k  cofa wskaźnik o k
pozycji wzdłuż tablicy
 gdy p1, p2 – dwa wskaźniki pokazujące elementy tej samej
tablicy to p1-p2  ilość pozycji w tablicy o które różnią się
elementy wskazywane przez oba wskaźniki
 inne operacje na wskaźnikach
o porównywanie wskaźników między sobą; dozwolone
operacje:
 == , != czy obiekty wskazywane są tożsame,
czy też różne
 < , > , <= , >= - dla wskaźników wskazujących
elementy tej samej tablicy
 < , > , <= , >= - dla wskaźników nie
wskazujących elementów tej samej tablicy, ale
tego samego typu – sens: porównanie położenia
obiektów w pamięci
 porównanie wskaźnika z adresem 0
o oparte na założeniu, że adres 0 nie może być adresem
żadnego obiektu w C/C++
o gdy dostaniemy 0 jako wartość wskaźnika zwracanego
przez pewien proces, to oznaczało to, że ten proces
zakończył się niepowodzeniem – metoda sygnalizacji
błędu
o konwencja – gdy wskaźnik 0 – nic z nim nie rób
Równoważność wskaźnik – tablica: jak to działa
wewnątrz funkcji? Jak przekazywać i odbierać tablice
jako argumenty funkcji?
Przykład – funkcja konwertująca temperatury:
void konwertuj_t(float t[ ], int ile)
/* wersja tablicowa */
{
int k;
for (k=0; k<ile; k++)
t[k]=5.0*(t[k] – 32.0)/9.0;
}
main()
{
int m;
float temp[101];
for (m=0; m<101; m++)
temp[m]=m;
konwertuj_t(temp, 101);
}
Wersja wskaźnikowa funkcji konwertuj_t:
void konwertuj_t(float *t, int ile)
/* wersja wskaźnikowa */
{
int k;
for (k=0; k<ile; k++, t++)
*t =5.0*(*t – 32.0)/9.0;
}
 adres tablicy odbieramy jako wskaźnik
 przesuwamy wskaźnik wzdłuż obszaru pamięci zajmowanego
przez tablicę tak, by w obliczeniach wziąć odpowiedni
element
 przewaga wersji wskaźnikowej – program nie oblicza adresów
odpowiadających poszczególnym indeksom (tylko przesuwa
wskaźnik)
 możliwa wersja mieszana – odbieramy adres jako wskaźnik,
używamy jak tablicy
void konwertuj_t(float *t, int ile)
/* wersja mieszana */
{
int k;
for (k=0; k<ile; k++)
t[k]=5.0*(t[k] – 32.0)/9.0;
}
Funkcje znakowe – wersje wskaźnikowe
Przykład 1 – kopiowanie stringów:
a. wersja tablicowa:
void strcpy(char kopia[ ], char wzor[ ])
{
int k=0;
while (kopia[k]=wzor[k])
k++;
}
b. wersja wskaźnikowa
void strcpy(char *kopia, char *wzor)
{
while (*kopia++ = *wzor++)
;
}
Przykład 2 – sumowanie stringów (strcat)
Do końca stringu s1 dodaje string s2, zwraca wskaźnik do
stringu zawierającego sumę obu.
#include <iostream>
using namespace std;
char *strcat(char *suma, char* zrodlo)
{
char *start=suma;
/* znalezenie konca stringu suma */
while (*suma++) ;
suma--;
/* skopiowanie stringu zrodlo */
while (*suma++ = *zrodlo++) ;
return start;
}
int main()
{ char sss[80]={"Ala ma "};
cout << sss << strcat(sss,"kota !!!\n");
return 0;
}
Ala ma kota !!!
Ala ma kota !!!
Dostęp do wyróżnionych obszarów pamięci
 stosowane, gdy chcemy sięgnąć do konkretnej
komórki pamięci (np. o adresie 93960)
 ważne dla komórek wyróżnionych (np. sprzężonych z
zewnętrznym miernikiem temperatury
 przy normalnej definicji zmiennej nie mamy wpływ na
alokację pamięci
 rozwiązanie – użycie wskaźnika:
float *p;
p= (float*) 93960;
cout <<“Aktualna temperatura wynosi: ”<< *p << endl;
Dynamiczna alokacja pamięci
 normalna (statyczna) definicja tablicy – nie możemy określić
rozmiaru tablicy po rozpoczęciu pracy programu
 konieczność deklaracji nadmiarowej (nieefektywność)
 inna możliwość: dynamiczny przydział pamięci
Przykład 1: utworzenie pojedynczego obiektu typu int:
int *wint;
wint= new int;
*wint = 1000;
cout << *wint;
delete wint;
// powołanie do życia
// nadanie wartości
// wypisanie wartości
// likwidacja obiektu
Przykład 2: utworzenie tablicy o rozmiarze określonym przez
użytkownika:
#include <iostream>
#include <cstdlib>
using namespace std;
int main()
{
float *wf, *wfb;
int rozmiar,k;
cout << "Podaj rozmiar tablicy: ";
cin >> rozmiar;
cout << rozmiar << endl;
wf=new float[rozmiar];
// utworzenie tablicy
wfb=wf;
for (k=0; k<rozmiar; k++)
// obsługa tablicy
*wfb++=k/100.0;
cout << "Ostatni element = "
<< *(wf+rozmiar-1) << endl;
delete [ ] wf;
// usunięcie tablicy
}
 tworzenie obiektów – operatory new oraz new [ ] –
zdefiniowane w C++
 obiekty tworzone na stercie – własności różne od zmiennych
automatycznych
 obszar sterty nie jest nieograniczony – konieczność likwidacji
obiektów już niepotrzebnych: operatory delete oraz
delete []
Cechy obiektów tworzonych dynamicznie:
 istnieją od momentu utworzenia do momentu ich
skasowania – my decydujemy o ich czasie życia
 tak utworzone obiekty nie mają nazwy – są dostępne tylko
przez wskaźnik
 obiektów tych nie obowiązują zwykłe zasady dotyczące
zakresu ważności – jeżeli tylko dostępny jest wskaźnik
wskazujący na dany obiekt, to mamy do niego pełny
dostęp
 po utworzeniu zawiera śmiecie
Uwaga: w C – do dynamicznej alokacji pamięci stosuje
się funkcje biblioteki standardowej: malloc, calloc,
realloc, free.
void* malloc(size_t size) -- zwraca wskaźnik do
obszaru pamięci przeznaczonego dla obiektu o
razmiarze size, lub NULL gdy alokacja jest niemożliwa
void *calloc(size_t ile, size_t size) - zwraca wskaźnik
do obszaru pamięci przeznaczonego dla tablicy o ile
elementów o długości size każdy (NULL gdy alokacja
niemożliwa)
void *realloc(void *p, size_t size) – zmienia rozmiar obiektu
wskazywanego przez p na wartość określoną przez size.
Zawartość początkowej części obiektu o rozmiarze równym
min(nowy rozmiar, stary rozmiar). Zwraca wskaźnik do nowego
obszaru (NULL gdy polecenie nie może być wykonane – wtedy
zawartość *p się nie zmienia).
void free(void *p) – zwalnia obszar pamięci wskazywany przez
p. Nie robi nic, gdy p=NULL. Argument p musi być wskaźnikiem
zwróconym wcześniej przez malloc, calloc lub realloc.
Użycie tych funkcji – konieczność rzutowania wskaźników oraz
podawania rozmiarów obiektów
Przykład 2a
#include <iostream>
#include <cstdlib>
using namespace std;
int main()
{
float *wf, *wfb;
int rozmiar,k;
cout << "Podaj rozmiar tablicy: ";
cin >> rozmiar;
cout << rozmiar << endl;
if ((wf=(float*)malloc(rozmiar*sizeof(float)))==NULL)
{
printf("Blad alokacji pamieci !!!\n");
return 1;
}
wfb=wf;
for (k=0; k<rozmiar; k++)
// obsługa tablicy
*wfb++=k/100.0;
cout << "Ostatni element = "
<< *(wf+rozmiar-1) << endl;
free(wf)
// usunięcie tablicy
}