• Nie Znaleziono Wyników

Wykład 9 Statyczne i dynamiczne tablice struktur

N/A
N/A
Protected

Academic year: 2021

Share "Wykład 9 Statyczne i dynamiczne tablice struktur"

Copied!
1
0
0

Pełen tekst

(1)

Wykład 9

Statyczne i dynamiczne tablice struktur 1. Abstrakcyjne typy danych

2. Zastosowanie abstrakcyjnych typów danych do projektowania tablic w dziedzinie operacji

3. Dodatek

3.1. Elementy języka C++ wspomagające abstrakcję danych

3.2. Paradygmaty programowania (wg B. Stroustrup . Język C++) 3.3. Pliki nagłówkowe

3.4. Programy wieloplikowe. Dyrektywy preprocesora i kompilacja wieloplikowa (projekty)

3.5. Dynamiczny przydział pamięci

(2)

1. Abstrakcyjne typy danych

1.1.Główne cechy typów danych stosowanych w programach:

1) muszą być dostosowane do rozwiązywanego problemu 2) muszą zawierać dwa rodzaje informacji:

2.1) zbiór własności 2.2) zbiór działań.

Np. typ int

własności: reprezentuje liczby całkowite o wartościach od -32768 do +32767 zakodowanych w kodzie dwójkowym na dwóch bajtach

działania: zmiana znaku, dodawanie, mnożenie, dzielenie, modulo...

1.2. Trzy etapy procesu definiowania typów przez programistę [wg Stephen Prata - Szkoła programowania, Język C]:

1) Przygotowanie opisu ADT (abstrakcyjnego typu danych).

Abstrakcyjny opis obejmuje własności typu i operacji, jakie można na nim wykonać.

Opis ten nie powinien być związany z żadną konkretną implementacją i językiem programowania. Taka formalna charakterystyka nosi nazwę abstrakcyjnego typu danych ADT.

2) Opracowanie interfejsu programistycznego realizującego ADT -

Jest to wskazanie sposobu przechowywania danych i opisanie zbioru funkcji wykonujących potrzebne operacje. W języku C/C++ etap ten może polegać na utworzeniu definicji struktury i prototypów funkcji przetwarzających. Funkcje te pełnią dla nowego typu tę samą rolę, co operatory w przypadku podstawowych typów języka C/C++. Utworzony w ten sposób interfejs będzie stosowany przez osoby, które chcą skorzystać z nowego typu danych

3) Pisanie kodu implementującego interfejs

Ten krok jest kluczowy, w którym należy szczegółowo zrealizować wymagania wynikające z opisu. Należy zauważyć, że programista korzystający z interfejsu nie musi już orientować się w szczegółach implementacji

Wniosek: Utworzony typ danych, definiowany przez programistę stanowi pewien kompletny element języka, który może być używany wielokrotnie w programach.

Jedyny właściwy sposób wykorzystania nowego typu odbywa się za

pośrednictwem funkcji z interfejsu typu. Nie należy bezpośrednio odwoływać się

do zmiennych reprezentujących daną zdefiniowanego typu.

(3)

2.1. Definiowanie listy nieuporządkowanej jako statycznej tablicy struktur metodą ADT

Etap 1 - Opis ADT

Nazwa typu - Statyczna lista nieuporządkowanych elementów

Własności typu: Potrafi przechować ciąg elementów o ograniczonym rozmiarze Dostępne działania:

Inicjalizacja listy

Określenie, czy lista jest pełna Określenie, czy lista jest pusta Wyszukanie elementu

Dodanie elementu wewnątrz, na początku i na końcu listy, Usuwanie elementu wewnątrz, na początku i na końcu listy, Przejście przez listę i przetwarzanie każdego elementu

Przetwarzanie wyszukanego elementu Etap 2 - Budowa interfejsu

void Inicjalizacja (int & ile);

/* działanie: inicjuje listę

warunki wstępne: lista nie jest zainicjowana

warunki końcowe: lista zostaje zainicjowana jako pusta, ile jest równe 0 i jest numerem elementu pustego. Dopiero po wywołaniu tej funkcji prawdziwe są definicje pozostałych funkcji */

int Pusta (int ile);

/*działanie: określa czy lista jest pusta

warunki wstępne: ile jest numerem ostatniego elementu wstawionego do listy

warunki końcowe: funkcja zwraca wartość 1, gdy lista jest pusta, w przeciwnym wypadku zwraca 0*/

int Pelna (int ile);

/*działanie: określa czy lista jest pełna

warunki wstępne: ile jest numerem ostatniego elementu wstawionego do listy

warunki końcowe: funkcja zwraca wartość 1, gdy lista jest pełna, w przeciwnym wypadku zwraca 0*/

int Szukaj(int ile, int ktory);

/*działanie: szuka wskazanego elementu

warunki początkowe: który jest numerem szukanego elementu, ile jest numerem ostatniego elementu przy usuwaniu lub większym o 1 przy wstawianiu

warunki końcowe: funkcja zwraca 1, gdy znaleziono element o numerze ktory spełniający warunki 1<=ktory<=ile, w przeciwnym wypadku zwraca 0 */

(4)

void Wstaw(OSOBA tab[], OSOBA dane, int ktory, int &ile);

/*działanie: dodaje element na początku, wewnątrz lub na końcu listy

warunki początkowe: ile jest numerem ostatnio wstawionego elementu do listy lub elementu pustego, wynik funkcji Pelna po podstawieniu wartości ile jest równy 0, który jest numerem miejsca do wstawienia podanym przez funkcję Szukaj z wynikiem 1 dla ile=ile+1, który oznacza numer miejsca za ostatnio wstawionym elementem lub pustym

warunki końcowe: funkcja dodaje element na wskazanym miejscu ktory po rozsunięciu elementów lub na końcu ciągu, zwiększa o 1 numer ostatniego elementu ile */

void Usun(OSOBA tab[], int ktory, int &ile);

/*działanie: usuwa element należący do listy

warunki początkowe: wynik funkcji Pusta po podstawieniu wartości ile jest równy 0, ile jest numerem ostatniego elementu wstawionego do listy, który jest numerem elementu do usunięcia podanym przez funkcje Szukaj z wynikiem 1,

warunki końcowe: funkcja usuwa element na wskazanym miejscu ktory przez zsunięcie lub ostatni element ciągu, zmniejsza liczbę elementów w ile */

void Usun_tablice(int &ile);

/*działanie: kasuje liczbę elementów i inicjuje tablicę

warunki początkowe: tablica jest pusta lub niepusta, ile oznacza numer ostatniego elementu wstawionego do listy lub elementu pustego

warunki końcowe: ile jest numerem pustym, równym 0*/

void Dla_kazdego(OSOBA tab[], int ile, zrob);

/*działanie: wykonuje funkcje na każdym wstawionym elemencie do listy

warunki początkowe: wynik funkcji Pusta po podstawieniu wartości ile jest równy 0, ile jest numerem ostatniego elementu wstawionego do listy, zrob jest typem funkcji, która przetwarza element listy i nie zwraca wartości.

warunki końcowe: funkcja typu zrob jest wykonywana tylko raz dla każdego elementu wstawionego do listy*/

void Dla_jednego(OSOBA tab[], int ile, int ktory, zrob);

/*działanie: wykonuje funkcje na wybranym elemencie wstawionym do listy

warunki początkowe: wynik funkcji Pusta po podstawieniu wartości ile jest równy 0, ile jest numerem ostatniego elementu wstawionego do listy, zrob jest typem funkcji, która przetwarza element listy o numerze ktory podanym przez funkcje Szukaj z wynikiem 1 i nie zwraca wartości.

warunki końcowe: funkcja typu zrob jest wykonywana tylko raz dla elementu o numerze ktory*/

(5)

Etap 3 - Implementacja

 definicja typu OSOBA w pliku nagłówkowym strukt.h

#ifndef STRUKT

#define STRUKT const int DL=10;

struct OSOBA {

int Numer;

char Nazwisko[DL];

};

#endif

 zawartość pliku nagłówkowego we_wy.h zawierającego deklaracje uniwersalnych funkcji we/wy dla struktur typu OSOBA

#ifndef WE_WY

#define WE_WY

#include "strukt.h"

void Pokaz_dane (OSOBA &Dana);

OSOBA Dane();

#endif

 zawartość pliku modułowego we_wy.cpp zawierającego definicje uniwersalnych funkcji we/wy dla struktur typu OSOBA

#include <conio.h>

#include <stdlib.h>

#include <string.h>

#include "we_wy.h"

#include "strukt.h"

OSOBA Dane() {char bufor[DL+2];

OSOBA Nowy;

bufor[0]=DL;

cprintf("\r\nnumer: ");

cgets(bufor);

Nowy.Numer=atoi(bufor+2);

cprintf("\r\nnazwisko: ");

strcpy(Nowy.Nazwisko,cgets(bufor));

return Nowy;}

void Pokaz_dane(OSOBA &Dana)

{cprintf("\r\nNumer: %d\r\n", Dana.Numer);

cprintf("Nazwisko: %s\r\n", Dana.Nazwisko);

cprintf("Nacisnij dowolny klawisz...\r\n"); getch();}

(6)

 zawartość pliku nagłówkowego tablica1.h z deklaracjami funkcji interfejsowych defniowanej statycznej tablicy struktur jako listy nieuporządkowanej

#ifndef TABLICA

#define TABLICA

#include "strukt.h"

const int ROZ=5;

typedef void(*zrob)(OSOBA &);

void Inicjalizacja(int& ile);

int Pelna(int ile);

int Pusta(int ile);

int Szukaj(int ile, int ktory);

void Wstaw(OSOBA*tab, OSOBA dane, int ktory, int &ile);

void Usun(OSOBA*tab, int ktory, int &ile);

void Usun_tablice(int &ile);

void Dla_kazdego(OSOBA*tab, int ile, zrob);

void Dla_jednego(OSOBA*tab, int ktory, zrob);

#endif

 zawartość pliku modułowego tablica1.cpp z definicjami funkcji interfejsowych

#include "tablica1.h"

void Inicjalizacja(int & ile) {ile=0;}

int Pelna(int ile) {return ile==ROZ;} //wy=1 tablica pelna, wy=0 jest miejsce

int Pusta(int ile) {return ile==0;} //wy=1 tablica pusta, wy=0 są elementy

int Szukaj(int ile, int ktory)

{return ktory>=1 && ktory<=ile;} // wy-1 znalazl; wy = 0 nie

void Wstaw(OSOBA* tab, OSOBA dane, int ktory,int &ile)

{ for (int i=ile; i>=ktory; i--) // założenia: 0<=ile <N i 1<=ktory<=ile+1

tab[i]=tab[i-1];

tab[ktory-1]=dane;

ile++;}

void Usun(OSOBA* tab, int ktory, int &ile)

{ for (int i=ktory-1; i<ile-1; i++) // założenia: 0<ile <=N i 1<=ktory<=ile

tab[i]=tab[i+1];

ile--;}

void Usun_tablice (int &ile) { Inicjalizacja(ile); } void Dla_kazdego(OSOBA* tab, int ile, zrob funkcja)

{for (int i=0; i<ile;i++) funkcja(tab[i]);} //wykonaj czynność zrob na zawartości tablicy

void Dla_jednego(OSOBA* tab, int ktory, zrob funkcja)

(7)

{ funkcja(tab[ktory-1]);} //wykonaj czynność zrob dla elementu tablicy

(8)

p

rzykładowa aplikacja wykorzystująca listę nieuporządkowaną reprezentowaną przez statyczną tablicę struktur (tablist1.cpp)

#include <conio.h>

#include <stdlib.h>

#include "tablica1.h"

#include "dodatki.h" //uniwersalne funkcje obsługujące menu i komunikaty

#include "we_wy.h" //uniwersalne funkcje we/wy dla struktur typu OSOBA

char *Polecenia[POZ]={"Tablica OSOBA tab[Roz] - obsluga typu lista", " Nacisnij:",

" 1 - aby wstawic element do listy", " 2 - aby usunac element z listy",

" 3 - aby wyswietlic wybrany element z listy", " 4 - aby wyswietlic liste, ",

" 5 - aby usunac liste",

" > 5 - aby zakonczyc prace."};

// funkcje pośrednio przetwarzające tab i ile za pomocą funkcji z modulu tablica1

void Wstaw_(OSOBA*tab,int &ile);

void Usun_(OSOBA*tab, int &ile);

void Pokaz_jeden(OSOBA*tab,int ile);

void Pokaz_(OSOBA*tab,int ile);

// funkcja ogólnego przeznaczenia

int Losuj(int zakres);

void main(void) {int ile;

OSOBA tab[ROZ];

char Co;

randomize();

Inicjalizacja(ile);

do

{Co = Menu(POZ,Polecenia);

switch(Co)

{case '1' : Wstaw_(tab,ile); break;

case '2' : Usun_(tab,ile); break;

case '3' : Pokaz_jeden(tab,ile); break;

case '4' : Pokaz_(tab,ile); break;

case '5' : Usun_tablice(ile); break;

default : Komunikat("\r\nKoniec programu");

}

}while (Co < '6' && Co>'0'); }

(9)

int Losuj(int zakres) //model mylącego się użytkownika programu

{ int ktory=random(zakres);

cprintf("\r\n\nNumer elementu: %d",ktory); getch();

return ktory; }

void Pokaz_jeden(OSOBA* tab,int ile) //obsługa wyszukania

{

if (Pusta(ile)) Komunikat("\r\nTablica pusta");

else

{int ktory=Losuj(ile+4);

if (Szukaj(ile,ktory)) Dla_jednego(tab,ktory,Pokaz_dane);

else Komunikat("\r\nPodano zly numer"); } }

void Wstaw_(OSOBA*tab,int &ile) //obsługa wstawiania

{

if (Pelna(ile)) Komunikat("\r\nPelna tablica");

else

{int ktory=Losuj(ile+4);

if (!Szukaj(ile+1,ktory)) Komunikat("\r\nPodano zly numer");

else

{cprintf("\r\nPodaj tab[%d]",ktory-1);

Wstaw(tab,Dane(),ktory,ile); } } }

void Usun_(OSOBA* tab,int &ile) //obsługa usuwania

{

if (Pusta(ile)) Komunikat("\r\nTablica pusta");

else

{ int ktory=Losuj(ile+4);

if (!Szukaj(ile,ktory)) Komunikat("\r\nPodano zly numer");

else

{ Usun(tab,ktory,ile);

Komunikat("\r\nUsunieto element"); } } }

void Pokaz_(OSOBA* tab,int ile) //obsługa wyświetlenia zawartości tablicy

{

if (Pusta(ile)) Komunikat("\r\nTablica pusta");

else Dla_kazdego(tab,ile,Pokaz_dane); }

(10)

 zawartość pliku nagłówkowego dodatki.h z pomocniczymi funkcjami obsługującymi menu programu oraz komunikaty

#ifndef DODATKI

#define DODATKI const int POZ=8;

void Komunikat(char*);

char Menu(const int ile, char *Polecenia[POZ]);

#endif

 zawartość pliku modułowego dodatki.cpp z pomocniczymi funkcjami obsługującymi menu programu oraz komunikaty

#include <conio.h>

#include "dodatki.h"

char Menu(const int ile, char *Polecenia[])

{clrscr();

for (int i=0; i<ile;i++)

cprintf("\r\n%s",Polecenia[i]);

return getch();}

void Komunikat(char* s) {

cprintf(”%s”,s); getch();

}

(11)

2.2. Definiowanie listy nieuporządkowanej jako dynamicznej tablicy struktur metodą ADT

Etap 1 - Opis ADT

Nazwa typu - Dynamiczna lista nieuporządkowanych elementów

Własności typu: Potrafi przechować ciąg elementów o dowolnym rozmiarze Dostępne działania: Inicjalizacja listy

Określenie, czy lista jest pełna i zmiana rozmiaru listy Określenie, czy lista jest pusta

Wyszukanie elementu

Dodanie elementu wewnątrz, na początku i na końcu listy, Usuwanie elementu wewnątrz, na początku i na końcu listy, Przejście przez listę i przetwarzanie każdego elementu

Przetwarzanie wyszukanego elementu

Etap 2 - Budowa interfejsu

Funkcja Szukaj jest zadeklarowana tak, jak dla statycznej listy nieuporządkowanej z p.2.1.

void Inicjalizacja( OSOBA* &tab, int & ile);

/* działanie: inicjuje listę

warunki wstępne: lista nie jest zainicjowana,

warunki końcowe: lista zostaje zainicjowana jako pusta, ile jest równe 0 i jest numerem elementu pustego, tab wskazuje na listę pustą. Dopiero po wywołaniu tej funkcji prawdziwe są definicje pozostałych funkcji.*/

int Zmien(OSOBA* &tab, int & ile, int delta);

/*działanie: tworzy listę o zadanym rozmiarze

warunki wstępne: tab wskazuje na zainicjowaną lub pustą listę, ile jest numerem ostatniego elementu listy, wyrażenie ile+delta określa zmianę rozmiaru listy warunki końcowe: funkcja zwraca 1 i tab wskazuje na listę o zmienionym rozmiarze,

w przeciwnym wypadku zwraca 0 i tab wskazuje na wejściową listę*/

int Pusta(OSOBA* tab);

/* działanie: określa, czy lista jest pusta

warunki początkowe: tab jest zainicjowaną listą

warunki końcowe: funkcja zwraca 1, gdy tab jest listą jest pustą, w przeciwnym wypadku zwraca 0 */

(12)

void Wstaw(OSOBA* tab, OSOBA dane, int ktory, int &ile);

/*działanie: dodaje element na początku, wewnątrz lub na końcu listy

warunki początkowe: tab jest zainicjowaną listą, który jest numerem miejsca do wstawienia podanym przez funkcję Szukaj z wynikiem 1 dla ile= ile+1, który jest numerem miejsca za ostatnio wstawionym elementem do listy lub elementu pustego. Wynik funkcji Zmien po podstawieniu wartości ile i delty =1 jest równy 1.

warunki końcowe: funkcja dodaje element na wskazanym miejscu ktory po rozsunięciu elementów lub na końcu ciągu, zwiększa o 1 numer ostatniego elementu ile */

void Usun(OSOBA*& tab, int ktory, int &ile) /*działanie: usuwa element należący do listy

warunki początkowe: wynik funkcji Pusta dla tab jest równy 0, który jest numerem elementu do usunięcia podanym przez funkcje Szukaj, ile jest numerem ostatniego elementu wstawionego do listy

warunki końcowe: funkcja usuwa element na wskazanym miejscu ktory przez zsunięcie lub ostatni element ciągu, zmniejsza rozmiar listy o 1 oraz liczbę elementów w ile */

void Usun_tablice(OSOBA* &tab, int &ile);

/* działanie: usuwa tablicę

warunki początkowe: tab jest zainicjowaną tablicą pustą lub niepustą, ile jest numerem ostatniego elementu wstawionego do tablicy

warunki końcowe: tablica zostaje usunięta, tab wskazuje na pustą tablicę, a ile jest numerem pustego elementu */

void Dla_kazdego(OSOBA *tab, int ile, zrob);

/*działanie: wykonuje funkcje na każdym wstawionym elemencie do listy

warunki początkowe: wynik funkcji Pusta po podstawieniu wartości tab jest równy 0, ile jest numerem ostatniego elementu wstawionego do listy, zrob jest typem funkcji, która przetwarza element listy i nie zwraca wartości.

warunki końcowe: funkcja typu zrob jest wykonywana tylko raz dla każdego elementu wstawionego do listy*/

void Dla_jednego(OSOBA *tab, int ile, int ktory, zrob);

/*działanie: wykonuje funkcje na wybranym elemencie wstawionym do listy

warunki początkowe: wynik funkcji Pusta po podstawieniu wartości tab jest równy 0, ile jest numerem ostatniego elementu wstawionego do listy, zrob jest typem funkcji, która przetwarza element listy o numerze ktory podanym przez funkcje Szukaj z wynikiem 1 i nie zwraca wartości.

warunki końcowe: funkcja typu zrob jest wykonywana tylko raz dla elementu o numerze ktory*/

(13)

Etap 3 – implementacja

 plik nagłówkowy tablica2.h zawierający interfejs definiowanej tablicy

#ifndef TABLICA

#define TABLICA

#include "strukt.h"

typedef void(*zrob)(OSOBA &);

void Inicjalizacja(OSOBA*&tab,int &ile);

int Pusta(OSOBA*tab);

int Zmien(OSOBA*&tab,int ile, int delta);

int Szukaj(int ile, int ktory);

void Wstaw(OSOBA* tab, OSOBA dane, int ktory, int &ile);

void Usun(OSOBA* &tab, int ktory, int &ile);

void Usun_tablice(OSOBA*& tab,int &ile);

void Dla_kazdego(OSOBA*tab, int ile, zrob);

void Dla_jednego(OSOBA*tab, int ktory, zrob);

#endif

 plik modułowy tablica2.cpp z definicjami funkcji interfejsu. Podano definicje tych funkcji, które uległy zmianie w odniesieniu do funkcji z pliku tablica1.cpp

#include "tablica2.h"

#include <alloc.h>

void Inicjalizacja(OSOBA*&tab,int & ile) {ile=0; tab=NULL; } int Zmien(OSOBA*&tab,int ile, int delta)

{ OSOBA *pom;

pom = (OSOBA*)realloc(tab,sizeof(OSOBA)*(ile+delta));

if (pom) tab=pom;

return !Pusta(pom); }

int Pusta(OSOBA* tab) {return tab==NULL;}

void Usun(OSOBA*& tab, int ktory, int &ile)

{ for (int i=ktory-1; i<ile-1; i++) //zalożenia: 0<ile <=N i 1<=ktory<=ile

tab[i]=tab[i+1];

ile--;

if (ile==0) Usun_tablice(tab,ile);

else Zmien(tab,ile,0);

}

void Usun_tablice (OSOBA*&tab,int &ile) { if (!Pusta(tab))

{ free(tab);

tab=NULL;

ile=0; } }

(14)

 przykładowa aplikacja wykorzystująca zdefiniowaną tablicę (tablist2.cpp).

Zastosowano funkcje z modułu dodatki oraz we_wy przedstawione w p.2.1.

#include <conio.h>

#include <stdlib.h>

#include "tablica2.h"

#include "dodatki.h" //uniwersalne funkcje obsługujące menu i komunikaty

#include "we_wy.h" //uniwersalne funkcje we/wy dla struktur typu OSOBA

char *Polecenia[POZ]={"Tablica dynamiczna OSOBA* - obsluga typu lista", " Nacisnij:",

" 1 - aby wstawic element do listy", " 2 - aby usunac element z listy",

" 3 - aby wyswietlic wybrany element z listy", " 4 - aby wyswietlic liste, ",

" 5 - aby usunac liste",

" > 5 - aby zakonczyc prace."};

//funkcje pośrednio przetwarzające tab i ile za pomocą funkcji z modułu tablica2

void Wstaw_(OSOBA* &tab,int &ile);

void Usun_(OSOBA* &tab, int &ile);

void Pokaz_jeden(OSOBA* tab,int ile);

void Pokaz_(OSOBA* tab,int ile);

//funkcja ogólnego przeznaczenia

int Losuj(int zakres);

void main(void) {int ile;

OSOBA *tab;

char Co;

randomize();

Inicjalizacja(tab,ile);

do

{Co = Menu(POZ,Polecenia);

switch(Co)

{case '1' : Wstaw_(tab,ile); break;

case '2' : Usun_(tab,ile); break;

case '3' : Pokaz_jeden(tab,ile); break;

case '4' : Pokaz_(tab,ile); break;

case '5' : Usun_tablice(tab,ile); break;

default : Komunikat("\r\nKoniec programu");

}

}while (Co < '6' && Co>'0');

}

(15)

void Pokaz_jeden(OSOBA* tab,int ile) //obsługa wyszukania

{

if (Pusta(tab)) Komunikat("\r\nNie ma tablicy");

else {

int ktory=Losuj(ile+4);

if (Szukaj(ile,ktory)) Dla_jednego(tab,ktory,Pokaz_dane);

else Komunikat("\r\nPodano zly numer");

} }

void Wstaw_(OSOBA*& tab,int &ile) //obsługa wstawiania

{

int ktory=Losuj(ile+4);

if (!Szukaj(ile+1,ktory)) Komunikat("\r\nPodano zly numer");

else

if (!Zmien(tab,ile,1)) Komunikat("\r\nBrak miejsca w pamieci");

else {

cprintf("\r\nPodaj tab[%d]",ktory-1);

Wstaw(tab,Dane(),ktory,ile);

} }

void Usun_(OSOBA* &tab,int &ile) //obsługa usuwania

{

if (Pusta(tab)) Komunikat("\r\nNie ma tablicy");

else

{ int ktory=Losuj(ile+4);

if (!Szukaj(ile,ktory)) Komunikat("\r\nPodano zly numer");

else

{ Usun(tab,ktory,ile);

Komunikat("\r\nUsunieto element");

} } }

void Pokaz_(OSOBA* tab,int ile) //obsługa wyświetlenia zawartości tablicy

{

if (Pusta(tab)) Komunikat("\r\nNie ma tablicy");

else Dla_kazdego(tab,ile,Pokaz_dane);

}

(16)

3. Dodatek

3.1. Elementy języka C++ wspomagające abstrakcję danych Pojęcia podstawowe:

1) Deklaracje będące definicjami mogą wystąpić tylko raz programie:

opis typu strukturalnego (struct, union), wyliczeniowego(enum), definicja ciała funkcji itp

np.

struct KSIAZKA

{char autor[MAXNAZ];

char tytul[MAXNAZ];

int cena;

};

union A

{double z;

int x;

void (*a) ();

void (*b) (char *s);

};

enum kolory {R, G, B};

int suma(int a, int b) { return a+b;}

2) Definicje mogą wystąpić tylko raz w programie:

tworzenie zmiennych:

 lokalnych (w klasie pamięci automatycznej, przydzielonej blokowi),

 zewnętrznych, istniejących przez czas działania programu (w klasie pamięci statycznej),

 lokalnych statycznych (static), istniejących przez czas działania programu (w klasie pamięci statycznej), lecz dostępnych w bloku funkcji

np.

int liczba;

extern const stala = 100;

static float ulamek;

3) Deklaracje, które nie są definicjami, mogą wystąpić w programie wielokrotnie:

gdy nie wywołuje się funkcji ani nie pobiera się jej adresu itp np.

extern int a;

extern const stala;

int suma (int, int);

struct KSIAZKA;

typedef union A unia_A;

(17)

3.2. Paradygmaty programowania (wg B. Stroustrup . Język C++) 1) Programowanie proceduralne

Zadecyduj, jakie chcesz mieć procedury; stosuj najlepsze algorytmy, jakie możesz znaleźć.

Wspomaganie paradygmatu:

mechanizm przekazywania argumentów do funkcji i wyników z funkcji

zasięg obowiązywania zmiennych:

lokalny:

Nazwa zadeklarowana w bloku jest lokalna dla tego bloku, stąd można jej używać tylko w tym bloku i wewnątrz bloków w niej zawartych, począwszy od miejsca deklaracji. Nazwy argumentów funkcji należą do najbardziej zewnętrznego bloku w tej funkcji.

funkcji:

Etykiet można używać jedynie wewnątrz funkcji, w której zostały zadeklarowane (etykiety instrukcji goto)

pliku:

Dla nazwy, zadeklarowanej na zewnątrz wszystkich bloków zasięgiem jest plik. Są to nazwy globalne.

Oprogramowanie w realizacji jednoplikowej ogranicza się do jednokrotnego zastosowania w ograniczonym obszarze zastosowań.

2) Programowanie modularne + abstrakcja danych

Zadecyduj, jakie chcesz mieć moduły; podziel program w taki sposób, aby ukryć informację w modułach + zadecyduj, jakie chcesz mieć typy; dla każdego dostarcz pełny zbiór operacji.

Zasady abstrakcji i ukrywania informacji:

programowanie modularne umożliwia abstrakcję danych, ale jej nie wymusza

wszystkie operacje na danych statycznych lub niestatycznych wykonywane są jedynie za pośrednictwem funkcji stanowiący zbiór pełny operacji wzajemnie niesprzecznych:

programowanie modularne wspiera ukrywanie danych

dane typu static w plikach modułowych (ogranicza dane do jednego lub kilku egzemplarzy umieszczonych w plikach modułowych, dostępnych jedynie przez funkcje niestatyczne, wywoływane poza plikiem modułowym np. w pliku z funkcją main)

programowanie modularne wspiera ukrywanie funkcji

funkcje typu static w plikach modułowych (ukrywanie funkcji o charakterze pomocniczym w plikach modułowych, dostępnych pośrednio przez funkcje modułowe niestatyczne)

3) Programowanie obiektowe + abstrakcja danych

Zadecyduj, jakie chcesz mieć klasy; dla każdej klasy dostarcz pełny zbiór operacji; korzystając z mechanizmu dziedziczenia, jawnie wskaż to, co jest wspólne

 ustalenie wspólnej części wspomaganej dziedziczeniem i polimorfizmem - brak tej części ogranicza korzyści z zastosowania programowania obiektowego

 w przypadku braku części wspólnej wystarcza abstrakcja danych (stosowanie klas bez dziedziczenia i polimorfizmu)

(18)

3.3. Pliki nagłówkowe Pliki nagłówkowe powinny zawierać:

 stałe jawne #define TRUE 1

 funkcje makra #define MAX(x, y) ((x) > (y) ? (x) : (y))

 dyrektywy #include <iostream.h>

 prototypy funkcji np. void wyswietl_float(float);

 deklaracje definicyjne typów np.

class punkt { float x, y;

public: punkt (float, float);

void przesun (float, float);

};

 definicje stałych const int max = 3;

 definicje funkcji typu inline

inline int Większy(int x, int y) {return x > y; }

 deklaracje nazw

struct

kolo;

 deklaracje zmiennych extern int zmienna;

 wyliczenia enum Boolean {False, True};

 szablony funkcji i klas template <class T>

class stos { // ...}

Uwaga:

Nie należy nigdy wstawiać do pliu nagłówkowego:

1. definicji zwykłych funkcji: int Większy(int x, int y) {return x > y; } 2. definicji zmiennych: int zmienna;

3. definicji stałych agregatów (tablica, obiekt bez konstruktorów, składowych prywatnych i chronionych, klas podstawowych i funkcji wirtualnych):

const char tab[] =”aaa”;

(19)

3.4. Programy wieloplikowe

Dyrektywy preprocesora i kompilacja wieloplikowa (projekty) Polecenia dla preprocesora:

1) Dyrektywa #include <stdio.h> oznacza dołączenie w miejscu wystąpienia polecenia standardowego pliku nagłówkowego z deklaracjami typów, funkcji itp, natomiast #include ”tablica1.h” dołączenie pliku nagłówkowego użytkownika

2) Klauzula #define nazwa oznacza makrodefinicję, #undef nazwa unieważnia makrodefinicję

3) Polecenia kompilacji warunkowej pozwalają na kompilację tylko jednej z sekcji instrukcji:

#if wyrażenie1

sekcja _instrukcji1 #elif wyrażenie2

sekcja _instrukcji2 ....

#else

końcowa_sekcja_instrukcji

#endif

4) Polecenia warunkowej kompilacji uniemożliwiają wielokrotnego dołączania tego samego pliku nagłówkowego, lub jego fragmentu podczas kompilacji wieloplikowej:

#ifndef nazwa

#define nazwa

deklaracje

//deklaracje czyta kompilator tylko raz, podczas definiowania makro nazwa

#endif,

#ifdef nazwa

deklaracje

//kompilator czyta te deklaracje, gdy zdefiniowano makro nazwa

#endif

(20)

Tworzenie projektu umożliwiającego kompilację oraz łączenia plików (np.

tworzenie programu z p. 2.1)

1. Należy uruchomić Borland C++ i wybrać opcję Open Project z menu Project. Należy wpisać nazwę pliku projektowego w polu Open Project File z rozszerzeniem PRJ, np. tablist1.prj. Nazwa nadana plikowi PRJ będzie nadana programowi wynikowemu EXE czyli tablist1.exe. Po nadaniu nazwy należy nacisnąć klawisz ENTER. U dołu ekranu pojawi się okno o nazwie Project : TABLIST1

2. Aby dodać pliki do projektu należy nacisnąć klawisz INSERT lub wybrać opcję Add Item z menu Project. Pojawi się okno o nazwie Add to Project List.

W polu Name należy wpisać nazwy plików (przez wybór z listy), które należy dodać do projektu (w przykładzie pliki tablica1.cpp, dodatki.cpp, we_wy.cpp oraz tablist1.cpp). Nie należy dodawać plików nagłówkowych. Po zakończeniu należy nacisnąć przycisk Done w oknie Add to Project List.

3. Aby skompilować i połączyć pliki źródłowe podane w projekcie, należy

wybrać opcję Make z menu Compile lub nacisnąć klawisz F9. Nastąpi

kompilacja i utworzenie plików OBJ, a następnie połączenie tych plików i

utworzenie programu wynikowego tablist1.exe. Można również uruchomić

program w środowisku za pomocą polecenia Run z menu Run lub gotowego

programu tablist1.exe na poziomie systemu operacyjnego.

(21)

3.5. Dynamiczny przydział pamięci

1) Funkcje przydziału pamięci w obszarze bliskiego stosu w modelach: tiny, small i medium oraz modelach: compact, large i huge

(w obszarze stosu dalekiego, bo tylko taki istnieje w tych modelach)

1. deklaracja funkcji void * calloc (size_t ile, size_t rozmiar)

wynik pozytywny

Przydziela blok pamięci o rozmiarze ile*rozmiar i zwraca jego adres. Wszystkie bajty przydzielonego bloku mają wartość 0. Wymaga rzutowania w C++

wynik negatywny

zwraca wartość NULL

przykład int * tab1= (int*) calloc(10, sizeof(int));

interpretacja przykładu

wskazanie tab1 na tablicę 10 elementów typu int

2. deklaracja funkcji void * malloc (size_t rozmiar);

wynik pozytywny

Przydziela blok pamięci o rozmiarze rozmiar. Wymaga rzutowania w C++

wynik negatywny

zwraca wartość NULL

przykład int * tab2= (int*) malloc(10*sizeof(int));

interpretacja przykładu

wskazanie tab2 na tablicę 10 elementów typu int

3. deklaracja funkcji void * realloc (void*blok, size_t rozmiar);

wynik pozytywny

Przydziela blok pamięci o rozmiarze rozmiar i kopiuje zawartość obszaru wskazanego przez blok i usuwa go z pamięci . Jeśli blok ma wartość NULL, przydziela blok pamięci podobnie jak funkcja malloc. Wymaga rzutowania w C++

wynik negatywny

zwraca wartość NULL, pozostawiając blok pamięci wskazany przez blok bez zmian

przykład int * tab3= (int*) realloc(tab2, 20*sizeof(int));

interpretacja przykładu

wskazanie za pomocą tab3 na tablicę 20 elementów typu int z kopią zawartości tablicy wskazanej przez tab2

4. deklaracja funkcji void free (void*blok);

wynik pozytywny

Zwalnia blok pamięci wskazany przez blok, przydzielony przez jedną z funkcji: malloc, calloc, realloc. W przypadku próby usuwania pamięci nieprzydzielonej nie wykonuje żadnej czynności

przykład free(tab3);

interpretacja przykładu

zwalnia blok pamięci wskazany przez tab3

2) Funkcje przydziału pamięci w obszarze dalekiego stosu w modelach: small i medium oraz modelach: compact, large i huge

(w obszarze stosu dalekiego, bo tylko taki istnieje w tych modelach)

: farcalloc, farmalloc, farrealloc, farfree

3) Wskaźniki typu:

 near: określają przesunięcie względem aktualnego segmentu danych (adresują 64 kB)

 far: zawierają zarówno segment i offset adresu i mogą adresować 1 MB pamięci. W programie operacje na takich adresach odbywają się jedynie na ich częściach offsetowych, co może prowadzić do ryzykownych operacji w pamięci.

 huge: podlegają unormowaniu, które polega na przekształceniu wartości offsetu do przedziału <0,15>

np. 16*segment+offset czyli 0x1998:0x4511  0x1DE91  0x1DE9:0x0001

(22)

3)Operatory przydziału pamięci w C++

3.1) przydział pamięci dla pojedynczej danej

deklaracja operatora new wskaźnik żądanego typu = new typ_danej przykład int* wsk_liczba = new int;

interpretacja przykładu

przydziela pamięć dla danej typu int wskazanej przez wsk_liczba. Nie wymaga rzutowania w C++

deklaracja operatora delete delete wskaźnik-żądanego typu

przykład delete wsk_liczba;

interpretacja przykładu

zwalnia pamięć dla danej typu int wskazanej przez wsk_liczba

3.2) przydział pamięci dla tablic jedno- i wielowymiarowych Przykład:

const int N=5; const int M=4; const int P=3;

Definicja wskaźnika zgodnego z tablicą

statyczną

Definicja tablicy statycznej zgodnej z definicją wskaźnika

Przypisanie stałej wskaźnikowej do wskaźnika

Alokacja pamięci dla tablicy dynamicznej zgodnej z

reprezentacją tablicy statycznej

wskaźnik na int tablica N elementów typu int

1. int* wskt1; int tab1[N]; wskt1=tab1; wskt1=new int [N];

delete [] wskt1;

wskaźnik na tablicę

jednowymiarową M elementów typu int

tablica N elementów typu jednowymiarowa tablica M elementów typu int

2. int (*wskt2)[M]; int tab2[N][M]; wskt2=tab2; wskt2=new int[N][M];

delete [] wskt2;

wskaźnik na tablicę

dwywymiarową NxM elementów typu int

tablica P elementów typu dwuwymiarowa tablica NxM elementów typu int

3. int (*wskt3)[N][M] int tab3[P][N][M]; wskt3=tab3; wskt3=new int [P][N][M];

delete [] wskt3;

aliasowe nazwy typów tablic

typedef int t1 [M];

typedef t1 t2 [N];

typedef t2 t3[P];

t1- tablica M elementów typu int t2 -tablica N tablic typu t1 t3- tablica P tablic typu t2

wskt3=new t2 [P]; lub

wskt3=new t3;

delete [] wskt3;

wskaźnik na wskaźnik na int tablica N wskaźników na int tablica N wskaźników tablic M elementów typu int

4. int** wskt4; int *tab4[N];

for(int i=0;i<N;i++) tab4[i]=new int [M];

for(int i=0;i<N;i++) delete [] tab4[i];

wskt4=tab4; wskt4=new int* [N];

for(int i=0;i<N;i++) wskt4[i]=new int [M];

for(int i=0;i<N;i++) delete [] wskt4[i];

delete [] wskt4;

Cytaty

Powiązane dokumenty

Ile jest takich umów, proszę wskazać tytuł najstarszej i najnowszej umowy o tych tematach (oddzielnie dla każdego tematu)A. Znając tytuł umowy :

Jaka jest liczba różnych k-wymiarowych podprzestrzeni liniowych przestrzeni n-wymiarowej nad q-elementowym ciałem.. Zanim poznamy odpowiedź na to pytanie, przybliżymy pojęcia,

[r]

warunki początkowe: wynik funkcji Pusta po podstawieniu wartości tab jest równy 0, ile jest numerem ostatniego elementu wstawionego do listy, zrob jest typem funkcji, która

warunki początkowe: wynik funkcji Pusta po podstawieniu wartości ile jest równy 0, ile jest numerem wskazania ostatniego elementu wstawionego do listy, zrob jest typem

Wyka», »e iloczyn dowolnych 13 kolejnych liczb naturalnych dzieli si¦ przez 13!..

Ile różnych deserów może z tego sporządzić ekspedientka, jeśli w pucharku mieści się nie więcej niż 5 kulek lodów, a pusty pucharek nie jest deserem..

Uwaga, dwa sposoby usadzenia uważamy za takie same, jeśli w obu sposobach każda z osób ma tych samych sąsiadów zarówno po lewej, jak i prawej stronie..