przegląd
Transkrypt
przegląd
Zmienne globalne i lokalne • Zmienna globalna zadeklarowana poza wszystkimi funkcjami, jest rozpoznawana od miejsca deklaracji do końca pliku (programu) — poza miejscami, gdzie jest zasłonięta parametrami funkcji oraz deklaracjami lokalnymi. • Parametr funkcji zadeklarowany w jej nagłówku oraz zmienna lokalna funkcji zadeklarowana w jej ciele, jest rozpoznawana od miejsca deklaracji do nawiasu klamrowego zamykającego najmniejszy blok {. . . } , w którym mieści się deklaracja — poza miejscami, gdzie jest zasłonięta deklaracjami lokalnymi z mniejszych bloków. Wykład 8. Budowanie aplikacji, str. 2 Zmienne globalne i lokalne typ zmienna 0; typ zmienna 1; typ zmienna 2; globalna lokalna globalna lokalna lokalna typ funkcja (typ parametr) { typ zmienna 0; .................. . . . zmienna 0. . . . . . zmienna 1. . . . . . zmienna 3. . . { typ zmienna 2; . . . zmienna 0. . . . . . zmienna 1. . . . . . zmienna 2. . . } } niezadeklarowana Zmienna użyta w programie należy do deklaracji z najmniejszego bloku (ograniczonego nawiasami klamrowymi {...}), w jakim się znajduje. Zmienne globalne i lokalne #include<stdio.h> W zasadzie. . . Jeśli obliczenie programu opuści blok, w którym zmienna jest rozpoznawana, to wartość zmiennej przestaje istnieć. Po następnym wejściu do tego bloku (i ponownym przejściu przez deklarację) wartość zmiennej jest nieokreślona lub wynikająca z inicjalizacji. void qq ( ) { int a = 0; printf(" a == %i", a); a++; } int main ( ) { while (1) { qq( ); getchar( ); } return 0; } Wydruki: a == 0 a == 0 a == 0 ...... Wykład 8. Budowanie aplikacji, str. 4 Zmienne globalne i lokalne W zasadzie. . . Jeśli obliczenie programu opuści blok, w którym zmienna jest rozpoznawana, to wartość zmiennej przestaje istnieć. Po następnym wejściu do tego bloku (i ponownym przejściu przez deklarację) wartość zmiennej jest nieokreślona lub wynikająca z inicjalizacji. #include<stdio.h> int main ( ) { int po raz 1 = 1; while (1) { { int a[1]; if (po raz 1) a[0]=1; printf(" a[0] == %i", a[0]); getchar( ); po raz 1 = 0; } { int b[1]; b[0]=2; printf(" b[0] == %i\n", b[0]); } } Wydruki: return 0; a[0] == 1 } b[0] == 2 a[0] == 2 b[0] == 2 a[0] == 2 ...... Zmienne globalne i lokalne W zasadzie. . . Jeśli obliczenie programu opuści blok, w którym zmienna jest rozpoznawana, to wartość zmiennej przestaje istnieć. Po następnym wejściu do tego bloku (i ponownym przejściu przez deklarację) wartość zmiennej jest nieokreślona lub wynikająca z inicjalizacji. To można zmienić przez poprzedzenie deklaracji kwalifikatorem static. Zmienna statyczna przechowuje wartość poprzez swoje życie pozagrobowe aż do następnej reinkarnacji. Wykład 8. Budowanie aplikacji, str. 6 Zmienne globalne i lokalne #include<stdio.h> #include<stdio.h> void qq ( ) { int a = 0; printf(" a == %i", a); a++; } void qq ( ) { static int a = 0; printf(" a == %i", a); a++; } int main ( ) { while (1) { qq( ); getchar( ); } return 0; } int main ( ) { while (1) { qq( ); getchar( ); } return 0; } Wydruki: a == 0 a == 0 a == 0 a == 0 ......... Wydruki: a == 0 a == 1 a == 2 a == 3 ......... Zmienne globalne i lokalne W programie na jednym pliku • Ta sama nazwa w programie może zależnie od kontekstu oznaczać różne zmienne z różnych deklaracji. • Zmienna należy do deklaracji z najmniejszego bloku (ograniczonego nawiasami klamrowymi {...}), w jakim się znajduje. • Deklaracje z większego bloku są zasłaniane deklaracjami tych samych zmiennych w jego podblokach. • A jak jest z programem na wielu plikach? Wykład 8. Budowanie aplikacji, str. 8 Zmienne globalne i lokalne W programie na wielu plikach — skomplikowane. . . • Każda zmienna musi mieć deklarację w pliku, w którym występuje. Brak deklaracji we własnym pliku jest błędem. • Deklaracje tej samej nazwy w różnych plikach na najwyższym poziomie (czyli poza wszelkimi funkcjami) oznaczają tą samą zmienną. • Nazwa zadeklarowana w jednym pliku na najwyższym poziomie, a w innym wewnątrz funkcji, oznacza różne zmienne (zasłanianie). • Można to zmienić przy pomocy kwalifikatora extern . WSPÓŁPRACA JEST TRUDNA Deklar. w różnych plikach na najwyższym poziomie pomocniczy.c #include<stdio.h> glowny.c #include<stdio.h> int a; int a; void zmiana ( ) { a++; printf(" pom: a == %i\n", a); } void zmiana( ); Wydruki: glow: a == 0 pom: a == 1 glow: a == 1 pom: a == 3 int main ( ) { a=0; printf(" glow: a == %i\n", a); zmiana( ); printf(" glow: a == %i\n", a); a++; zmiana( ); return 0; } Wykład 8. Budowanie aplikacji, str. 10 Deklar. w różnych plikach na różnych poziomach pomocniczy.h #include<stdio.h> void nowa wartosc( ); pomocniczy.c #include"pomocniczy.h" glowny.c #include"pomocniczy.h" int wart=0; void nowa wart( ) { printf(" pom: %i\n", wart); wart++; } int main( ) { int wart; wart=10; printf(" glow: %i\n", wart); wart++; nowa wart( ); printf(" glow: %i\n", wart); wart++; nowa wart( ); return 0; } Wydruki: główny pomocniczy 10 11 0 1 DWIE RÓŻNE ZMIENNE wart Użycie extern pomocniczy.h #include<stdio.h> void nowa wartosc( ); pomocniczy.c #include"pomocniczy.h" glowny.c #include"pomocniczy.h" int wart=0; int main( ) { extern int wart; wart=10; printf(" glow: %i\n", wart); wart++; nowa wart( ); printf(" glow: %i\n", wart); wart++; nowa wart( ); return 0; } void nowa wart( ) { printf(" pom: %i\n", wart); wart++; } Wydruki: główny pomocniczy 10 12 11 13 TA SAMA ZMIENNA wart Z PLIKU pomocniczy.c Wykład 8. Budowanie aplikacji, str. 12 Ucząca się gra w zapałki REGUŁY GRY: 1. Początkowo na stole leży n zapałek. 2. Gracze kolejno zabierają po 1, lub 2, lub 3 zapałki. 3. Kto weźmie ostatnią zapałkę, ten przegrywa. ZAŁOŻENIA PROGRAMU: • Człowiek gra z komputerem. • Komputer początkowo nie ma żadnej taktyki gry; zabiera przypadkowo po 1, lub 2, lub 3 zapałki. Nawet nie wie, o co toczy się gra (nie zna reguły 3). • Po każdej rozgrywce dostaje odpowiedź, czy wygrał, czy przegrał. W następnych rozgrywkach dąży do wykonywania ruchów wygrywających, a unikania przegrywających. • Dla przyspieszenia nauki, rozgrywkę z człowiekiem poprzedzamy treningowymi rozgrywkami komputera samego ze sobą. Ucząca się gra w zapałki Trening: W dwóch różnych plikach zapisujemy (identyczne) programy • komp 0.c — z którym chcemy grać; oraz • komp 1.c — sparring-partnera dla komp 0.c . Każdy z nich uczy się niezależnie i nie dzieli się wiedzą z partnerem. Po każdej rozgrywce osobny program arbiter.c informuje oba programy, który z nich wygrał. Gra z człowiekiem: Po treningach gramy z programem komp 0.c . Wykład 8. Budowanie aplikacji, str. 14 Ucząca się gra w zapałki Nauka: Obaj komputerowi gracze zapamiętują swoje ruchy, a po zakończonej rozgrywce dowiadują się, czy skutkiem był sukces czy porażka. Wtedy uaktualniają sobie tabele z punktacją ruchów: punkty: ile zapałek 1 2 ··· k ··· n 1 2 3 Ile warte jest zabranie 2 zapałek, gdy przede mną leży ich k : ··· ··· ··· ✾ ··· ··· ··· liczba sukcesów − liczba porażek z gier, w których tak zrobiłem. Ucząca się gra w zapałki Wybór ruchu: Jeśli przeciwnik pozostawił k zapałek, to należy zabrać takie ℓ zapałek, żeby liczba punkty[k][ℓ] była jak największa. Kompilacja: gcc -Wall -std=c99 *.c -o gra Wywołanie: ./gra ile zapalek ile treningow PROGRAM MIEŚCI SIĘ W KATALOGU 08-GraWZapalki Wykład 8. Budowanie aplikacji, str. 16 Ucząca się gra w zapałki Struktura plików w programie: <stdio.h> <stdlib.h> "wspolne.h" komp 0.c komp 1.c arbiter.c gra.c Plik wspolne.h : #define MAX ZAPALEK ... typedef struct {...} dwuciag; Plik arbiter.c : int legalny ruch (int ile zapalek, int branie); int wygral (int ile zapalek, int ile ruchow); Ucząca się gra w zapałki Struktura plików w programie: <stdio.h> <stdlib.h> "wspolne.h" komp 0.c komp 1.c arbiter.c gra.c Plik komp 0.c : void inic punktow 0 (void); int ruch kompa 0 (int ile zapalek); void nauka 0 (int wygrana); void wydruk nauki 0 (void); Plik komp 1.c : tak samo Wykład 8. Budowanie aplikacji, str. 18 Ucząca się gra w zapałki W plikach komp 0.c i komp 1.c • spisy wykonanych ruchów oraz • tablice z punktami muszą być zapamiętywane w tablicach statycznych: static int punkty 0[MAX ZAPALEK+1][3]; To powoduje, że • każda z nich jest lokalna w swoim pliku, oraz • ich wartości nie zostają zapomniane w trakcie działania innych części programu. Od programu źródłowego do wykonywalnego • Każdy program musi zostać skompilowany (compiled) i skonsolidowany (linked) do programu wykonywalnego. kompilacja z konsolidacją ............................................................ ............................................gcc ...................... ....................... . . .................. . . . . . . . . . . . . . . . . . . ............... . . . . . . . . . ❥... ........ ... .c źródło kompilacja gcc -c ✲ ... .o obiekt konsolidacja gcc ✲ ... wykonywalny • Oba etapy mogą zostać wykonane pojedynczym wywołaniem gcc. • Można też je wykonać jeden po drugim: – gcc -c — żeby uzyskać obiekt; – gcc — żeby z pliku źródłowego lub obiektu uzyskać kod wykonywalny. Wykład 8. Budowanie aplikacji, str. 20 Od programu źródłowego do wykonywalnego • Program na wielu plikach również musi zostać skompilowany i skonsolidowany do programu wykonywalnego. kompilacja z konsolidacją ... .c źródło ... .c źródło ................................... .........................................................gcc .................... ..................... . . . . . ................ . . . . . . . . . . .............. ......... ............ ........... ........... kompilacja ✲ ... .o ❥... gcc -c kompilacja gcc -c obiekt ✲ ... .o obiekt ... konsolidacja ✲ gcc wykonywalny • Kompilowany jest każdy plik z osobna. • Konsolidowane są wszystkie pliki obiektowe razem. Kiedy kompilacja jest długa i kosztowna. . . xxx.h ......... aaa.c #include "xxx.h" ......... yyy.h ......... ccc.c #include "xxx.h" #include "yyy.h" ......... bbb.c #include "yyy.h" ......... abc gcc -Wall -std=c99 *.c Ta komenda dokona kompilacji i konsolidacji wszystkich plików. Jeśli trzeba wprowadzić poprawkę, to znowu trzeba skompilować i skonsolidować wszystkie pliki. Wykład 8. Budowanie aplikacji, str. 22 Kiedy kompilacja jest długa i kosztowna. . . xxx.h ......... yyy.h ......... aaa.c #include "xxx.h" ......... ccc.c #include "xxx.h" #include "yyy.h" ......... bbb.c #include "yyy.h" ......... aaa.o ccc.o bbb.o abc gcc -c -Wall -std=c99 *.c gcc *.o Te komendy dokonają kompilacji od źródeł do obiektów, a następnie konsolidacji. Kiedy kompilacja jest długa i kosztowna. . . xxx.h ......... yyy.h ......... aaa.c #include "xxx.h" ......... ccc.c #include "xxx.h" #include "yyy.h" ......... bbb.c #include "yyy.h" ......... aaa.o ccc.o bbb.o abc gcc -c -Wall -std=c99 aaa.c gcc *.o Zmiana jednego ze źródeł wymaga kompilacji tylko tego źródła oraz konsolidacji. Wykład 8. Budowanie aplikacji, str. 24 Kiedy kompilacja jest długa i kosztowna. . . xxx.h ......... yyy.h ......... aaa.c #include "xxx.h" ......... ccc.c #include "xxx.h" #include "yyy.h" ......... bbb.c #include "yyy.h" ......... aaa.o ccc.o bbb.o abc gcc -c -Wall -std=c99 aaa.c gcc -c -Wall -std=c99 ccc.c gcc *.o Zmiana w pliku nagłówkowym — rekompilacja plików, które go inkludują. Kiedy kompilacja jest długa i kosztowna. . . • Jeśli dysponujemy w katalogu plikami obiektowymi ... .o , to zmiana niektórych plików źródłowych ... .c wymaga ponownej kompilacji tylko zmienionych plików; oraz konsolidacji całości. • Pomyłka w tym, które pliki rekompilujemy, powoduje błąd trudny do znalezienia. • Specjalne narzędzie make , towarzyszące kompilatorowi gcc , dba o to, żeby rekompilowane były tylko potrzebne pliki. Dystrybucja oprogramowania Ze względu na różnorodność komputerów, systemów operacyjnych, konfiguracji sprzętu i oprogramowania rozpowszechnianie nowych programów w gotowej postaci wykonywalnej jest niepraktyczne. Rozpowszechnia się źródła oraz „ściągę”, wg której program make dokonuje potrzebnych kompilacji, konsolidacji i innych czynności. Wykład 8. Budowanie aplikacji, str. 26 make i Makefile 0 1 2 3 4 5 6 7 8 9 10 Makefile wszystko: pl1 pl2 pl3 cat pl1 pl2 pl3 > calosc ls -l --time=ctime pl1: p1 tr ’Oo’ ’Aa’ < p1 > pl1 pl2: echo -n " ma" > pl2 pl3: echo -n " kota." > pl3 wyczysc: rm pl1 pl2 pl3 calosc * Wiersz 0: cel wszystko zależy od celów pl1, pl2 i pl3. Wiersze 1 i 2: co trzeba zrobić dla realizacji celu wszystko. Wiersz 3: cel pl1 zależy od celu p1. Wiersz 4: co trzeba zrobić dla realizacji celu pl1. Wiersze 5, 7 i 9: cele pl2, pl3 i wyczysc nie zależą od innych. Wiersze 6, 8 i 10: co trzeba zrobić dla ich realizacji. Program make wykonuje komendy zapisane w pliku Makefile znajdującym się w aktualnym katalogu. W pliku Makefile zapisane są cele do realizacji, zależności od innych celów, komendy bash -a. make i Makefile 0 1 2 3 4 5 6 7 8 9 10 Makefile wszystko: pl1 pl2 pl3 cat pl1 pl2 pl3 > calosc ls -l --time=ctime pl1: p1 tr ’Oo’ ’Aa’ < p1 > pl1 pl2: echo -n " ma" > pl2 pl3: echo -n " kota." > pl3 wyczysc: rm pl1 pl2 pl3 calosc * DRZEWO ZALEŻNOŚCI wszystko pl1 pl2 pl3 p1 wyczysc „Cel A zależy od celu B” oznacza, że dla realizacji celu A trzeba sprawdzić, czy w międzyczasie nie uległo zmianie coś, od czego zależą wyniki B i jeśli tak, to powtórzyć realizację B. Wykład 8. Budowanie aplikacji, str. 28 Typowy Makefile dla programów w C xxx.h ......... yyy.h ......... ccc.c aaa.c bbb.c #include "xxx.h" #include "xxx.h" #include "yyy.h" #include "yyy.h" ......... ......... ......... Makefile OBJ = aaa.o bbb.o ccc.o aaa.o ccc.o bbb.o abc Komend gcc -c , kompilujących plik.c do plik.o , oraz zależności plik.o od plik.c nie trzeba pisać, bo są domyślne. prog: $(OBJ) gcc -o abc $(OBJ) aaa.o: xxx.h bbb.o: yyy.h ccc.o: xxx.h yyy.h clean : rm abc $(OBJ) Typowy Makefile dla programów w C • Jeśli program jest rozbity na kilka plików, to warto jawnie rozdzielić kompilację i konsolidację programu na – etap kompilacji — komenda gcc -c ; oraz – etap konsolidacji — komenda gcc . • W tym celu należy sporządzić pomocniczy plik Makefile . Potem po każdej zmianie w którymkolwiek pliku źródłowym komenda make uaktualni przekład, nie wykonując niepotrzebnych kompilacji. • Język zastosowany w konstruowaniu pliku Makefile zawiera w sobie makrogenerator podobny do mechanizmu #define w preprocesorze C. • Język zastosowany w konstruowaniu pliku Makefile to już czwarty język programowania poznany przez Państwo, po C, preprocesorze C oraz języku powłoki bash .