Podstawy
programowania I
Wykład nr 4
Zmienne i ich typy. Rzutowanie typów
autor: dr inż. Michał Łabowski
▪ michal.labowski@wat.edu.pl
▪ www.mlabowski.wel.wat.edu.pl
Typy danych w C++
2
Typ danej opisuje sposób, w jaki kompilator traktuje dane binarne przechowywane w konkretnym fragmencie pamięci. Typ określa rozmiar potrzebny do przechowania danej informacji (liczbę bajtów).
Dane w C++ mogą być reprezentować wartości zmienne lub stałe. Wartości zmiennych mogą zmieniać się w trakcie działania programu, natomiast wartość stałej jest niezmienna. Wartość do stałej może być wpisania jedynie w momencie jej tworzenia (jest to tzw. inicjalizacja).
Dane
Zmienne Stałe
Typy
Wbudowane
Proste (podstawowe)
Złożone
Własne
Deklaracje zmiennych Rozmiar pamięci zajmowanej przez zmienną zależy więc od jej typu, dlatego przed użyciem zmiennej musimy poinformować kompilator (za pomocą deklaracji) czym ona jest i jaki obszar pamięci zajmuje (adres początku + liczba bajtów).
Deklaracja zmiennej:
typ_zmiennej nazwa_zmiennej;
„Zarezerwuj w pamięci obszar na obiekt typu typ_zmiennej i przypisz mu etykietę nazwa_zmiennej”
0x472000 0x472006 0x472008
Obszar zmiennej pewnego typu
Obszar zmiennej innego typu
Obszar zmiennej kolejnego typu
Adres początku obszaru pamięci
Komórki pamięci
Deklaracje zmiennych
4
Deklaracje zmiennych:
• w C++ wszystkie zmienne muszą być zadeklarowane (w przeciwnym wypadku pojawią się błędy kompilacji)
• zaleca się, aby zmienne były deklarowane możliwe blisko miejsca ich pierwszego użycia
• wymóg deklaracji pozwala wykryć błędy w nazwie użytej zmiennej (niektóre języki programowania, takie jak Python, nie wymagają deklarowania zmiennych)
W wyniku deklaracji zmiennej typu prostego kompilator zapamiętuje:
• miejsce (adres) przechowywania informacji
• przechowywaną wartość
• typ zmiennej (rodzaj przechowywanej wartości)
Wprowadzanie wartości
Wprowadzenie wartości:
1. Deklaracja zmiennej + późniejsze przypisanie wartości:
2. Inicjalizacja (deklaracja + przypisanie wartości):
• W przypadku operacji przypisania może być wykonanie niejawne rzutowanie typu (jeśli jest możliwe) polegające na dopasowaniu przypisywanej wartości do typu zmiennej:
Inicjalizacja klamrowa w C++
• wprowadzona w standardzie C++11:
•
Dostęp do danych – typy proste i typ string
6
Dostęp do danych:
• mechanizm pozwalający na pobranie wartości przechowywanej w zmiennej/stałej o określonej nazwie/adresie*
• w przypadku posiadania nazwy zmiennej dostęp do jej wartości uzyskujemy poprzez podanie nazwy tej zmiennej
• w przypadku posiadania adresu zmiennej dostęp do wartości przechowywanej
uzyskujemy stosując operator dereferencji (wyłuskania wartości)*.
Nazwy zmiennych/stałych w C++
Nazwy zmiennych / stałych w C++:
• nazwy powinny być znaczące, np. cena_produktu zamiast x.
• dopuszczalne znaki litery, cyfry i znaki podkreślenia _
• pierwszy znak nie może być cyfrą
• wielkość liter jest rozróżnialna (cena i Cena to dwie różne nazwy)
• nazwa nie może być słowem kluczowym C++, np. default, case, double itd.
• nazwy zaczynające się od dwóch podkreśleń lub podkreślenia i następującej po nim wielkiej litery są zarezerwowane dla kompilatora
Popularne sposoby zapisu nazw składających się z kilku wyrazów:
int metodaPomiaru lub int metoda_pomiaru
Nazwy zmiennych/stałych w C++
8
Nazwy zmiennych / stałych w C++:
• nazwy powinny być znaczące, np. cena_produktu zamiast x.
• dopuszczalne znaki litery, cyfry i znaki podkreślenia _
• pierwszy znak nie może być cyfrą
• wielkość liter jest rozróżnialna (cena i Cena to dwie różne nazwy)
• nazwa nie może być słowem kluczowym C++, np. default, case, double itd.
• nazwy zaczynające się od dwóch podkreśleń lub podkreślenia i następującej po nim wielkiej litery są zarezerwowane dla kompilatora
Popularne sposoby zapisu nazw składających się z kilku wyrazów:
int metodaPomiaru lub int metoda_pomiaru Zadanie. Wskaż niepoprawne nazwy zmiennych:
a) marchewki b) _rezystory
c) __kondensatory
d) _obwody drukowane e) _płytkiPCB
f) int
g) _Cewki
Typy całkowitoliczbowe
Typy całkowitoliczbowe w C++:
• podstawowy typ całkowitoliczbowy: int (skrót od integer)
• modyfikatory wpływające na znak: signed i unsigned
• typ bez modyfikatora (jak powyżej) lub poprzedzony modyfikatorem signed może przechowywać wartości dodatnie i ujemne:
signed int stan_konta to to samo co int stan_konta
• modyfikator unsigned umieszczony przed nazwą typu sprawia, że może on przechować jedynie liczby nieujemne, np.
unsigned int objetosc; // objętość może być jedynie nieujemna
• modyfikatory wpływające na rozmiar: short, long oraz long long
• short - liczba ma przynajmniej 16 bitów
• int – liczby muszą być nie mniejsze od typu short
• long – liczba ma przynajmniej 32 bitów i musi być nie mniejsza od typu int
• long long (wprowadzone przez C++11) – liczba ma przynajmniej 64 bitów i musi Typy proste
Całkowitoliczbowe Zmiennoprzecinkowe
Logiczny
Typ int
Podstawowe typy całkowite i ich właściwości 10
Literały całkowitoliczbowe Literał – stała, którą zapisujemy jawnie w programie. Może być typu całkowitoliczbowego, zmiennoprzecinkowego czy znakowego.
Literał całkowitoliczbowy – literał wykorzystujący typ całkowitoliczbowy. Może być zapisany w kodzie dziesiętnym, ósemkowym (przedrostek 0), szesnastkowym (przedrostek 0X lub 0x) lub binarnym (od C++14, przedrostek 0b)
W standardzie C++14 dodano możliwość wprowadzenia separatora ułatwiającego
odczyt wartości przez człowieka:
Typ char
Podstawowe typy całkowite i ich właściwości 12
Typ znakowy char:
• służy do przechowywania znaków
• jest typem całkowitoliczbowym, ponieważ znaki kodowane są jako liczby
• w Polsce najczęściej używanym kodowaniem jest ISO 8859-2
• posiada modyfikatory signed i unsigned (brak modyfikatora oznacza domyślne użycie jednego z nich, zależnie od implementacji C++)
• pojedynczy znak przypisujemy z wykorzystaniem apostrofów: np. 'a'
• przykład definicji zmiennej typu char:
char litera = 'A';
char litera = 65;
Standardowy zapis liczb zmiennoprzecinkowych:
Typy zmiennoprzecinkowe
Typy zmiennoprzecinkowe w C++:
• posiadają część ułamkową
• kodowanie liczb zmiennoprzecinkowych odbywa się zgodnie ze standardem IEE- 754
• typy zmiennoprzecinkowe w C++: float, double, long double
• różnią się osiąganą „precyzją” i zakresem liczb
• rozmiary: float – zwykle 32 bity, double – zwykle 64 bity, long double – zwykle 96 lub 128 bitów.
• separatorem dziesiętnym jest KROPKA, np. 10.5
• dwa sposoby zapisu liczb: standardowy (z kropką) i notacja naukowa Typy proste
Całkowitoliczbowe Zmiennoprzecinkowe
Logiczny
Typy zmiennoprzecinkowe
14
Notacja naukowa:
• korzysta z oznaczenia E lub e, np. 3.45E6 oznacza pomnożenie 3.45 przez 1000000 ponieważ E6 oznacza 10
6(6 to wykładnik, 3.45 to mantysa)
• przydatna w przypadku zapisu bardzo dużych i bardzo małych liczb
• gwarantuje, że liczba zostanie zapisana jako liczba zmiennoprzecinkowa, nawet jeśli nie zostanie użyta kropka dziesiętna.
• wewnątrz liczby nie mogą wystąpić żadne spacje.
• zapis d.dddE+n oznacza przesunięcie separatora dziesiętnego n miejsc w prawo, zapis d.dddE-n oznacza przesunięcie separatora o n miejsc w lewo (stąd nazwa
„typ zmiennoprzecinkowy”)
Typy zmiennoprzecinkowe Stałe (literały) zmiennoprzecinkowe:
• przypisując stałą wartość zmiennoprzecinkową (literał zmiennoprzecinkowy) do zmiennej, np.:
kompilator użyje typu double do przechowania wartości 2.34, jeśli chcemy aby wykorzystał typ float należy do stałej dopisać przyrostek f lub F:
• w przypadku chęci wykorzystania literału typu long double:
Zalety i wady liczb zmiennoprzecinkowych:
• pozwalają na zapis liczb rzeczywistych
• szeroki zakres przechowywanych wartości
• czas wykonywania obliczeń jest dłuższy niż w przypadku liczb całkowitych (zobacz. ang. Floating Point Unit - FPU)
• mogą tracić dokładność: (2.34E+22f) + 1 to w dalszym ciągu (2.34E+22f): liczba
ma 23 cyfry na lewo od przecinka, chcemy dodać 1 do 23. cyfry, lecz float ma 6
cyfr znaczących.
Typ logiczny
16
Typ logiczny w C++:
• typ bool
• nazwa pochodzi od matematyka George’a Boole’a
• zmienna logiczna może przyjąć dwie wartości: prawdę (true) i fałsz (false)
• wartości niezerowe interpretowane są jako prawda, a zera jako fałsz:
Typy proste
Całkowitoliczbowe Zmiennoprzecinkowe
Logiczny
Zakres liczb przechowanych w zmiennych wybranych typów
Kwalifikator const
18
Kwalifikator const:
• służy do zapisu stałych symbolicznych
• jeśli stała symboliczna występuje w wielu miejscach w programie powinna jej zostać nadana nazwa (powinna powstał zmienna z kwalifikatorem const)
• w C++ powinien być używany zamiast dyrektywy #define nazwa wartość
• wartość stałej może być nadana jedyne w momencie tworzenia, zatem należy
użyć inicjalizacji.
Stałe symboliczne preprocesora Stałe symboliczne preprocesora:
• rozpoczynają się od dyrektywy #define
• składnia:
#define nazwa_stałej wartość
• preprocesor wykonuje operację: zajdź-i-zamień: znajduje wszystkie wystąpienia nazwy nazwa_stałej i zastępuje je zdefiniowaną wartością
• relikt języka C, w C++ należy wykorzystywać stałe const lub constexpr
• stałe symboliczne nie mają adresu
• przyjęło się zapisywać nazwę drukowanymi literami
Specyfikator constexpr (C++11)
20
Specyfikator constexpr:
• wprowadzony przez C++11
• dotyczy funkcji i zmiennych
• określa, że wartość wyrażenia może być wyznaczona na etapie kompilacji
• obliczenia, które wykonane są podczas kompilacji nie są wykonywane podczas działania programu – zyskujemy czas
• jeśli wartość stałej da się określić dopiero po uruchomieniu programu wówczas
należy skorzystać ze specyfikatora const
Specyfikator constexpr (C++11)
Nr linii Opis
2 Dołączamy bibliotekę cmath zawierającą deklaracje stałych matematycznych (np.
π) i funkcji matematycznych (np. potęgowanie) 8 Wyznaczamy wartość wyrażenia 4
3𝜋, użyliśmy specyfikatora constexpr ponieważ wartość tę możemy obliczyć już na etapie kompilacji i wykorzystać podczas działania programu
10 Do potęgowania liczb typu double wykorzystuje się funkcję pow (ang. power) z biblioteki cmath. Składnia:
wynik = pow(podstawa, wykładnik)
Typu o precyzyjnej szerokości
22
Typy o precyzyjnie określonej szerokości (liczbie bitów):
• ze znakiem:
int8_t int16_t int32_t int64_t
• bez znaku:
uint8_t uint16_t uint32_t uint64_t
• zastosowanie: przechowywanie danych pomiarowych o określonej długości
• aby z nich korzystać należy dołączyć bibliotekę cstdint
Rzutowanie Rzutowanie (konwersja) typu – mechanizm pozwalający na traktowanie danej pewnego typu, jak daną innego typu.
• może być jawne i niejawne.
• rzutowanie jawne wykonywane jest przez programistę, niejawne – przez kompilator
• rzutowanie jawne może mieć następującą składnię:
(nazwa_typu) wartość nazwa_typu (wartość)
Oba wyrażenia zwracają wartość po konwersji na typ nazwa_typu, np. rzutowanie zmiennej typu int na typ long int:
• rzutowanie niejawne zachodzi, gdy konieczne jest dostosowanie typów danych
(np. przy operacji przypisania, przekazania argumentów do funkcji przez
kopiowanie) i gdy jest możliwe. Wykonuje je kompilator:
Rzutowanie - operatory
24
Rzutowanie – cechy:
• może prowadzić do utraty danych (rzutujemy int, np. 654, na typ char)
• powinno być unikane
• jest oznaką niskiej jakości kodu
• w przypadku „zwykłego” rzutowania kompilator może zgłaszać ostrzeżenia/błędy jeśli konwersja nie jest możliwa
• jeśli chcemy zmusić kompilator do konwersji można wykorzystać operatory rzutowania
Operatory rzutowania:
• C++: static_cast, const_cast, dynamic_cast, reinterpret_cast
• operator static_cast powinien być wykorzystywany zamiast „starych”
mechanizmów rzutowania. Składnia:
static_cast<nazwa_typu>(wyrażenie)
• stosując operator static_cast programista bierze na siebie odpowiedzialność za sens konwersji
Na ten typ rzutujemy
Rzutowana
wartość
Typy złożone
Typy złożone:
• są tworzone na bazie typów podstawowych
• często wykorzystywane typy złożone (poziom podstawowy): tablice i string
• typ złożony: tablica – tablica nie może być po prostu tablicą, jest tablicą obiektów konkretnego typu, np. typu int.
• typ złożony: string – służy do wygodnego przechowywania i manipulacji ciągiem znaków
Typy
Wbudowane
Proste (podstawowe)
Złożone
Własne
string
26
Typy string:
• służy do przechowywania ciągu znaków (bazuje na danych typu char)
• typ wynikający z biblioteki standardowej (z nią związana jest przestrzeń nazw std)
• plik nagłówkowy string:
Funkcja to_string():
• pozwala na konwersję liczby na ciąg cyfr (czyli ciąg znaków)
string Sprawdzenie liczby znaków w obiekcie string:
• metoda size()
Operatory w C++
28
Operatory arytmetyczne w C++:
• 5 operatorów: dodawanie, odejmowanie, mnożenie, dzielenie i dzielenie modulo
• są to operatory dwuargumentowe: wykonują działania na dwóch argumentach
• uwaga praktyczna: po obu stronach operatora arytmetycznego stosuj spację w celu poprawy czytelności: zapis a + b jest czytelniejszy od a+b
Operator Opis Przykład
* mnożenie x * y
/ dzielenie, jeśli wynik przypisywany jest do zmiennej całkowitej to część dziesiętna wyniku dzielenia jest ucinana, jeśli oba operandy są liczbami całkowitymi,
to wynik jest całkowitą częścią ilorazu
x / y
% dzielenie modulo (reszta z dzielenia, tylko liczby całkowite)
x % y
+ dodawanie x + y
- odejmowanie x - y
Operator dodawania
Nr linii Opis
2 Dodajemy plik nagłówkowy climits, który zawiera definicje stałych symbolicznych opisujących wartości ekstremalne (minima i maksima) typów podstawowych, np.
INT_MAX to maksymalna dopuszczalna wartość przechowywana w zmiennej typu
Operator dodawania
30
Nr linii Opis
8-12 inicjalizacje zmiennych
10 sumowanie bezpieczne (bez przekroczenia zakresu zmiennej typu int)
11 sumowanie z przepełnieniem (ang. overflow) – dodajemy 1 do wartości maks.
Operator dodawania
Nr linii Opis
12 Możemy sumować literały całkowitoliczbowe (możliwe jest także mieszane wykorzystanie literałów i zmiennych)
Przepełnienie (ang. overflow)
32
Przepełnienie liczb całkowitych:
• ang. (overflow)
• stan, w którym wartość zmiennej jest zbyt duża (mała) by można było ją zapisać w obszarze pamięci przewidzianym dla zmiennej danego typu,
• w przypadku liczb całkowitych bez znaku standard C++ zapewnia zachowanie polegające na przekręceniu wartości, tzn. :
MIN - 1 → MAX, MAX + 1 → MIN
• większość kompilatorów stosuje ten mechanizm także dla typów ze znakiem
Operator odejmowania
Nr linii Opis
11 Odejmowanie, którego wynikiem jest przepełnienie – „licznik” wartości się przekręca i wchodzimy na zakres dodatni
Operator mnożenia
34
Nr linii Opis
11 Mnożenie, którego wynikiem jest przepełnienie
Operator dzielenia
Operator dzielenia
36
Nr linii Opis
10 Dzielna i dzielnik mają typ całkowity, zatem wynik też jest liczbą całkowitą (część dziesiętną ucinamy, nie zaokrąglamy)
14 Dzielna i dzielnik mają typ całkowity, zatem wynik też jest liczbą całkowitą (część dziesiętną ucinamy, nie zaokrąglamy), całkowity wynik jest następnie niejawnie rzutowany na typ zmiennoprzecinkowy
18 Jeśli choć jeden z argumentów dzielenia ma typ zmiennoprzecinkowy, to rezultat dzielenia także jest zmiennoprzecinkowy
25 Przypadek „idealny” – dzielna, dzielni i iloraz mają typ zmiennoprzecinkowy
29 Dzielna i dzielnik mają typ zmiennoprzecinkowy, wynik dzielenia jest pierwotnie zmiennoprzecinkowy, po czym następuje jego niejawne rzutowanie na typ całkowity
33 Dzielenie przez 0 zwraca wartość Inf (infinity)
Operator dzielenia modulo
Operatory unarne
38
Operator Opis Przykład
+ wartość dodatnia (rzadko stosowany) +a
- wartość ujemna -(a + b)
++ operator zwiększenia (inkrementacji) ++a jest równoważne a = a + 1 a++
-- operator zmniejszenia (dekrementacji) --a jest równoważne a = a - 1 a--
Operacja ++a (forma przedrostkowa, prefiks) zwiększa wartość zmiennej a o jeden przed jej użyciem
Operacja a++ (forma przyrostkowa, postfix) zwiększa wartość zmiennej a o jeden po użyciu jej wartości
Operatory unarne są operatorami działającymi na jednym argumencie
Operatory inkrementacji
Nr linii Opis
9 forma przyrostkowa, wartość zmiennej a jest przypisywana zmiennej b, następnie wartość zmiennej a jest inkrementowana
14 forma przedrostkowa, wartość zmiennej a jest zwiększana o 1, następnie ta wartość jest przypisywana zmiennej c
Operator przypisania
40
Operator przypisania:
• =
• dwuargumentowy
• sprawia, że do obiektu znajdującego się po lewej stronie przypisywana zostaje wartość wyrażenia stojącego po prawej stronie
• przypisanie samo w sobie jest wyrażeniem, mającym taką wartość, jaka jest przypisywana, zatem wartość wyrażenia (x = 2) wynosi 2.
Operator PRZYPISANIA to =
Operatory logiczne
Zapis Opis Przykład
< mniejszy niż a < b
<= mniejszy lub równy a <= b
> większy niż a > b
>= większy lub równy a >= b
== jest równy (porównanie) a == b
!= jest różny od a != b
Operatory logiczne:
• zwracają wartość typu bool: true lub false
• wyróżniamy: operatory relacji, operator sumy logicznej, operator iloczynu logicznego i operator negacji
Operatory relacji:
Operator PORÓWNANIA to ==
Częstym błędem jest pominięcie jednego ze znaków =
Operatory logiczne
42
Należy zwrócić szczególną uwagę na różnice pomiędzy operatorem przypisania (=) i porównania (==)!
Operator Działanie Przykład
= przypisanie a = b (weź wartość zmiennej b i przypisz ją wartości zmiennej a)
== porównanie a == b (porównaj wartości zmiennych a i b, jeśli są takie same zwróć true, w
przeciwnym wypadku zwróć false)
Operatory logiczne Operatory sumy logicznej i iloczynu logicznego:
• wartość wyrażenia obliczana jest od lewej do prawej strony
• kompilator oblicza wartość wyrażenia do momentu, aż nie ma pewności jaki będzie wynik, np. w wyrażeniu (m == 0) && (k > 3), jeśli wartość zmiennej m jest różna od 0 kompilator nie będzie obliczał wyrażenia (k > 3) ponieważ wynik całego wyrażenia jest już znany i wynosi false.
Operator Znaczenie Przykład Opis
|| suma logiczna (alternatywa)
( a == b ) || ( a == c ) a jest równe b lub a jest równe c
&& iloczyn logiczny (koniunkcja)
( a >= b ) && ( a <= c ) a jest większe bądź równe b i jednocześnie a
jest mniejsze bądź równe c (zetem a należy
do przedziału <b, c>) Zaleca się wstawiać nawiasy, aby uniknąć
problemów z priorytetami operatorów
Operator Nowa postać Przykład
|| or ( a == b ) or ( a == c )
Operatory logiczne
44
Nr linii Opis
10
sprawdzenie warunku, czy wartość zmiennej a jest równa 2 i wartość zmiennej k jest mniejsza od 3. Drugi warunek nie jest spełniony.
Operator negacji:
• operator jednoargumentowy
• argument stoi po jego prawej stronie
Zapis Opis Przykład
! zaneguj: jeśli wartość zmiennej wynosi true – zwraca false, jeśli wartość zmiennej wynosi
false – zwraca true
!a
not a
Priorytety operatorów Priorytety operatorów:
• priorytety dzielą się na grupy (mniejszy numer grupy tym wyższy priorytet)
• w obrębie grupy priorytety są równe
• mnożenie, dzielenie, dodawanie i odejmowanie mają względem siebie takie same priorytety, jak w matematyce
• skomplikowane wyrażenia matematyczne lepiej umieszczać w nawiasach –
stosujemy tylko nawiasy okrągłe (), w tym przypadku nie stosujemy nawiasów
kwadratowych [] i klamer {}
Priorytety operatorów
46
Łączność operatorów Łączność operatorów:
• operatory jednoargumentowe (poza postikrementacją i postdekrementacją) są prawostronnie łączne – działają na argument znajdujący się po ich prawej stronie:
+1 -2.0 !a
• operatory postdekrementacji i postdekrementacji mają łączność lewostronną (działają na operator znajdujący się z ich lewej strony):
a++ b--
• operatory dwuargumentowe są lewostronnie łączne (poza operatorami przypisania).
• w przypadku operatorów dwuargumentowych łączność określa, w jaki sposób grupowane jest wykonywanie wyrażenia.
Przykład: operator dodawania jest lewostronnie łączny, zatem wyrażenie:
odpowiada wyrażeniu:
Łączność operatorów
48