Podstawy algorytmiki i programowania - wykład 1 Tablice powtórzenie Tablice znaków Tablice dwuwymiarowe Treści prezentowane w wykładzie zostały oparte o: S. Prata, Język C++. Szkoła programowania. Wydanie VI, Helion, 2012 www.cplusplus.com Jerzy Grębosz, Opus magnum C++11, Helion, 2017 B. Stroustrup, Język C++. Kompendium wiedzy. Wydanie IV, Helion, 2014 S. B. Lippman, J. Lajoie, Podstawy języka C++, WNT, Warszawa 2003. 1
Funkcje - przypomnienie Napisz funkcję iledziel, która zwraca liczbę dzielników naturalnych liczby całkowitej n podanej jako argument, a w dodatkowym parametrze s zwraca sumę tych dzielników. Np. dla argumentu n=6 funkcja powinna zwrócić wartość 4 (dzielniki 6 to: 1,2,3,6), zaś parametr s winien być =12 int iledziel( int n, int &s) { n = abs(n);//wartość bezwzględna int ildz = 0;//licznik dzielników s = 0; for(int i= 1; i<=n; i++) { if(n%i==0)//jeśli i jest dzielnikiem n { s+= i; //dodajemy dzielnik do sumy ildz++;//zwiększamy licznik return ildz;
Tablice jednowymiarowe - przypomnienie Tablice są podstawową złożoną strukturą danych. Tablice są zestawami elementów (wartości) tego samego typu, ułożonych na określonych pozycjach. Do każdego z tych elementów mamy bezpośredni dostęp poprzez nazwę tablicy i pozycję elementu w zestawie, określaną przez indeks. Numerowanie elementów zawsze rozpoczyna się od zera, tzn. element pierwszy ma numer 0, a element ostatni numer (indeks) o jeden mniejszy od rozmiaru tablicy (czyli liczby elementów tablicy). Jeśli tab jest tablicą, to jej element o indeksie k jest oznaczany tab[k], a więc używamy tu nawiasów kwadratowych.
Tablice jednowymiarowe - deklaracja Na razie zajmujemy się tablicami statycznymi, to znaczy, że ich rozmiar musi być znany już na etapie kompilacji programu. Deklarując tablicę musimy określić jej rozmiar. const int N = 20; int tab1[100], tab2[n],//n zmienna z modyfikatorem const tab3[] = {1,2,3,4,5,/* rozmiar tablicy wynika z liczby elementów podanych w nawiasach*/ tab4[5] = {1,2;/*tablica pięcioelementowa, zainicjowane dwa pierwsze elementy podanymi liczbami, natomiast pozostałe zerami*/ int tab5[]{1,2,3,4,5, /* C++11 znaki równości przed inicjatorem klamrowym mogą być opuszczone*/ 4
5 Tablice jednowymiarowe -przekazywanie do funkcji Przekazując tablicę do funkcji przekazujemy tylko adres jej początku. We wnętrzu funkcji wymiar tablicy nie jest znany. Wniosek z tego jest taki, że niemal zawsze, kiedy posyłamy do funkcji tablicę, musimy w osobnym argumencie przesłać informację o jej rozmiarze (liczbie elementów). void printtable(int size, double tab[]) { for (int i = 0; i < size; i++) cout << tab[i] << endl; int main() { double naszatab[] = {1,2,3; printtable(3,naszatab); return 0;
Tablice jednowymiarowe -przekazywanie do funkcji Napisz funkcję zerujwieksze, o parametrach: int n; //n<=100 double t[100]; double x; która przekształca elementy tablicy t[0],...,t[n-1] w ten sposób, że elementy większe od x są zastępowane zerami. Wartością funkcji ma być liczba wyzerowanych elementów. int zerujwieksze(int n, double t[], double x) { int ilezm = 0; for (int i = 0; i < n; i++) if(t[i] > x) { ilezm++; t[i] = 0; return ilezm; 6
7 Tablice jednowymiarowe -przekazywanie do funkcji int main() { double tabtest[] = {1,5,3,2,4; double x = 2; cout<<"tablica przed zmianami "<<endl; printtable(5,tabtest); int ile = zerujwieksze(5, tabtest, x); cout<<"w tablicy wyzerowano "<< ile <<" elementow."<<endl; cout<<"tablica po zmianach "<<endl; printtable(5,tabtest); return 0;
8 Tablice znaków (C-napisy) ( null-terminated string ) W klasycznym C nie ma typu napisowego, a rolę zmiennych napisowych pełnią tablice znaków, przy czym koniec napisu oznaczany jest znakiem ' \0'. Znak ten nazywany jest NUL. Znak NUL to znak o kodzie ASCII równym zeru, który nie odpowiada żadnemu znakowi graficznemu. Znak ten możemy wprowadzić do kodu programu używając literału '\0' (z apostrofami). Znak ten jest po to by zaznaczyć gdzie kończy się ciąg liter. Definiowanie tablic znakowych char tab1[] = "Kasia"; tworzy tablicę sześciu znaków na podstawie literału napisowego: pięć znaków imienia i jako szósty znak '\0', który oznacza koniec napisu - kompilator doda automatycznie. Znak '\0' został automatycznie dopisany po ostatniej literze dzięki temu, że inicjalizowaliśmy tablicę ciągiem znaków ograniczonym cudzysłowem.
9 Tablice znaków (C-napisy) Zdefiniujmy teraz tablicę znakową o 40 elementach i zainicjalizujmy napisem uwaga: char t[40]={ "uwaga"; W wyniku powyższej inicjalizacji poszczególne elementy tablicy mają wartości: t[0]='u' t[1]='w' t[2]='a' t[3]='g' t[4]='a' t[5]=nul Dalsze elementy tablicy nas obecnie nie interesują. Inicjalizacja sposobem analogicznym do tego, jaki znamy dla tablic innych typów: poprzez podanie wartości kolejnych elementów tablicy w nawiasie klamrowym. char t1[40]={'u','w','a','g','a','\0'; Tu znak '\0' musimy dodać ręcznie.
10 Tablice znaków (C-napisy) Jeśli natomiast zainicjujemy tablicę następująco char t2[40]={'u','w','a','g','a'; to kompilator nie dokończy tego znakiem NUL. Początkowych 5 elementów tablicy ma taką samą wartość jak w poprzednim przypadku, a pozostałe są inicjalizowane zerami ( a znak NUL ma przecież też wartość 0). Zatem c-string t2 jest poprawnie zakończony. Różnica pojawi się w sytuacji, gdy nie podamy rozmiaru tablicy. W przypadku char t4[]={'u','w','a','g','a'; będziemy mieli zarezerwowane miejsce na jedynie 5 elementów tablicy.
11 Tablice znaków (C-napisy) Uwaga: przekazując do funkcji napis w postaci tablicy znakowej nie musimy przekazywać jej wymiaru: obecność znaku ' \0' pozwala bowiem określić koniec napisu, a więc i jego długość. Wyjątkowe jest również traktowanie zmiennych typu char* (char []) przez operator wstawiania do strumienia. W zasadzie po 'cout «nap', gdzie nap jest typu char*, powinna zostać wypisana wartość zmiennej nap, czyli pewien adres (tak by było, gdyby zmienna nap była np. typu int*). Wskaźniki do zmiennych typu char są jednak traktowane odmiennie: zakłada się, że wskaźnik wskazuje na pierwszy znak napisu i wypisywane są wszystkie znaki od tego pierwszego poczynając aż do napotkania znaku ' \0'. Ważne więc, aby znak ' \0' tam był.
12 Przykład. void napisz (char tab[]) { int main() { Tablice znaków (C-napisy) cout << "Napis: " << tab << endl; char tab1[] = "Kasia"; tab1[0] = 'B'; napisz(tab1); //Basia return 0;
14 Tablice znaków (C-napisy) Przykład 2.Napisać funkcję, która przekopiuje dany C-string litera po literze do danej tablicy. void strcpy(char cel[ ], char zrodlo[ ]) { for(int i = 0 ; ; i++) { cel[i] = zrodlo[i]; //kopiujemy 1 znak if(cel[i] == 0)//sprawdzamy czy napotkaliśmy // koniec c-stringa(ma kod ASCII: zero.) break; //przerywamy kopiowanie //nasza wersja funkcji nie bada czy cel nie ma //mniejszego rozmiaru i nie wyjdziemy poza tablice
Tablice wielowymiarowe 16 Elementami tablic mogą być w szczególności także inne tablice. Jeśli elementem tablicy jest inna tablica jednowymiarowa, mamy do czynienia z tablicą dwuwymiarową. W zasadzie tablica dwuwymiarowa jest tablicą wskaźników do tablic jednowymiarowych. Implementacja takich tablic jest nieco inna dla napisów i dla danych innych typów. Jako przykład tablic tego drugiego rodzaju rozpatrzymy macierze liczbowe. Pamiętajmy, że na razie mówimy tylko o tablicach statycznych, a więc takich które tworzone są jako zmienne lokalne (na stosie) i ich wymiar jest znany na etapie kompilacji.
Deklaracja Taką tablicę możemy zadeklarować następująco: int t[3][2]; //t jest tablicą 3-elementową, //której elementy to dwuelementowe tablice int Zadeklarowaliśmy tu tablicę dwuwymiarową (macierz) o trzech wierszach i dwóch kolumnach. Umawiamy się bowiem, że pierwszy indeks numeruje wiersze, a drugi kolumny (jest to oczywiście konwencja, w pamięci komputera struktura tych danych i tak będzie liniowa). Elementy naszej tablicy to: t[0][0] t[0][1] t[1][0] t[1][1] t[2][0] t[2][1] Poszczególne elementy takiej tablicy są umieszczane kolejno w pamięci komputera tak, ze najszybciej zmienia się najbardziej skrajny prawy indeks. 17
18 Zatem poniższa inicjalizacja: int t[3][2]={1,2,3,4,5,6; Inicjalizacja spowoduje nadanie następujących wartości elementom tablicy: t[0][0]=1 t[1][0]=3 t[2][0]=5 t[0][1]=2 t[1][1]=4 t[2][1]=6 Lepiej (wskazane) jest jednak inicjować tablice następująco: int t[3][2]={{1,2,{3,4,{5,6; biorąc wiersze w nawiasy klamrowe; ma to tę dodatkową zaletę, że oddaje prawdziwą naturę takiej tablicy jako tablicy tablic. Inicjując tablicę następująco int tab[3][4] = { {1,2,3,4, {5,6, {1 ; otrzymamy 1 2 3 4 5 6 0 0 1 0 0 0 (pozostałe elementy są automatycznie inicjowane zerami ).
Dostęp do elementów tablicy dwuwymiarowej Do elementów tablicy dwuwymiarowej odwołujemy się poprzez nazwę tablicy z dwoma indeksami, każdy w osobnej parze nawiasów kwadratowych(np. t[0][0] ) Przykład. Poniżej fragment programu nadaje poszczególnym elementom tablicy wartość będącą sumą numeru jej wiersza i kolumny, a następnie wyświetla te wartości w postaci macierzy: int t[3][2]; //nadanie wartości for (int i=0; i<3; i++) for (int j=0; j<2; j++) t[i][j] = i+j; //wyświetlenie for (int i=0; i<3; i++) { for (int j=0; j<2; j++) cout << t[i][j] << " " ; cout << endl; 19
20 Przekazywanie tablic wielowymiarowych do funkcji Załóżmy, że zadeklarowaliśmy tablicę dwuwymiarową o dim1 wierszach i dim2 kolumnach const int dim1 =...; const int dim2 =...; int tab[dim1][dim2]; Elementy tablicy rozmieszczone są w pamięci wierszami. Zatem mając adres początku tablicy i dwa indeksy: m i n, element tab[m][n]odpowiadający tym indeksom znajduje się w wierszu o indeksie m, a więc, licząc od początku tablicy, musimy najpierw przeskoczyć m wierszy, każdy o długości dim2, czyli m*dim2 elementów. Następnie musimy przeskoczyć n elementów tego wiersza, w którym znajduje się szukany element (który ma indeks kolumnowy n).
21 Przekazywanie tablic wielowymiarowych do funkcji Zatem do obliczenia tego przesunięcia nie jest potrzebna znajomość pierwszego wymiaru tablicy, czyli liczby wierszy dim1. Ogólnie, dla tablic wielowymiarowych, do prawidłowego obliczenia przesunięcia elementu względem początku tablicy potrzebna jest znajomość wszystkich wymiarów prócz pierwszego. W funkcjach mających jako parametry tablice dwuwymiarowe musimy zawsze jawnie specyfikować drugi wymiar tablicy.
Przekazywanie tablic wielowymiarowych do funkcji - przykład #include <iostream> #include <iomanip> using namespace std; void printtable(int tab[][4], int w, int k) { for (int i = 0; i < w; i++) { for (int j = 0; j < k; j++) cout <<setw(6) << tab[i][j] ; cout << endl; 22
23 Przekazywanie tablic wielowymiarowych do funkcji - przykład int main() { int tab[3][4] = { {1,2,3,4, {5,6, {1 ; cout << "Tablica:\n"; printtable(tab,3,4); return 0;
24 #include <iostream> #include <iomanip> using namespace std; Przykład 1- pobieranie macierzy const int DIM1 = 10; //max liczba wierszy const int DIM2 = 10; //max liczba kolumn void pobmac(int &n, int & m, float a[][dim2]) { //pobieranie liczby wierszy do { cout << "Podaj liczbe wierszy\n n = "; cin >> n; while((n < 1) (n > DIM2));
Przykład 1- pobieranie macierzy cd. //pobieranie liczby kolumn do { cout << "Podaj liczbe kolumn\n m = "; cin >> m; while(!(m >= 1 && m <= DIM2)); //pobieranie el. macierzy cout << "\npodaj elementy macierzy A\n"; for(int i = 0; i < n; i++) for(int j = 0; j < m; j++) { cout << "A["<< i << ", " << j << "] = "; cin >> a[i][j]; 25
Przykład 1- wyświetlanie macierzy 26 void drukmacierz(int n, int m, float a[][dim2]) { //wyswietlanie el.macierzy //z dokladn.do 2-ch miejsc po przecinku cout<<fixed<<setprecision(2); for(int i=0; i < n; i++) {//wyswietlamy el. i-tego wiersza for(int j=0; j < n; j++) cout << setw(5) << a[i][j]; cout << endl;//przechodzimy do nowego wiersza
27 Przykład 1 - funkcja main() int main() { //deklaracja macierzy DIM1 x DIM2 float a[dim1][dim2]; int n, m; //n-liczba wierszy, // m-liczba kolumn pobmacierz(n,m,a); drukmacierz(n,m,a); return 0; OUTPUT: Podaj liczbe wierszy n = 3 Podaj liczbe kolumn m = 3
28 Podaj elementy macierzy A A[0, 0] = 1 A[0, 1] = 2 A[0, 2] = 3 A[1, 0] = 4 A[1, 1] = 5 A[1, 2] = 6 A[2, 0] = 7 A[2, 1] = 8 A[2, 2] = 9 1.00 2.00 3.00 4.00 5.00 6.00 7.00 8.00 9.00 Przykład 1 - funkcja main()
Przykład 2 - zamiana dwóch wierszy w macierzy //Funkcja zamienia dwa wiersze o podanych indeksach //Pierwszymi parametrami funkcji są liczba kolumn i //tablica dwuwymiarowa //Indeksy wierszy są ostatnimi parametrami funkcji //Zakładamy, ze są one poprawne void zamien(int m, float tab[][dim2], int w1, int w2) { float temp; //zmienna robocza //przechodzimy wzdluz wiersza (po kolumnach) for (int k = 0; k < m; k++) { //zamieniamy k-te elementy temp = tab[w1][k]; tab[w1][k] = tab[w2][k]; tab[w2][k] = temp; 29
30 Przykład 2 - funkcja main() int main() { float mac[dim1][dim2] = {{1,2,3,{4,5,6,{7,8,9; //f-cja z poprzedniego przykładu drukmacierz(3,3,mac); zamien(3,mac,1,2); cout<<"macierz po zamianie: "<<endl; drukmacierz(3,3,mac); return 0;
OUTPUT: 1.00 2.00 3.00 4.00 5.00 6.00 7.00 8.00 9.00 macierz po zamianie: 1.00 2.00 3.00 7.00 8.00 9.00 4.00 5.00 6.00 Przykład 2 - funkcja main() Napisz funkcję void sumywierszami(double t[][10],int w, int k, double sw[]); która w kolejnych składowych wektora sw zapisze sumy odpowiednich wierszy macierzy t. Macierz t ma w wierszy i k kolumn (k<=10). Przetestuj funkcję. 31
Przykład 3. Suma elementów w wierszach 32 void sumywierszami(double t[][10], int w, int k, double sw[]) { double s; //zmienna na sumę for(int i=0; i<w; i++) { //dla i-tego wiersza: s = 0.0; //zerujemy sumę //przechodzimy po kolumnach for(int j=0; j<k; j++) s += t[i][j]; //zwiększamy sumę o kolejne el. sw[i] = s; //przypisujemy obliczoną sume i-tego //wiersza i-temu elementowi tablicy sw
33 int main() Przykład 3 - funkcja main() { double m1[3][10]={{1,2,3,4,{0,2,3,5, {2,4,6,8; double tabsw[10]; sumywierszami(m1,3,4,tabsw); cout<<"sumy wierszy: "<<endl; for(int i = 0; i < 3; i++) cout << setw(3) << i << " - "<< setw(8) << tabsw[i] << endl; return 0;
Przykład 3 - funkcja main() 34 OUTPUT: sumy wierszy: 0-10.00 1-10.00 2-20.00