Informatyka dla klas II

Tablice dynamiczne

Wskaźniki

Informacja przechowywana jest w pamięci RAM w postaci bitów umieszczanych w komórkach (ang memory cell), których mogą być miliardy. Aby komputer mógł uzyskiwać w prosty sposób dostęp do każdej komórki pamięci, zostały one ponumerowane. Numery komórek nazywamy adresami komórek pamięci (ang. memory cell address). Poniżej przedstawiamy fragment logicznej struktury pamięci (czyli tak, jak widzi swoją pamięć komputer):

 

Pamięć
Adres Zawartość komórki
0 11000110
1 00001111
2 11000011
3 11111110
4 00000001
5 11100111
... ...

 

Ze względów ekonomicznych poszczególne komórki pamięci przechowują grupę kilku bitów (najczęściej jest ich 8 - czyli 1 bajt, ale rozmiar bitowy komórki pamięci zależy od architektury systemu komputerowego). W przykładzie powyżej komórka o adresie 3 przechowuje 8 bitów o zawartości 11111110.

Wskaźnik (ang. pointer) jest zmienną, która przechowuje adres, pod którym w pamięci umieszczone są dane. W języku C++ wskaźniki posiadają typy, mówimy np. o wskaźniku do danych typu int lub o wskaźniku do danych typu double. Typ wskaźnika określa rodzaj wskazywanego obiektu. Wskaźnik definiujemy następująco:

 

typ  * wskaźnik;

typ  –  określa typ wskazywanego przez wskaźnik obiektu.
*  –  operator, który mówi, iż zmienna jest wskaźnikiem i zawiera adres danych a nie same dane.
wskaźnik  –  zmienna, która będzie mogła przechowywać adresy. W systemie 32-bitowym wskaźniki są 32-bitowe, tzn. adresy zapisywane są za pomocą 4 bajtów.

 

Przykłady:

 

int * a;    // zmienna a jest wskaźnikiem do danych całkowitych ze znakiem
double * y; // zmienna y jest wskaźnikiem do danych typu double

 

W zmiennej typu wskaźnik umieszcza się zwykle tylko adresy. Zatem instrukcja przypisania ma zwykle postać:

 

wskaźnik  = adres;

 

wskaźnik  –  zmienna typu wskaźnik.
adres  –  wyrażenie, które jest adresem - co więcej wskazywany przez ten adres obiekt musi być taki sam jak typ adresu wskaźnika. Poza bardzo nielicznymi wyjątkami bez sensu jest przypisywanie np. wskaźnikowi do danych int adresu do danych typu double.

 

Na przykład wskaźnikowi można przypisać adres innej zmiennej wykorzystując operator adresu &:

 

wskaźnik = & zmienna;

 

Dostęp do danych zapisanych w pamięci wskazywanej przez wskaźnik uzyskujemy poprzez operator *, który umieszczamy przed zmienną wskazującą:

 

* wskaźnik  - oznacza wskazywany przez wskaźnik obiekt.

 

* wskaźnik  = wyrażenie// wartość wyrażenia trafi do obiektu wskazywanego przez wskaźnik

 

// Wskaźniki
// program demonstruje dostęp
// do danych poprzez wskaźnik
// (C)2015 I LO w Tarnowie
//------------------------

#include <iostream>

using namespace std;

int main()
{
    int a,b;

    a = 15;  b = 287;

    int * p; // definiujemy wskaźnik

    p = & a; // p wskazuje a

    cout << * p << endl;

    p = & b; // p wskazuje b

    cout << * p << endl;

    return 0;
} 
 
// Wskaźniki
// program demonstruje modyfikację
// danych wskazywanych przez wskaźnik
// (C)2015 I LO w Tarnowie
//------------------------

#include <iostream>

using namespace std;

int main()
{
    int a,b;
    
    a = 15; b = 287;

    cout << a << " " << b << endl;

    int * p;  // definiujemy wskaźnik

    p = & a;  // p wskazuje a

    * p = 3;  // zmieniamy a

    cout << a << " " << b << endl;

    p = & b;  // p wskazuje b

    * p = 8;  // zmieniamy b

    cout << a << " " << b << endl;

    return 0;
}

 

Dane dynamiczne

W pamięci komputera możemy rezerwować obszary na przechowywanie danych określonego typu. Do tego celu służy operator new, który stosujemy w sposób następujący:

 

wskaźnik  = new typ;
new  –  tworzy obszar pamięci i zwraca jego adres, który zostaje umieszczony we wskaźniku.
typ  –  określa rodzaj informacji, która będzie przechowywana w utworzonym przez new obszarze. Typ ten musi być zgodny z typem obiektów wskazywanych przez wskaźnik.

 

Po wykorzystaniu obszar pamięci można zwrócić do puli systemu za pomocą polecenia:

 

delete wskaźnik;

 

Wskaźnik w powyższym poleceniu musi zawierać adres obszaru utworzonego przez new. Zwrócona pamięć może zostać wykorzystana do innych celów.

 

// Wskaźniki
// program demonstruje daną dynamiczną
// (C)2014 I LO w Tarnowie
//------------------------

#include <iostream>

using namespace std;

int main()
{
    double * p;     // definiujemy wskaźnik

    p = new double; // przydzielamy pamięć

    * p = 3.1415;   // w przydzielonej pamięci umieszczamy dane

    cout << * p << endl;

    delete p;       // zwalniamy przydzielony obszar pamięci

    return 0;
} 

 

Tablice dynamiczne

Tablica dynamiczna (ang. dynamic array) jest tworzona w czasie uruchomienia programu. Jej rozmiar może być wyliczany. Co więcej, gdy przestanie być potrzebna możemy ją usunąć z pamięci. Dzięki tym własnościom program efektywniej wykorzystuje zasoby pamięciowe komputera.

 

Tworzenie i wykorzystanie tablicy dynamicznej

  1. Definiujemy zmienną wskaźnikową, która będzie przechowywała adres pierwszego elementu tablicy. Jest to zwykła definicja wskaźnika:

    typ  * wskaźnik;
  2. Przydzielamy obszar pamięci dla tablicy. Stosujemy następującą konstrukcję:

    wskaźnik  = new typ[liczba_elementów];

    liczba_elementów  określa rozmiar tablicy. Może być to dowolne wyrażenie arytmetyczne.
  3. Do elementów tak utworzonej tablicy odwołujemy się poprzez ich indeksy:

    wskaźnik[indeks]...

    indeks  - powinien być w zakresie od 0 do liczba_elementów  - 1. Kompilator nie sprawdza, czy element o danym indeksie znajduje się faktycznie w tablicy. Należy zachować ostrożność.
  4. Gdy tablica dynamiczna przestanie być potrzebna, usuwamy ją z pamięci za pomocą instrukcji:

    delete [] wskaźnik;

    Po tej operacji obszar pamięci zajęty przez tablicę zostaje zwrócony do systemu. Wskaźnik można wykorzystać ponownie do innej tablicy wg powyższych punktów.
// Tablica dynamiczna
// program demonstruje tworzenie tablicy dynamicznej
// oraz dostęp do jej elementów
// (C)2014 I LO w Tarnowie
//------------------------

#include <iostream>

using namespace std;

int main()
{
    int i,n;
    int * T;        // wskaźnik tablicy dynamicznej

    cin >> n;       // odczytujemy rozmiar tablicy

    T = new int[n]; // rezerwujemy obszar pamięci

    // tablicę wypełniamy kolejnymi liczbami parzystymi

    for(i = 0; i < n; i++) T[i] = 2 + i + i;

    // wyświetlamy zawartość komórek tablicy dynamicznej

    for(i = 0; i < n; i++) cout << T[i] << endl;

    // zwalniamy przydzielony obszar

    delete [] T;

    return 0;
}

 

Odczyt tablicy dynamicznej ze standardowego wejścia

Tablicę dynamiczną można tworzyć z danych odczytanych ze standardowego wejścia. W tym celu dane te powinny posiadać określoną postać. Umówmy się, iż pierwsza liczba n  będzie określała liczbę komórek tablicy dynamicznej, a następne n liczb będzie zawartością tych komórek.

Przykładowe dane:

 

25
76 139 19 25 31 49 93 101 227 38 62 915 428 253 111 9 73 85 52 167 428 239 924 189 332

 

Skopiuj powyższe dane do schowka. Następnie uruchom poniższy program:

 

// Tablica dynamiczna
// program demonstruje odczyt tablicy
// (C)2014 I LO w Tarnowie
//------------------------

#include <iostream>

using namespace std;

int main()
{
    int i,n;
    int * T;        // wskaźnik tablicy dynamicznej

    cin >> n;       // odczytujemy rozmiar tablicy

    T = new int[n]; // rezerwujemy obszar pamięci

    // odczytujemy dane dla kolejnych komórek tablicy

    for(i = 0; i < n; i++) cin >> T[i];

    // wyświetlamy zawartość komórek tablicy dynamicznej

    for(i = 0; i < n; i++) cout << T[i] << endl;

    // zwalniamy przydzielony obszar

    delete [] T;

    return 0;
} 

 

Po uruchomieniu programu klikamy prawym przyciskiem myszki w pasek tytułowy okna konsoli. Z menu wybieramy opcję Edytuj → Wklej (być może trzeba będzie jeszcze wcisnąć klawisz Enter). Dane ze schowka zostaną wklejone do okna konsoli, program je odczyta i przetworzy w tablicy dynamicznej. Ten sposób pozwala nam szybko i wielokrotnie wprowadzać większą porcję danych do programu bez konieczności ich wpisywania z klawiatury.

Jeśli danych jest bardzo dużo, to lepszym rozwiązaniem będzie przekierowanie standardowego wejścia. Wyszukaj na dysku katalog, w którym zapisany jest plik programu. Katalog ten będzie w katalogu projektowym pod nazwą bin/Debug - dla wersji uruchomieniowej lub bin/Release - dla wersji ostatecznej. Zapisz w tym katalogu plik z danymi, np. pod nazwą d.txt.

Uruchom okno poleceń Windows (w menu Start wybierz opcję Uruchom i wpisz cmd). Za pomocą poleceń cd przejdź do katalogu z programem. Następnie wpisz:

 

./nazwa_programu < d.txt

 

Program będzie odczytywał dane z pliku d.txt zamiast z okna konsoli. Standardowe wyjście również można przekierować do pliku:

./nazwa_programu > out.txt


Teraz program zamiast pisać dane do okna konsoli, umieści je w pliku out.txt. Plik ten można odczytać np. za pomocą notatnika Windows i spokojnie przejrzeć sobie treść - szczególnie przydatne, gdy program produkuje dużo danych i nie mieszczą się one w oknie konsoli.

Można jednocześnie przekierować i wejście, i wyjście danych:

 

./nazwa_programu  < d.txt > out.txt


Teraz program odczyta dane z pliku d.txt, a wyniki zapisze w pliku out.txt. Zwróć uwagę, iż w samym programie nie musisz robić

 


   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2024 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.

Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl

W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe