Wykład 1
Powtórzenie podstawowych elementów programowania 1. Zastosowanie w programach:
1.1. dynamicznych tablic wskaźników na dynamiczne struktury 1.2. rekurencyjnych struktur danych
2. Budowa programów wersji wieloplikowej Problem:
Zapisz dane osób, umieszczając je w wybranym miejscu wykazu osób.
Algorytm tworzenia wykazu:
Numer danej wejściowej
1 2 3 4 5 6 7
Miejsce wstawienia
dowolne 1 2 1 4 6 8
Kolejność danych podawanych
do listy
C
D
A
B
P
W
Z Kolejność
danych wstawianych do listy
C Błąd!
nie ma takiego miejsca
na liście
D C
D A C
B D A C
B D A P C
B D A P C W
Koncepcja budowy listy nieuporządkowanej
//ile=3, //ktory=2
1. Szukaj (który, ile); // dalej, jeśli podano 1<=który<=ile+1 //koniec, jeśli nie spełniono warunku 2. Zwiększ tablicę, jeśli brakuje miejsca //ile==rozmiar- pełna,
// ile<rozmiar – jest miejsce 3. Przesuń wskaźniki na prawo:
// tab[ile]=tab[ile-1], tab[ile-1]=tab[ile-2]...tab[który]=tab[który-1]
for(int i=ile;i>=który ;i--) tab[i]=tab[i-1];
4. Pom=new OSOBA;
5. *Pom=Dane();
6. ile++; //ile=4 0
4
pamięć dynamiczna (sterta) tab
2 1
1
3
3
2 ktory-1
Wstawianie pamięć statyczna
ktory=2 ile=3 rozmiar=3
ktory=2 ile=3 rozmiar=6
0 tab
2 ktory-1 1
4 5
ktory=2 3 ile=4 rozmiar=6
0 tab
2 ktory-1 1
4 5
Rozwiązanie 1
Założenie: Zakłada się, że liczba osób jest znana.
Nazwa struktury: Lista nieuporządkowana jako dynamiczna tablica wskaźników na dane
Podstawowe operacje:
Inicjalizacja tablicy
Pusta
Pelna
Wyszukaj
Wstaw
Dla_kazdego
Projekt typów danych
Projekt operacji na dynamicznej tablicy wskaźników na dane void Inicjalizacja( POSOBA* &tab, int & ile, int rozmiar);
/* działanie: inicjuje tablice tab,
warunki wstępne: tablica tab nie jest zainicjowana,
warunki końcowe: tablica tab zostaje zainicjowana jako pusta i zawiera rozmiar niezainicjowanych wskaźników POSOBA po przydziale pamięci
ile jest liczbą elementów równą 0
Dopiero po wywołaniu tej funkcji prawdziwe są definicje pozostałych funkcji.*/
int Pusta(POSOBA* tab);
/* działanie: określa, czy tablica jest pusta
warunki początkowe: tab jest zainicjowaną tablicą wskaźników
warunki końcowe: funkcja zwraca 1, gdy tab jest tablicą jest pustą, w przeciwnym wypadku zwraca 0 */
int Zmien(POSOBA* &tab, int & ile, int rozmiar);
/*działanie: tworzy nową tablicę o zadanym rozmiarze rozmiar
warunki wstępne: tab wskazuje na zainicjowaną lub pustą listę, ile jest liczbą wskaźników, wyrażenie ile+/-delta określa rozmiar tablicy
warunki końcowe: funkcja zwraca 1 i tab jest nową tablicą o zmienionym rozmiarze, w przeciwnym wypadku zwraca 0 i tab wskazuje na wejściową listę*/
int Szukaj(int ile, int ktory);
/*działanie: szuka wskazanego elementu
warunki początkowe: który jest numerem wskazania szukanego elementu, ile jest numerem wskazania ostatniego elementu przy usuwaniu lub większym o 1 przy wstawianiu warunki końcowe: funkcja zwraca 1, gdy znaleziono element o numerz wskazania ktory
spełniający warunki 1<=ktory<=ile, w przeciwnym wypadku zwraca 0 */
void Wstaw(POSOBA *tab, POSOBA dane, int ktory, int &ile);
/*działanie: dodaje wskaźnik na element niepusty na początku, wewnątrz lub na końcu listy warunki początkowe:
tab – wskaźnik do pierwszego elementu tablicy ile – liczba wskaźników w tablicy
ktory – numer miejsca w tablicy, gdzie należy umieścić kolejny wskaźnik. Możliwość wstawienia danej na miejscu ktory jest sprawdzana przez funkcję Szukaj przed wywołaniem funkcji Wstaw
dane – wskaźnik na nowe dane umieszczane na miejscu ktory
warunki końcowe: funkcja dodaje wskazanie elementu dane na miejscu ktory po rozsunięciu wskaźników elementów lub na końcu ciągu wskaźników, zwiększa o 1 numer wskazania ostatniego elementu ile */
void Dla_kazdego(POSOBA *tab, int ile, zrob funkcja);
/*działanie: wykonuje funkcje typu zrob na każdym wskazanym elemencie z tablicy warunki początkowe: wynik funkcji Pusta po podstawieniu wartości ile jest równy 0, ile – liczba wszystkich wskaźników w tablicy tab
zrob - wskaźnik do funkcji,
funkcja - wskaźnik do funkcji, która przetwarza elementy, których wskaźniki są przechowywane w tablicy tab
warunki końcowe: funkcja typu zrob jest wykonywana tylko raz dla każdego wskazanego elementu, którego wskazanie wstawiono do tablicy*/
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
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.
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 */
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*/
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();}
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)
{ funkcja(tab[ktory-1]);} //wykonaj czynność zrob dla elementu tablicy
przykł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'); }
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); }
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();
}
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 */
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*/
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; } }
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');
}
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);
}
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;
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)
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
structkolo;
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”;
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
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.
1. Schemat podziału programu na wiele plików
Podział programu tablica.cpp
strukt.h dodatki.h
dodatki.cpp we_wy.cpp
mtab.h mtab.cpp tab.cpp
main() we_wy.h
dodatki.obj we_wy.obj tab.obj mtab.obj definiowany typ
metodą ADT funkcje
interfejsu programu
funkcje we_wy
funkcja main, funkcje użytku jednorazowego
tworzenie jednego pliku, definiowanie nazw z plików nagłówkowych
Łączenie Kompilacja
tab.exe
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ść NULLprzykład int * tab1= (int*) calloc(10, sizeof(int));
interpretacja przykładu
wskazanie tab1 na tablicę 10 elementów typu int2. deklaracja funkcji void * malloc (size_t rozmiar);
wynik pozytywny
Przydziela blok pamięci o rozmiarze rozmiar. Wymaga rzutowania w C++wynik negatywny
zwraca wartość NULLprzykład int * tab2= (int*) malloc(10*sizeof(int));
interpretacja przykładu
wskazanie tab2 na tablicę 10 elementów typu int3. 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 zmianprzykł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 tab24. 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ściprzykład free(tab3);
interpretacja przykładu
zwalnia blok pamięci wskazany przez tab32) 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
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_liczba3.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;