Struktury

Transkrypt

Struktury
Struktury
• tablica – przykład obiektu grupującego wiele
obiektów składowych pod jedną nazwą;
ograniczenie – wszystkie składowe tego samego
typu
• potrzeba struktury danych grupującej zmienne
róŜnego typu (np. dane opisujące pracownika)
• struktura – obiekt złoŜony z jednej lub kilku
zmiennych (dopuszczalnie róŜnych typów)
zgromadzonych razem i występujących pod
wspólną nazwą
• formy definicji struktur
o struct {
char imie[24];
char nazwisko[32];
int rok;
} o1,o2;
- definiuje dwa „egzemplarze” struktury. Gdy chcemy
zdefiniować trzeci – musimy powtórzyć całą definicję
o struct osoba {
char imie[24];
char nazwisko[32];
int rok;
} o1,o2;
- zawiera nazwę struktury (etykietę) – pozwala to na
definicję dodatkowych „egzemplarzy”
struct osoba o3, o4;
o typedef struct osoba {
char imie[24];
char nazwisko[32];
int rok;
} OSOBA;
OSOBA o1,o2,o3,o4;
przez typedef definiujemy typ OSOBA, który potem
wykorzystujemy do definicji kolejnych „wcieleń” struktury
• inicjalizacja struktury – moŜliwa w trakcie definicji
OSOBA o1={”James”,”Bond”,1965);
• odwołanie do poszczególnych składowych (pól)
struktury – uŜycie operatora ”.” składowej struktury
o1.imie
/* odwołanie do pola imie */
o1.nazwisko /* odwołanie do pola nazwisko */
o1.rok
/* odwołanie do pola rok */
nazwa pola jest nazwą lokalną widoczną tylko wewnątrz
struktury
• dozwolone operacje na strukturach
o przypisanie jej innej struktury jako całości
o kopiowanie jej w całości na inną strukturę (w tym –
przesyłanie argumentów funkcjom i zwracanie przez
funkcje wartości)
o inicjalizacja w trakcie definicji
o pobieranie adresu struktury przez operator &
o odwołanie się do składowych struktury
• niedozwolone jest porównywanie struktur
Przykład – ilustracja uŜycia struktur:
#include <iostream>
#include <cstring>
#include <cstdlib>
using namespace std;
typedef struct {
char imie[24];
char nazwisko[32];
int rok;
} OSOBA;
OSOBA utworz(char *imie, char *nazwisko, int rok)
{
OSOBA temp;
strcpy(temp.imie,imie);
strcpy(temp.nazwisko,nazwisko);
temp.rok=rok;
return temp;
}
void wypisz(OSOBA kto)
{
cout << "Imie
: " << kto.imie << endl;
cout << "Nazwisko : " << kto.nazwisko << endl;
cout << "Rok urodz.: " << kto.rok << endl;
}
main()
{
OSOBA o1={"Jan","Nowak",1983}; /* inicjalizacja */
OSOBA o2;
wypisz(o1);
o2=o1;
/* przypisanie struktury */
/* porownanie struktur - nielegalne
if (o1==o2) cout << "Struktury identyczne\n");
*/
wypisz(o2);
wypisz(utworz("John","Lennon",1940));
}
Imie
: Jan
Nazwisko : Nowak
Rok urodz.: 1983
Imie
:
Nazwisko :
Rok urodz.:
John
Lennon
1940
• zagnieŜdŜanie struktur – wcześniej zdefiniowane struktury
mogą być uŜyte do definicji nowych
struct punkt {
int x;
int y;
}
struct prosto {
struct punkt p1;
struct punkt p2;
}
struct punkt d,g;
struct prosto ekran;
odwołanie do pól takiej struktury:
ekran.p1
ekran.p1.x
/* dolny lewy punkt ekranu */
/* współrzędna x dolnego lewego punktu
ekranu */
• struktury jako argumenty funkcji
o struktury mogą być argumentami funkcji oraz mogą być
zwracane przez funkcje
o są przekazywane do funkcji poprzez wartość (jak
wszystkie inne zmienne) – na stosie tworzona jest kopia
struktury, którą funkcja manipuluje bez zmiany oryginału
#include <iostream>
using namespace std;
struct punkt {
int x;
int y;
};
struct prosto {
struct punkt p1;
struct punkt p2;
};
struct punkt rob_punkt(int x, int y)
{
struct punkt temp;
temp.x=x;
temp.y=y;
return temp;
};
struct prosto rob_prosto(struct punkt p1, struct punkt p2)
{
struct prosto temp;
temp.p1=p1;
temp.p2=p2;
return temp;
};
void wypisz_rozmiar(struct prosto pp)
{
cout << "Ekran ma rozmiary: " << pp.p2.x-pp.p1.x << ”na ”
<< pp.p2.y-pp.p1.y << pikseli.\n",
}
struct punkt dodaj_punkty(struct punkt p1, struct punkt p2)
{
p1.x += p2.x;
p1.y += p2.y;
return p1;
}
main()
{
struct punkt d, m, g;
struct prosto ekran;
d=rob_punkt(10,10);
m=rob_punkt(320,200);
g=dodaj_punkty(d,m);
printf("Punkt d: (%3d, %3d)\n",d.x,d.y);
printf("Punkt m: (%3d, %3d)\n",m.x,m.y);
printf("Punkt g: (%3d, %3d)\n",g.x,g.y);
ekran=rob_prosto(d,g);
wypisz_rozmiar(ekran);
}
Punkt
Punkt
Punkt
Ekran
d:
m:
g:
ma
( 10, 10)
(320, 200)
(330, 310)
rozmiary: 320 na 200 pikseli.
Wskaźniki do struktur
• przekazywanie do funkcji duŜej struktury nieefektywne –
zamiast tego moŜemy uŜyć wskaźnika
• definicja wskaźnika – analogicznie jak dla innych typów
OSOBA ktos = { ”John”, ”Lennon”, 1940};
OSOBA *po;
po = &ktos;
cout << ”ktos.imie ” << ktos.nazwisko << ktos.rok);
%s, urodzony: %d\n”, ktos.imie,
ktos.nazwisko,ktos.rok);
cout << (*po).imie %s, (*po).nazwisko urodzony: %d\n”,
(*po).imie, (*po).nazwisko,(*po).rok);
• udostępnienie składowej struktury wskazywanej przez
wskaźnik – zamiast:
(*wskaźnik).pole
moŜemy napisać:
wskaźnik->pole
printf(”%s %s, urodzony: %d\n”, po->imie,
po->nazwisko,po->rok);
• operatory ”.” oraz ”->” – najwyŜszy priorytet (równy (), [])
struct {
int len;
char *str;
} *p
++p-> len
(++p)->len
≡
-
++(p->len) – zwiększa zmienną len
zwiększa p przed odwołaniem do len
p++->len ≡ (p++)->len - zwiększa p po odwołaniu do len
*p->str
- udostępnia coś, na co wskazuje str
*p->len
- błąd !!! (p->len nie jest wskaźnikiem)
Tablice struktur
• struktury i wskaźniki do nich moŜna gromadzić w tablicach:
OSOBA t[10], *s[10];
t – tablica 10 elementów, z których kaŜdy to struktura typu
OSOBA
t[0] t[0].rok t[0].imie[0] -
struktura typu OSOBA o indeksie 0
składnik rok struktury typu OSOBA o indeksie 0,
zmienna typu int
pierwszy znak składnika imie struktury OSOBA o
indeksie 0; zmienna typu char
s – tablica wskaźników do struktur typu OSOBA
s[0] zerowy element tablicy typu OSOBA* (adres)
*s[0].rok błędny zapis (s[0].rok to nie jest wskaźnik)
(*s[0]).imie[1] – drugi znak składnika imie struktury
wskazywanej przez s[0]
s[0]->imie[1] - inny zapis tego, co wyŜej
Struktury odwołujące się do samych siebie
• są to struktury, które w swojej definicji zawierają odwołanie
(wskaźnik) do innej struktury tego samego typu
• przy pomocy takich struktur moŜna zaimplementować
struktury dynamiczne, takie jak: listy jednokierunkowe, listy
dwukierunkowe, drzewa binarne itp.
• struktury takie są szczególnie uŜyteczne do obsługi ciągu
zmiennej liczby obiektów opisywanych strukturami
Przykład – struktura do tworzenia listy jednokierunkowej:
typedef struct pers {
char *imie;
char *nazwisko;
int
rok;
pers *nast;
} OSOBA;
Obsługa plików
• do tej pory korzystaliśmy tylko ze standardowego
wejścia i wyjścia (C++ : cin, cout, operatory <<, >>)
• inna moŜliwość – korzystanie z plików zewnętrznych
• wymagania:
o konieczność powiązanie pliku (elementu systemu
plików systemu operacyjnego) z obiektem języka
(otwarcie pliku)
o uŜywanie właściwych funkcji bibliotecznych do
transferu danych
Rozwiązanie w C:
• plik – opisany przez strukturę typu FILE zdefiniowaną
w bibliotece stdio – konieczność włączenie nagłówka
cstdio
• egzemplarz tej struktury zawiera dane potrzebne do
obsługi pliku (bufor wymiany danych, dane adresowe,
informacja o błędach itp.)
• znajomość tej struktury niekonieczna – wystarczy
mieć wskaźnik do tej struktury i uŜywać właściwych
funkcji bibliotecznych
• uzyskanie powiązania między plikiem (z systemu
plików OS) a wskaźnikiem do FILE – otwarcie pliku –
odpowiada za to funkcja fopen
• składnia fopen:
fp = fopen(”nazwa_pliku”, ”typ_dost”);
fp – wskaźnik do FILE
typ_dost : r – otwiera plik do czytania
w – otwiera plik do pisania; kasuje poprzednią wersję (jeśli
istnieje)
a – otwiera plik do dopisywania
gdy plik binarny – dodatkowo ”b”, np.:
FILE *fp;
fp=fopen(„”aaa”,”rb”); //otwarcie pliku binarnego aaa do czytania
• zwykle środowisko systemu operacyjnego jest
odpowiedzialne za otwarcie 3 plików standardowych
i udostępnienie programowi ich wskaźników; ich
standardowe nazwy to stdin (klawiatura), stdout
i stderr (ekran)
• inne funkcje działające na plikach:
fclose(FILE *fp);
fprintf(FILE *fp, format, ...);
fscanf(FILE *fp, format, ...);
int getc(FILE *fp);
int putc(int c, FILE *fp);
Przykłady:
// Obsługa plików w C – funkcje fopen, fclose, fprintf
#include <stdio.h>
#define T_DOL
0
#define T_GORA 150
#define T_KROK
5
int main()
{
FILE *fp;
if ((fp=fopen("zestaw","wb"))==NULL)
{
printf("Nie moge otworzyc pliku zestaw\n");
return 1;
}
fprintf(fp," T[F] T[C]\n");
fprintf(fp,"----------------\n");
for (int fahr=T_DOL; fahr <=T_GORA; fahr=fahr+T_KROK)
fprintf(fp,"%6.1f %6.1f\n",fahr, 5*(fahr-32.0)/9);
fclose(fp);
return 0;
}
// Obsługa plików w C - prosty program kopiujacy pliki
#include <stdio.h>
int main(int ac, char * av[ ])
{
FILE *fs, *fd;
int c;
if (ac!=3)
{
printf("uzycie: kopiuj nazwa_orygial nazwa_kopia");
return 1;
}
if ((fs=fopen(av[1],"rb"))==NULL)
{
printf("Nie moge otworzyc pliku %s\n",av[1]);
return 1;
}
if ((fd=fopen(av[2],"wb"))==NULL)
{
printf("Nie moge otworzyc pliku %s\n",av[2]);
return 1;
}
while ((c=getc(fs))!=EOF) putc(c,fd);
fclose(fs);
fclose(fd);
}
• stała EOF – zwracana, gdy osiągnięty jest koniec
pliku
• zmienna c typu int, by móc przenieść normalne znaki
oraz EOF
• limit na liczbę jednocześnie otwartych plików –
konieczność zamykania nieuŜywanych plików
Rozwiązanie w C++:
• pliki – reprezentowane przez obiekty specjalnego typu
(klasy) zdefiniowanej w bibliotece standardowej
• pełną realizację operacji I/O zapewnia system
powiązanych (przez dziedziczenie) klas –
konieczność włączenia bibliotek iostream, fstream
• w C++ - wpływająca i wypływająca informacja srumień bajtów; by zrealizować I/O musimy:
o zdefiniować w pamięci ”centrum zarządzania”
strumieniem (definicja egzemplarza klasy)
o powiązać go z fizycznym plikiem (metoda open()
lub konstruktor)
o uŜyć właściwych funkcji (metod klasy fstream)
o zlikwidować niepotrzebny strumień metoda close()
• stała EOF – zwracana, gdy osiągnięty jest koniec
pliku
• błędy powstałe w czasie pracy strumieni – ustawiają
flagi bitowe w słowie iostate w klasie ios_base;
wykrycie błędu ułatwia operator konwersji na bool
// Obsługa plików w C++ – funkcje fopen, fclose, fprintf
/* Pisanie do pliku – funkcje open, close, operator << */
#include <iostream>
#include <iomanip>
#include <fstream>
using namespace std;
const int T_DOL=0, T_GORA=150, T_KROK=5;
int main()
{
ofstream fp;
fp.open("zestaw.dat");
if (!fp)
{
printf("Nie moge otworzyc pliku zestaw.dat\n");
return 1;
}
fp<<" T[F] T[C]\n";
fp<<"----------------\n";
for (int fahr=T_DOL; fahr <=T_GORA; fahr=fahr+T_KROK)
fp<<setw(6) << setprecision(1) << fixed << fahr << setw(8)
<< setprecision(1) << 5*(fahr-32.0)/9 << endl;
fp.close();
return 0;
}
• zwykle do reprezentowania plików dyskowych
uŜywamy obiektów typu ofstream (zapis do pliku) lub
ifstream (czytanie z plików)
• metoda open() moŜe mieć drugi argument określający
tryb pracy z danym plikiem; wartości domyślne tego
parametru to: ios_base::in dla klasy ifstream,
ios_base::out dla klasy ofstream
// Obsługa plików w C++ - prosty program kopiujacy pliki
#include <iostream>
#include <iomanip>
#include <fstream>
using namespace std;
int main(int ac, char * av[ ])
{
if (ac!=3)
{
printf("uzycie: kopiuj nazwa_orygial nazwa_kopia");
return 1;
}
ifstream fs(av[1],ios::in|ios::binary);
if (!fs)
{
printf("Nie moge otworzyc pliku %s\n",av[1]);
return 1;
}
ofstream fd(av[2],ios::out|ios::binary);
if (!fd)
{
printf("Nie moge otworzyc pliku %s\n",av[2]);
return 1;
}
int c;
while ((c=fs.get())!=EOF) fd.put(c);
if (!fs.eof()||!fd)
{
cout << "Jakis dziwny blad !!!\n";
return(1);
}
fs.close(); fd.close();
}
Struktury odwołujące się do samych siebie – przykłady
• najprostszy przykład – lista jednokierunkowa
• realizuje liniowe uporządkowanie
• lista określona poprzez:
o nagłówek listy (wskaźnik do struktury składowej
listy) i system wskaźników pokazujących na kolejny
element
• operacje podstawowe:
o wstawianie elementu do listy
o usuwanie elementu z listy
o przeglądanie listy
#include <cstdio>
#include <cstdlib>
#include <cstring>
using namespace std;
#define MAX_BUFOR 40
typedef struct pers {
char *imie;
char *nazwisko;
int
rok;
struct pers *nast;
} OSOBA;
OSOBA *S;
//nagłówek listy
void lista1(FILE *fp)
//tworzenie listy – dodawanie do początku
{
char b1[MAX_BUFOR],b2[MAX_BUFOR];
unsigned len;
OSOBA *B;
//egzemplarz aktualnie dodawany
int rok;
S=(OSOBA*)NULL;
//wstępne ustawienie nagłowka listy
while (fscanf(fp,"%s %s %d",b1,b2,&rok)!=EOF)
{
B=new OSOBA;
B->nast=S;
S=B;
len=(unsigned)strlen(b1);
B->imie=new char[len+1];
strcpy(B->imie,b1);
len=(unsigned)strlen(b2);
B->nazwisko=new char[len+1];
strcpy(B->nazwisko,b2);
B->rok=rok;
}
}
void lista(FILE *fp)
//tworzenie listy – dodawanie do konca
{
char b1[MAX_BUFOR],b2[MAX_BUFOR];
unsigned len;
OSOBA *B,*P;
//B – aktualnie dodawany, P – dodany poprzednio
int rok;
S=(OSOBA*)NULL;
while (fscanf(fp,"%s %s %d",b1,b2,&rok)!=EOF)
{
B= new OSOBA;
if (S==(OSOBA*)NULL) S=B;
else P->nast=B;
B->nast=(OSOBA*)NULL;
len=(unsigned)strlen(b1);
B->imie= new char[len+1];
strcpy(B->imie,b1);
len=(unsigned)strlen(b2);
B->nazwisko=new char[len+1];
strcpy(B->nazwisko,b2);
B->rok=rok;
P=B;
}
}
void drukuj_lista()
{
OSOBA *B;
B=S;
do
printf("%s %s %d \n",B->nazwisko,B->imie,B->rok);
while (B=B->nast);
}
OSOBA *najstarsza()
{
OSOBA *w, *x;
int min;
min=S->rok;
x=S;
for (w=S; w!=(OSOBA*)NULL; w=w->nast)
if (w->rok<min)
{
min=w->rok;
x=w;
}
return x;
}
main()
{
FILE *fp;
OSOBA *stara;
fp=fopen("dane","rb");
lista(fp);
fclose(fp);
drukuj_lista();
stara=najstarsza();
printf("\nNajstarsza: %s %s, %d\n",stara->imie,stara->nazwisko,
stara->rok);
system("PAUSE");
}
Drzewa
• podstawowa dynamiczna struktura danych; opisuje
hierarchiczny układ danych
• elementy struktury drzewa:
o wyróŜniony element – korzeń
o węzły: wewnętrzne, zewnętrzne (liście)
o relacje pomiędzy węzłami: przodek, potomek
• często stosowane drzewa: drzewa binarne (o stopniu 2)
• szczególne przypadki drzew binarnych:
o binarne drzewa poszukiwań (BST): x – węzeł, jeŜeli y naleŜy
do lewego poddrzewa x, to klucz[y] ≤ klucz[x]; gdy y naleŜy do
prawego poddrzewa x, to klucz[x] ≤ klucz[y]
o kopiec (stóg) – binarne drzewo wypełnione na wszystkich
poziomach ewentualnie z wyjątkiem ostatniego (który jest
wypełniony od lewej do prawej), w kaŜdym węźle jest
spełniona relacja kopca: klucz[potomka] ≤ klucz[rodzica]
(stąd: korzeń kopca zawiera element o maksymalnym kluczu)
• realizacja drzew – najczęściej: dynamiczne struktury
wskaźnikowe; niekiedy: tablice (np. dla kopców)
• przykład uŜyteczności drzew – sortowania drzewiaste
o przekształć listę wejściową w binarne drzewo
poszukiwań
o obejdź drzewo w kolejności ”najpierw w lewo”; wypisz
elementy danych przy ich powtórnym odwiedzeniu
ZłoŜoność – O(N2); powód – pewne sekwencje danych
prowadzą do wąskich i długich drzew – ich konstrukcja
to proces o złoŜoności O(N2)
Przykład: sekwencja: 28, 1018, 402, 396, 35, 46, 354, 76,
128,112, 106, 100
procedura obejdz(T)
if T puste then return
else
begin
obejdz(L(T))
wypisz element w korzeniu T
obejdz(P(T))
return
end
Drzewa – przykład: sortowanie drzewiaste
#include <iostream>
#include <cstdlib>
using namespace std;
typedef struct node {
int k;
struct node *lewy;
struct node *prawy;
} NODE;
NODE *Root;
//korzen drzewa
void dodaj(int k)
{
NODE *x,*y,*c;
c=new NODE;
c->k=k;
c->lewy=(NODE*) NULL;
c->prawy=(NODE*) NULL;
y=(NODE*) NULL;
x=Root;
while(x)
{
y=x;
if(k<x->k) x=x->lewy;
else x=x->prawy;
}
if(!y)
Root=c;
else
{
if(k<y->k) y->lewy=c;
else y->prawy=c;
}
}
void obejdz(NODE *TREE)
{
if (!TREE) return;
obejdz(TREE->lewy);
cout << TREE->k<< endl;
obejdz(TREE->prawy);
}
main()
{
int nr;
Root=(NODE*) NULL;
for(int i=0; i< 10; i++)
{
cout << "Podaj kolejna liczbe: ";
cin >> nr;
dodaj(nr);
}
obejdz(Root);
system("PAUSE");
}
Unie
• typ zmiennej, która pozwala na wykorzystanie tego
samego miejsca w pamięci (w róŜnym czasie) do
zapisania wartości róŜnych typów
o mamy zapisać dwie tablice:
int a[30];
char b[100];
których znajomość nie będzie nigdy potrzebna
jednocześnie
o definiujemy unię:
union tablice {
int a[30];
char b[100];
} x;
o unia x zajmuje w pamięci tyle co dłuŜsza z tablic a i b (czyli
4*30 = 120 bajtów)
o tablice a i b zaczynają się od tego samego adresu (zajmują
ten sam obszar pamięci)
o do x będziemy mogli wstawiać liczby całkowite lub teksty
(ale nie jednocześnie – więc albo jeden albo drugi typ)
o za kontrolę tego, jaki typ znajduje się aktualnie w unii
odpowiada programista
o odwołanie do składowych – jak dla struktur:
union tablice x;
union tablice *p;
x.a[5];
/* nazwa_unii.składowa */
p->a[5];
/* wskaźnik_unii->składowa */
• unie mogą wystąpić w strukturach i tablicach (i na odwrót)
Przykład:
#define NSYMB
100
struct {
/*
char *nazwa;
/*
int flaga;
/*
int utype;
/*
union {
/*
int ival;
float fval;
char *sval;
} u;
}
tab_symb[NSYMB];
symbol */
nazwa symbolu */
znacznik stanu */
typ wartości
*/
wartości symbolu */
• odwołanie do składowej ival k-tego symbolu:
tab_symb[k].u.ival
• odwołanie do pierwszego znaku tekstu wskazywanego przez sval:
*tab_symb[k].sval
lub
tab_symb[k].u.sval[0]
Pole bitowe
• stosowane w sytuacji gdy chcemy zapakować kilka
danych bitowych (typu: tak – nie) w pojedynczym
obiekcie typu całkowitego
Przykład: tablica symboli omawiana wyŜej, potrzebne
określenie, czy symbol jest
• słowem kluczowym
• obiektem zewnętrznym,
• obiektem statycznym
MoŜliwe reprezentacje:
• przez maskowania:
#define KEYWORD 01 /* słowo kluczowe */
#define EXTERNAL 02 /* obiekt zewnętrzny */
#define STATIC
04 /* obiekt statyczny */
unsigned int flags;
flags |=EXTERNAL|STATIC; /* ustawia bity EXERNAL i
STATIC */
flags &= ~(EXTERNAL | STATIC); /* kasuje te bity */
• przez pola bitowe
struct {
unsigned int is_keyword
unsigned int is_extern
unsigned int is_static
} flags;
: 1;
: 1;
: 1;
definiuje flags jako zmienną zawierającą trzy jednobitowe pola
(liczba po dwukropku – rozmiar pola)
• ustawienie pól:
flags.is_extern=flags.is_static=1;
• kasowanie pól:
flags.is_extern=flags.is_static=0;
• zaleŜność od implementacji (umieszczenie pól w słowie –
od lewej do prawej czy na odwrót; moŜliwość
przekraczania słów)
• deklarujemy jako int (signed lub unsigned)
• pola bitowe nie są tablicami, nie mają adresów (nie moŜna
stosować operatora &), nie na wskaźników do pól
bitowych)

Podobne dokumenty