KURS C/C++
WYKAAD 6
Wskazniki
Każda zmienna ma unikalny adres wskazujący początkowy obszar pamięci zajmowany przez
tą zmienną. Ilość pamięci zajmowanej przez zmienną zależy od typu zmiennej.
Adres można przechowywać, zmienna która przechowuje adres do obiektu nazywa się
wskaznikiem.
Wskazniki deklarujemy używając następującej składni:
typ_zmiennej *nazwa_zmiennej
Operator * informuje nas, że mamy do czynienia ze wskaznikiem.
Wskaznik nazywa siÄ™ tak jak zmienna.
float pi = 3.14;
float *wsk;
wsk = π
float pi = 3.14; float *wsk; - wsk żÿ przechowuje adres
zmiennej typu float
wsk=π
pi = 3.14 wsk = FFF0
wsk żÿ przechowuje adres zmiennej pi
&pi żÿ adres zmiennej pi
printf("pi = %f", pi); printf("adres zmiennej pi = %p", wsk);
EKRAN: pi = 3.14 EKRAN: adres zmiennej pi = FFF0;
Przykłady:
char *wsk; - wsk jest wskaznikiem do pokazywania na obiekty typu char
int *wsk; - wsk jest wskaznikiem do pokazywania na obiekty typu int
float *wsk; - wsk jest wskaznikiem do pokazywania na obiekty typu float
void *wsk; - wsk jest wskaznikiem do pokazywania na obiektu nieznanego typu
float *wsk_tab[10]; - tablica 10 wskazników do liczb rzeczywistych
float (*wsk_tab)[10]; - wskaznik do tablicy 10 liczb rzeczywistych
Treścią wskaznika jest informacja, gdzie wskazany obiekt się znajduje.
Przed użyciem musimy wskaznikowi nadać wartość początkową, czyli przypisać go do
konkretnego obiektu.
Do inicjowania wskaznika może służyć operator adresu & i może być stosowany tylko do
obiektów zajmujących pamięć: zmienne, elementy tablic. Nie można go stosować do wyrażeń
stałych i zmiennych typu register.
int *wsk; int *wsk;
float a; int i;
wsk = &a; //nieprawidłowo. wsk = &i; //prawidłowo.
Chcemy wskaznikiem do int pokazywać na float
Kiedy wskaznik pokazuje już na konkretnie miejsce możemy odnieść się do tego obiektu na
który on wskazuje, odczytać jego wartość, lub wpisać wartość pod wskazany adres.
Podstawową operacją na wskazniku jest wyłuskanie, czyli odwołanie się do obiektu
wskazywanego przez wskaznik. Operacja ta nazywa się adresowaniem pośrednim.
Operatorem adresowania pośredniego jest jednoargumentowa * zapisywana jako przedrostek.
Zastosowana do wskaznika daje zawartość obiektu wskazanego przez ten wskaznik np.:
int *wsk ;
int j, i=3;
wsk = &i;
wsk przechowuje adres zmiennej i.
cout<<"wartość zmiennej i wynosi "<<*wsk;
Ekran: wartość zmiennej i wynosi 3
int i, j;
int *wsk;
i=3;
wsk=&i;
j =*wsk;
=*wsk - pobierz wartość spod adresu
(1) j =*wsk - pobranie wartości spod adresu i przypisanie tej wartośći zmiennej j
po wykonaniu intsrukcji (1) j=3;
(2) *wsk = 200; // wstaw wartość 200 pod adres wskazywany przez zmienną wsk
po wykonaniu intsrukcji (2) i = 200;
Zmieni się wartość zmiennej i z 3 na 200.
Jeśli wsk wskazuje na zmienną całkowitą, to *wsk może wystąpić wszędzie tam gdzie może
wystąpić zmienna, np:
int *wsk, x, zm;
zm=10;
wsk = &zm;
zm=zm+2; //zm=12
zm=10;
*wsk = *wsk + 2; // zm = 12;
x = *wsk + 1; //x=13;
Ponieważ operatory * oraz & są silniejsze niż operatory arytmetyczne, to dla wyrażeń:
*wsk = *wsk + 2;
x = *wsk + 1;
najpierw pobierana jest zawartość spod adresu wsk i zwiększona o 1 zostaje przypisana
zmiennej x.
int i, j;
int *wsk=&i, *wsk1;
wsk1 = wsk;
Zapis ten mówi, że teraz wsk będzie wskazywał na to samo co wsk1, czyli na zmienną i.
wsk=wsk+1; wskaznik zostaje powiększony o rozmiar typu obiektu na który wskazuje, w naszym
przypadku o sizeof (int) (czyli o dwa bajty) ponieważ wskazuje na obiekt typu int.
wsk=wsk+1; lub wsk++;
Wskazniki a tablice
W C++ wskażniki i tablice są ze sobą ścisle związane. Nazwa tablicy może byc używana jako
wskaznik do jej pierwszego elementu.
Zadeklarujemy wskaznik i tablicÄ™:
int *wsk;
int tab[10];
instrukcja: wsk = &tab[n];
ustawia wskaznik na n-tym elemencie tablicy. Typ wskaznika musi się zgadzać z typem tablicy.
instrukcja: wsk = &tab[0]; jest równoważna instrukcji: wsk = tab;
i oznacza ustawienie wskaznika na pierwszy element tablicy czyli na jej poczÄ…tek. Zapisy sÄ…
równoważne, ponieważ jak już wspomnieliśmy, nazwa tablicy stanowi adres jej zerowego
elementu.
int *wsk; int tab[4]={-1,-20, 3, 5};
wsk = tab;
2000 2002 2004 2006
tab:
-1 -20 3 5
wsk
wsk +1
wsk +2
wsk +3
cout<<*wsk << " to wyświetlenie wartości tab[0]\n";
cout<<*(wsk+1) << " to wyświetlwnie wartości tab[1]\n";
cout<<*(wsk+2) << " to wyświetlwnie wartości tab[2]\n";
cout<
Możemy również zmieniać adres dodając do wskaznika liczbę całkowitą.
Przejście do następnego elementu tablicy umożliwia instrukcja :
wsk= wsk + 1; lub wsk ++; //gubimy adres poczÄ…tkowy
Aby przesunąć się o n elementów w tablicy piszemy
instrukcja: wsk = wsk+n;
Z definicji wskaznika wynika że wsk jest wskaznikiem do int. Stąd kompilator wnioskuje, że
aby odnalezć następny element typu int należy przesunąć się o sizeof (int).
cout<<*wsk << "to wyświetlwnie wartości tab[0]";
wsk++; //wsk=wsk+1;
cout<<*wsk<<żÿ\nżÿ<< "to wyÅ›wietlwnie wartoÅ›ci tab[1]";
wsk++; //wsk=wsk+2;
cout<<*wsk<<żÿ\nżÿ<< "to wyÅ›wietlwnie wartoÅ›ci tab[2]";
int *wsk;
int tab[10]={1, 3, 5, 6, 7, 8, 9}, i;
wsk = tab; //inicjowanie wskaznika
for(i = 0; i<10; i++) printf("%d\n", *wsk++);
Uwaga!!!
Mimo, że możemy zapisać:
wsk = tab;
to o ile możemy zapisać:
wsk++;
to nie możemy napisać:
tab++;
Różnicą między wskaznikiem a nazwą tablicy jest taka, że na wskazniku możemy dokonywać
operacji arytmetycznych, a na adresie tablicy nie, jest ona traktowana jak wielkość stała.
Dla tak zadeklarowanego wskaznika:
int *wsk;
adresem tego wskaznika jest wartość wyrażenia:
&wsk;
Wskaznik void
Deklaracja wskaznika niesie w sobie dwie informacje: adres miejsca w pamięci, oraz typ obiektu na
który te adres wskazuje.
Przy deklaracji
void *wsk;
wskaznik wsk wskazuje jedynie na konkretne miejsce w pamięci nie informując o typie obiektów
tam przechowywanych.
void *adres;
char *wsk;
int *wsk1;
//inicjalizowanie zmiennych wsk i wsk1.
.................................
adres = wsk; //lub adres = wsk1;
Zapisy te oznaczają, że teraz wskaznik typu void wskazuje na to samo, na co wskazuje
wskaznik typu char //int.
Wskaznik każdego typu można przypisać wskaznikowi typu void, bez konieczności konwersji.
Odwrotne przypisanie wymaga stosowania operatora konwersji.
float *wsk;
void *wsk1;
....................
wsk = wsk1; //błąd
wsk = (float *) wsk1; //poprawnie z operatorem rzutowania
Arytmetyka wskazników
1. Możemy dodawać i odejmować liczby całkowite od wskazników tak, aby w potrzebny sposób
przesuwać je po tablicy. Operacje te nie są sprawdzane przez kompilator, i możemy przesunąć
wskaznik poza zadeklarowany obszar tablicy i zniszczyć istniejące dane. Takie błędy są trudne
do wykrycia.
int *wsk; int tab[4]={-1,-20, 3, 5};
wsk = tab;
wsk=wsk+10; lub wsk=wsk-10;
co najwyżej wsk=wsk+3 lub wsk=wsk-3
2. Możemy odjąć dwa wskazniki od siebie:
wsk_a - wsk_b
Gdy pokazują one na różne elementy tej samej tablicy to wynikiem takiej operacji jest liczba
dzielących je elementów. Liczba może być ze znakiem - lub +.
int tab[12], *wsk;
wsk= &tab[0] żÿ &tab[2]; //wsk = -2
3. Wskazniki można ze sobą porównać. Do tego celu służą nam operatory:
== != < > <= >=
Dla dwóch wskazników:
int *wsk1, *wsk2;
przypisanie: wsk1 = wsk2 oznacza, że wskazują one na ten sam obiekt.
if (wsk1 = = wsk2)
cout<<"Oba wskazniki pokazujÄ… na ten sam obiekt";
Jeśli wskazniki wskazują na elementy tej samej tablicy, to wyrażenie wsk1 < wsk2 oznacza,
że wsk1 wskazuje na element tablicy o mniejszym indeksie.
4. Każdy wskaznik można porównać z adresem 0 zwanym NULL. Takie ustawienie wskaznika:
wsk = 0; //lub wsk = NULL
informuje, że wskaznik nie pokazuje na nic konkretnego, niektóre funkcje biblioteczne
zwracają wskaznik NULL (null pointer), możemy go użyć do kontroli np:
if (wsk = = 0) if(wsk = =NULL) if(!wsk)
Inicjowanie wskazników
W tym punkcie zbierzemy wszystkie sposoby inicjowania wskazników:
1. można przypisać adres konkretnego obiektu:
int *wsk, obiekt, tab[12];
wsk = &obiekt;
wsk = tab; // wsk = &tab[0]; wsk = &tab[2];
2. zarezerwować obszar dynamicznie
char *wsk;
wsk = new char[12];
wsk = (char*) malloc (12 * sizeof (char));
3. można przypisać inny wskaznik:
int *wsk, *ptr;
wsk = new int;
ptr = wsk;
4. ustawić wskaznik na konkretny
wsk = FFDA
Dynamiczna alokacja pamięci
Stajemy więc przed problemem, jak tworzyć tymczasowe obiekty tak, i gdy nie będą potrzebne
pozbyć się ich z pamięci.Odpowiedz jest prosta - rezerwować pamięć.
W języku C pamięć dostępna dla programu w czasie jego uruchomienia nazywa się HEAP'em, w
C++ - FREE STORE- pamięć wolna. Różnica leży tylko w funkcjach używanych do dostępu do tej
pamięci.
Funkcje alokacji pamięci: malloc(...), calloc(...)
W języku C, do alokacji pamięci służy grupa funkcji malloc.
void *malloc (size_t size); typedef unsigned size_t
Przydziela w obszarze stosu zmiennych dynamicznych obszar o rozmiarze size i zwraca
wskaznik do n bajtów niezainicjowanej pamięci, lub NULL jeśli żądanie nie może być spełnione.
void *calloc (size_t n, size_t size);
Przydziela w obszarze stosu zmiennych dynamicznych obszar o rozmiarze size n*size oraz zwraca NULL, gdy
pamięć nie może być przydzielona. Pamięć inicjowana jest zerami.
char *wsk = (char*) malloc (100 * sizeof (char));
char *ptr = (char*) calloc (100, sizeof (char));
Funkcje zwalniania pamięci: free(...)
Po wykorzystaniu pamięci można ją zwolnić. Do tego celu służy funkcja free().
void free ( void *p);
free (ptr); free (wsk);
zwalnia pamięć wskazaną przez p, przy czym p musi być wynikiem wcześniejszego wywołania
funkcji malloc() lub calloc(). Nie ma ograniczeń na kolejność zwalniania pamięci, natomiast
poważnym błędem jest zwalnianie czegoś, co nie było poprzednio przydzielone w/w funkcjami.
W języku C możemy korzystać z jednego z 6 standardowych modeli pamięci: tiny, small,
medium,compact, large, huge które różnią się min. ilością pamięci przeznaczonej na dane. Dla
modelu compact, large i huge, gdzie pamięć na dane jest ponad 64 kB, funkcja malloc zamieniana
jest na funkcję farmalloc, farfree operujące na pamięci o długości ponad 1 segment.
Operatory new i delete.
Alternatywą do tych funkcji w języku C++ jest operator new i delete. Operator new tworzy
obiekt, a operator delete usuwa obiekt z pamięci. Jeśli zdefiniujemy wskaznik:
char *wsk;
Alokacja pamięci.
wsk = new char;
powoduje utworzenie nowego obiektu typu char. Nie ma on nazwy, ale możemy się do niego
odwoływać poprzez wskaznik zawierający adres tego obiektu.
int *wsk_tab;
wsk_tab = new int[10];
operator new utworzył 10-elementowa tablicę typu int.
Zwalnianie pamięci.
delete wsk;
powoduje usunięcie obiektu wskazanego przez wsk z pamięci.
Kasowanie tablicy zarezerwowanej dynamicznie:
delete [] wsk_tab;
Zwróćmy uwagę na nawiasy kwadratowe.
Cechy obiektów utworzonych operatorem new
1. obiekty żyją od momentu utworzenia operatorem new aż do
momentu usunięcia operatorem delete
2. obiekty nie mają nazwy. Operujemy na nich tylko przy pomocy wskazników.
3. obiekty utworzone operatorem new nie sÄ… automatycznie
inicjowane
int dl_tab,i;
cout<<"podaj rozmiar tablicy: "
cin>>dl_tab;
int *wsk_tab = new int[dl_tab];
for(i = 0;i *wsk_tab++ = i;
................................
delete [] wsk_tab;
Za pomocÄ… operatora delete kasuje siÄ™ tylko obiekty utworzone przy pomocy operatora new,
przy czym nie należy kasować wcześniej skasowanego obiektu. Można kasować natomiast
wskaznik ustawiony na NULL:
wsk = NULL;
delete wsk;
W trakcie alokowania pamięci może zdarzyć się tak, że operator new zwróci NULL. Oznacza to, że
wyczerpaliśmy pamięć dostępną na dane. W związku z tym w programach tworzących dużą liczbę
dużych obiektów należy kontrolować poprawność operacji alokacji. Można tego dokonać albo
poprzez fragment programu:
int *wsk;
wsk = new int[10000];
if(!wsk)
cout<<"pamięć się wyczerpała";
lub przy wykorzystaniu funkcji set_new-handler:
Przykład
#include
#include // exit
#include //set_new_handler
long k;
void alarm(){
cout<<"Brak pamięci przy k = "< exit(1);
}
void main(){
set_new_handler(alarm);
for( k = 0; ; k++)
new int;
}
W funkcji main wykonuje się nieskończona pętla tworząca dynamicznie obiekty. Jeśli w
którymś momencie zabraknie pamięci, sterowanie automatycznie przejmuje funkcja
set_new_handler uruchamiająca funkcję alarmową napisaną przez użytkownika. Argumentem tej
funkcji jest wskaznik do funkcji alarm
Wskazniki a stringi
Jeśli wskaznik ma pokazywać na ciąg znaków, to można go zadeklarować jako:
char *text;
można go inicjalizować:
char *text = "Poniedziałek";
do tej pory:
char text_tab [] = "Poniedziałek";
char text_tab [13] = "Poniedziałek";
Przykład:
printf (3 %s3 , text); Ekran: poniedziałek
printf (3 %s3 , text_tab); Ekran: poniedziałek
printf (3 %c3 , text_tab[1]); Ekran: o
printf (3 %c3 , text[1]); Ekran: o
text++;
printf (3 %s3 , text); Ekran: oniedziałek
Przykład:
char text[]="BORLAND C++";
char *cel, *zrodlo;
il = strlen (text)+1; // +1 na znak NULL
cel = new char [il]; // rezerwacja miejsca
zrodlo=text;
(1) while ((*zrodlo) != NULL) {
*cel = *zrodlo;
cel++; zrodlo++; // BEZ NULLżÿA
}
*cel=NULL;
(2) while (*cel = *zrodlo) // inaczej while ((*cel = *zrodlo) != żÿ\0żÿ)
{
cel++; zrodlo++; // Z NULLżÿEM
}
(3) while (*cel++ = *zrodlo++); //Z NULLżÿEM
(4) Można wykorzystać funkcję biblioteczną:
strcpy (cel, zrodlo); - kopiowanie znaków ze zródła do celu, znak NULL jest kopiowany.
Przykład: char *cel, zrodlo[]=3 informatyka3 ;
cel=new char [strlen (zrodlo)+1];
strcpy (cel, zrodlo);
Wyszukiwarka
Podobne podstrony:
Wyklad 06
Wykład 06 Filtracja
2010 11 06 WIL Wyklad 06
wyklad 06
logika wyklad 06
KPC Wykład (6) 06 11 2012
06 mechanika budowli wykład 06 metoda ciezarow sprezystych
wyklad 06
Wykład 2 (06 03 2009) ruchy kamery, plan, punkty widzenia kamery
Wykład 06
Wyklad 06 Pomiary
wykład 06 macierze
Wyklad 06#
Analiza Finansowa Wykład 06 16 12 09
więcej podobnych podstron