Języki i paradygmaty programowania 1 studia niestacjonarne 2018/19 Lab 4. Program zapisany w kilku plikach. Pliki nagłówkowe (*h.). Słowa kluczowe static, const. Klasy pamięci (auto), static, extern. Tablice liczbowe. Operacje macierzowo-wektorowe, memcpy, memmove, memset. Wyrażenie warunkowe. Operacje na tablicach o dwóch indeksach. Dynamiczna alokacja pamięci dla tablic wielowymiarowych. 1. Projekty składają się zazwyczaj z wielu, różnych plików. Zawartość każdego pliku programista wyznacza zgodnie z jego przeznaczeniem. Bezpośredni dostęp do danych jest możliwy wyłącznie z tego pliku, w którym dane te są zdefiniowane. Dostęp do danych z innych plików nie jest wspierany w języku C. Aby tworzone programy były bezpieczne należy: a. podstawowe dane w pliku definiować, jako zmienne globalne z modyfikatorem static, który nie pozwala na udostępnianie tych zmiennych w innych plikach; b. prototypy funkcji interfejsowych umieścić w odpowiednim headerze (bez modyfikatora static); c. prototypy funkcji wewnętrznych umieścić w pliku, jako obiekty globalne z modyfikatorem static; d. dane globalne, które mogą być widoczne w innych plikach: i. udostępniać poprzez funkcje ii. Definiować bez modyfikatora static iii. Samo udostępnienie w innym pliku wprowadzić przy pomocy specyfikatora extern iv. Nie poleca się umieszczania tych danych w pliku nagłówkowym header. e. Dane pomocnicze (indeksy tablic, zmienne tymczasowe itd.) używać jako dane lokalne (auto). 2. Headery, czyli pliki nagłówkowe, są używane do deklaracji typów danych (struktur, unii, typedef), makr, dyrektyw kompilatora (pragma), prototypów funkcji itd., które mają być widoczne w różnych plikach projektu. 3. Każdy header musi zawierać tak zwane straże, które w projektach złożonych chronią przed duplikacją definicji umieszczanych w headerze. 4. Klasy pamięci dzielą się na: a. auto wszystkie zmienne i tablice zdefiniowane są w dowolnym bloku. Rezerwacja miejsca w pamięci następuje w momencie wejścia do bloku programu. Pamięć ta zostaje zwolniona w chwili wyjścia z bloku, w którym dana zmienna została zdefiniowana następuje utrata danych. Widoczność (zasięg deklaracji) zmiennej w klasie auto to tzw. blok programu. Czas życia zmiennej od momentu definicji do momentu wyjścia z bloku. Zmienne domyślnie nie są inicjowane - jeśli użytkownik nie przypisze im wartości, zawierają przypadkowe śmieci. b. static (zmienne i tablice zdefiniowane lokalnie wewnątrz bloku programu). Rezerwacja pamięci następuje na początku działania programu. Obszar widoczności zmiennych klasy static od miejsca definicji w bloku, w którym zostały zdefiniowane, do końca bloku. Czas życia zmiennej czas trwania całego programu. Oznacza to, że wartości zmiennych tej klasy pozostają chronione nawet po wyjściu z bloku i są aktualne przy kolejnym wejściu w blok. Zmienne domyślnie inicjalizowane są wartością: zero. Wydział Fizyki, Matematyki i Informatyki Politechniki Krakowskiej 1
void fun() int a = 0; a++; pierwsze wejście: a = 0 przed wyjściem: a = 1 drugie wejście: a = 0 przed wyjściem: a = 1 void fun() static int a; a++; pierwsze wejście: a = 0 wyjście: a = 1 drugie wejście: a = 1 wyjście: a = 2 c. static (definicja globalna w pliku poza blokami). Rezerwacja pamięci dokonywana jest na początku działania programu. Obszar widoczności zmiennych od miejsca definicji do końca pliku. Czas życia zmiennej czas trwania całego programu. Zmienne domyślnie inicjalizowane są wartością: zero. Są dostępne tylko w podanym pliku. d. Zmienne zewnętrzne (globalne) (bez specyfikatora static). Rezerwacja pamięci dokonywana jest na początku działania programu. Obszar widoczności zmiennych globalnych od miejsca definicji do końca pliku. Czas życia zmiennej czas trwania całego programu. Zmienne domyślnie inicjalizowane są wartością: zero. Mogą być udostępnianie w innych plikach, jeśli zostaną w nich zadeklarowane, tzn. deklaracja typu zostanie poprzedzona słowem kluczowym extern. e. Klasa pamięci register oznacza, że dane zostaną umieszczone w rejestrze procesora, kiedy będzie to możliwe. f. Specyfikator static używany przy deklaracji funkcji, ogranicza widoczność tej funkcji obszarem pliku. Nie wolno umieszczać deklaracji funkcji statycznych w plikach nagłówkowych. 5. Do czego służą słowa kluczowe static i const? Czym się różnią? 6. Utwórz nowy projekt składający się z dwóch poniższych plików: plik_1.cpp, plik_2.cpp. Na podstawie informacji zawartych w pkt. 4 określ typ, klasę pamięci, obszar działania każdego występującego w programie identyfikatora. Przeanalizuj odwołania do różnych funkcji i znajdź te fragmenty programu, które na takie odwołania pozwalają. Sprawdź jakie teksty pojawią się na monitorze podczas wykonania programu. //*************************PLIK_1***************************** #include <stdio.h> double fun1(); static int fun2(); extern int fun3(); static int c = 5; double a, b = 10; char *xx[] = "mama","tato","stryjek",(char *)0; double aa[] = 1,2,3,4,5,6 ; int main() double x, y = 5; int i, j, k; static double aa[] = 11,12,13,14,15,16 ; printf("%lf %lf \n", aa[0], aa[1]); j = fun2(); k = fun3(); printf("j=%d, k= %d\n", j, k); double fun1(int x, int y) Wydział Fizyki, Matematyki i Informatyki Politechniki Krakowskiej 2
static char *xx[] = "pies","kot","mysz",(char *)0 ; int i = 0; i++; char *xxxx[] = "zima","wiosna","lato",(char *)0 ; static int fun2() static int k = 0; puts("ppp fun2"); k++; return(k); //*************************PLIK_2***************************** #include <stdio.h> extern double fun1(); static double fun2(); int fun3(); static char *c[] = "slon","lew","pantera",(char *)0 ; extern char *xx[]; static double fun2() static char *zz[] = "krzeslo","szafa","tapczan",(char *)0 ; puts("qqq fun2"); return ((double) 2.0); int fun3() double ff; puts("qqq fun3"); ff = fun2(); return (5); 7. Wybierz jeden spośród Twoich programów (np. projekt nr 1 lub nr2) i podziel go na kilka plików, w taki sposób aby nie dzielić funkcjonalności, które tego nie wymagają. 8. Wektory i macierze: a. Przykład 1. Przechowywanie macierzy (tablicy jednowymiarowej) wiersz po wierszu. i numer wiersza, j numer kolumny, kolejność indeksowania: for (j = 0; j < n; j++) b. Przykład 2. Przechowywanie macierzy (tablicy jednowymiarowej) kolumna po kolumnie. i numer wiersza, j numer kolumny, kolejność indeksowania: for (j = 0; j < n; j++) Wydział Fizyki, Matematyki i Informatyki Politechniki Krakowskiej 3
c. Przykład 3. Mnożenie macierzy przez macierz c ij=σk a ik b kj metodą: i. klasyczną, czyli wiersz po wierszu : for (i = 0; i < M; i++) for (j = 0; j < N; j++) for (k = 0; k < K; k++) C ij = C ij + A ik * B kj ii. Element macierzy C w pętli wewnętrznej nie zależy od indeksu k. Pobieranie elementów macierzy A następuje ciągle, bez skoków, natomiast elementy macierzy B pobierane są ze skokami. W związku z tym metoda ta nie jest skuteczna. Przyśpieszoną: for (i = 0; i < M; i++) for (k = 0; k < K; k++) for (j = 0; j < N; j++) C ij = C ij + A ik * B kj Elementy macierzy A w pętli wewnętrznej nie zależą od indeksu j. Pobieranie elementów macierzy C następuje ciągle, bez skoków, natomiast elementy macierzy B pobierane są ze skokami. W związku z tym metoda ta jest lepsza od poprzedniej. 9. Funkcje memcpy, memmove, memset znajdują się w bibliotece #include <memory.h>. void *memcpy(void *dest, const void *src, size_t count); funkcja kopiuje count bajtów z src do dest. Jako wynik zwraca wskaźnik do dest. Jeśli obszar pamięci src i dest pokrywa się działanie funkcji jest nieprzewidywalne. void *memmove(void *dest, const void *src, size_t count); funkcja kopiuje count bajtów z src do dest. Jeśli jakiś obszar pamięci src i dest pokrywa się funkcji gwarantuje, że bajty źródłowe w pokrywającym się regionie zostaną skopiowane zanim zostaną przepisane. void *memset(void *dest, int c, size_t count); Przypisuje symbol c pierwszym count symbolom tablicy dest. Zwraca wskaźnik do dest. 10. Utwórz nowy projekt, a następnie skopiuj i przeanalizuj poniższe przykłady użycia funkcji memcpy, memmove, memset. a) Przykład 1. #include <memory.h> #include <string.h> #include <stdio.h> char str1[7] = "aabbcc"; int main(void) printf("the string: %s\n", str1); //Pokrywający się region! Kopiowanie może nie być poprawne memcpy(str1 + 2, str1, 4); printf("new string: %s\n", str1); strcpy_s(str1, sizeof(str1), "aabbcc"); // reset string Wydział Fizyki, Matematyki i Informatyki Politechniki Krakowskiej 4
printf("the string: %s\n", str1); //Pokrywający się region! Kopiowanie poprawne memmove(str1 + 2, str1, 4); printf("new string: %s\n", str1); b) Przykład 2. #include <memory.h> #include <stdio.h> int main(void) char buffer[] = "This is a test of the memset function"; printf("before: %s\n", buffer); memset(buffer, '*', 4); printf("after: %s\n", buffer); Wynik działania: Before: This is a test of the memset function After : **** is a test of the memset function c) Przykład 3. #include <memory.h> #include <stdio.h> int main(void) char str[256]; double aa[200]; double cc[10] = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ; double dd[5] = 20, 30, 40, 50, 60 ; memset((void *)str, 0, 256 * sizeof(char)); //OK memset((void *)str, ' ', 256 * sizeof(char)); //!OK pisanie po pamięci memset((void *)str, ' ', 255 * sizeof(char)); //OK memset((void *)aa, 0, 200 * sizeof(double)); //OK memcpy((void *)&cc[3], (const void *)&dd[2], 2 * sizeof(double)); Wynik działania: cc: 1 2 3 40 50 6 7 8 9 10 11. Przykład. Napisz program, który dla danych dwóch wektorów, policzy: i. Sumę tych wektorów: = + ; ii. Iloczyn skalarny wektorów: = ; iii. Maksymalną współrzędną wektorów, //==============================PLIK_1=================================== #pragma warning (disable:4996) #include<stdio.h> #include<stdlib.h> #define LL 200 extern void error(int, char *); void argumenty(int, char **); int main(int argc, char *argv[]) double x[ll], y[ll], z[ll], s, mx, my; FILE *fw, *fd; int n, k; argumenty(argc, argv); if (!(fd = fopen(argv[1], "r"))) error(2, "dane"); if (!(fw = fopen(argv[2], "w"))) error(2, "wyniki"); fscanf(fd, "%d", &n); Wydział Fizyki, Matematyki i Informatyki Politechniki Krakowskiej 5
for (k = 0; k < n; k++) fscanf(fd, "%lf", &x[k]); for (k = 0; k < n; k++) fscanf(fd, "%lf", &y[k]); s = 0; mx = x[0]; my = y[0]; for (k = 0; k < n; k++) z[k] = x[k] + y[k]; mx = x[k] > mx? x[k] : mx; my = y[k] > my? y[k] : my; s += x[k] * y[k]; for (k = 0; k < n; k++) fprintf(fw, "%lf ", z[k]); if (!((k + 1) % 5)) fprintf(fw, "\n"); fprintf(fw, "\nilocz.skal=%lf mx=%lf my=%lf\n", s, mx, my); void argumenty(int argc, char *argv[]) int len; char *usage; if (argc!= 3) len = strlen(argv[0]) + 19; if (!(usage = (char*)malloc((unsigned)len * sizeof(char)))) error(3, "tablica usage"); strcpy(usage, argv[0]); strcat(usage, " file_in file_out"); error(4, usage); /**************** plik util_1.c ******************************/ #include<stdio.h> #define MAX_ERR 5 static char *p[] = "", " zle dane", " otwarcie pliku", " brak pamieci", " Usage : ", " nieznany ", ; void error(int nr, char *str) int k; k = nr >= MAX_ERR? MAX_ERR : nr; fprintf(stderr, "Blad(%d) - %s %s\n", nr, p[k], str); system("pause"); exit(nr); 12. Wyrażenie warunkowe przyjmuje postać: wyrażenie1? wyrażenie2 : wyrażenie3 wartością wyrażenia warunkowego jest: Wartość wyrażenia2, o ile wyrażenie1 jest różne od zera, Wartość wyrażenia3, o ile wyrażenie1 jest równe zero. Operator pytajnik, dwukropek (?:) jest prawostronnie łączny i ma priorytet wyższy jedynie od operatorów przypisania i operatora przecinkowego. Wydział Fizyki, Matematyki i Informatyki Politechniki Krakowskiej 6
13. Napisz procedurę, która korzystając z funkcji: srand, time, rand oraz operatora dzielenia modulo uzupełni przekazaną do niej tablicę typu int o losowe liczby z zakresu od 0 do 10. Tablica niech będzie przekazywana przez referencję. 14. Do poprzedniego programu dopisz procedurę, która będzie wyświetlać zawartość tablicy oraz informację ile razy funkcja była wywoływana (skorzystaj ze zmiennej statycznej). Tablica niech będzie przekazywana przez wskaźnik. 15. Operatorem sizeof sprawdź rozmiar tablicy w trzech miejscach (po jej deklaracji i w dwóch procedurach z powyższych zadań). Wytłumacz otrzymane wyniki. 16. Zadeklaruj i uzupełnij liczbami tablicę 5 elementową. Wypisz wartość 3 elementu BEZ KORZYSTANIA Z INDEKSÓW (złe rozwiązanie: printf("%d\n", tablica[2]);). 17. Zmodyfikuj program z przykładu w pkt.11 tak aby: Zamiast tablic zdefiniować zmienne typu double *; Zarezerwować dokładnie n miejsc na zmienne typu double (alokacja pamięci), np.: x = (double*)malloc((unsigned)n * sizeof(double)) 18. Algorytm z pkt.17 podziel na kilka funkcji: a) Alokacja pamięci dla tablicy: double *DajWekt(int n) b) Czytanie elementów tablicy z pliku: void CzytWekt(FILE *fd, double *we, int n) c) Pisanie elementów tablicy do pliku: void PiszWekt(FILE *fw, double *we, int n) d) Obliczanie sumy dwóch wektorów: void DodWekt(double *w1, double *w2, double *w3, int n) e) Obliczanie iloczynu skalarnego: double IloczynSkal(double *w1,double *w2,int n) f) Obliczanie maksymalnej współrzędnej wektora: double MaxElem(double *w, int n) Funkcje realizujące zadania a), b), c) zapisz w pliku util_2.cpp. Funkcje realizujące zadania d), e), f) zapisz w pliku util_3.cpp. 19. Dynamiczna alokacja pamięci dla tablic wielowymiarowych - Przykładowa tablica 10x5 o elementach typu double. Wersja 1. double **tab; int n = 10, m = 5; tab = (double **)malloc(sizeof(double *) * n); if (!tab) //błąd alokacji for (int i = 0; i < n; i++) tab[i] = (double *)malloc(sizeof(double) * m); if (!tab[i]) //błąd alokacji if (tab) for (int i = 0; i < n; i++) if (tab[i]) free(tab[i]); free(tab); tab = NULL; Wydział Fizyki, Matematyki i Informatyki Politechniki Krakowskiej 7
Wersja 2. double **tab_a, *tab_b; int n = 10, m = 5; tab_b = (double *)malloc(sizeof(double) * n * m ); if (!tab_b) //błąd alokacji tab_a = (double **)malloc(sizeof(double *) * n); if (!tab_a) //błąd alokacji for (int i = 0; i < n; i++) tab_a[i] = &tab_b[ m * i ]; if (tab_b) free(tab_b); // free(tab_a[0]); tab_b = NULL; if (tab_a) free(tab_a); tab_a = NULL; 20. Przykład. Napisz program, który dla danych dwóch macierzy: =, = 0,1,2,, 1;! = 0,1,2,, " 1, #$ = %, = 0,1,2,, 1;! = 0,1,2,, " 1 oraz wektora = & ', = 0,1,2,, " 1, policzy: i. Sumę macierzy: ( = + #, ) *+ * = + % ; ii. Iloczyn macierzy przez wektor : = ; = -,,, ; = 0,1,2,, 1; Do kontroli poprawności obliczeń wykorzystaj funkcję error z pliku util_1.cpp (patrz lab. 8). Dane pobrać z pliku, wyniki zapisać do pliku, nazwy plików przekazać przez argumenty wywołania programu. //==============================PLIK_1=================================== #include "pch.h" #include<stdio.h> #include<stdlib.h> #include<string.h> #pragma warning(disable:4996) #define LL 20 FILE *fw, *fd; void argumenty(int, char **); extern void error(int, char *); int main(int argc, char *argv[]) double a[ll][ll], b[ll][ll], c[ll][ll], x[ll], y[ll], r; int i, j, k, n, m; argumenty(argc, argv); if (!(fd = fopen(argv[1], "r"))) error(2, "dane"); Wydział Fizyki, Matematyki i Informatyki Politechniki Krakowskiej 8
if (!(fw = fopen(argv[2], "w"))) error(2, "wyniki"); fscanf(fd, "%d %d", &n, &m); for (j = 0; j < m; j++) fscanf(fd, "%lf", &a[i][j]); for (i = 0; i < m; i++) fscanf(fd, "%lf", &x[i]); for (j = 0; j < m; j++) fscanf(fd, "%lf", &b[i][j]); for (j = 0; j < m; j++) c[i][j] = a[i][j] + b[i][j]; r = 0; for (k = 0; k < m; k++) r += a[i][k] * x[k]; y[i] = r; for (j = 0; j < m; j++) fprintf(fw, "%lf ", c[i][j]); fprintf(fw, "\n"); fprintf(fw, "%lf ", y[i]); if (!((i + 1) % 5)) fprintf(fw, "\n"); system("pause"); return 0; void argumenty(int argc, char *argv[]) int len; char *usage; if (argc!= 3) len = strlen(argv[0]) + 19; if (!(usage = (char*)malloc((unsigned)len * sizeof(char)))) error(3, "tablica usage"); strcpy(usage, argv[0]); strcat(usage, " file_in file_out"); error(4, usage); /**************** plik util_1.cpp ******************************/ #include<stdio.h> #define MAX_ERR 5 static char *p[] = "", " zle dane", " otwarcie pliku", " brak pamieci", " Usage : ", " nieznany ", Wydział Fizyki, Matematyki i Informatyki Politechniki Krakowskiej 9
; void error(int nr, char *str) int k; k = nr >= MAX_ERR? MAX_ERR : nr; fprintf(stderr, "Blad(%d) - %s %s\n", nr, p[k], str); system("pause"); exit(nr); 21. Zmodyfikuj program z powyższego przykładu tak aby: Zamiast tablic jednowymiarowych zdefiniować zmienne typu double *, zamiast tablic dwuwymiarowych zdefiniować zmienne typu double **; Zarezerwować dokładnie m miejsc na tablice jednowymiarowe typu double (alokacja pamięci), np.: x = (double*)malloc((unsigned)m * sizeof(double)); Zarezerwować dokładnie n*m miejsc na tablice dwuwymiarowe typu double (alokacja pamięci patrz pkt.1), np.: a = (double**)malloc((unsigned)n * sizeof(double*)); a[i] = (double*)malloc((unsigned)m * sizeof(double)); 22. Algorytm z pkt.20 podziel na kilka funkcji: g) Alokacja pamięci (dwie funkcje - wer.1 i wer.2 patrz pkt.19) : double **DajMac_1(int n, int m) double **DajMac_2(int n, int m) h) Zwolnienie pamięci (dwie funkcje): void ZwrocMac_1(double **ma, int n, int m) void ZwrocMac_2(double **ma, int n, int m) i) Czytanie elementów tablicy o dwóch indeksach z pliku: void CzytMac(FILE *fd, double **ma, int n, int m) int i, j; char *err; for (j = 0; j < m; j++) if (fscanf(fd, "%lf", &ma[i][j])!= 1) printf("blad - element nr %d %d\n", i, j); j) Pisanie elementów tablicy o dwóch indeksach do pliku: void PiszMac(FILE *fw, double **ma, int n, int m) k) Dodawanie macierzy: void DodMac(double **ma1, double **ma2, double **ma3, int n, int m) l) Mnożenie macierzy przez wektor: void Mac_x_Wekt(double **ma, double *we, double *wy, int n, int m) m) Napisz dodatkowo funkcję, która dla danych macierzy:.$ =, = 0,1,2,, 1;! = 0,1,2,, " 1, /$ =, = 0,1,2,, " 1;! = 0,1,2,, 0 1 policzy macierz 1 =./ $$$$ czyli - = 2,,, = 0,1,2,, 1,! = 0,1,2, 0 1, void Mac_x_Mac(double **x, double **y, double **z, int n, int m, int p) Funkcje realizujące zadania a), b), c), d) zapisz w pliku util_4.cpp. Funkcje realizujące zadania e), f), g) zapisz w pliku util_5.cpp. Wydział Fizyki, Matematyki i Informatyki Politechniki Krakowskiej 10
23. Zrealizuj zadania z powyższego przykładu (pkt.20 i następne) wykorzystując utworzone wcześniej funkcje zawarte w plikach util_2.cpp, util_3.cpp, util_4.cpp, util_5.cpp. Fragment programu: FILE *fw, *fd; extern void error(int, char*),piszwekt(file *, double *, int), CzytWekt(FILE *, double *, int),czytmac(file *, double **, int, int), PiszMac(FILE *, double **, int, int); extern double *DajWekt(int), **DajMac_1(int, int); extern void DodMac(double **, double **, double **, int, int), Mac_x_Wekt(double **, double *, double *, int, int); void argumenty(int argc, char *argv[]); int main(int argc, char *argv[]) double *x, *y; double **a, **b, **c; int n, m; argumenty(argc, argv); if (!(fd = fopen(argv[1], "r"))) error(2, "dane"); if (!(fw = fopen(argv[2], "w"))) error(2, "wyniki"); fscanf(fd, "%d %d", &n, &m); x = DajWekt(m); y = DajWekt(m); a = DajMac_1(n, m); b = DajMac_1(n, m); c = DajMac_1(n, m); CzytMac(fd, a, n, m); CzytMac(fd, b, n, m); CzytWekt(fd, x, m); DodMac(a, b, c, n, m); Mac_x_Wekt(a, x, y, n, m); printf("macierz\n"); PiszMac(stdout, c, n, m); fprintf(fw, "Macierz\n"); PiszMac(fw, c, n, m); printf("wektor\n"); PiszWekt(stdout, y, n); fprintf(fw, "Wektor\n"); PiszWekt(fw, y, n); Pliki util_2.cpp i util_3.cpp dostępne pod adresem: http:/http://riad.pk.edu.pl/~jwojtas/jipp/util_2.pdf http:/http://riad.pk.edu.pl/~jwojtas/jipp/util_3.pdf 24. Dana jest macierz kwadratowa B[n][n]. Znaleźć sumę elementów leżących poniżej głównej przekątnej i jednocześnie należących do zadanego przedziału [a,b]. 25. Dana jest macierz kwadratowa B[n][n]. Znaleźć sumę tych elementów leżących na obu przekątnych macierzy, które jednocześnie spełniają warunek (na głównej przekątnej j=i, na drugiej j=n-i): sin6#787!89 ; 0 <+ =+="=>ó@ Ałó@=! 0C=Dą>=! 0,5 <+ =+="=>ó@ <CGA=! 0C=Dą>=! 26. Przeanalizuj w trybie pracy krokowej przykłady do wykładu: Wszystkie przykłady dostępne pod adresem: http://torus.uck.pk.edu.pl/~fialko/text/cc/przykl/ *Treści oznaczone kursywą pochodzą z różnych źródeł internetowych. Wydział Fizyki, Matematyki i Informatyki Politechniki Krakowskiej 11