ZMIENNE 39
zmienne stanowią abstrakcję komórek pamięci: programista może przechowywać dane w pamięci, nie martwiąc się o techniczne szczegóły (np. przydział pamięci). 40
Nazewnictwo zmiennych Dozwolone znaki. Tradycyjnie: litery, cyfry i zwykle znak podkreślenia. Ale np. najnowsza wersja języka Ada (2005) przewiduje użycie dowolnych znaków z zestawu Unicode. Zwykle nazwa zmiennej musi zaczynać się literą. Maksymalna liczba znaków nazwy jest zwykle duża, np. 255. Rozróżnianie wielkich i małych liter (C, C++ i pochodne języki, jak np. Java, C# rozróżniają) 41
Cechy zmiennej 42
1. Nazwa zmiennej Nie każda zmienna ma nazwę: np. tworząc zmienne dynamiczne w C (malloc) tworzymy zmienną bez nazwy (nazwę ma użyty wskaźnik, ale nie sama zmienna). Tablice to zbiór wielu zmiennych tego samego typu, tablica zwykle posiada nazwę, więc możemy ją też traktować jako pojedynczą zmienną tablicową (wektor czy macierz) 43
2. Adres Adres to numer komórki pamięci, gdzie zmienna jest składowana (zależnie od typu może być to więcej komórek, adres opisuje pierwszą z nich) Program może zawierać dwie zmienne o tej samej nazwie, ale nie mające ze sobą nic wspólnego, np. dwie zmienne lokalne o nazwie i w dwóch różnych podprogramach. 44
2. Adres (cd.) Ta sama zmienna lokalna może mieć różne adresy w czasie różnych wywołań tego samego podprogramu. Nie ma więc jednoznacznej odpowiedniości między nazwą a adresem. Bieżący adres zmiennej zwany jest l- wartością (l-value); to określenie bierze się stąd, że właśnie adres zmiennej jest potrzebny przy wpisywaniu wartości do zmiennej (po lewej stronie instrukcji przypisania). 45
Aliasowanie To sytuacja, gdy do tej samej komórki pamięci można dotrzeć za pomocą dwóch różnych nazw. Tak jest np. wtedy, gdy dwa wskaźniki ustawimy na ten sam adres: Aliasowanie może prowadzić do przeoczeń i niejasności w kodzie, lepiej więc go unikać. 46
3. Wartość Jest to po prostu zawartość komórki (komórek) pamięci związanej z daną zmienną. Zwana niekiedy r-wartością (r-value) ponieważ to wartość zmiennej odczytujemy, gdy zmienna występuje po prawej stronie przypisania. 47
4. Typ Typ to zbiór dopuszczalnych wartości, jakie zmienna może przyjmować. Z typem wiąże się również zbiór operacji dopuszczalnych dla danej zmiennej. Przykład: typ int w języku C najczęściej oznacza liczbę całkowitą ze znakiem zapisaną na 32 bitach (jeden z nich jest przeznaczony na znak). Zbiór dopuszczalnych wartości: (-2 31 ;2 31 ]. Przykładowe operacje: dodawanie, odejmowanie, itp. Gdy mówimy o zmiennych w reprezentacji zmiennopozycyjnej, typ określa też precyzję, z jaką liczby są reprezentowane. 48
Wiązania Różne byty i atrybuty, w pewnym momencie zostają ze sobą powiązane. Rzecz dotyczy bardzo rozmaitych bytów takich jak zmienna, operator, wywołania podprogramu i cech takich jak wartość, typ, adres. Przykładowo, deklaracja zmiennej powoduje związanie zmiennej z typem, zaś wykonanie instrukcji podstawienia powoduje powiązanie zmiennej z (nową) wartością. 49
Wiązania statyczne i dynamiczne Wiązania dzielimy na dwie klasy. Wiązania statyczne to te, które następują przed wykonaniem programu i nie zmieniają się w trakcie jego działania. Praktycznie następują w czasie kompilacji programu. Wiązania dynamiczne to te, które następują lub zmieniają się w trakcie działania programu. Praktycznie następują w czasie wykonania programu. 50
Wiązanie typu Każda zmienna musi zostać związana z typem przed pierwszym użyciem w programie. Rozpatrujemy dwa aspekty tej sprawy: 1. Jak określamy ów typ? 2. Kiedy następuje wiązanie? 51
1. Jak określamy typ zmiennej? We współczesnych językach zazwyczaj mamy jawną deklarację. Bywają też deklaracje niejawne, np. pierwsze użycie zmiennej może stanowić deklarację. W przypadku deklaracji niejawnych konwencja nazewnictwa zmiennej może określać typ. W Fortranie pierwsza litera nazwy wyznaczała typ zmiennej, chyba że zmienna została zadeklarowana jawnie. W Perlu zasada pierwszego znaku jest obligatoryjna. Występuje także mechanizm wnioskowania o typie z kontekstu, np. w języku ML i Haskellu. 52
2. Kiedy następuje wiązanie zmiennej z typem? Deklaracje zmiennych dają wiązanie statyczne. Gdy zmienna jest wiązana z typem przy pierwszym podstawieniu pod nią wartości, (np. w PHP i JavaScripcie) jest to wiązanie dynamiczne. Wiązanie dynamiczne gwarantuje elastyczność, ale ma dwie wady: jest kosztowne (trzeba dynamicznie sprawdzać typ) i utrudnia wykrywanie błędów (kompilator ma małe szanse wykryć niezgodność typów). 53
Wiązanie pamięci Pojęcia związanych z wiązaniem pamięci: Okres życia zmiennej to czas, w którym jest ona związana z konkretnym miejscem w pamięci. Alokacja (przydział) pamięci oznacza pobranie bloku pamięci odpowiedniej wielkości z puli wolnej pamięci i związanie go ze zmienną. Dealokacja (zwolnienie) pamięci oznacza unicestwienie wiązania bloku pamięci ze zmienną i oddanie go do puli wolnej pamięci. Okres życia zmiennej to czas pomiędzy alokacją a dealokacją. 54
Cztery kategorie zmiennych, związane z ich okresem życia: 1. Statyczne mówimy wówczas o zmiennych statycznych 2. Dynamiczne na stosie najczęściej mówi się o zmiennych automatycznych lub po prostu lokalnych 3. Dynamiczne na stercie, jawne. 4. Dynamiczne na stercie, niejawne. 55
1. Zmienne statyczne Wiązane z miejscem w pamięci przed rozpoczęciem wykonania programu; wiązanie to nie zmienia się w trakcie wykonania. Zaleta: Efektywne, ze względu na bezpośrednie adresowanie. Wada: Mało elastyczne, nie mogą być używane do obsługi wywołań rekurencyjnych. Przykład: Zmienne globalne oraz zmienne zadeklarowane jako static w języku C. Uwaga: W językach obiektowych takich jak C++, C# i Java deklaracja zmiennej z użyciem static zwykle oznacza coś innego. 56
2. Zmienne dynamiczne na stosie Wiązane z pamięcią w chwili, gdy wykonanie programu dociera do ich deklaracji. (dynamicznie) Pamięć dla nich jest przydzielana na stosie. Pamięć zwalniana, gdy kończy się wykonanie bloku zawierającego daną zmienną. Dla typowych zmiennych prostych (całkowite, zmiennopozycyjne) wszystkie atrybuty z wyjątkiem pamięci są wiązane statycznie. Zalety: Mogą być używane w wywołaniach rekurencyjnych. Wady: Mniejsza efektywność ze względu na pośrednie adresowanie, narzut związany z alokacją i dealokacją, brak historii (każde wywołanie podprogramu tworzy nową instancję zmiennych). Przykład: Zmienne lokalne w funkcjach w języku C i w metodach w Javie. 57
3. Zmienne dynamiczne na stercie, jawne Alokowane przez programistę w trakcie wykonania programu za pomocą jawnych poleceń, np. new, malloc. Dealokowane również jawnie (w C i C++ za pomocą free i delete) lub niejawnie poprzez mechanizm odśmiecania (Java, C#). Nie mają nazwy; dostępne są poprzez wskaźnik lub referencję. Zalety: Mogą być używane do tworzenia dynamicznych struktur danych, np. list wiązanych i drzew. Wady: Niska efektywność z powodu pośredniego trybu adresowania i skomplikowanego zarządzania stertą. Duże ryzyko nadużyć ze strony nieostrożnego programisty. 58
4. Zmienne dynamiczne na stercie, niejawne Alokowane i dealokowane niejawnie w trakcie wykonania programu w chwili wykonania podstawienia. Przykład: Napisy i tablice w Perlu. Zalety: Elastyczność posunięta do granic. Wady: Wysoki koszt, związany z dynamicznym przechowywaniem atrybutów. Trudne wykrywanie błędów. 59
Operatory i operandy Operator wykonuje jakąś czynność przy pomocy dostarczonych operandów. Podprogramy będziemy traktowali jako operatory, których operandami są parametry. Instrukcję przypisania będziemy uważali za operację dwuargumentową, której operandami są lewa i prawa strona przypisania. 60
Sprawdzanie zgodności typów Sprawdzanie zgodności typów to sprawdzenie, czy typy operandów są odpowiednie. Określenie odpowiedni (lub zgodny) typ oznacza typ bezpośrednio dozwolony w danym kontekście lub typ, który jest dozwolony po zastosowaniu niejawnej konwersji typu narzuconej przez reguły języka programowania. Błąd typu to użycie operatora z operandem nieodpowiedniego typu. 61
Niejawna konwersja typu - przykład W poniższym fragmencie wartość zmiennej j jest automatycznie zamieniana z typu int na float i wykonywane jest dodawanie zmiennopozycyjne. float x, y; int j; x = y + j; 62
Kiedy następuje sprawdzanie zgodności typów? Jeśli wiązanie typów jest statyczne, to sprawdzanie zgodności typów na ogół także może być statyczne, tzn. w czasie kompilacji. Jeśli natomiast wiązanie typów jest dynamiczne, to sprawdzanie zgodności typów musi być dynamiczne. Statyczne sp. zg. t. jest korzystniejsze, ponieważ potencjalnie daje programiście więcej informacji. 63
Języki silnie typowane Język nazywamy silnie typowanym, jeśli błędy typu są w nim zawsze wykrywane. Zaletą silnego typowania jest możliwość wykrywania wielu pospolitych błędów. Ada, C# i Java są niemal silnie typowane - odstępstwo to jawna konwersja typów. Pascal nie jest silnie typowany ze względu na tzw. unie (nazywane tam wariantami w rekordzie). C i C++ zdecydowanie nie są silnie typowane. Można w nich np. uniknąć sprawdzania typów parametrów, występują tam też unie oraz niejawne konwersje typu. 64
Niejawne konwersje typu powodują, że np. błędne podstawienia mogą formalnie nie być błędami typu. Silne typowanie służy wykrywaniu błędów. Silne typowanie bez konwersji sprzyja niezawodności (kosztem wygody programisty). 65
Jak zdefiniować zgodność typów? Dwie metody: 1. zgodność nazwy 2. zgodność struktury 66
1. Zgodność nazwy Dwie zmienne uznajemy za zgodne co do typu, jeśli zostały zdefiniowane w tej samej deklaracji lub jeśli do ich zadeklarowania użyto tej samej nazwy typu. Łatwe w implementacji, ale bardzo restrykcyjne. Przykład: Podzakresy typu całkowitego nie są z nim zgodne. 67
2. Zgodność struktury Dwie zmienne uznajemy za zgodne co do typu, jeśli mają taką samą strukturę. Problem: Nie da się rozróżnić typów o takiej samej strukturze, nawet jeśli w naszym zamierzeniu służą zupełnie różnym celom. np. odległość wyrażona w dwóch różnych jednostkach jeżeli jest przechowywana w zmiennych typu float to jest zgodna co do struktury (niepożądane). 68
Typy pochodne Typ pochodny to nowy typ oparty na typie już istniejącym. Dziedziczy wszystkie własności typu bazowego. Zakłada się, że nie jest zgodny z typem bazowym. To pozwala konstruować typy, które są identyczne co do struktury, ale niezgodne. Przykład: type metry is new Float; type stopy is new Float; 69
Podtypy Podtyp to typ już istniejący z pewnym ograniczeniem zakresu. Zakłada się, że jest zgodny z typem bazowym. Przykład: subtype SmallInt is Integer range 0..99; 70