ZMIENNE
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).
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ą)
Cechy zmiennej
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)
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.
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).
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ć.
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.
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: (-231;231].
Przykładowe operacje: dodawanie, odejmowanie, itp.
Gdy mówimy o zmiennych w reprezentacji
zmiennopozycyjnej, typ określa też precyzję, z jaką liczby są reprezentowane.
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ą.
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”.
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?
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.
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).
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ą.
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.
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.
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.
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.
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.
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.
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.
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;
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.
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.
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).
Jak zdefiniować zgodność typów?
Dwie metody:
1.
zgodność nazwy
2.
zgodność struktury
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.
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).
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;
Podtypy
Podtyp to typ już istniejący z pewnym ograniczeniem zakresu.
Zakłada się, że jest zgodny z typem bazowym.