wykład 7 Agata Półrola Wydział Matematyki i Informatyki UŁ semestr zimowy 2018/2019
Tablice wielowymiarowe Tablice wielowymiarowe, podobnie jak jednowymiarowe, przechowują wartości tego samego typu. Przykłady: tabela temperatur w stolicach europejskich w kolejnych dniach tygodnia szachownica plansza do gry w okręty sudoku...
Tablice wielowymiarowe w C++ Tablice wielowymiarowe można uważać za tablice tablic. Przykładowe definicje: int szachownica [8][8]; zwyczajowo przyjmuje się że pierwszy (lewy) indeks oznacza wiersz, a drugi (prawy) - kolumnę, ale taka interpretacja nie jest obowiązkowe liczba wymiarów może być dowolna int przykladtablicy [2][4][2][100][5];
Tablice wielowymiarowe w C++ - cd do elementu tablicy wielowymiarowej odwołujemy się poprzez podanie nazwy zmiennej tablocowej i wartości indeksow dla wszystikich wymiarów (indeksowanie zaczyna się od zera, tak jak w tablicach jednowymiarowych): szachownica [1][0] = 1; ustawiamy pionej w kwadracie drugim poziomo i pierwszym pionowo elementy tablicy wielowymiarowej możemy przeglądać używając zagnieżdżonych pętli, po jednej dla każdego wymiaru: for (int i=0; i<8; i++) { for (int j=0; j<8; j++) cout << szachownica[i][j] << " "; cout << endl; }
Inicjalizacja tablic wielowymiarowych tablice dwuwymiarowe: w celu inicjalizacji tablicy dwuwymiarowej najprościej jest użyć zagnieżdżonych nawiasów klamrowych. Wewnętrzne nawiasy zawierają liczby reprezentujące zawartość wierszy: int tab[2][3] = { {1,2,3}, {4,5,6}}; (przy czym wewnętrzne nawiasy klamrowe można pominąć) dozwolone jest pominięcie wartości pierwszego indeksu inicjalizowanej tablicy: int tab[ ][3] = { {1,2,3}, {4,5,6}}; (pominięcie obu specyfikacji rozmiaru jest niedozwolone)
Inicjalizacja tablic wielowymiarowych - cd inicjalizacja tablic o większej liczbie wymiarów przebiega podobnie: int aa[2][3][4] = {{{1,1,1,1}, {2,2,2,2}, {3,3,3,3}}, {{4,4,4,4}, {5,5,5,5}, {6,6,6,6}} }; podobnie jak w tablicach dwuwymiarowych specyfikację pierwszego rozmiaru można pominąć
Tablice wielowymiarowe o rozmiarze określanym w czasie wykonania programu Tworzenie statycznych tablic wielowymiarowych o rozmiarze określanym w czasie wykonania progamu, tj. użycie konstrukcji postaci int N,M; cout << " podaj liczbe wierszy i kolumn tablicy : " ; cin >> N >> M; int tab [N] [M]; jest niezgodne ze standardem (podobnie jak w przypadku tablic jednowymiarowych), ale akceptowane przez część kompilatorów konstrukcja zgodna ze standardem to tworzenie tablic dynamicznych (z alokacją pamięci za pomocą operatora new i zwalnianiem pamięci za pomocą delete)
Przykład (Dwuwymiarowa tablica o rozmiarze podanym przez uzytkownika - wersja.1) # include <iostream > using namespace std ; int main () { int N, M; cout << " ile wierszy ma miec tablica? " ; cin >> N; cout << " ile kolumn ma miec tablica? "; cin >> M; int tab [N][M]; // niezgodne ze standardem } for ( int i =0; i<n; i ++) for ( int j =0; j<m; j ++) tab [i][j] = i*j; for ( int i =0; i<n; i ++) { for ( int j = 0; j<m; j ++) cout << tab [i][j] << " "; cout << endl ; } return 0;
Przykład (Dwuwymiarowa tablica o rozmiarze podanym przez uzytkownika - wersja.1) # include <iostream > using namespace std ; int main (){ int N, M; cout << " ile wierszy ma miec tablica? " ; cin >> N; cout << " ile kolumn ma miec tablica? "; cin >> M; int (** tab ); // zgodne ze standardem // alokacja pamieci tab = new int *[N]; for ( int i =0;i<N; i ++) tab [i] = new int [M]; // uzycie tablicy for ( int i =0; i<n; i ++) for ( int j =0; j<m; j ++) tab [i][j] = i*j; for ( int i =0; i<n; i ++) { for ( int j = 0; j<m; j ++) cout << tab [i][j] << " "; cout << endl ; } // zwolnienie pamieci for ( int i =0;i<N; i ++) delete [] tab [i]; delete [] * tab ; return 0; }
Tablice wielowymiarowe inaczej Efekt tablicy wielowymiarowej można uzyskać definiując tablicę jednowymiarową odpowiedniego rozmiaru, a następnie odpowiednio przeliczając indeksy
Przykład (Tablica dwuwymiarowa za pomocą jednowymiarowej) # include <iostream > using namespace std ; int main () { int tab2 [3*3]; for ( int i =0;i <3; i ++) for ( int j =0;j <3; j ++) { } } cout << " obliczam element na pozycji ( iiniowo ) " << i *3+ j << endl ; tab2 [i *3+ j] = i *3+ j; for ( int i =0;i <3; i ++) { for ( int j =0;j <3; j ++) cout << tab2 [i *3+ j]; } cout << endl ; return 0;
Przykład (Tablica 3-wymiarowa za pomocą jednowymiarowej) # include <iostream > using namespace std ; int main () { const int W1 = 3; const int W2 = 3; const int W3 = 4; int tab3 [W1*W2*W3 ]; for ( int i =0;i<W1;i ++) for ( int j =0;j<W2;j ++) for ( int k =0; k<w3; k ++) { } cout << " obliczam element na pozycji " << i << "," << j << "," << k << " czyli liniowo " << (i*w2*w3)+(j*w3)+k << endl ; tab3 [(i*w2*w3)+(j*w3)+k] = (i*w2*w3)+(j*w3)+k; cout << " --------------------------------------------" << endl ; for ( int i =0;i<W1;i ++) { for ( int j =0;j<W2;j ++) { for ( int k =0;k<W3;k ++) cout << tab3 [(i*w2*w3)+(j*w3)+k] << " "; cout << endl ; } cout << endl ; } return 0; Agata Półrola } Wydział Matematyki i Informatyki UŁ
Napisy w stylu C (tablice znaków) Łańcuch to ciąg znaków przechowywanych w kolejnych bajtach. Łańcuchy reprezentują napisy. do przechowywania łańcuchów w stylu C służą tablice znakowe. łańcuch reprezentuje napis, przy czym jego ostatnim znakiem jest \0 (znak o kodzie ASCII zero). tylko taka tablica znaków w której występuje znak \0 jest łańcuchem. stałe łańcuchowe to ciągi znaków otoczone cudzysłowem. Znak kończący jest w nich umieszczony niejawnie tablica przechowująca łańcuch musi być zatem o jeden element dłuższa niż liczba znaków w reprezentowanym napisie
Inicjalizacja łańcuchów tablicę znaków reprezentującą łańcuch można wypełnić w czasie definiowania: char zdanie1 [] = { A, l, a,, m, a,, k, o, t, a, \0 }; char zdanie2 [12] = {" Ala ma kota "}; char zdanie3 [12] = " Ala ma kota "; char zdanie4 [20]= " Ala ma kota "; Możliwe jest podanie w nawiasach klamrowych wszystkich znaków które mają być umieszczone w tablicy, oddzielonych przecinkami, ostatnim znakiem musi być \0 można użyć zapisów skróconych - napisu ujętego w cudzysłowy, w nawiasach klamrowych lub bez. Znak \0 zostanie dodany automatycznie długość tablicy może zostać automatycznie określona przez kompilator na podstawie przypisywanego napisu (puste nawiasy kwadratowe) lub podana jawnie
Wypisywanie łańcuchów Przyjmijmy że mamy zdefiniowaną zmienną: char napis[20]; wypisywanie łańcuchów polega na wysłaniu ich do strumienia wyjściowego, np.: cout << napis << endl; (wysyłamy całą zmienną tablicową przechowującą łańcuch, wypisywana jest część do znaku \0 )
O pobieraniu danych Przy pobieraniu danych za pomocą cin >> zmienna (czyli wyjmowaniu danych ze strumienia wejściowego) przyjmuje się domyślnie że: wczytywane dane, niezależnie od ich typu, mogą byc poprzedzone białymi znakami (spacją, tabulatorem itp), które przy wczytywaniu są ignorowane. przy wczytywaniu typów przechowujących liczby całkowite znaki przychodzące z klawiatury interpretowane są jako podane w zapisie dziesiątkowym jeśli chcemy wczytać liczbę ze znakiem (+ lub -), to między tym znakiem a liczbą nie można umieszczać spacji, wczytywanie liczby całkowitej zostaje zakończone, gdy napotkany zostaje znak nie będący cyfrą, wczytywanie liczb zmiennoprzecinkowych przebiega podobnie, przy czym w przypadku notacji wykładniczej może wystąpić po raz drugi znak + lub - (jako znak wykładnika), a także odpowiednia litera określająca wykładnik (np. -10.3e-2). Zapis liczby nie może zawierać spacji.
Pobieranie łańcuchów w konsekwencji za pomocą cin >> napis można pobrać tylko jedno słowo (następuje wczytanie danych do zmiennej aż do napotkania białego znaku, początkowe białe znaki są ignorowane). Jeśli wprowadzimy więcej niż jedno słowo, dalsza część napisu zostaje w strumieniu. Jeśli wprowadzimy jedno słowo - w strumieniu pozostaje znak końca ( enter czyli znak \n ) w obu przypadkach znaki pozostałe w strumieniu mogą wpływać na pobieranie kolejnych danych uwaga - próba wczytania do tablicy znakowej wyrazu dłuższego niż ta tablica powoduje, że znaki napisu nie mieszczące się w tablicy zacierają jakieś fragmenty pamięci (niszczą ich zawartość, być może istotną).
Pobieranie łańcuchów - cd łańcuch można też pobrać za pomocą funkcji get (będącej tzw. metodą klasy-strumienia istream). Sposób użycia: cin.get(zmienna łańcuchowa, długość maksymalna, znak kończący ) np. cin.get(napis,20); Funkcja pobiera bajty (znaki) ze strumienia wejściowego, umieszczając je w zmiennej zmienna łańcuchowa. Liczba wczytanych znaków nie może być większa niż długość maksymalna (jeślli znaków w strumieniu jest więcej to w nim pozostają). Znaki wczytywane są do momentu napotkania znaku kończącego, którym domyślnie (jeśli nie podamy inaczej) jest znak końca linii \n. Znak kończący nie jest wyjmowany ze strumienia (nadal w nim pozostaje). Niezależnie od powodu zakończenia wczytywania ze strumienia do tablicy zmienna łańcuchowa wstawiany jest znak \0, czyli ze strumienia można pobrać maksymalnie (długość maksymalna - 1) znaków użycie get pozwala pobrać więcej niż jedno słowo, ale pozostawia w strumieniu (co najmniej) znak końca linii, co może mieć wpływ na kolejne pobierania danych
Pobieranie łańcuchów - cd funkcja get (metod klasy istream) występuje też w postaci pozwalającej na wyjęcie ze strumienia jednego znaku. Wersje tej funkcji (sposoby użycia): char znak; int kod znaku; cin.get(znak); kod znaku=cin.get(); Wynikiem jest albo pobranie znaku i wstawienie go do zmiennej typu char (zmienna znak), albo pobranie znaku i umieszczenie jego kodu liczbowego w zmiennej całkowitej kod znaku). Uwaga - są to dwie różne funkcje o tej samej nazwie! jeśli chcemy po prostu pobrać ze strumienia jeden znak i nie zapamiętywać go, możemy użyć get w następujący sposób: cin.get(); można w ten sposób usunąć ze strumienia enter ( \n ) pozostały po wcześniejszym pobieraniu danych
Pobieranie łańcuchów - cd łańcuch można też pobrać za pomocą funkcji getline (będącej tzw. metodą klasy-strrumienia istream). Sposób użycia: cin.getline(zmienna łańcuchowa, długość maksymalna, znak kończący ) np. cin.getline(napis,20); Funkcja pobiera bajty (znaki) ze strumienia wejściowego, umieszczając je w zmiennej zmienna łańcuchowa. Liczba wczytanych znaków nie może być większa niż długość maksymalna, przy czym jeśli wczytywanie zatrzyma się z powodu przekroczenia tego argumentu, to strumień przechodzi w stan błędu. uwaga - na obecnym etapie nie uczymy się rozwiązania tego problemu! Znaki wczytywane są do momentu napotkania znaku kończącego, którym domyślnie (jeśli nie podamy inaczej) jest znak końca liniii \n. Znak ten jest wyjmowany ze strumienia. Do tablicy zmienna łańcuchowa wstawiany jest znak \0, czyli ze strumienia można pobrać maksymalnie długość maksymalna - 1 znaków jeśli pobierany tekst mieści się w zmiennej, to użycie getline pozwala pobrać więcej niż jedno słowo oraz nie pozostawia w strumieniu znaku końca linii
Pobieranie łańcuchów - przydatne cin.ignore każde wczytywanie nie korzystające z cin.getline (także np. wczytywanie liczb!) pozostawia w strumieniu znak końca linii ( enter z klawiatury). Może on przeszkadzać we wczytaniu kolejnych danych przydatną funkcją może być zatem cin.ignore(ile, znak kończący). Funkcja ta wyjmuje ze strumienia liczbę znaków określoną parametrem ile (domyślnie jeden), bez ich zapamiętywania. Wyjmowanie znaków może zakończyć się wcześniej jeśli zostanie napotkany znak kończący (domyślnie jest EOF - znak końca pliku) funkcji tej najczęściej używamy pisząc cin.ignore(); - zazwyczaj chcemy wyjąć ze strumienia jeden znak, którym przeważnie jest znak \n - enter wstawiony tam przy podawaniu z klawiatury liczby czy napisu.
Przykład (Pobranie kolejno liczby i dwóch napisów) # include < iostream > using namespace std ; int main () { char imie [30], nazwisko [30]; int rok ; cout << " podaj rok urodzenia osoby : "; cin >> rok ; cin. ignore (); // usuniecie ze strumienia \n, bez tego nie da sie pobrac imienia cout << " podaj imie / imiona tej osoby : "; cin. getline (imie,30) ; cout << " podaj nazwisko : "; cin. getline ( nazwisko,30) ; cout << " osoba to " << imie << " " << nazwisko << " ur. w roku " << rok << endl ; } return 0;
Funkcje dla tablic znakowych - biblioteka cstring Dołączenie do programu biblioteki cstring daje nam dostęp do przydatnych funkcji: strcmp(napis1, napis2), gdzie napis1 i napis2 są tablicami znakowymi Funkcja zwraca wartość zero jeśli napisy podane jako parametry są identyczne. Zwrócenie wartości niezerowej oznacza, że podczas porównywania napisów znak po znaku napotkana została pozycja na której stoją różne znaki, przy czym: zwrócenie wartości dodatniej oznacza że znak występujący w napisie1 ma wartość (tj. numer w kodzie ASCII) większą niż znak występujący w napisie2, zwrócenie wartości ujemnej oznacza że znak występujący w napisie1 ma mniejszą wartość niż znak występujący w napisie2. Uwaga - nie można porównać zawartości napisów za pomocą operatorów ==, < czy >!!
Przykład (Porownywanie napisow) # include < iostream > # include < cstring > int main () { using namespace std ; char poprawna_ odpowiedz [30] = " wtorek "; char odp [80]; do { cout << " Jaki dzien tygodnia lubie najbardziej? "; cin >> odp ; } while ( strcmp ( odp, poprawna_ odpowiedz )!= 0); cout << " Zgadles! \n"; return 0; }
Funkcje dla tablic znakowych - cd strlen(napis), gdzie napis jest tablicą znakową Funkcja zwracająca długość napisu podanego jako parametr (długość jest liczbą właściwych znaków w napisie, znak \0 kończący napis nie jest liczony)
Przykład (Dlugosc napisu) # include < iostream > # include < cstring > int main () { using namespace std ; char napis [300]; int dlugosc ; } cout << " podaj zdanie : \n"; cin. getline ( napis,300) ; dlugosc = strlen ( napis ); cout << " Zdanie to: " << napis << endl ; cout << " Dlugosc zdania to " << dlugosc << " znakow " << endl ; return 0;
Funkcje dla tablic znakowych - cd strcpy(napis docelowy, napis zrodlowy), gdzie napis zrodlowy i napis docelowy to tablice znakowe Funkcja kopiuje napis umieszczony w zmiennej napis źródłowy do zmiennej napis docelowy. Kopiowane są wszystkie znaki napisu, łącznie z kończącym znakiem \0. strcat(napis zrodlowy, napis doklejany), gdzie napis zrodlowy i napis doklejany to tablice znakowe Funkcja doklejająca kopię napisu napis doklejany na końcu napisu napis docelowy. Znak \0 kończący napis docelowy jest nadpisywany przez pierwszy znak napisu doklejanego; powstała konstrukcja zakańczana jest znakiem \0 (jest poprawnym C-stringiem)
Przykład (Kopiowanie i konkatenacja napisów) # include < iostream > # include < cstring > using namespace std ; int main () { char napis [80]; strcpy ( napis,"to sa "); cout << napis << "... \n"; strcat ( napis, " posklejane "); cout << napis << "... \n"; strcat ( napis, " lancuchy "); cout << napis << ".\n"; return 0; }
Typy wyliczeniowe enum Typy wyliczeniowe w C++ pozwalają na przyporządkowanie pewnym nazwom wartości liczbowych (całkowitych) powyższe nazwy stają się wartościami typu w którym zostały określone
Definiowanie typu wyliczeniowego definicja typu wyliczeniowego: enum nazwa typu {nazwa1 = wartosc1, nazwa2 = wartosc2,... }; e.g., enum dni tygodnia {pon = 1, wt, sr, czw, pt, so, nie}; jest to typ arytmetyczny (całkowity) przykład zmiennej tego typu: dni tygodnia dzis, kiedys; dzis= pt; kiedys = dni tygodnia(3); // przypisanie 3 niedozwolone zmiennej typu wyliczeniowego można przypisać jedynie którąś z wartości podanych na liście wyliczeniowej definiującej typ
Lista wyliczeniowa jeśli nie określimy inaczej, to pierwsza pozycja na liście wyliczeniowej odpowiada liczbie 0 jeśli nie określimy inaczej, to kolejne pozycje odpowiadają kolejnym liczbom naturalnym Przykład reprezentacje liczbowe nie muszą być różne enum silawiatru {cisza=0, powiew, slaby wiatr, sztorm=8, silny sztorm, huragan=12}; Elementy mają przypisane wartości odpowiadające im w skali Beauforta. Powiew odpowiada liczbie 1 (jako kolejnej po 0), slaby wiatr - 2, silny sztorm - 9 (jako kolejnej po 8)
Przykład (Wykorzystanie typu wyliczeniowego) # include <iostream > using namespace std ; int main () { enum stolice { Warszawa=0, Moskwa,Berlin,Praga, Paryz }; enum kwartaly {I, II, III, IV }; int nr; stolice s; float sr_temperatury [5][4]; sr_temperatury [ Warszawa ][I] = -12.5; sr_temperatury [ Warszawa ][ II] = 15; sr_temperatury [ Warszawa ][ III ] = 18.5; sr_temperatury [ Warszawa ] [IV] = 5; cout << " podaj stolice : 1 - Warszawa, 2 - Moskwa, 3 - Berlin, 4 - Praga, 5 - Paryz "; cin >> nr; s = stolice (nr -1) ; switch (s) { case Warszawa : cout << " srednie temperatury w Warszawie : \n"; break ; case Moskwa : cout << " srednie temperatury w Moskwie : \n"; break ; case Berlin : cout << " srednie temperatury w Berlinie : \n"; break ; case Praga : cout << " srednie temperatury w Pradze : \n"; break ; case Paryz : cout << " srednie temperatury w Paryzu : \n"; break ; } for ( int i =0; i <4; i ++) cout << " kwartal " << i+1 << ": " << sr_temperatury [s][i] << endl ; return 0; }