Ć
wiczenia 12 VI 2012
REKORDY (STRUKTURY)
W tablicach mieliśmy do czynienia z elementami tego samego typu, natomiast tutaj możemy łączyć różne
typy danych. Matematycznie, możemy to potraktować jako iloczyn kartezjański, tzn.
Mając dane zbiory Z
1
, ..., Z
n
możemy utworzyć iloczyn kartezjański Z
1
x...xZ
n
. Wówczas element ma postać:
(z
1
,...,z
n
). Każdy taki ciąg nazywamy rekordem. W tym przypadku można wybrać operator pozwalający
pobrać poszczególne wartości. Operator ten wybiera z rekordu (z1,...,zn) np. i-tą składową. W tablicy
mogliśmy za pomocą pętli for oraz indeksu (liczba całkowita) wykonać potrzebne operacje, natomiast w
przypadku rekordu, identyfikatory stanowią nieuporządkowany zbiór różnych nazw, nie tworząc ustalonego
typu danych. Składowe rekordów należy nazywać oddzielnie nie mogąc się do nich odwoływać kolejno.
Rekordy warto stosować wówczas, gdy chcemy operować złożoną strukturą danych mając do niej dostęp
przez jedną zmienną. Struktury są deklarowane ze słowem kluczowym struct. Deklaracja ma postać: struct
nazwa;
Natomiast jej definicja: struct nazwa { /*...*/ };
Zauważmy, że nazwa jest nazwą nowo zdefiniowanego typu. Na końcu struktury dajemy średnik po
nawiasie klamrowym.
Przykład
#include <cstdlib>
#include <iostream>
using namespace std;
struct pracownik {
char *tytul_stopien;
char *nazwisko;
int wiek, liczba_publikacji; };
int main() {
pracownik prac;
prac.tytul_stopien ="Dr";
prac.nazwisko = "Jan Nowak";
prac.wiek = 35;
prac.liczba_publikacji = 32;
cout <<"info1 : "<<prac.tytul_stopien <<endl;
cout <<"info2 : "<<prac.nazwisko << endl;
cout <<"info3 : "<<prac.wiek<< endl;
cout <<"info4 : "<<prac.liczba_publikacji <<endl;
system("PAUSE");
return EXIT_SUCCESS;
}
Szczególną uwagę przy korzystaniu z rekordów należy zwrócić na zgodność typów. Każda deklaracja
struktury wprowadza nowy, niepowtarzalny typ, np.
struct sI { int n ; };
struct sII { int m ; };
są to dwa różne typy, stąd w deklaracjach
sI a,b ;
sII c ;
zmienne a oraz b są tego samego typu sI, ale c już nie!
Wobec tego przypisania
a=b;
b=a;
są poprawne!
a = c;
nie jest poprawne!
Natomiast przypisania składowych rekordów o tych samych typach są poprawne, np.
a.i = b.j;
WSKAŹNIKI
Do tej pory deklarowaliśmy np.
int i;
rezerwując miejsce w pamięci dla zmiennej i typu całkowitego.
Wskaźnik (ang. pointer) jest to specjalny rodzaj zmiennej, w której zapisany jest adres w pamięci
komputera, tzn. wskaźnik wskazuje miejsce, gdzie zapisana jest jakaś informacja (stąd nazwa zmienna
wskaźnikowa).
Adres to pewna liczba całkowita, w sposób jednoznaczny definiująca położenie pewnego obiektu (np.
liczby, znaku, struktury czy tablicy) w pamięci komputera. Wskaźnik ma ścisłą kontrolę typów i z tego
powodu nie tylko wskazuje miejsce zajmowane przez zmienną, ale także ile bajtów ta zmienna potrzebuje,
Definicja zmiennej typu wskaźnikowego ma następującą strukturę:
typ *identyfikator_zmiennej;
Przykład:
int *i; (zapis może być różny: int *i1; int * i2; int* i3; int*i4;)
float *x;
float *x,y;
Jak już wspominaliśmy, operator & służy do pobrania adresu miejsca w pamięci, gdzie istnieje dana
zmienna, jest nazywany referencją a operator * jest nazywany operatorem dereferencji lub wskaźnikiem.
Przykład
#include <cstdlib>
#include <iostream>
using namespace std;
int main()
{
int i=3;
int *j;
j=&i; //przypisanie zmiennej, która przechowuje adres adresu zmiennej i
cout<<"wartosc liczby j="<<*j<<endl;
cout<<"adres liczby i ="<<&i<<endl;
system("PAUSE");
return EXIT_SUCCESS;
}
Inicjalizacja wskaźnika może być wykonana następująco:
int *wsk = NULL;
(NULL oznacza element nie istniejący)
int *wsk = 0;
Przy tworzeniu i używania wskaźników łatwo można popełnić błąd. Jeżeli zaniedbamy przypisanie
wskaźnikowi adresu.
int *wskaznik_numer;
*wskaznik_numer = 1532;
Jest to błąd, ponieważ nie wiemy co oznacza komórka o adresie 1532, może się okazać, iż adres ten jest już
zajęty przez program, wówczas nie będzie można zapisać nic w miejsce wskazane przez wskaznik_numer
(jest on dość trudny do wykrycia).
TABLICE-WSKAŹNIKI
Przykład:
#include <cstdlib>
#include <iostream>
using namespace std;
int main()
{ int x[15];
int k;
cout<<"podaj rozmiar swojej tablicy"<<endl;
cin>>k;
for (int i=0; i<k; i++)
x[i]=rand()%100;
for (int i=0; i<k; i++)
cout<<"x "<<i<<"= "<<*(x+i)<<"* "<<x[i]<<endl;
system("PAUSE");
return EXIT_SUCCESS;
}
Zadanie 10.1 (domowe):
Posługując się tablicą z rekordami – zaprojektuj listę towarów strukturze: dostawca (rekord), rodzaj towaru
(art. papiernicze, zabawki, etc.), ilość, cena, dzień tygodnia, w którym realizowana jest dostawa, itd. Dla
wszystkich przypadków wprowadź przykładowe dane.
SORTOWANIE BĄBELKOWE
Porównywane są kolejne elementy tablicy i jeśli elementy są w nieodpowiedniej kolejności, to są one
zamieniane miejscami. Jeśli sortowana struktura danych posiada k elementów to aby sprawdzić wszystkie
elementy musi być wykonane k-1 porównań. W najmniej korzystnym przypadku element, który powinien
znaleźć się na początku tablicy znajduje się na końcu. Ponieważ przy jednym sprawdzeniu element może się
przesunąć tylko o jedno miejsce to musimy wykonać k-1 sprawdzeń listy, czyli zostanie wykonanych (k-1)
2
porównań. W trakcie zamiany wartości w tablicy wykorzystywana jest zmienna pomocnicza, nazwijmy ją
np. tmp.
Przykład:
int i, j, k, x, tmp;
int tablica[10];
cout << "Podaj ilość liczb : \n";
cin>>k;
cout << "Podaj elementy swojej tablicy: \n";
for (i=0; i<k; i++)
cin >>tablica[i];
for (i=0;i<k; i++)
for (j=0;j<k; j++)
if (tablica[j]>tablica[j+1])
{
tmp = tablica[j];
tablica[j] = tablica[j+1];
tablica[j+1] = tmp;
}
for (i=0; i<=4; i++) /* wyświetlanie posortowanych liczb */
cout << tablica[i] << " , "<<endl;
system("pause");
return 0;
}
TABLICE DYNAMICZNE
Ich pamięć i wymiar są alokowane podczas pracy programu. Dynamiczna alokacja tablicy jednowymiarowej
może wyglądać (przy użyciu słowa kluczowego new) następująco:
int * tablica;
tablica = new int[rozmiar];
gdzie rozmiar jest wyrażeniem typu int.
lub
int *tablica= new int [rozmiar];
Każdą zadeklarowaną tablicę należy „zlikwidować” tzn. zwolnić zajętą przez nią pamięć:
delete [] tablica;
Pamięć komputera nie jest nieograniczona. Trzeba się z tym liczyć i sprawdzać czy operacja się powiodła
float *wsk;
wsk=new float[8192];
if(!wsk) //czyli if(wsk==NULL)
{
cout<< ”Pamiec wyczerpana”;}
Przykład: Dynamiczna alokacja tablicy jednowymiarowej:
#include <iostream>
using namespace std;
main()
{
int *T; // tworzymy wskaźnik
int i,n;
cin >> n; // odczytujemy ilość komórek
T = new int[n]; // tworzymy tablicę dynamiczną o n komórkach
for(i = 0; i < n; i++)
cin >> T[i]; // wczytujemy kolejne komórki
for(i = 0; i < n; i++)// wypisujemy odczytaną tablicę
cout << endl << "T[" << i << "] = " << T[i];
cout << endl;
system("PAUSE");
}
Zadanie 10.2 (domowe):
Zadeklarować dynamiczną jednowymiarową tablicę elementów typu rzeczywistego, wypełnić ją danymi a
następnie: posortować tablicę rosnąco, malejąco, podać wartość mediany.
Przykład: Dynamiczna alokacja tablicy dwuwymiarowej:
#include <iostream>
using namespace std;
main()
{
int **tablica;
int l_wierszy=5,l_kolumn=6;
tablica=new int*[ l_wierszy];
for(int i=0;i< l_wierszy;i++)
tablica[i]=new int[l_kolumn];
//I teraz już mamy tablicę tablica[5][6];i możemy ją wypełnić
for(int i=0;i< l_wierszy; i++)
for(int j=0;j< l_kolumn; j++)
cin>>tablica[i][j];
cout<<tablica[3][4];
for(int i=0;i< l_wierszy;i++)// zwolnienie pamięci
delete []tablica [i];
delete []tablica;
}
Funkcja (czasami zwana procedurą) to fragment programu, któremu nadano odrębną nazwę (powinna
mówić o jej działaniu), dzięki czemu może być wykonywany poprzez podanie nazwy oraz ewentualnych
argumentów. Argumentami są natomiast dane przekazywane do funkcji.
Budowa funkcji:
typ_zwracanej_wartosci_z_funkcji nazwa_funkcji( typ_argumentu_1 nazwa_argumentu_1 ,…,
typ_argumentu_m nazwa_argumentu_m ).
{
return zwracana_wartosc
;
}
Słowem kluczowym return regulujemy, co ma zostać zwrócone przez funkcję.
Wywoływanie funkcji
Polega na wpisaniu jej nazwę i przekazaniu wartości do funkcji:
nazwa_funkcji( wartosc_argumentu_1 ,…, wartosc_argumentu_m );
Przykład (bez argumentów i zwracania wartości)
#include <cstdlib>
#include <iostream>
using namespace std;
int gwiazdki(int ile)
{
int i ;
for(i = 0 ; i < ile ; i++)
{
cout << " * " ;
}
return 13 ;
}
int main()
{
int m = 100 ;
cout << "Zaczynamy" << endl ;
m = gwiazdki(20) ;
cout << "\nNa wyjsciu zmiana wartosci i m = " << m <<endl;
system("PAUSE");
return EXIT_SUCCESS;
}
Przykład 2 (tym razem ze zwracaniem wartości)
I sposób realizacji
#include <cstdlib>
#include <iostream>
using namespace std;
int kukulka(int ile) //
{ //
int i ;
for(i = 0 ; i < ile ; i++)
{
cout << "Ku-ku ! " ;
}
return 77 ; //
}
/**************************************************/
int main()
{
int m = 20 ;
cout << "Zaczynamy" << endl ;
m = kukulka(5) ; //
cout << "\nNa koniec m = " << m ; //
system("PAUSE");
return EXIT_SUCCESS;
}
II sposób realizacji
#include <cstdlib>
#include <iostream>
using namespace std;
int gwiazdki(int ile);
int main()
{
int m = 100 ;
cout << "Zaczynamy" << endl ;
m = gwiazdki(20) ;
cout << "\nNa wyjsciu zmiana wartosci i m = " << m <<endl;
system("PAUSE");
return EXIT_SUCCESS;
}
int gwiazdki(int ile)
{
int i ;
for(i = 0 ; i < ile ; i++)
{
cout << " * " ;
}
return 13 ;
}
Czas życia zmiennych w funkcjach
Zmienne, które zostały utworzone w funkcji są tymczasowe, pojawiają się do użycia przy każdorazowym
wywołaniu funkcji a znikają po jej opuszczeniu. Zmienne te nie zachowują niczego z poprzedniego.
Przykład 3
#include <cstdlib>
#include <iostream>
using namespace std;
float mnozenie_liczb()
{ float a, b;
cin >> a;
cin >> b;
return a * b;
}
int main()
{
cout << "Prosze podac dwie liczby: ";
float wynik = mnozenie_liczb();
cout << "Wynik dzialania wynosi: " << wynik << endl;
system("PAUSE");
return EXIT_SUCCESS;
}
Zadanie 10.3:
Napisać program, który odwołując się do funkcji potega (zdefiniowanej przed programem) dla liczb
całkowitych z określonego przedziału, policzy wszystkie potęgi od 2 do 5.
Zadanie 10.4:
Napisać program, który odwołując się do funkcji silnia (zdefiniowanej przed programem) dla wczytanej
liczby naturalnej policzy silnię.