Wykłady z programowania obiektowego f& Język C/C++
7. Tablice, wskazniki i referencje
Podstawowe konstrukcje języka, takie jak zapis działań na danych w
postaci wyrażeń, definiowanie zmiennych prostych, instrukcja przypisania, czy
operacje wejścia/wyjścia, pozwalają na stosowanie najprostszego stylu
programowania. W stylu tym algorytmy zapisywane są w postaci ciągłego
potoku instrukcji bez podziału na fragmenty, a więc bez wyraznej struktury.
Programowanie strukturalne wymaga stosowania bardziej zaawanso-
wanych konstrukcji językowych. Poznane instrukcje, takie jak instrukcje
decyzyjne, iteracyjne, instrukcja grupująca, należą do grupy konstrukcji o
charakterze strukturalnym, lecz pozwalają osiągnąć jedynie podstawowy
poziom struktury w programie i to jedynie w zakresie jego kodu.
Dalsze zwiększenie stopnia złożoności struktury w programie jest
możliwe poprzez stosowanie złożonych typów i struktur danych, podział kodu
na procedury oraz łączenie procedur i danych w moduły.
Struktury danych
Współczesne techniki programowania dotyczą nie tylko implementacji
algorytmów w postaci kodu programu o odpowiedniej strukturze, ale również
tworzenia struktur danych, przechowywanych w pamięci komputera i przetwa-
rzanych przez algorytmy.
Podobnie jak w przypadku struktury kodu, wprowadzenie struktury do
zbioru danych w programie polega na podziale tego zbioru na mniejsze zwarte
fragmenty zwane też strukturami danych. Dzięki temu uzyskujemy
uporządkowanie informacji i zwiększenie jej czytelności.
Odpowiedni dobór struktury danych wpływa ponadto na bardziej
optymalny przydział pamięci operacyjnej programowi oraz na zmniejszenie
czasu wykonania programu. Niestety jak pokazuje praktyka, niemożliwe jest
dla wielu zagadnień dobranie takiej struktury, aby zmniejszyć przydział
pamięci przy jednoczesnym skróceniu czasu wykonania programu.
1
PDF created with pdfFactory Pro trial version www.pdffactory.com
Wykłady z programowania obiektowego f& Język C/C++
Współczesne techniki obejmują wiele rozmaitych struktur danych, o
ogólnym przeznaczeniu lub dostosowanych do specyficznej grupy problemów.
Do najczęściej używanych w programach rodzajów struktur danych zaliczamy:
tablice, rekordy, kolejki, stosy, listy, grafy itp.
Ze względu na wewnętrzną organizację zajmowanej pamięci struktury
danych dzielimy na:
struktury spójne (agregaty danych) - dane przechowywane są
we pojedynczym obszarze pamięci (bloku), przydzielonym w
całości przez system operacyjny
struktury wiÄ…zane - dane przechowywane sÄ… we oddzielnych
obszarach pamięci (każdy przydzielony osobno), powiązanych
poprzez odpowiednie wskazniki zawierające adresy obszarów.
Struktury danych możemy również podzielić na jednorodne, gdy
zawarte w nich dane ograniczone sÄ… do jednakowego typu, i niejednorodne,
gdy możliwe jest przechowywanie danych różnego typu.
Tablice danych
NajprostszÄ… strukturÄ… danych jest tablica (ang. array); jest to struktura
jednorodna. Prawie wszystkie języki wspierają tworzenie tablic, najczęściej w
postaci agregatu danych. W takim przypadku do zdefiniowania tablicy w
programie służy osobny typ danych - typ tablicowy, występujący jako element
języka. W przypadku braku wsparcia ze strony języka tablicę można utworzyć
jedynie pośrednio jako strukturę wiązaną.
Dane w tablicy jednowymiarowej (1D) zorganizowane sÄ… w postaci
ponumerowanego ciągu lub wektora elementów, których wspólny typ wartości
określony jest wprost w definicji tablicy.
Rozmiar tablicy podany w definicji określa liczbę jej elementów.
Dostęp do danych jest bezpośredni i odbywa się poprzez podanie
indeksu elementu tablicy, czyli bieżącego numeru zgodnie z przyjętą w języku
numeracjÄ… dla tablic.
2
PDF created with pdfFactory Pro trial version www.pdffactory.com
Wykłady z programowania obiektowego f& Język C/C++
W języku C definicja tablicy jednowymiarowej podobna jest do definicji
zmiennej prostej i dla podanego typu elementów tablicy oraz podanej nazwy
tablicy definicja ta przyjmuje jednÄ… z trzech postaci:
z elementami o nieokreślonej wartości początkowej:
typ nazwa[rozmiar];
z jawnymi wartościami początkowymi elementów w postaci
podanych wyrażeń:
typ nazwa[rozmiar] = {
wyrażenie_1, wyrażenie_2,... wyrażenie_N
};
z niezmiennymi jawnie podanymi wartościami początkowymi:
const typ nazwa[rozmiar] = {
wyrażenie_1, wyrażenie_2,... wyrażenie_N
};
Podany na początku definicji typ wspólny dla wszystkich elementów
tablicy jest zwykle jednym z typów podstawowych.
Podany rozmiar tablicy musi być stałym wyrażeniem numerycznym,
którego wartość znana jest w czasie kompilacji programu. Tworzenie tablic o
rozmiarze znanym w jedynie w czasie wykonywania programu wymaga w
języku C i C++ użycia alokacji pamięci dynamicznej.
Uwaga: Liczba bajtów pamięci zajmowanej przez tablicę, czyli rozmiar pamięci zwracany
przez operator sizeof zastosowany do nazwy tablicy, na ogół różni się od rozmiaru samej
tablicy podanego w jej definicji. Rozmiar pamięci jest mianowicie równy iloczynowi
liczby elementów rozmiaru tablicy i rozmiaru typu jej elementów, czyli liczbie bajtów
zajmowanych przez pojedynczy element:
sizeof nazwa == rozmiar * sizeof(typ)
3
PDF created with pdfFactory Pro trial version www.pdffactory.com
Wykłady z programowania obiektowego f& Język C/C++
W przypadku definicji tablicy z nieokreśloną wartością początkową jej
elementów faktyczna wartość elementów zależy od zakresu definicji. Dla
tablicy lokalnej wartość początkowa jest przypadkowa i zależy od stanu
pamięci operacyjnej, natomiast dla tablicy globalnej przyjmuje się, że niejawna
wartość początkowa równa jest zero.
Natomiast w przypadku definicji z jawnie podanymi wartościami
poczÄ…tkowymi kolejnym elementom tablicy (w porzÄ…dku od lewej do prawej)
zostaną przypisane wartości kolejnych wyrażeń podanych w postaci ciągu
ujętego w nawiasach klamrowych i podzielonego przecinkami.
Liczba N wszystkich wyrażeń w tym ciągu nie może być większa od
podanego rozmiaru tablicy. Jeżeli liczba ta jest mniejsza od rozmiaru, to
wartości kolejnych wyrażeń ciągu są przypisane kolejnym elementom z
początku tablicy, natomiast pozostałe elementy tablicy są domyślnie
wyzerowane.
Aby uniknąć problemów dotyczących zgodności liczby wyrażeń
inicjujących z liczbą elementów tablicy, można nie podać jawnie rozmiaru
tablicy pozostawiajÄ…c puste nawiasy kwadratowe. W takim przypadku rozmiar
tablicy jest ustalany przez kompilator jako równy liczbie N.
Typ wszystkich wartości wyrażeń podanych w definicji jako jawne
wartości początkowe dla elementów tablicy musi być zgodny z typem
elementów podanym na początku definicji.
Jeżeli definicja tablicy poprzedzona jest słowem kluczowym const, to
dotyczy tablicy ustalonej, której wartość nie może ulec zmianie w trakcie
działania programu. Tablice takie w programie można jedynie używać do
odczytu wartości ich elementów, stąd wartości początkowe elementów tablicy
ustalonej muszą być jawnie podane w jej definicji.
Elementy tablicy nieustalonej (zdefiniowanej bez const) w programie
mogą być użyte zarówno do odczytu ich wartości, jak i w celu zapisu nowej
wartości, stąd ich wartość można dowolnie zmieniać w następującym po
definicji kodzie programu.
4
PDF created with pdfFactory Pro trial version www.pdffactory.com
Wykłady z programowania obiektowego f& Język C/C++
Przykłady:
const int n = 5;
int x = 5;
int A[5];
int B1[n];
int B2[x]; // błąd: rozmiar nie może być zmienna
int C[5] = {1,2,3,4,5};
int D[5] = {1,2,3}; // => {1,2,3,0,0}
int E[] = {1,2,3}; // => int E[3] = {1,2,3};
Dostęp do poszczególnych elementów tablicy w celu odczytu lub zapisu
ich wartości zachodzi poprzez operator indeksowania [] , w którym podaje się
indeks danego elementu. Operator ten posiada wysoki priorytet.
Operacje odczytu i zapisu wartości elementu tablicy o podanym indeksie
można w ogólności przedstawić następująco:
Wyrażenie tablica[indeks] jest
przykładem złożonej l-wartości,
gdyż dla nieustalonej tablicy
reprezentuje zwykłą zmienną (tj.
.... = tablica[indeks];
element tablicy) i stąd może
występować z lewej strony
tablica[indeks] = .... ; operatora przypisania.
W języku C indeks elementów tablicy o danym rozmiarze zmienia się w
przedziale od 0 do rozmiar-1 i może być podany w postaci literału całkowitego
lub dowolnego wyrażenia numerycznego.
Uwaga: W języku C/C++ nie jest wykonywane sprawdzanie zgodności indeksu elementu
ze rozmiarem tablicy. Przypadkowe odwołanie się do elementu spoza tablicy (przed lub za
tablicą) jest zródłem trudnych do wykrycia błędów, pojawiających się w czasie działania
programu. Na przykład kod:
const int n = 5;
int A[n];
cin >> A[5]; // błąd, poprawniej: A[n-1]
spowoduje powstanie błędu pamięci w czasie działania programu. Aby tego uniknąć,
należy podawać indeksy w postaci wyrażenia n-1 lub licznika odpowiedniej pętli.
5
PDF created with pdfFactory Pro trial version www.pdffactory.com
Wykłady z programowania obiektowego f& Język C/C++
Przykłady:
tablice danych:
int y, i = 2; // zwykłe zmienne całkowite
int v[5]; // tablica 1D z 5 elementami całkowitymi o nieokreślonej wartości
v[2] = 10; // bezpośredni zapis wartości do elem. v[2] (v[2] jest l-wartością)
int w[] = {18, 20, 14}; // tablica 1D (wektor) 3 liczb całkowitych
y = w[0]+w[1]+w[2]; // bezpośredni odczyt wartości ze w
v[i+1] = w[i]; // indeks może być bardziej złożonym wyrażeniem
v[5] = w[-1]; // błąd: przekroczenie zakresu indeksu
const int n = 5;
int i, suma;
int A[n];
cin >> A[0]; // zapisz pierwszy element
cin >> A[n-1]; // zapisz ostatni element, A[4]
for (i=0; i
cin >> A[i]; // zapisz i-ty element
for (i=0; i suma+=A[i]; // suma odczytanych liczb
6
PDF created with pdfFactory Pro trial version www.pdffactory.com
Wykłady z programowania obiektowego f& Język C/C++
tablice znaków - zmienne tekstowe (łańcuchowe):
char imie1[] = {'J', 'a', 'n', '\0'}; // tablica 4 znakowa
char imie2[] = "Jan"; // tablica 3+1 znakowa z wartością tekstową
char napis[4]; // tablica z 4 elementami znakowymi o nieokreślonej wartości
napis[0] = 'J'; // zapis kolejnych wartości znakowych do tablicy
napis[1] = 'a';
napis[2] = 'n';
napis[3] = '\0'; // niewidoczny znak końca łańcucha
cin >> napis; // odczytaj łańcuch ze strumienia
cout << napis; // zapisz łańcuch do strumienia
Wskazniki
Wskaznik (ang. pointer) jest specjalną zmienną, która przeznaczona jest
do przechowywania adresu innej zmiennej lub dowolnego obszaru w pamięci.
Pamięć jest podzielona na kolejno numerowane komórki (bajty). Każda
zmienna lub dowolny obszar umieszczone są w danym miejscu pamięci,
jednoznacznie określonym przez adres pamięci.
Postać adresu jest zależna od sprzętu komputerowego; na ogół jednak
jest to liczba całkowita lub para liczb całkowitych. Ponieważ wskaznik jest
również zmienną, więc znajduje się w pewnym obszarze pamięci o ustalonym
rozmiarze, również zależnym od sprzętu.
W języku C wskaznik do zmiennej o danym typie definiuje się pisząc
przed jego nazwÄ… znak gwiazdki:
typ *nazwa;
Podany typ jest zwykle jednym z poznanych typów podstawowym.
W definicji wskaznika, podobnie jak dla zwykłych zmiennych, można
podać jawnie wartość początkową - określony adres zmiennej. Można również
definicję poprzedzić słowem const, co daje wskaznik do zmiennej ustalonej.
7
PDF created with pdfFactory Pro trial version www.pdffactory.com
Wykłady z programowania obiektowego f& Język C/C++
W miejsce typu w definicji wskaznika można wstawić słowo void,
wtedy wskaznik zawiera jedynie informację o adresie obszaru pamięci bez
określenia typu wskazywanych danych, czyli jest to wskaznik amorficzny
(zastosowanie tego typu wskazników zostanie opisane szerzej na wykładzie
poświęconym dynamicznemu przydziałowi pamięci).
Aby wskaznik wskazywał na pewną zmienną, należy przypisać mu adres
tej zmiennej posługując się operatorem adresowania & :
wskaznik = &zmienna;
Jeżeli do zwykłej zmiennej zapisywana jest pewna wartość lub ze
zmiennej tej wartość jest odczytywana, to uzyskujemy bezpośredni dostęp do
obszaru pamięci, w którym znajduje się ta zmienna; jest to tzw. bezpośrednie
adresowanie pamięci. Wskazniki umożliwiają pośredni dostęp do zmiennej,
której adres przechowują, co daje adresowanie pośrednie pamięci.
Aby uzyskać dostęp do zmiennej poprzez wskaznik, należy zastosować
operator dereferencji * :
Gdy wyrażenie dereferencji
postaci *wskaznik przedstawia
nieustalonÄ… zmiennÄ…, wtedy
.... = *wskaznik;
może występować z lewej
strony operatora przypisania;
*wskaznik = .... ;
jest to zatem kolejny przykład
złożonej l-wartości.
W pierwszym przypadku wskaznik jest użyty do odczytu wartości ze
wskazywanej zmiennej, natomiast w drugim - do zapisu.
Uwaga: Stosując operator wyłuskania należy pamiętać, aby wskaznik został prawidłowo
zainicjowany adresem konkretnej zmiennej. Jeżeli wskaznik posiada wartość zerową (ang.
null pointer) lub nieokreśloną (ang. wild pointer), wtedy dereferencja wskaznika jest
niepoprawna; jest to zródło częstych błędów pamięci trudnych do wykrycia. Podobna
sytuacja występuje, gdy wskazywana zmienna dynamiczna przestaje istnieć, wtedy
wskaznik tej zmiennej (ang. dangling pointer) posiada nieaktualny adres i stÄ…d jego
dereferencja jest również błędem.
8
PDF created with pdfFactory Pro trial version www.pdffactory.com
Wykłady z programowania obiektowego f& Język C/C++
Przykład:
int x, y; // zwykłe zmienne całkowite
int *p; // wskaznik do zmiennej całkowitej
x = 10; // bezpośredni zapis wartości do x (x jest l-wartością)
y = 2*x+1; // bezpośredni odczyt wartości ze x
p = &x; // wpisanie adresu zmiennej x do wskaznika
*p = 20; // pośredni zapis wartości do x (*p jest l-wartością)
y = *p; // pośredni odczyt wartości ze x
9
PDF created with pdfFactory Pro trial version www.pdffactory.com
Wykłady z programowania obiektowego f& Język C/C++
Dowolny wskaznik do zmiennej lub wskaznik amorficzny można
traktować również jako zmienną, podobną do zmiennych prostych, ale o typie
pochodnym w stosunku do typów podstawowych.
W języku C, podobnie jak w przypadku zmiennych prostych, do
wskazników można również stosować operator rozmiaru, operator przypisania,
operatory porównania itd.
Ponadto, wskazniki posiadają określoną lokalizację w pamięci oraz
własny adres. Wobec tego w C można definiować wskazniki do wskazników i
stosować operator adresowania również w stosunku do wskaznika.
Wskazniki do wskazników umożliwiają dla pierwotnej zmiennej
pośredni dostęp wyższego rzędu, który w porównaniu do dostępu za
pośrednictwem zwykłego wskaznika do zmiennej zachodzi poprzez
wielokrotne adresowanie pośrednie.
Przykłady:
int x = 10, y; // zmienne proste
int *p, *q; // wskazniki do zmiennej
p = &x; // *p to x
q = p; // przypisanie wskaznika, *q to też x
if (p == q) { // porównanie wskazników
*p = 20;
y = *q; // y == 20
}
int **pp; // wskaznik do wskaznika
pp = &p; // *pp to p oraz **pp to *p czyli x
x = 30; // dostęp bezpośredni do x
*p = 40; // dostęp pośredni do x pierwszego rzędu
**pp = 50; // dostęp pośredni do x drugiego rzędu
y = **pp; y = *p; // y == 50
int v = 15;
*pp = &v; // zmiana wartości wskaznika p, *p to v
y = **pp; y = *p; // y == 15
10
PDF created with pdfFactory Pro trial version www.pdffactory.com
Wykłady z programowania obiektowego f& Język C/C++
Uwaga: Jeżeli zmienna wskazywana jest zmienną ustaloną, to w definicji
odpowiedniego wskaznika należy na początku umieścić słowo const, np.:
const int cx = 5; // zmienna ustalona
const int *pc = &cx;
*pc = 10; // błąd: modyfikacja zmiennej ustalonej
Taki zapis należy odróżnić od definicji ustalonego wskaznika, tzn. wskaznika o
ustalonej wartości równej adresowi pewnej zmiennej, np.:
int x = 5, v = 6;
int * const cp = &x; // wskaznik ustalony
*cp = 10; // poprawne
cp = &v; // błąd: modyfikacja stałego wskaznika
Jak widać, w deklaracji stałego wskaznika słowo const umieszczamy za
znakiem gwiazdki. Istnieje również możliwość sformułowania definicji
mieszanego wskaznika, w której występuje dwukrotnie słowo const, np.:
const int cx = 5;
const int * const cpc = &cx;
W takim przypadku definicja dotyczy ustalonego wskaznika do ustalonej
zmiennej.
Wskazniki a tablice
Dowolną tablicę danych można traktować również jako zmienną,
podobną do zmiennych prostych, ale o typie pochodnym w stosunku do typów
podstawowych.
W języku C, w odróżnieniu od zmiennych prostych, dla tablic nie można
stosować operatora przypisania, operatorów porównania itd. Istnieje natomiast
możliwość podziałania na tablicę operatorem rozmiaru zmiennej.
11
PDF created with pdfFactory Pro trial version www.pdffactory.com
Wykłady z programowania obiektowego f& Język C/C++
Ponieważ tablice posiadają określoną lokalizację w pamięci oraz własny
adres, więc w C można również stosować dla tablicy operator adresowania
uzyskując wskazanie do tablicy. Jednakże ze względów praktycznych częściej
posługujemy się wskaznikami do elementów tablicy (elementy są również
zmiennymi i to na ogół zmiennymi prostymi).
Dla dowolnej jednowymiarowej tablicy danych definicja wskaznika do
tablicy przyjmuje najczęściej następującą postać:
typ tablica[rozmiar];
typ (*wskaznik_do_tablicy)[rozmiar];
wskaznik_do_tablicy = &tablica;
Zamiast p/w złożonego wskaznika zawierającego adres tablicy można
określić w zwykły sposób wskazanie na jej pierwszy element, tj. element o
indeksie równym zero, które zastępuje adres całej tablicy. W tym celu
posługujemy się wskaznikiem do elementu:
typ tablica[rozmiar];
typ *wskaznik_do_elementu;
wskaznik_do_elementu = &tablica[0];
W języku C przyjmuje się, iż nazwa dowolnej jednowymiarowej tablicy
danych reprezentuje wskazanie na jej pierwszy element, stąd możliwe jest
uproszczenie p/w zapisu:
wskaznik_do_elementu = tablica;
Nazwa tablicy jest przy tym wskazaniem stałym, stąd nie można
przypisać tej nazwie innego adresu.
Należy zwrócić uwagę, iż mimo różnic składniowych pomiędzy
wskaznikiem do tablicy a p/w wskaznikiem do elementu istnieje podobieństwo
semantyczne. Obydwa wskazniki bowiem zawierają adresy określające
początek tablicy. Co więcej, adresy te są na ogół sobie równe.
12
PDF created with pdfFactory Pro trial version www.pdffactory.com
Wykłady z programowania obiektowego f& Język C/C++
Przykłady:
int A[5] = {1,2,3,4,5}; // tu A ma typ: "int[5]"
int *p; // wskaznik do zmiennej lub elementu typu int
int (*Q)[5]; // wskaznik do tablicy typu "int[5]"
p = A; // tu A ma typ: "int * const"
p = &A[0]; // równoważne p/w zapisowi
Q = &A; // na ogół Q i p przechowują te same adresy
*p = 10; // równoważne zapisowi: A[0] = 10
(*Q)[1] = 20; // równoważne zapisowi: A[1] = 20
int B[] = {10,20,30,40,50}; // tu B ma typ: "int[5]"
A = p; // błąd: modyfikacja stałego wskaznika
A = B; // błąd j/w stąd kopiowanie tablic zabronione
for (int i=0; i<5; i++) A[i]=B[i]; // poprawne
Można również przypisać wskaznikowi do elementu adres dowolnego
innego elementu tablicy o podanym indeksie:
wskaznik_do_elementu = &tablica[indeks];
Jeżeli wskazniki zostały przypisane pewnym elementom tablicy o
podanych indeksach, wtedy ze względu na spójność obszaru pamięci
zajmowanego przez tablice na takich wskaznikach można wykonywać operacje
relacji, dodawania lub odejmowania, co sprowadza siÄ™ do wykonywania tych
operacji na odpowiednich indeksach wskazywanych elementów:
wskaznik_1 = &tablica[indeks_1];
wskaznik_2 = &tablica[indeks_2];
relacje pomiędzy wskaznikami:
wskaznik_1 ! wskaznik_2 Ô! indeks_1 ! indeks_2
gdzie symbol ! oznacza jedno z: < > <= >= ==
13
PDF created with pdfFactory Pro trial version www.pdffactory.com
Wykłady z programowania obiektowego f& Język C/C++
dodanie liczby całkowitej do wskaznika:
wskaznik + liczba Ô! indeks + liczba
odejmowania wskazników:
wskaznik_1 - wskaznik_2 Ô! indeks_1 - indeks_2
Ponieważ dodanie pewnej liczby całkowitej do wskaznika związanego z
tablicą daje wskazanie na inny element tej tablicy, więc operacja indeksowania
tablicy jest równoważna operacji z użyciem operatora dereferencji w dwóch
przypadkach:
indeksowanie tablicy poprzez jej nazwÄ™:
tablica[indeks] Ô! *(tablica + indeks)
indeksowanie tablicy poprzez wskaznik:
wskaznik[liczba] Ô! *(wskaznik + liczba)
Przykłady:
int A[5] = {10,20,30,40,50};
int *p = &A[1];
int *q = &A[3];
cout << *p; // <=> A[1] == 20
cout << *q; // <=> A[3] == 40
if (p < q) // porównanie indeksów: 1 < 3
cout << q - p; // różnica indeksów: 3 - 1 = 2
p++; // inkrementacja indeksu: p = p+1 => 1+1 = 2
cout << *p; // <=> A[2] == 30
cout << *(A+4); // <=> A[4] == 50
cout << p[2]; // <=> *(p+2) <=> *(A+2+2) <=> A[4] == 50
14
PDF created with pdfFactory Pro trial version www.pdffactory.com
Wykłady z programowania obiektowego f& Język C/C++
Uwaga: Deklarację wskaznika do tablicy danych np. liczb całkowitych:
int tablica_liczb[5] = {10, 20, 30, 40, 50};
int (*wsk_do_tablicy)[5] = tablica_liczb;
int x = (*wsk_do_tablicy)[1]; // x == 20
należy odróżnić od deklaracji tablicy wskazników, np.:
int x1, x2, x3, x4, x5;
int *tablica_wsk[5] = {&x1, &x2, &x3, &x4, &x5};
int *q = tablica_wsk[1]; // q == &x2
Jak widać, opuszczenie nawiasów okrągłych wokół zapisu *nazwa daje
deklarację tablicy zawierającej zwykłe wskazniki do dowolnych zmiennych
całkowitych np. zmiennych x1...x5.
Referencje
Referencje zostały wprowadzone do języka C++ w celu stworzenia
możliwości definiowania bezpiecznych wskazników, których dereferencja nie
jest narażona na powstanie błędu pamięci, np. gdy wskaznik jest zerowy.
Referencja jest aliasem (inną nazwą) zmiennej. Na ogół po kompilacji
programu referencja zastępowana jest odpowiednim wskaznikiem (adresem
zmiennej).
Ogólna postać deklaracji referencji jest następująca:
typ &nazwa = zmienna;
W deklaracji należy zawsze umieścić nazwę zmiennej, do której odnosi
się referencja. Dzięki temu właśnie stosowanie referencji nie jest narażone na
odwoływanie się do adresu nieokreślonego lub zerowego.
15
PDF created with pdfFactory Pro trial version www.pdffactory.com
Wykłady z programowania obiektowego f& Język C/C++
Przykład:
int x = 10, y; // zwykłe zmienne całkowite
int &r = x; // referencja do zmiennej całkowitej x
y = x; // odczyt bezpośredni
y = r; // odczyt (pośredni) poprzez alias
x = 20; // zapis bezpośredni
r = 30; // zapis (pośredni) poprzez alias
const int cx = 15;
const int &rc = cx; // referencja zmiennej ustalonej
rc = 10; // błąd: modyfikacja zmiennej ustalonej
Podobnie jak w przypadku wskazników do zmiennych ustalonych użycie słowa
kluczowego const przed deklaracją referencji daje również referencję do
zmiennej ustalonej, poprzez którą nie można modyfikować tej zmiennej.
Tablice wielowymiarowe
W języku C istnieje możliwość definiowania tablic wielowymiarowych,
przy czym z każdym kolejnym wymiarem tablicy związany jest osobny
rozmiar tablicy oraz indeks elementu. Wobec tego tablicÄ™ wielowymiarowÄ…
traktuje się jako strukturę złożoną z mniej wymiarowych podtablic danych.
Ogólna postać definicji tablicy N wymiarowej jest następująca:
typ nazwa[rozmiar_1][rozmiar_2]...[rozmiar_N];
Dostęp do kolejnych elementów tablicy wielowymiarowej zachodzi przy
pomocy operacji wielokrotnego indeksowania:
.... = tablica[indeks_1][indeks_2]...[indeks_N];
tablica[indeks_1][indeks_2]...[indeks_N] = .... ;
Kolejne indeksy w operacji odpowiadajÄ… kolejnym rozmiarom tablicy.
16
PDF created with pdfFactory Pro trial version www.pdffactory.com
Wykłady z programowania obiektowego f& Język C/C++
Przykład:
int y; // zwykła zmienna całkowita
int A[3][3] = { // tablica 2D (macierz) 3 x 3 liczb całkowitych
{10, 12, 6},
{24, 16, 8},
{12, 35, 1}
};
y = A[2][1]; // bezpośredni odczyt wartości ze A
A[1][2] = 2*y+1; // bezpośredni zapis wartości do elem. A[1][2]
cin >> A[0][0]; // pierwszy element pierwszego wiersza tablicy
for (int i=0; i<3; i++)
for (int j=0; j<3; j++)
cin >> A[i][j]; // j-ty element i-tego wiersza
Uwaga: Istnieje również możliwość stosowania wskazników w połączeniu z
tablicami wielowymiarowymi. Podobnie jak w przypadku tablic 1D, nazwa
dowolnej tablicy N wymiarowej oznacza stały wskaznik na pierwszy jej
element, czyli tablicę o wymiarze N-1. Zatem możliwa jest operacja
przypisania nazwy całej tablicy N wymiarowej do wskaznika na jej podtablice.
Na przykład dla N = 2 mamy:
int A[5][10] = {...}; // tu A ma typ: "int[][10]"
int (*p)[10] = A; // tu A ma typ: "int (* const)[10]"
// i jest równoważne wyrażeniu: &A[0]
co daje wskazanie na pierwszy wiersz macierzy A. Można również przypisać
wskaznikowi adres dowolnego innego wiersza, np.:
int (*q)[10] = &A[3]; // A[i] jest zmiennÄ… typu "int[10]"
Zmienna p jest wskaznikiem na wiersz - tablicę 1D zawierającą 10 elementów
typu int. Zauważmy, że w p/w kodzie wartości 10 nie można pominąć, gdyż
określa ona rozmiar bazowy wskazywanego obszaru pamięci. Znajomość tego
rozmiaru jest niezbędna, aby można było wykonać operacje na wskaznikach,
takie jak np. dodawanie liczby całkowitej: p + 2 , co daje w wyniku
wskazanie o 2 wiersze dalej niż wskazanie początkowe.
17
PDF created with pdfFactory Pro trial version www.pdffactory.com
Wykłady z programowania obiektowego f& Język C/C++
Ponieważ p wskazuje na wiersze macierzy będące tablicami 1D, więc
dereferencja postaci *(p+i) jest tablicą 1D, zatem można do jej elementów
uzyskać dostęp poprzez kolejną dereferencję, np.:
p++;
cout << *(*(p+2)+1); // => A[3][1] <=> *(*(A+3)+1)
Oznacza to, że dostęp do elementu tablicy 2D o indeksach i, j można uzyskać
na dwa sposoby:
a) poprzez zwykłe indeksowanie nazwy tablicy:
A[i][j]
b) poprzez podwójną dereferencję nazwy tablicy lub wskaznika jej wiersza:
*(*(A+i)+j) lub *(*(p+i)+j)
18
PDF created with pdfFactory Pro trial version www.pdffactory.com
Wyszukiwarka
Podobne podstrony:
w08 PodstPrzy roznor
KOMUNIKACJA PODSTPSYCH WYK2
podstprog01
w02 PodstPrzy zycie
podstprog02
podstpr
w04 PodstPrzy proddekomp
w07 PodstPrzy krajobrazy
podstprog04
podstprog10
podstprog09
w05 PodstPrzy cykle
podstprog06
w06 PodstPrzy klimat
więcej podobnych podstron