Algorytmika i Programowanie.
Wskazniki, tablice, referencje.
Zakład Zastosowań Informatyki
w In\ynierii Lądowej
Wydział In\ynierii Lądowej
Politechnika Warszawska
Sławomir Czarnecki
Wskazniki
" Istnieje w języku C++ typ zmiennych, które przechowują inny rodzaj
danych ni\ te, które normalnie słu\ą do przeprowadzania obliczeń.
" Ten typ zmiennych nazywamy wskaznikami.
" Ka\dy obszar w pamięci, który słu\y do przechowywania wartości
zmiennych definiowanych w naszych programach ma swój adres.
" Innymi słowy, adresy w sposób jednoznaczny określają poło\enia
komórek w pamięci operacyjnej, w których przechowywane,
przetwarzane i definiowane są w naszych programach zmienne i
funkcje.
" Wskaznik jest zmienną, która przechowuje adres innej zmiennej
określonego typu.
" Wskaznik ma swoją nazwę (jak ka\da inna zmienna) oraz typ, który
determinuje typ przechowywanej zmiennej do której się odnosi.
" Np. wskaznik, który przechowuje adres zmiennej przechowującej
liczby całkowite typu int nazywamy wskaznikiem na int .
Deklarowanie wskazników
" Deklaracja wskaznika jest podobna do deklaracji zwykłej zmiennej,
i ró\ni się tylko tym, \e wskaznik ma znak gwiazdki * przed swoją
nazwą, która symbolizuje właśnie wskaznik.
" Na przykład, aby zadeklarować wskaznik pNumber, który wskazuje
na zmienną typu double, nale\y to zrobić w następujący sposób:
double* pNumber;
" Znak gwiazdki * w tej deklaracji mo\e stać tu\ za nazwą typu do
którego się odnosi, tu\ przed identyfikatorem zmiennej lub w
w dowolnym innym miejscu pomiędzy nimi (odstępy nie mają
\adnego znaczenia), np. double *pNumber;
" Mo\emy tak\e w jednej linijce kodu mieszać deklaracje wskazników
z deklaracjami innych zmiennych (nawet z ich inicjalizacjami), np.
double* pNumber , Number = 3.14;
jednak lepiej (i bezpieczniej) jest deklarować wskazniki oddzielnie.
" Mając wskaznik, pNumber, typu wskaznik na double, mo\emy u\yć
go do przechowywania np. adresu naszej zmiennej Number.
" Lecz jak mo\emy otrzymać adres zmiennej?
" Do tego u\ywamy tzw. operatora adresu, &.
" Jest to operator unarny, który zwraca adres zmiennej przy
której stoi .
" Jest on tak\e nazwany operatorem referencji.
" Inicjalizacja wskaznika pNumber, mogłaby w naszym przypadku
wyglądać następująco:
pNumber = &Number;
" Zmienna pNumber przechowuje w tym momencie adres zmiennej
Number.
" Mo\emy oczywiście u\yć operatora & do otrzymania adresu
jakiejkolwiek innej zmiennej typu double i przechować go np.
w zmiennej pNumber.
Operator wyłuskania
" Operator wyłuskania * jest u\ywany wraz ze wskaznikiem w celu
zwrócenia wartości zmiennej przechowywanej pod adresem, który
zawiera właśnie ten wskaznik.
" Zwróćmy uwagę na fakt, \e operator * ma kilka ró\nych znaczeń,
które zale\ą od kontekstu, w którym został u\yty.
" Są to:
- operator mno\enia *,
- operator wyłuskania *,
- deklaracja wskaznika *.
" Kompilator oczywiście jednoznacznie interpretuje ka\de wystąpienie
znaku *, które zale\y od kontekstu.
#include
" Po zainicjalizowaniu
using namespace std; wskaznika pNumber,
void main() adresem zmiennej Number,
adres zmiennej Number
{
double* pNumber = NULL; oraz zawartość komórki pod
double Number = 3.14; tym adresem jest wyświetlona,
double newNumber = 10; przy wykorzystaniu operatora
pNumber=&Number; wyłuskania * .
cout<<"pNumber="<cout<<"*pNumber="<<*pNumber;
Number *= 2;
cout<<"*pNumber="<<*pNumber;
*pNumber *= 2;
cout<<"*pNumber="<<*pNumber;
pNumber = &newNumber;
cout<<"pNumber="<cout<<"*pNumber="<<*pNumber;
}
#include
" Wartość zmiennej Number
using namespace std;
jest podwojona:
void main()
Number *= 2;
{
i ponownie jej wartość
double* pNumber = NULL;
została wyświetlona przy
double Number = 3.14;
u\yciu operatora
double newNumber = 10;
wyłuskania * .
pNumber=&Number;
cout<<"pNumber="<cout<<"*pNumber="<<*pNumber;
Number *= 2;
cout<<"*pNumber="<<*pNumber;
*pNumber *= 2;
cout<<"*pNumber="<<*pNumber;
pNumber = &newNumber;
cout<<"pNumber="<cout<<"*pNumber="<<*pNumber;
}
#include
" Wartość zmiennej
using namespace std;
Number jest ponownie
void main()
podwojona, ale tym razem
{
przy u\yciu wskaznika i
double* pNumber = NULL;
operatora wyłuskania:
double Number = 3.14;
*pNumber *= 2;
double newNumber = 10;
a następnie jej wartość
pNumber=&Number;
została wyświetlona na
cout<<"pNumber="<ekranie.
cout<<"*pNumber="<<*pNumber;
Number *= 2;
cout<<"*pNumber="<<*pNumber;
*pNumber *= 2;
cout<<"*pNumber="<<*pNumber;
pNumber = &newNumber;
cout<<"pNumber="<cout<<"*pNumber="<<*pNumber;
}
#include " Adres zmiennej newNumber
using namespace std; jest teraz przechowywany
void main() w zmiennej pNumber, który
następnie wyświetlamy na
{
double* pNumber = NULL; ekranie wraz z zawartością
double Number = 3.14; komórki o tym adresie
double newNumber = 10; wykorzystując operator
pNumber=&Number; wyłuskania * .
cout<<"pNumber="<cout<<"*pNumber="<<*pNumber;
Number *= 2;
cout<<"*pNumber="<<*pNumber;
*pNumber *= 2;
cout<<"*pNumber="<<*pNumber;
pNumber = &newNumber;
cout<<"pNumber="<cout<<"*pNumber="<<*pNumber;
}
Tablice jednowymiarowe
" Tablicą jest ciągiem obiektów tego samego typu, które zajmują
ciągły obszar w pamięci i do których mo\na się odnosić przez
jedną nazwę.
" Deklaracja tablicy jest ró\ni się od deklaracji zwykłej zmiennej tylko
tym, \e dodatkowo umieszczamy w nawiasie kwadratowym,
umieszczonym bezpośrednio za nazwą tablicy, liczbę jej elementów.
" Rozmiarem tablicy jest liczba jej elementów.
" Dostęp do pojedyńczej składowej tablicy uzyskuje się poprzez
podanie nazwy tablicy zakończonej indeksem z przedziału
0 ... rozmiar tablicy 1
umieszczonym w parze nawiasów kwadratowych.
" Na przykład trzeci element w tablicy A będzie miał indeks A[2].
" Deklaracja 5-cio elementowej tablicy A przechowującej liczby
całkowite:
long A[5];
A[0]=12
A[1]=24
A[2]=18
A[3]=17
A[4]=35
" Pamiętajmy, \e indeksowanie rozpoczyna się
od 0, zatem ostatnim elementem w tablicy
5-cio elementowej jest A[4].
#include " Wymiar tablicy A u\yty do
using namespace std;
definicji rozmiaru tablicy jest
void main()
stałą zmienną DIM typu const.
{
" Zmieniając wartość tej stałej
const int DIM=5;
mo\emy w pewien sposób
int i=0;
long A[DIM]; (niezbyt jednak elastyczny)
for(i=0 ; i < DIM ; i++)
zmieniać rozmiary tablicy A.
{
cout<cin>>A[i];
A[i] += 100;
}
for(i=0;icout<}
" Przykład wygodnego sposobu inicjalizacji tablicy stałą wartością, np.
zerem
long B[5]={0};
" W tym momencie mamy zdefiniowaną 5-cio elementową tablicę,
której wszystkie składowe mają wartość 0.
" Wymiar tablicy przy jej definiowaniu mo\e być pominięty jeśli jej
inicjalizacja będzie przeprowadzona tak jak w poni\szym przykładzie
double C[] = {2.2 , 3.125 , 0.4};
w którym zdefiniowana została 3 elementowa tablica z
zainicjalizowanymi składowymi odpowiednio: 2.2 , 3.125 i 0.4.
Tablice wielowymiarowe
" Tablice mogą mieć więcej ni\ jeden indeks i są wtedy nazywane
tablicami wielowymiarowymi (macierzami dla 2 indeksów).
" Deklaracja 2-wymiarowej tablicy D o wymiarach 102 10
(macierzy 102 10) przechowującej zmienne rzeczywiste typu double:
double D[102][10];
" Pierwszy indeks oznacza liczbę wierszy a drugi liczbę kolumn w
macierzy D.
" Odwołanie się do elementu w trzecim wierszu i w piątej kolumnie,
np. nadanie mu wartości 125.3 odbywa się za pomocą instrukcji:
D[2][4]=125.3;
Dynamiczny sposób alokacji pamięci
" Praca wyłącznie ze stałą liczbą zmiennych w programie byłaby z
oczywistych powodów bardzo ograniczająca.
" W większości (szczególnie większych) aplikacji pojawia się potrzeba
operowania na zmiennej liczbie parametrów, która jest znana dopiero
po rozpoczęciu programu i dodatkowo liczba ta mo\e zmieniać się
w trakcie wykonywania programu (w zale\ności np. od początkowych
wartości parametrów).
" Szczególnie w przypadku tablic, deklarowanie ich wymiaru jeszcze
przed kompilacją programu jest absolutnie nie do zaakceptowania w
większości programów, szczególnie w obliczeniach numerycznych.
" W języku C++ istnieje mechanizm tzw. dynamicznego przydzielania
pamięci, który nie wymaga deklarowania, na przykład w przypadku
tablic, ich rozmiaru.
" Oczywiście, poniewa\ dynamicznie tworzone zmienne nie mogą być
zdefiniowane przed kompilacją, nie mogą one tym samym być
nazwane w kodzie naszego programu.
" Identyfikacja obiektów tworzonych dynamicznie związana będzie
ściśle z adresem pod którym będą one tworzone.
Sterta (Heap)
Obszar
" W większości przypadków,
pamieci
kiedy nasz program zostaje
zwany
uruchomiony, istnieje pewien
stosem
wolny i nieu\ywany obszar
w pamięci operacji nazywany
stertą (heap).
" W tym właśnie obszarze
mo\emy definiować
Obszar
dynamicznie zmienne (w tym
pamieci
tablice) u\ywając operatora
zwany
new, który zwraca adres
sterta
przydzielonej w ten sposób
pamięci do przechowywania
wartości zmiennych
określonego typu.
Adres
Zawartosc
" Z operatorem new jest ściśle związany operator delete, który zwalnia
zarezerwowaną wcześniej przez operator new pamięć (de-alokacja
pamięci).
" Pamięć mo\emy przydzielić w jednej części programu aby pózniej
w innej, części programu, gdy nie jest nam ju\ potrzebna, zwolnić ją,
co pozwala w wielu przypadkach pomimo skończonych zasobów
pamięci operacyjnej, zarządzać wielokrotnie większymi jej zasobami
ni\ wynikałoby to z rozmiaru (skończonych z konieczności) zasobów
RAM-u na danym komputerze.
Operatory new i delete
" Załó\my, \e chcemy zarezerwować miejsce w pamięci na
przechowywanie zmiennej typu double.
" Mo\emy zdefiniować zmienną typu wskaznik na double a następnie
za\ądać przydzielenia pamięci u\ywając operator new w następujący
sposób:
double* pNumber = NULL;//Wskaznik zainicjalizowany NULL
pNumber=new double;
" Operator new w drugiej linijce kodu powinien zwrócić adres w
pamięci na stercie, pod którym zarezerwowana została pamięć na
przechowywanie zmiennych typu double i adres ten zostaje
przechowany w zmiennej pNumber.
" Od tej pory inicjalizacja dynamicznie przydzielonej zmiennej typu
double, np. wartością 3.14 mo\e być dokonana następująco:
*pNumber=3.14;
albo jednocześnie mo\emy dokonać alokacji pamięci wraz z
inicjalizacją (jedna, zamiast trzech linijek kodu) za pomocą instrukcji
pNumber=new double(3.14);
" W momencie kiedy zmienna przydzielona dynamicznie nie jest nam
ju\ potrzebna, nale\y zawsze zwolnić przydzielony jej obszar
stosując operator delete w sposób następujący:
delete pNumber; //Zwolnienie pamięci spod adresu pNumber
" Obszar ten mo\e być w dalszej części programu wykorzystany na
przechowywanie innych zmiennych dynamicznych.
#include
Dynamiczna alokacja
using namespace std;
pamięci dla tablic
void main()
jednowymiarowych
{
double* A = NULL;
" Dynamiczne przydzielenie
int dim,i;
pamięci dla tablicy
cout<<"dim=";
jednowymiarowej (wektora)
cin>>dim;
A=new double[dim];
o dim składowych typu
cout<(A);
double, przy zało\eniu, \e
for(i=0;iA jest zmienną typu
A[i]=(static_cast(i))/(i+1);
for(i=0;iwskaznik na double:
cout<delete [] A;
A=new double[dim];
}
" Zwróćmy uwagę na fakt, \e
rozmiar dim wektora A jest
znany dopiero w momencie
uruchomienia programu, a
nie w chwili kompilacji.
#include
" Zwolnienie pamięci za
using namespace std;
pomocą operatora delete:
void main()
{
double* A = NULL;
delete [] A;
int dim,i;
cout<<"dim=";
" Zwróćmy uwagę na parę
cin>>dim;
A=new double[dim];
pustych nawiasów []
cout<(A);
kwadratowych umieszczoną
for(i=0;ipomiędzy operatorem
A[i]=(static_cast(i))/(i+1);
for(i=0;idelete a nazwą tablicy.
cout<" UWAGA !!! W nawiasach
delete [] A;
nie zamieszczamy wymiaru
}
likwidowanej tablicy.
#include
" U\yliśmy rzutowania typu
using namespace std;
reinterpret_cast(A);
void main()
do bezpiecznego rzutowania
{
double* A = NULL; nie związanych ze
int dim,i;
ze sobą typów: liczby
cout<<"dim=";
całkowitej (typu long) i
cin>>dim;
A=new double[dim]; wskaznika na liczbę
cout<(A);
zmiennoprzecinkową
for(i=0;i(typu double*)
A[i]=(static_cast(i))/(i+1);
for(i=0;icout<ekranie adresu A w bardziej
delete [] A;
czytelnej (dziesiętnej) postaci.
}
" Rzutowanie (static_cast(i))/(i+1); zostało natomiast u\yte
w celu otrzymania rzeczywistej wartości ilorazu i / (i+1) dwóch liczb
całkowitych.
#include
using namespace std;
void main()
{
double* A = NULL;
int dim,i;
cout<<"dim=";
cin>>dim;
A=new double[dim];
cout<(A);
for(i=0;iA[i]=(static_cast(i))/(i+1);
for(i=0;icout<delete [] A;
}
" Pamiętajmy o operatorze delete [] i jednocześnie pamiętajmy, \e
nadmiarowe np. podwójne u\ycie tego operatora prowadzi
najczęściej do przerwania programu i jego zawieszenia.
" Zdefiniujmy zmienną całkowitą dim
int dim = 4;
" Adres (wskaznik) A zainicjalizowany w instrukcji
double* A=new double[dim];
jest pierwszym, z serii dim adresów komórek zarezerwowanych w
pamięci z przeznaczeniem do przechowywania zmiennych typu double.
Adresy te to: a zmienne, które przechowują to:
! !
! !
! !
! !
A *A
A+1 *(A+1)
A+2 *(A+2)
... ...
A+(dim-1) *(A+(dim-1))
" W instrukcji:
A + i; to jest tak\e adres !!!
nie dodajemy dwóch liczb całkowitych (adresu A oraz liczby i) lecz
inkrementujemy adres A o i kolejnych pozycji dalej w pamięci
operacyjnej.
" A + i dla ró\nych i = 0,1,...,dim zmienia zatem adres komórki.
Adres Zawartosc
A
*A
A+1
*(A+1)
A+2
*(A+2)
...
A+i
*(A+i)
...
A+dim-1
*(A+dim-1)
" W ten sposób mo\emy w alternatywny sposób odnosić się do
składowych tablicy jednowymiarowej, a mianowicie:
#include
using namespace std;
void main()
{
int dim,i;
cout<<"dim=";
cin>>dim;
double* A = new double[dim];
for(i=0;i*(A+i)=(static_cast(i))/(i+1);
for(i=0;icout<delete [] A;
}
" Przyjrzyjmy się dwóm poni\szym instrukcjom i zastanówmy się co ...
... jest zwracane ? new double [m]; w tym przypadku ...
...a co jest zwracane ? new double* [m]; w tym przypadku.
" W new double [m]; operator new zwraca adres pierwszej komórki
w pamięci, spośród m kolejnych adresów komórek do przechowywania
zmiennych typu double.
" W new double* [m]; operator new zwraca adres pierwszej komórki
w pamięci, spośród m kolejnych adresów komórek do przechowywania
zmiennych typu double* (tzn. adresów komórek przeznaczonych do
przechowywania zmiennych typu double).
" Mo\emy zatem zapisać:
double* vec=new double[m];
double** mat=new double* [m];
double* vec=new double[m]; double** mat=new double* [m];
Adres Zawartosc
Adres Zawartosc
mat
vec *mat
*vec
mat+1
vec+1 *(mat+1)
*(vec+1)
mat+2
vec+2 *(mat+2)
*(vec+2)
mat+3
vec+3 *(vec+3) *(mat+3)
typ: double*
typ: double
(adres)
Model alokowania pamięci na stercie dla powy\szych przykładów
Adres Zawartosc
Adres Zawartosc
mat
vec *mat
*vec
mat+1
vec+1 *(mat+1)
*(vec+1)
mat+2
vec+2 *(mat+2)
*(vec+2)
mat+3
vec+3 *(vec+3) *(mat+3)
typ: double*
typ: double
(adres)
" Mo\emy np. wypełnić komórki o adresach vec+i (i=0,1,2,3) zerami:
for(i = 0 ; i < m ; i++) lub w innej notacji for(i = 0 ; i < m ; i++)
*(vec + i) = 0; vec[i] = 0;
Adres Zawartosc
Adres Zawartosc
mat
vec *mat
0
mat+1
vec+1 *(mat+1)
0
mat+2
vec+2 *(mat+2)
0
mat+3
vec+3 *(mat+3)
0
typ: double*
typ: double
(adres)
" Ale jak sensownie i poprawnie zainicjalizować zawartość komórek
mat+i ?
" Wiemy, \e komórki te przechowują zmienne typu double* tj. zmienna
*(mat+i) musi być adresem ...?... np. adresem nowego n wymiarowego
wektora, gdzie n jest dowolną liczbą naturalną np. liczbą kolumn w
macierzy m x n :
for(i = 0 ; i < m ; i++)
*(mat + i) = new double[n];
" W innej notacji mo\emy napisać:
for(i = 0 ; i < m ; i++)
mat[i] = new double[n];
zamiast
for(i = 0 ; i < m ; i++)
*(mat + i) = new double[n];
" Dla ka\dego i = 0,1,...,m-1, kolejny adres *(mat + i) (tj. mat[i])
jest zainicjalizowany wartością wskaznikiem na double adresem
pierwszej komórki z serii n adresów komórek:
*(mat + i) + j (j = 0,1,...,n-1)
przeznaczonych do przechowywania zmiennych typu double.
Przypadek: m = 4 , n = 3
Adres Zawartosc
mat *mat+1 *mat+2
*mat
mat+1 *(mat+1)+1 *(mat+1)+2
*(mat+1)
mat+2 *(mat+2)+1 *(mat+2)+2
*(mat+2)
mat+3 *(mat+3)+1 *(mat+3)+2
*(mat+3)
Zainicjalizowane adresy: i kolejne adresy: *(mat + i) + j
*(mat + i) = new double[n]; "i = 0,1,...,m-1=3
"i = 0,1,...,m-1=3 "j = 0,1,...,n-1=2
Zatem, utworzyliśmy
nową macierz
*mat *mat+1 *mat+2
adresów komórek
*(mat+1) *(mat+1)+1 *(mat+1)+2
przeznaczonych do
przechowywania
*(mat+2) *(mat+2)+1 *(mat+2)+2
zmiennych typu
*(mat+3) *(mat+3)+1 *(mat+3)+2
double.
Komórki *(mat + i) + j ( lub mat[i]+j )
*mat *mat+1 *mat+2
*(mat+1) *(mat+1)+1 *(mat+1)+2
*(mat+2) *(mat+2)+1 *(mat+2)+2
*(mat+3) *(mat+3)+1 *(mat+3)+2
przechowują zmienne *(*(mat + i) + j) ( lub *(mat[i]+j) lub mat[i][j] )
**mat *(*mat+1) *(*mat+2)
**(mat+1) *(*(mat+1)+1) *(*(mat+1)+2)
**(mat+2) *(*(mat+2)+1) *(*(mat+2)+2)
**(mat+3) *(*(mat+3)+1) *(*(mat+3)+2)
mat[0][0] mat[0][1] mat[0][2]
mat[1][0] mat[1][1] mat[1][2]
Klarowniejsza
mat[2][0] mat[2][1] mat[2][2]
(alternatywna) notacja
mat[3][0] mat[3][1] mat[3][2]
" Teraz mo\emy zainicjalizować składowe mat[i][j] (i=0,1,2,3 , j=0,1,2)
tej macierzy, np. zerami:
for(i = 0 ; i < m ; i++)
for(j = 0 ; j < n ; j++)
*(*(mat + i) + j) = 0; //lub w innej notacji mat[i][j] = 0;
mat[0][0] mat[0][1] mat[0][2] 0 0 0
mat[1][0] mat[1][1] mat[1][2] 0 0 0
=
mat[2][0] mat[2][1] mat[2][2] 0 0 0
mat[3][0] mat[3][1] mat[3][2] 0 0 0
Przypadek macierzy (dwu-wymiarowej tablicy)
z m = 4 wierszami i n = 3 kolumnami
" Pamiętajmy o operatorze delete [], który w przypadku macierzy nale\y
u\yć dwukrotnie w sposób pokazany poni\ej:
for(i=0;idelete [] mat[i]; //równowa\ne usunięciu kolumn macierzy
delete [] mat; //równowa\ne usunięciu wierszy macierzy
" Zauwa\my, \e macierz o m rzędach i n kolumnach mo\na zawsze
przedstawić jako jednowymiarową tablicę o wymiarze m n zgodnie
z poni\szą definicją:
double* v=new double[m*n];
for(i = 0 ; i < m ; i++)
for(j = 0 ; j < n ; j++)
{
k = i * n + j;
v[k] = mat[i][j];
}
for(k = 0 ; k < m*n ; k++)
cout<mat[0][0] mat[0][1] mat[0][2] mat[1][0] mat[1][1] mat[1][2] mat[2][0] mat[2][1] mat[2][2] mat[3][0] mat[3][1] mat[3][2]
v[0] v[1] v[2] v[3] v[4] v[5] v[6] v[7] v[8] v[9] v[11] v[12]
k = i * 3 + j = 1 * 3 + 2 = 5
Macierz z m = 4 wierszami i n = 3 kolumnami jako wektor mn
Istnieje zasadnicza ró\nica w generowaniu kodu wynikowego
przez kompilator dla instrukcji, w której pojawia się wyra\enie typu
a[i][j] w przypadku, gdyby macierz a została zadeklarowana raz jako
double a[m][n]; przypadek pierwszy (m , n = const)
a drugi raz jako
double** a; przypadek drugi
W pierwszym przypadku (deklaracja double a[m][n]) wygenerowany
kod jest oparty o rozumowanie następujące:
do adresu *a dodaj n i, a następnie do tak otrzymanego adresu dodaj j,
zwracając wartość przechowywaną pod tym adresem, czyli wyłuskaj
*(*a + n i + j) .
W drugim przypadku, rozumowanie jest następujące:
do adresu a dodaj i, wez spod tak otrzymanego adresu a + i wartość,
która jest nowym adresem *(a + i), a następnie do tego nowego adresu
dodaj j, otrzymując kolejny nowy adres i zwróć na koniec wartość
przechowywaną pod tym właśnie adresem, czyli wyłuskaj
*(*(a + i) + j) .
" Zauwa\my, \e w pierwszym przypadku dynamicznego tworzenia,
tablicy, wymagana jest w momencie deklaracji macierzy a znajomość
liczby jej kolumn jeszcze przed kompilacją, tzn.
int m=4;
const int n=3;
double (*a) [n] = new double[m][n];
...
delete [] a;
" W drugim przypadku ani znajomość liczby kolumn ani tym bardziej
znajomość liczby wierszy deklarowanej macierzy a nie jest konieczna
przed kompilacją programu.
" Tworzenia dynamiczne tablic według schematu z pierwszego
przypadku, nie jest zatem w pełni dynamicznym sposobem tworzenia
macierzy, bowiem liczba kolumn w tej wersji musi być znana jeszcze
przed kompilacją.
Krótkie podsumowanie jak definiujemy tablice dwuwymiarowe w C++
void main(void)
{
int i,j;
//Zmienne M i N są const !!!
const int M = 20; //liczba M wierszy
const int N = 10; //liczba N kolumn
double A[M][N]; //definicja macierzy A[M][N]
//Zmienne m i n nie są const, inicjalizacja w czasie wykonywania programu !!!
int m = M; //liczba m wierszy
int n = N; //liczba n kolumn
double** a; //deklaracja wskaznika na wskaznik
a = new double*[m]; //równowa\ne utworzeniu m wierszy
for(i = 0 ; i < m ; i++)
a[i]=new double[n]; //równowa\ne utworzeniu n kolumn
for(i = 0 ; i < m;i++)
for(j = 0 ; j < n ; j++)
A[i][j]=a[i][j]=0;
//... jakiś kod
for(i = 0 ; i < m ; i++)
delete [] a[i]; //równowa\ne likwidacji kolumn
delete [] a; //równowa\ne likwidacji wierszy
}
Wzmianka o mo\liwości u\ycia tablic definiowanych nie-dynamicznie
w roli parametrów wywołania funkcji, w których argumentami są
wskazniki na wskaznik na dany typ.
Parametrem funkcji wykorzystującej tablice dwuwymiarowe, jest
z reguły wskaznik na wskaznik na dany typ, np.
void DrukujMacierz(double** a,int m,int n)
{
cout<<"\nMacierz "<for(int i=0;i{
for(int j=0;jcout<cout<}
}
jest funkcją wyświetlającą na ekranie składowe a[i][j]
(0 <= i <= m 1 , 0 <= j <= n 1) dowolnej
(utworzonej dynamicznie) macierzy typu double** a .
Czy mo\na wywołać taką funkcję z identyfikatorem A, który u\yty
został do zdefiniowania macierzy A[M][N] ?
const int M = 20;
const int N = 10;
double A[M][N];
Odpowiedz brzmi: nie mo\na, czyli wywołanie funkcji:
DrukujMacierz(A,M,N);
będzie błędne (DrukujMacierz(a,M,N); jest oczywiście
poprawne)
UWAGA !!!
Mo\na jednak, stosując pewien prosty chwyt , wykorzystać tego typu
funkcje, dla macierzy o stałej liczbie wierszy i kolumn, po uprzednim
zdefiniowaniu i odpowiednim zainicjalizowaniu pomocniczego wektora
adresów do wierszy, w sposób pokazany na kolejnym slajdzie.
//Przykład (*)
void main(void)
{
const int M = 20;
const int N = 10;
double A[M][N];
for(int i=0;ifor(int j=0;jA[i][j]=0;
double* w[M];
for(int i = 0;i < M;i++)
w[i] = &A[i][0];
(*) przykład, będący rozwiązaniem
DrukujMacierz(w,M,N);
problemu z poprzedniego slajdu,
pokazał mi Dr In\. Tomasz Sokół
}
z Zakładu Zastosowań Informatyki
w In\ynierii Lądowej PW
Referencje
" Referencja ma wiele cech wspólnych ze wskaznikiem.
" Istotne ró\nice ujawniają się dopiero w kontekście u\ycia referencji
jako parametrów funkcji i wartości przez nich zwracanych oraz w
kontekście programowania obiektowego.
" Referencje wzbogacają dodatkowo wiele charakterystycznych
technik programistycznych języka C++, które zdecydowanie
odró\niają ten język od pozostałych współczesnych języków
programowania.
" Same wskazniki (bez referencji) nie wystarczyłyby do
zaproponowania szerokiego wachlarza, nieznanych w innych
językach, rozwiązań programistycznych, które stanowią o sile
współczesnego języka C++.
Co to jest referencja?
" Referencja, mówiąc najprościej, jest przezwiskiem innej istniejącej
ju\ zmiennej.
" Referencja ma nazwę i mo\e być u\yta w ka\dym miejscu oryginalnej
zmiennej, którą reprezentuje.
Deklarowanie i inicjalizacja referencji
" Dla zmiennej
long number = 0;
mo\emy zadeklarować referencję do tej zmiennej w następujący
sposób:
long& rnumber= number; //Deklaracja referencji do zm. number
" Znak ampersandu & za słowem kluczowym long i przed nazwą
rnumber oznacza, \e deklarowana jest zmienna typu referencyjnego,
która reprezentuje zmienną number, co zostało wyspecyfikowane
u\yciem znaku równości i następującemu po nim przypisaniu.
" Odtąd, rnumber jest referencją na typ long.
" Zmienna ta, od tej pory mo\e być u\yta wszędzie tam gdzie pojawić
by się mogła zmienna number.
" Na przykład w instrukcji ,
rnumber += 10;
mamy efekt zwiększenia wartości zmiennej number o 10.
" Dla kontrastu wskaznik pnumber, mo\emy zadeklarować i
zainicjalizować w następujący sposób:
long* pnumber = &number;//Inicjalizacja wskaznika adresem
" Analogicznie, chcąc uzyskać taki sam efekt jak poprzednio, mo\emy
u\yć wskaznika w następujący (nieco jednak bardziej skomplikowany)
sposób:
*pnumber += 10; //Inkrementacja liczby przy u\yciu wskaznika
" Warto podkreślić pewne subtelne ró\nice pomiędzy wskaznikiem a
referencją.
" U\ywając wskaznika, musimy z reguły u\ywać równolegle
operatora wyłuskania w celu inicjalizowania lub modyfikowania
wartości, która jest aktualnie przechowywana pod tym adresem.
" U\ywając referencji nie musimy u\ywać ju\ \adnego dodatkowego
operatora de-referencji (bo i nie ma takiego operatora).
" W pewnym sensie, referencja zachowuje się, w momencie jej u\ycia,
jak wskaznik, do którego w sposób niejawny, u\yto ju\ wcześniej
operator wyłuskania.
" Referencja jawi się jako alternatywny sposób odnoszenia się do
zdefiniowanej ju\ wcześniej (gdzieś w programie) zmiennej.
" Są jeszcze inne ciekawe własności referencji, o których powiemy
jeszcze nieco pózniej.
Wyszukiwarka
Podobne podstrony:
AiP wyklad01
AiP wyklad05
AiP wyklad04
AiP wyklad02
Sieci komputerowe wyklady dr Furtak
Wykład 05 Opadanie i fluidyzacja
WYKŁAD 1 Wprowadzenie do biotechnologii farmaceutycznej
mo3 wykladyJJ
ZARZĄDZANIE WARTOŚCIĄ PRZEDSIĘBIORSTWA Z DNIA 26 MARZEC 2011 WYKŁAD NR 3
Wyklad 2 PNOP 08 9 zaoczne
Wyklad studport 8
Kryptografia wyklad
Budownictwo Ogolne II zaoczne wyklad 13 ppoz
więcej podobnych podstron