5226


Programowanie komputerów

0x08 graphic
mgr Teresa Podhorska

2.3. Wskaźniki 2.3.1. Wskaźniki a tablice

2.3.2. Wskaźnik void

2.3.3. Arytmetyka wskaźników

2.3.4. Podsumowanie inicjowania wskaźników

2.3.5. Wskaźniki a stringi

2.3.6. Stale wskaźniki i wskaźniki do stałych

2.3. Wskaźniki

Każda zmienna ma unikalny adres wskazujący początkowy obszar pamięci zajmowany przez tą zmienną. Adres można przechowywać, zmienna która przechowuje adres obiektu nazywa się wskaźnikiem.

typ_zmiennej *nazwa_zmiennej

int *wsk; - wsk jest wskaźnikiem do pokazywania na obiekty typu int

0x08 graphic

0x08 graphic
wsk

0x08 graphic
&wsk

0x08 graphic

0x08 graphic

model tiny, small, medium - wskaźnik zajmuje 2 bajty pamięci

model compact, large, huge - wskaźnik zajmuje 4 bajty pamięci

Przykłady:

char *wsk; - wsk jest wskaźnikiem do pokazywania na obiekty typu char

float *wsk;- wsk jest wskaźnikiem do pokazywania na obiekty typu float

void *wsk; - wsk jest wskaźnikiem do pokazywania na obiektu nieznanego typu

float *wsk_tab[10]; - tablica 10 wskaźników do liczb rzeczywistych

float (*wsk_tab)[10]; - wskaźnik do tablicy 10 liczb rzeczywistych

int (*wsk) (int, int); - wskaźnik do funkcji o argumentach typu (int, int) przekazującej wynik typu całowitego

int *wsk (int, int); - funkcja o argumentach typu (int, int) przekazująca wskaźnik do typu całkowitego

char ** wsk; - wskaźnik do wskaźnika do znaku (adres do adresu)

int *wsk;

float a;

wsk = &a; //nieprawidłowo.

Chcemy wskaźnikiem do int pokazywać na float

int *wsk;

int i;

wsk = &i; //prawidłowo.

int *wsk; int i;

0x08 graphic

0x08 graphic
0x08 graphic
wsk

wsk = &i;

0x08 graphic

0x08 graphic
wsk

printf (″ %p″, wsk); printf (″ %p″, &i);

FFF4 FFF4

Podstawową operacją na wskaźniku jest wyłuskanie, czyli odwołanie się do obiektu wskazywanego przez wskaźnik. Operacja ta nazywa się adresowaniem pośrednim. Operatorem adresowania pośredniego jest jednoargumentowa * zapisywana jako przedrostek.

Zastosowana do wskaźnika daje zawartość obiektu wskazanego przez ten wskaźnik np.:

int *wsk ;

int j, i=3;

wsk = &i;

wsk przechowuje adres zmiennej i.

cout<<*adres zmiennej i wynosi: *<< wsk << * wartość zmiennej i wynosi: *<<*wsk;

Ekran: adres zmiennej i wynosi : 0xFFDA 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;

  1. *wsk = 200; wstaw wartość 200 pod adres wskazywany przez zmienną wsk

po wykonaniu intsrukcji (2) i = 200;

Zmieni się wartość zmiennej i z wartości 3 na 200. i=200 j=3

int *wsk=&i; int *ptr=&i;

int **p; int **p=&ptr;

*p=wsk;

Jeśli wsk wskazuje na zmienną całkowitą, to *wsk może wystąpić wszędzie tam gdzie może wystąpić zmienna, więc np:

int *wsk;

int zm = 10, x;

wsk = &zm;

x = *wsk + 1; // x= zm + 1 = 12 + 1;

*wsk = *wsk + 2; // zm = zm + 2 = 12 + 2;

Ponieważ operatory * oraz & są silniejsze niż operatory arytmetyczne, to w wyrażeniach:

*wsk = *wsk + 2;

x = *wsk + 1;

nie trzeba używać nawiasu: najpierw pobierana jest zawartość spod adresu wsk i zwiększona o 1 zostaje zapisana do zmiennej x.

int i=2, x; int *wsk=&i;

  1. x= ++(*wsk); x = ; //wartość spod adresu wsk zostaje zwiększona o 1

  2. x= ++*wsk; x = ; //wartość spod adresu wsk zostaje zwiększona o 1

  3. x= (*wsk)++; x = ; // wartość spod adresu wsk zostaje zwiększona o 1

  4. x= *wsk++; x = ; //pobierz wartość spod adresu i zwiększ adres o 1

wsk wsk++

FFE2

FFE4

wsk++ przesunięcie adresu o sizeof (int), ponieważ wsk jest adresem zmiennej typu całkowitego.

*wsk++ pobranie obiektu przed zwiększeniem wskaźnika i zwiększenie wskaźnika o jeden

*++wsk zwiększenie wskaźnika o jeden i pobranie obiektu po zwiększeniu wskaźnika.

wsk = wsk1;

Zapis ten mówi, że teraz wsk będzie wskazywał na to samo co wsk2.

2.3.1. Wskaźniki a tablice

Nazwa tablicy jest adresem początku obszaru zarezewowanego dla tablicy, jest to równocześnie wskaźnik do jej zerowego elementu.

Zadeklarujemy tablicę:

int tab[4]={-1,-20, 3, 5};

0x08 graphic

0x08 graphic

tab(&tab[0]) &tab[1] &tab[2] &tab[3]

tab[0] -> *(tab)

tab[1] -> *(tab+1)

tab[2] -> *(tab+2)

tab[3] -> *(tab+3)

adresy: 2000 2002 2004 2006

wartości:

-1

-20

3

5

0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic

0x08 graphic
tab

&tab[0]

0x08 graphic
tab + 1

&tab[1]

0x08 graphic
tab + 2

&tab[2]

tab + 3

&tab[3]0x08 graphic

cout<<*tab << * to wyświetlenie wartości tab[0]\n*;

cout<<*(tab+1) << * to wyświetlwnie wartości tab[1]\n*;

cout<<*( tab +2) << * to wyświetlwnie wartości tab[2]\n*;

int *wsk;

instrukcja: wsk = &tab[n];

ustawia wskaźnik na n-tym elemencie tablicy.

instrukcja: wsk = &tab[0]; jest równoważna instrukcji: wsk = tab;

oznacza ustawienie wskaźnika na pierwszy element tablicy czyli na jej początek

cout<<wsk[0]<< *\n*<<wsk[1] <<*\n*<<wsk[2];

Możemy również zmieniać adres dodając do wskaźnika liczbę całkowitą.

Przejście do następnego elementu tablicy:

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 wskaźnika wynika że wsk jest wskaźnikiem do int. Stąd kompilator wnioskuje, że aby odnaleźć następny element typu int należy przesunąć się o sizeof (int).

int *wsk, i;

int tab[10]={1, 3, 5, 6, 7, 8, 9};

wsk = tab; //inicjowanie wskaźnika

cout<<*wsk << * to wyświetlenie wartości tab[0]*;

cout<<*(wsk+1)<<* to wyświetlenie wartości tab[1]*;

cout<<wsk[0] << * to wyświetlenie wartości tab[0]*;

cout<<wsk[1]<< * to wyświetlenie wartości tab[1]*;

for(i = 0; i<10; i++) printf("%d\n", *wsk++);

for(i = 0; i<10; i++) printf("%d\n", wsk[i]);

Uwaga!!!

Mimo, że możemy zapisać:

wsk = tab;

to o ile możemy zapisać:

wsk++;

to nie możemy napisać:

tab++;

Dla tak zadeklarowanego wskaźnika:

int *wsk;

adresem tego wskaźnika jest wartość wyrażenia:

&wsk;

2.3.2. Wskaźnik void

Deklaracja wskaźnika niesie w sobie dwie informacje: adres miejsca w pamięci, oraz typ obiektu na który te adres wskazuje.

Przy deklaracji: void *wsk;

Wskaźnik wsk wskazuje jedynie na konkretne miejsce w pamięci nie informując o typie obiektów tam przechowywanych.

Wskaźnik void służy on do przekazywania wskaźników do funkcji które nie mogą czynić założeń co do typu przekazywanego obiektu i przy zwracania przez funkcje obiektów nieznanego typu.

void *ptr;

char *wsk;

int *wsk1;

ptr = wsk; lub ptr = wsk1;

Wskaźnik każdego typu można przypisać wskaźnikowi typu void, bez konieczności konwersji.

Odwrotne przypisanie wymaga stosowania operatora konwersji.

float *wsk;

void *ptr;

wsk = ptr; //błąd, bez konwersji

wsk = (float *) ptr; //poprawnie z operatorem rzutowania

void *malloc (size_t size);

wsk = (float *) malloc (100 * sizeof (float)); - operator rzutowania

2.3.3. Arytmetyka wskaźników

Dopuszczalne operacje na wskaźniku:

  1. Możemy dodawać i odejmować liczby całkowite od wskaźników tak, aby w potrzebny sposób przesuwać je po tablicy. Operacje te nie są sprawdzane przez kompilator.

  2. Możemy odjąć dwa wskaźniki 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.

3. Wskaźniki można ze sobą porównać. Do tego celu służą nam operatory:

= = != < > <= >=

Dla dwóch wskaźników:

int *wsk1, *wsk2;

if (wsk1 = = wsk2) cout<<"Oba wskaźniki pokazują na ten sam obiekt";

W operacjach dodawania i odejmowania na wskaźniku, bierze udział tylko część offsetowa adresu.

4. Każdy wskaźnik można porównać z adresem 0 zwanym NULL. Takie ustawienie wskaźnika:

wsk = 0; //lub wsk = NULL

informuje, że wskaźnik nie pokazuje na nic konkretnego (niektóre funkcje biblioteczne zwracają wskaźnik NULL (null pointer), możemy go użyć do kontroli np:

if (!wsk) // if(wsk = =NULL) if(wsk= =0)

2.3.4. Inicjowanie wskaźników

W tym punkcie zbierzemy wszystkie sposoby inicjowania wskaźników:

1. można przypisać adres konkretnego obiektu:

int *wsk, obiekt, tab[12];

wsk = &obiekt;

wsk = tab; // wsk = &tab[0]; wsk = &tab[2];

2. ustawić wskaźnik na adres funkcji.

int fill (char) {return 1;}

int (*wsk) (char);

wsk = fill;

wywołanie funkcji wsk ('*'); lub (*wsk) ('*');

char znak='*'; wsk (znak); lub (*wsk) (znak);

3. zarezerwować obszar dynamicznie

int *wsk;

wsk = new int[k];

wsk = (int*) malloc (k * sizeof (int));

4. można przypisać inny wskaźnik:

int *wsk, *ptr;

wsk = new int; ptr=wsk;

5. ustawić wskaźnik na konkretny adres którego np. zawartość chcemy sprawdzić np: wsk = FFDA;

Dynamiczna alokacja pamięci .

Funkcje przydziału i zwalaniania pamięci w obszarze bliskiego stosu.

malloc(), calloc(), realloc(), free ()

Alokacja pamięci.

void *malloc (size_t n); typedef unsigned size_t

Funkcja zwraca wskaźnik 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); Pamięć inicjowana jest zerami.

char *wsk = (char*) malloc (100 * sizeof (char));

char *wsk = (char*) calloc (100, sizeof (char));

if (wsk= =NULL) cout<<″brak pamięci″;

void *realloc (void *block, size_t size);

wsk = (char*) realloc (wsk, 300 * sizeof (char));

Funkcja może zwiększać lub zmniejszać rozmiar pamięci przydzielonej poprzednio za pomocą funkcji malloc lub calloc zachowując w miarę możliwości zawartość.

Zwalnianie pamięci.

Po wykorzystaniu pamięci można ją zwolnić. Do tego celu służy funkcja free().

void free ( void *p);

Zwalnia pamięć wskazaną przez p, przy czym p musi być wynikiem wcześniejszego wywołania funkcji malloc() lub calloc(). Błędem jest zwalnianie obszary, który nie był poprzednio przydzielony w/w funkcjami.

free (ptr); free (wsk);

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.

Funkcje przydziału i zwalaniania pamięci w obszarze dalekiego stosu.

farmalloc(), farcalloc, farrealoc() - funkcje pozwalają na przydział bloków pamięci powyżej 64kB. Są bezużyteczne w model tiny.

farfree() - zwalnianie pamięci.

W modelach compact, large, huge - istnieje obszar dalekiego stosu zmiennych dynamicznych

W modelach small, medium istnieją dwa stosy zmiennych dynamicznych bliski i daleki.

Operatory new i delete.

Operator new tworzy obiekt, a operator delete usuwa obiekt z pamięci.

char *wsk;

Alokacja pamięci operatorem new.

wsk = new char;

powoduje utworzenie nowego obiektu typu char. Nie ma on nazwy, ale możemy się do niego odwoływać poprzez wskaźnik zawierający adres tego obiektu.

int *wsk_tab;

wsk_tab = new int[k];

operator new utworzył k-elementowa tablicę typu int.

for (i=0; i<k; i++) wsk_tab[i] = 1; lub

for (i=0; i<k; i++) *wsk_tab++ = 1; lub

for (i=0; i<k; i++) *(wsk_tab+i) = 1;

int (*wsk_tab)[k];

wsk_tab = new int[w][k];

for (i=0; i<w; i++)

for (j=0; j<k; j++) wsk_tab[i][j]=1;

lub

for (i=0; i<w; i++)

for (j=0; j<k; j++) *(wsk_tab[i] + j) =1;

lub

for (i=0; i<w; i++)

for (j=0; j<k; j++) *(*(wsk_tab+i) +j )=1;

int (*wsk)[k];

wsk = new int[w][k];

Przykład:

wsk[w][k] - tablice wielowymiarowe są traktowane jak wskaźniki do wskaźników

*(wsk+w) - początek w-tego wiersza

*(wsk+w) + k - wskazanie kolumny w tablicy

W tablicach jednowymiarowych dostęp za pomocą wskaźnika jest czytelny natomiast w tablicach wielowymiarowych raczej nie.

*(*(wsk+0)+0)

*(*(wsk+0)+1)

*(*(wsk+0) +2)

wsk[0][0]

wsk[0][1]

wsk[0][2]

*(*(wsk+1) +0)

*(*(wsk+1) +1)

*(*(wsk+1) +2)

wsk[1][0]

wsk[1][1]

wsk[1][2]

Zwalnianie pamięci.

delete wsk; delete [] wsk_tab;

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 wskaźników.

3. obiekty utworzone operatorem new nie są automatycznie inicjowane

Nie należy kasować wcześniej skasowanego obiektu. Można kasować natomiast wskaźnik 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. Należy kontrolować poprawność operacji alokacji.

int *wsk;

wsk = new int[10000];

if(!wsk) cout<<"pamięć się wyczerpała";

lub przy wykorzystaniu funkcji set_new-handler():

Przykład

#include<iostram.h>

#include<new.h> //set_new_handler()

void alarm(){

cout<<"Brak pamięci przy k = "<<k;

}

long k;

void main(){

set_new_handler(alarm);

for(k = 0; ; k++)

new int;

}

Jeśli w którymś momencie zabraknie pamięci, sterowanie przejmuje automatycznie funkcja set_new_handler() uruchamiająca funkcję alarmową napisaną przez użytkownika. Argumentem tej funkcji jest wskaźnik do funkcji alarm.

Dostęp do konkretnych komórek pamięci:

Czasami zachodzi konieczność uzyskania bezpośredniego dostępu do konkretnej komórki pamięci bez podania jej nazwy. Wtedy odnosimy się do niej poprzez wskaźnik:

wsk = 0xF11D;

podajemy po prostu do wskaźnika adres komórki i odtąd możemy posługiwać się tym wskaźnikiem

cout<<"wartość przechowywana pod adresem"<<wsk <<*wsk;

2.3.5. Wskaźniki a stringi

String to tablica znaków zakończona znakiem NULL.

Jeżeli wskaźnik ma pokazywać na ciąg znaków, to można go zadeklarować jako:

char *text;

można go też inicjalizować:

(1) char *text = "to jest próbny tekst";

Porównajmy zapis:

(2) char text_tab [] = "To jest próbny tekst";

text_tab++ - błąd

text++ - poprawnie

Przykład:

char text[]=* BORLAND C++*;

char *cel, *zrodlo;

zrodlo = text;

il = strlen (zrodlo);

cel = new [il] +1;

(1) while ((*zrodlo) != NULL) {

*cel = *zrodlo;

cel++; zrodlo++;

}

*cel=NULL; // wskaźnik cel wskazuje na koniec stringu.

(2) while(*cel++ = *zrodlo++); kopiujemy razem ze znakiem NULL

Funkcje bilblioteczne:

strcpy (cel, zrodlo); - kopiowanie łańcucha zrodlo do celu, kończy operację po osiągnięciu znaku NULL.

memcpy (cel, zrodlo, strlen(zrodlo)+1); kopiowanien bajtów z obszaru wskazywango przez zrodlo do obszaru wskazywanego przez cel.

Inicjowanie tablicy wskaźników do stringów. Jej elementami są wskaźniki mogące pokazywać na stringi:

char *text[5]; // 5 wskaźników.

Wskaźniki te można inicjalizować:

char *text[5] = {"poniedzialek", "wtorek", "sroda", "czwartek", *piątek*};

char *text[] = {"poniedzialek", "wtorek", "sroda", "czwartek", *piątek*};

Elementami tej tablicy są adresy tych miejsc pamięci, gdzie umieszczone są te stringi,

(1) printf (*\ntext: %s*,text[0]); // poniedzialek

(2) printf (*\ntext: %s*,text[1]); // wtorek

dostęp do poszczególnych znaków:

(3) printf (*\nznak: %c*,text[0][0]); //p

(4) printf (*\nznak: %c*,text[0][1]); //o

char tab[2][15]={″Ala″, ″Irek″};

char (*wsk)[15];

wsk=tab; printf (″%s″, wsk); //Ala

char *ptr;

ptr=tab[0]; printf (″%s″, ptr); //Ala

ptr=tab[1]; printf (″%s″, ptr); // Irek

0x08 graphic
ptr=tab; //błąd char [15]* char *

2.3.6. Stale wskaźniki i wskaźniki do stałych

Deklaracja:

int *const wsk = tab;

Jest to deklaracja stałego wskaźnika. Musi być inicjowany podczas deklarowania. Wskaźnik jest deklarowany jako stały więc nie można go modyfikować, ale zmienna przez niego wskazywana może być zmieniana:

int tab[3];

*wsk = 3; // OK

wsk++; // wskaźnik nie może być zmieniany, wskaźnik const

Deklaracja :

const int *wsk;

Jest to deklaracja wskaźnika do stałego obiektu. Nie musi być od razu inicjalizowany.

Wskaźnik uznaje wskazywany obiekt za stały, więc używając tego wskaźnika obiektu nie można modyfikować, natomiast wskaźnik może być zmieniany.

int tab[3];

wsk = tab; // ustawienie wskaźnika na początek tablicy

*wsk=9; // błąd, dla tego wskaźnika obiekt const

wsk ++; // wskaźnik może być zmieniany

Deklaracja :

int tab[3];

const int *const wsk=tab;

Sały wskaźnik do stałego obiektu. Musi być inicjowany podczas deklarowania. Wskaźnik jest deklarowany jako stały więc nie można go modyfikować, uznaje wskazywany obiekt za stały, więc używając tego wskaźnika obiektu też nie można modyfikować.

*wsk=9; // błąd,za pomoca tego wskaźnika obiektu modyfikować nie wolno, obiekt const

wsk ++; //błąd, wskaźnik nie może być zmieniany, wskaźnik const

Programowanie komputerów C/C++

4

wykład 5

Wykład 5

semestr III - studia dzienne magistereskie

Wykład 5

semestr III - studia dzienne zawodowe

Zajmuje 2 lub 4 bajty pamięci w zależności od modelu pamięci

Przechowuje adres do zmiennej typu int

&i - adres zmiennej i

FFF4

Miejsce na adres do zmiennej typu int

FFF4

adres początku obszaru zarezerwowanego dla tablicy



Wyszukiwarka

Podobne podstrony:
5226
5226
5226
5226
5226
04 wykładid 5226 ppt
5226

więcej podobnych podstron