Podręcznik

Strona: SEZAM - System Edukacyjnych Zasobów Akademickich i Multimedialnych
Kurs: 2. Podstawowe instrukcje i typy danych
Książka: Podręcznik
Wydrukowane przez użytkownika: Gość
Data: czwartek, 3 lipca 2025, 14:05

1. Typy danych

Program operuje na pewnych danych, które muszą być umieszczone w pamięci komputera. Poznaliście dane, których wartość może zostać zmieniona w trakcie pracy programu. Taki rodzaj danych nazywamy zmiennymi. Oprócz nich mogą być potrzebne dane, które nie powinny się zmieniać podczas wykonania programu - dane takie nazywamy stałymi.

Dane możemy podzielić na dwie grupy: stałe i zmienne - analogicznie jak w matematyce.
W języku C++ zmienne mogą być zapisywane w różnych miejscach w pamięci. Wpływa to na czas ich życia oraz sposób zarządzania pamięcią. Pierwszym typem zmiennych jaki możemy wyróżnić są zmienne lokalne. Są  one deklarowane wewnątrz funkcji lub bloku kodu np. w pętli lub instrukcji warunkowej. Zmienne te mają zasięg ograniczony do bloku, w którym zostały zadeklarowane. Oznacza to, że są dostępne tylko w ramach tego bloku. Przechowywane są na stosie (ang. stack) oraz są automatycznie usuwane po wyjściu z tego bloku. 

Kolejnym typem zmiennych są zmienne statyczne, które mogą być zarówno lokalne, jak i globalne. Zmienne te są przechowywane w specjalnym segmencie pamięci dla zmiennych statycznych. Zmienne te są deklarowane z użyciem słowa kluczowego static. W przypadku zmiennych lokalnych, słowo static sprawia, że zmienna zachowuje swój stan pomiędzy wywołaniami funkcji, w której jest zadeklarowana, mimo że jej zasięg pozostaje ograniczony do tej funkcji. Zmienne globalne zadeklarowane jako statyczne mają zasięg ograniczony do pliku, w którym są zadeklarowane, ale ich czas życia obejmuje cały czas trwania programu.

Zmienne globalne, które są deklarowane poza wszystkimi funkcjami i klasami, mają zasięg globalny, co oznacza, że są dostępne w całym programie. Są one przechowywane w segmencie danych (data segment) programu i mają trwałość przez cały czas trwania programu. 

Zmienne dynamiczne są przechowywane na stercie (ang. heap) i mają czas życia kontrolowany przez programistę. Oznacza to, że istnieją dopóki nie zostaną zwolnione. Są alokowane w pamięci za pomocą operatorów new i delete. Zarządzanie pamięcią dla zmiennych dynamicznych wymaga uwagi w celu unikania wycieków pamięci. To programista jest odpowiedzialny za zwalnianie pamięci po jej użyciu.

Istnieją także zmienne są zmienne rejestrowe, które są deklarowane z użyciem słowa kluczowego register. Zmienne są przechowywane w rejestrze procesora zamiast w pamięci operacyjnej co może przyspieszyć dostęp do niej. Zmienne te mogą mieć charakter lokalny lub globalny.

Wykorzystanie poszczególnych rodzajów zmiennych zostanie wyjaśnione w trakcie całego podręcznika. W kolejnym rozdziale przedstawiono podstawowe typy danych dostępne w języku C++, które umożliwiają deklarowanie poszczególnych zmiennych.

2. Wbudowane typy danych

W języku C++ zmienne są deklarowane poprzez podanie typu zmiennej oraz nazwy zmiennej.

Pamięć operacyjna komputera, znana również jako RAM (ang. Random Access Memory), jest podzielona na komórki pamięci.  Każda jest identyfikowana unikalnym adresem. Każda komórka pamięci przechowuje określoną ilość danych. Zazwyczaj jest to jeden bajt (8 bitów). Każdy bajt może przechowywać liczbę od 0 do 255.

Bajt to podstawowa jednostka pamięci w komputerze, składająca się z 8 bitów.



Każda komórka pamięci przechowuje jeden bajt danych. Komórki pamięci są numerowane kolejnymi adresami, które identyfikują ich położenie w pamięci. Oczywiście korzystając z komórek pamięci możliwe jest zapisywanie większych liczb. W tym celu należy zająć więcej komórek pamięci. Posiadając wiedzę w jakich komórkach pamięci umieszczone są poszczególne elementy większej liczby możliwa jest realizacja w zasadzie każdej aplikacji. Taki sposób realizacji aplikacji wiązałby się z bardzo dużym obciążeniem po stronie programisty. W zasadzie trudno sobie wyobrazić realizację bardziej rozbudowanej aplikacji. Między innymi z tego powodu w językach wysokiego poziomu zdefiniowano typy danych, które są najczęściej stosowane. Są to tzw. typy wbudowane

Typy wbudowane danych zostały wprowadzone w językach programowania z kilku powodów, które związane są z różnymi aspektami programowania. O jednym aspekcie już wspominaliśmy na początku rozdziału tzn. zarządzanie pamięcią. Dzięki zastosowaniu typów danych możemy określić ile pamięci powinno zostać zarezerwowane dla zmiennej. Oczywiście różne typy danych zajmują różną ilość miejsca pamięci o czym powiemy nieco później. Dzięki temu kompilator posiada wiedzę, jaką ilość pamięci przydzielić każdej zmiennej oraz w jaki sposób przydzielić adresy dla różnych zmiennych. Ponadto zastosowanie typów pozwala zwiększyć wydajność. Kompilator znając typy zmiennych może dokonać optymalizacji kodu maszynowego, który powstaje w trakcie kompilacji. Różne typy danych mogą w trakcie realizacji programu wymagać różnych operacji na poziomie sprzętowym z tego powodu optymalizacja kodu maszynowego może być bardziej skuteczna, gdy posiadamy wiedze o typach poszczególnych zmiennych. Innym aspektem związanym z wykorzystaniem typów wbudowanych jest zabezpieczenie przed błędami związanymi z niezgodnością typów. Kompilator dzięki typom zmiennych może sprawdzić poprawność przypisania danych do zmiennych. Bezpieczeństwo typów zwiększa stabilność i niezawodność kodu. Zastosowanie typów danych wpływa także na czytelność kodu. Programista widząc deklarację zmiennej z określonym typem może łatwiej zrozumieć, jakie dane są przechowywane w zmiennej oraz jakie operacje mogą być na niej wykonywane. 

Język C++ jest statycznie typowanym językiem programowania. Oznacza to, że typy zmiennych są określane i sprawdzane w czasie kompilacji. Jest to cecha języków mocno typowanych. Jednocześnie należy podkreślić, że język C++ oferuje również mechanizmy rzutowania i wskaźników, które mogą obejść system typów. Możemy więc stwierdzić że język C++ łączy elementy zarówno mocnego, jak i słabego typowania, dając programistom dużą elastyczność. Jednocześnie wiąże się to z wymaganiem od programisty świadomego zarządzania typami i pamięcią. Należy także podkreślić, że nie wszystkie języki programowania są statycznie typowane. Przykładem języka, który jest dynamicznie typowanym jest język Python. Typy zmiennych są określane w nim w czasie wykonania programu, a nie w czasie kompilacji. Oczywiście z tego powodu język nie posiada wymienionych wyżej zalet. Posiada z kolei inne zalety. Jednak wróćmy do języka C++, który jest głównym bohaterem tego podręcznika.

Poznaliśmy już z jakiego powodu do języka C++ zostały wprowadzone wbudowane typy danych. Teraz przedstawimy jakie mamy typy wbudowane dostępne w języku C++.  Wszystkie typy podstawowe w C++ zamieszczono poniżej:

    • typ logiczny służy do przechowywania wartości logicznych (prawda, fałsz),
    • typy znakowe służą do przechowywania pojedynczych znaków,
    • typy całkowite służą do przechowywania liczb całkowitych,
    • typy zmiennopozycyjne służą do przechowywania liczb rzeczywistych,
    • typ void jest wykorzystywany do zaznaczenia braku informacji.

Każdy z wbudowanych (podstawowych) typów ma przypisaną określającą go nazwę predefiniowaną. 

Każda zmienna w języku C++ zanim zostanie użyta w aplikacji musi zostać zadeklarowana oraz zdefiniowana.

W języku C++ zmienne są deklarowane poprzez podanie typu zmiennej oraz nazwy zmiennej.

TYP_ZMIENNEJ NAZWA_ZMIENNEJ;
W podanej definicji deklaracja jest równocześnie definicją.
W języku C++ definicja oznacza przydzielenie miejsca na zadeklarowany obiekt. Powołuje do życia obiekt. 
Definicja zmiennej jest zawsze jednocześnie deklaracją zmiennej. Jeżeli jakaś zmienna jest już zdefiniowana, a my checmy tylko przypomnieć kompilatorowi o tym fakcie to możemy to zrobić przy wykorzystaniu słowa kluczowego extern. Informuje ono kompilator, że dana zmienna już gdzieś istnieje. Wywołanie mogłoby wyglądać w następujący sposób: 

extern TYP_ZMIENNEJ NAZWA_ZMIENNEJ;

W języku C++ nazwy zmiennych mogą być wybierane zgodnie z określonymi regułami dotyczącymi nazewnictwa. Możemy wymienić następujące ogólne zasady oraz cechy nazewnictwa zmiennych w  języku C++:

  • nazwa zmiennej może składać się z liter, cyfr i podkreślenia _,
  • nazwa zmiennej nie może zaczynać się od cyfry oraz zawierać spacji,
  • nie można używać znaków specjalnych, które mają znaczenie dla kompilatora i są używane do różnych celów, takich jak operatory arytmetyczne (+,-,*,/,% ), logiczne (==, !=, <,>,<=,>=,&,|,&&,||,!), przypisania (=, +=, -=,-=,*=,%=), znaki specjalne (;, :, ., , , (), {}, []) czy też określenie struktur oraz składni,
  • nazwy zmiennych nie mogą zawierać znaków spoza standardowego zestawu znaków ASCI, a więc nie mogą też zawierać polskich znaków,
  • rozróżniane są wielkości liter więc mojaZmienna, MojaZmienna i MOJAZMIENNA są traktowane jako różne nazwy,\
  • w teorii nie ma ograniczeń co do długości nazwy zmiennej, ale zaleca się używanie sensownych i zrozumiałych nazw, które ułatwiają czytanie i zrozumienie kodu.
Znamy już podstawowe zasady nazewnictwa zmiennych. Możemy także zaobserwować, że na końcu definicji, czy deklaracji zmiennej występuje znak średnika. 
W języku C++ każda instrukcja kończy się znakiem średnika ;
W języku C++ możemy także posługiwać się stałymi (ang. constants).  Są to wyrażeniami, których wartość nie może zostać zmieniona po zainicjalizowaniu. Stałe są używane do reprezentowania danych, których wartość nie powinna być zmieniana, na przykład wartości matematyczne, stałe fizyczne, identyfikatory, itp. 

Stałe w programie występują w dwu postaciach:

1. Jako wartości wpisane explicite wszędzie tam, gdzie są wykorzystywane (przykładowo w zapisie y = 2*x stałą jest liczba 2).
2. Jako wartości oznaczone nazwami i wcześniej zdefiniowane (przykładowo w zapisie y = A*x nazwa A może być stałą, o ile tylko wcześniej poinformowaliśmy o tym kompilator).

Stałe drugiego typu muszą zostać zdefiniowane w aplikacji przy pomocy słowa kluczowego const.
const double G = 9.807; // definicja stałej będącej liczbą rzeczywistą
const string AUTOR = "Mikołaj Srebrempisany"; // definicja stałej będącej napisem

2.1. Typ logiczny

W języku C++ typ logiczny jest reprezentowany przez typ danych bool. Typ został wprowadzony w standardzie C++98. Typ wbudowany bool może przyjmować jedną z dwóch wartości: true lub false. Typ ten jest wykorzystywany głównie do wyrażania warunków logicznych w instrukcjach sterujących.

Przykład deklarowania zmiennej logicznej:

bool  zmienna_logiczna;

Zamiast wartości logicznych do zmiennej logicznej można przypisać wartość innego typu. Przypisanie wartości 0 lub NULL oznacza logiczny fałsz (ang. false) oraz przypisanie innej wartości niezerowej oznacza prawdę (ang. true).

2.2. Typy znakowe

Typ znakowy char w języku C++ służy do przechowywania pojedynczych znaków. Może być używany do reprezentowania znaków drukowalnych, cyfr, znaków specjalnych i innych znaków w kodzie ASCII lub innym kodowaniu znaków. Typ char umożliwia wykonywanie różnych operacji na znakach oraz jest często wykorzystywany w łańcuchach znakowych i operacjach wejścia/wyjścia. 

W kodzie ASCII (American Standard Code for Information Interchange), każdy znak jest reprezentowany przez odpowiadającą mu wartość numeryczną. 

Na przykład, litera 'A' jest reprezentowana przez wartość 65. Zmienna typu char zajmuje więc dokładnie jeden bajt w pamięci. Kod ASCII zawiera znaki drukowalne oraz znaki specjalne. System ASCII był pierwszym szeroko rozpowszechnionym standardem kodowania. Pojawił się w 1960 za sprawą American National Standards Institute (ANSI). Tablica znaków ASCII to tabela składająca się z 128 znaków (7-bitowy zapis). Poniżej przedstawiona jest tablica ASCI z wartościami kodowanego znaku oraz odpowiadającym wartością w systemie dziesiętnym.

Kod dziesiętny

Znak

Kod dziesiętny

Znak

Kod dziesiętny

Znak

Kod dziesiętny

Znak

0

NUL

32

Space

64

@

96

`

1

SOH

33

!

65

A

97

a

2

STX

34

66

B

98

b

3

ETX

35

#

67

C

99

c

4

EOT

36

$

68

D

100

d

5

ENQ

37

%

69

E

101

e

6

ACK

38

&

70

F

102

f

7

BEL

39

71

G

103

g

8

BS

40

(

72

H

104

h

9

TAB

41

)

73

I

105

i

10

LF

42

*

74

J

106

j

11

VT

43

+

75

K

107

k

12

FF

44

,

76

L

108

l

13

CR

45

77

M

109

m

14

SO

46

.

78

N

110

n

15

SI

47

/

79

O

111

o

16

DLE

48

0

80

P

112

p

17

DC1

49

1

81

Q

113

q

18

DC2

50

2

82

R

114

r

19

DC3

51

3

83

S

115

s

20

DC4

52

4

84

T

116

t

21

NAK

53

5

85

U

117

u

22

SYN

54

6

86

V

118

v

23

ETB

55

7

87

W

119

w

24

CAN

56

8

88

X

120

x

25

EM

57

9

89

Y

121

y

26

SUB

58

:

90

Z

122

z

27

ESC

59

;

91

[

123

{

28

FS

60

<

92

\

124

|

29

GS

61

=

93

]

125

}

30

RS

62

>

94

^

126

~

31

US

63

?

95

_

127

DEL

Wartości kodów ASCII są zazwyczaj wyrażane w systemie dziesiętnym, ale mogą być również przedstawiane w systemie szesnastkowym lub ósemkowym.

Przykład deklaracji zmiennej przechowującej pojedyncze znaki:

char znak;

Pojedynczy znak w kodzie programu można zapisać w apostrofach np. 'A', '!', '9' lub w postaci liczbowej stosując odpowiadającą danemu znakowi liczbę z tablicy ASCII. 
Szybko po wprowadzeniu tablicy ASCII do kodowania znaków okazało się, że niemożliwe jest uwzględnienie wszyskich istniejących znaków. Niemożliwe jest kodowanie np. polskich znaków dialektycznych czy znaków języka chińskiego. W celu udoskonalenia tablica ASCII została rozszerzona do 256 znaków, a więc wprowadzono zapis 8 bitowy. W dlaszym ciągu nie było możliwe uwzględnienie wszystkich znaków specjalnych. Microsoft zaproponował osobne wersje tabel ze znakami dla różnych grup językowych zwane stronami kodowymi (ang. ANSI code page).
Przykłady stron kodowych:
  • ISO 8859-1 (Latin-1): Strona kodowa używana w wielu językach zachodnioeuropejskich, obejmująca znaki diakrytyczne takie jak é, ñ i ü,
  • Windows-1252: Rozszerzenie ISO 8859-1, używane w systemach operacyjnych Windows, zawierające dodatkowe znaki takie jak znaki cudzysłowu typograficznego,
  • UTF-8: Uniwersalna strona kodowa, która jest wstecznie kompatybilna z ASCII i może reprezentować każdy znak Unicode. UTF-8 jest powszechnie używana w Internecie.
Różne strony kodowe mogą powodować problemy z kompatybilnością. Tekst zapisany w jednej stronie kodowej może być nieprawidłowo interpretowany w innej stronie kodowej, prowadząc do nieczytelnych znaków lub "krzaczków". Unicode jest standardem, który został opracowany, aby rozwiązać problemy z różnorodnymi stronami kodowymi. Unicode przypisuje unikalny kod każdemu znakowi z każdego języka, co pozwala na reprezentowanie tekstu w różnych językach za pomocą jednolitego systemu kodowania. UTF-8, UTF-16 i UTF-32 to popularne sposoby kodowania znaków Unicode.

2.3. Typy całkowite

Typ całkowity w języku C++ to kategoria typów danych przeznaczonych do przechowywania liczb całkowitych. W C++ dostępnych jest kilka różnych typów całkowitych, różniących się rozmiarem i zakresem wartości, które mogą przechowywać. Możemy wyróżnić następujące typy całkowite:

  • Typ int to podstawowy typ całkowity w C++. We współczesnych systemach typ ten jest zazwyczaj 32-bitowy. Można przechowywać wartości w zakresie od -2,147,483,648 do 2,147,483,647. 
Przykład definicji zmiennej typu int:

int liczba_calkowita;
  • Typ short jest mniejszym typem całkowitym. Przeważnie rozmiar jest 16-bitowy. Może przechowywać wartości w zakresie od -32,768 do 32,767.
Przykład definicji zmiennej typu int:

short liczba_calkowita;
  • Typ long jest większym typem całkowitym. Zazwyczaj jest 32-bitowy na 32-bitowych systemach, ale na 64-bitowych systemach może być 64-bitowy. Dla 32-bitowego systemu zakres wynosi od -2,147,483,648 do 2,147,483,647.
Przykład definicji zmiennej typu long:

long liczba_calkowita;
  • Typ long long jest jeszcze większym typem całkowitym. Jest zazwyczaj 64-bitowy, co oznacza, że może przechowywać wartości w zakresie od -9,223,372,036,854,775,808 do 9,223,372,036,854,775,807.
Przykład definicji zmiennej typu long long:

long long liczba_calkowita;
Wszystkie podane typy zmiennych były zmiennymi ze znakiem. Co to oznacza ze znakiem? Zmienne "ze znakiem" w kontekście typów całkowitych w języku C++ oznaczają, że mogą one przechowywać zarówno liczby dodatnie, jak i ujemne. To rozróżnienie jest ważne, ponieważ pozwala na reprezentowanie pełnego zakresu liczb całkowitych, w tym wartości ujemnych, co jest niezbędne w wielu aplikacjach obliczeniowych.

Zmienne ze znakiem i bez znaku różnią się sposobem zapisu w pamięci komputera. Zmienne ze znakiem używają metody dwójkowego uzupełnienia do reprezentowania liczb ujemnych, co pozwala na reprezentowanie zarówno liczb dodatnich, jak i ujemnych. Zmienne bez znaku przechowują tylko liczby dodatnie, co pozwala na większy zakres dodatnich wartości liczbowych dla tej samej liczby bitów.

W przykładzie poniżej przedsatwiono na przykładzie 4 bitowego zapisu różnice pomiędzy zmiennymi całkowitymi ze znakiem oraz bez znaku.



W C++ typy całkowite mogą być modyfikowane za pomocą specyfikatorów signed i unsigned, aby wyraźnie określić, czy mają przechowywać wartości ujemne i dodatnie, czy tylko dodatnie. Na przykład:

  • signed int jest równoznaczny z int.
  • unsigned int przechowuje tylko liczby dodatnie.
W języku C++ mamy dostępne następujące typy zmiennych bez znaku:
  • Typ unsigned int przechowuje tylko liczby nieujemne. Dla 32-bitowego systemu zakres wynosi od 0 do 4,294,967,295.
Przykład definicji zmiennej typu unsigned int:

unisgned int liczba_calkowita;
  • Typ unsigned short przechowuje liczby nieujemne i zazwyczaj jest 16-bitowy. Zakres wynosi od 0 do 65,535.
Przykład definicji zmiennej typu unsigned short:

unsigned short liczba_calkowita;
  • Typ unsigned long zazwyczaj jest 32-bitowy, a zakres wynosi od 0 do 4,294,967,295.
Przykład definicji zmiennej typu unisgned long:

unsigned long liczba_calkowita;
  • Typ unsigned long long jest zazwyczaj 64-bitowy i może przechowywać wartości w zakresie od 0 do 18,446,744,073,709,551,615.
Przykład definicji zmiennej typu long:

unsigned long long liczba_calkowita;

2.4. Typy zmiennoprzecinkowe

Typ zmiennoprzecinkowy w języku C++ służy do zapisywania liczb rzeczywistych. Liczby rzeczywiste odgrywają istotną rolę w obliczeniach naukowych, inżynierskich oraz w wielu aplikacjach, w których kluczowym aspektem jest dokładność. W tym przypadku dokładność zapewniana przez liczby całkowite nie jest wystarczająca.  W języku C++ dostępne są trzy główne typy zmiennoprzecinkowe: float, double oraz long double. Każdy z wymienionych typów zmiennych różni się precyzją i zakresem wartości, które może przechowywać.

Poza standardowym sposobem zapisu liczb tzn. 10, 200.46 możemy zapisywać liczby używając notacji naukowej. Notacja naukowa jest sposobem reprezentowania bardzo dużych lub bardzo małych liczb w zwięzły sposób. Jest szczególnie użyteczna w naukach ścisłych i inżynierii, gdzie liczby mogą mieć wiele cyfr znaczących i szeroki zakres wartości. 

Możemy zapisać liczbę  0.000001234 jako  1.234 \cdot 10^{-6} .

W notacji naukowej wartość liczby obliczamy zgodnie ze wzorem:

 L = m \cdot p^{c}

gdzie:

 m - mantysa,

 p - podstawa,

 c - cecha.

Przy pomocy wzoru możliwe jest wyznaczenie wartość liczby zmiennoprzecinkowej zapisanej w dowolnym systemie pozycyjnym, a nie tylko dziesiętnym. W przypadku dziesiętnego systemu  p = 10 .

Liczby wymierne i niewymierne wchodzą w skład zbioru liczb rzeczywistych, a ich reprezentacja za pomocą notacji naukowej (formy wykładniczej) stanowi oficjalną definicję liczb zmiennoprzecinkowych. W tym momencie warto zastanowić się w jaki sposób liczby zmiennoprzecinkowe są przechowywane w pamieci komputera. Oczywiście zapis jest binarny. W łatwy sposób można zapisać część całkowitą, ale w jaki sposób zapisać część dziesiętną? Najlepiej będzie zaprezentować to na przykładzie. 

Mamy liczbę zmiennoprzecinkową 5.125. Część całkowitą możemy zapisać jako 101
Kolejnym krokiem jest przedstawienie w postaci binarnej liczby 0.125. W tym celu należy wykonać działania przedsatwione w tabeli, które polegają na kolejnym mnożeniu liczb przez 2 oraz zapisie części całkowitej w postaci binarnej.


Liczba wejściowa (ułamkowa) Wynik mnożenia przez 2 Wartość całkowita wyniku
0.125 2*0.125 = 0.250 0
0.250 2*0.250 = 0.500 0
0.500 2*0.500 = 1.000 1

Proces mnożenia kończy się gdy część ułamkowa równa się 0. 
Liczbę zmiennoprzecinkową możemy zapisać jako 101.001.

Należy zwrócić uwagę, że istnieją przypadki w których proces mnożenia może nigdy się nie kończyć. Mamy wtedy do czynienia z liczbami nieskończonymi. W przypadku liczby nieskończonej otrzymujemy pewną powtarzającą się sekwencję binarnych wartości. Mówimy wtedy, że wartość jest w okresie. Możemy to zaobserwować na przykładzie liczby 0.33.

Przedstawienie w postaci binarnej liczby 0.33 wymaga wykonać działania przedsatwione w tabeli, które polegają na kolejnym mnożeniu liczb przez 2 oraz zapisie części całkowitej w postaci binarnej.

Liczba wejściowa (ułamkowa) Wynik mnożenia przez 2 Wartość całkowita wyniku
0.33 2*0.33 = 0.66 0
0.66 2*0.66 = 1.32 1
0.32 2*0.32 = 0.64 0
0.64 2*0.64 = 1.28 1
0.28 2*0.28 = 0.56 0
0.56 2*0.56 = 1.12 1
0.12 2*0.12 = 0.24 0
0.24 2*0.24 = 0.48 0
0.48 2*0.48 = 0.96 0
0.96 2*0.96 = 1.92 1
0.92 2*0.92 = 1.84 1

Zbierając wszystkie części całkowite uzyskane w kolejnych krokach, otrzymujemy: 0.33≈0.0101011100001111…

Reprezentacja binarna liczby  0.33 jest nieskończona i okresowa. W praktyce, można ją przybliżyć do określonej liczby miejsc po przecinku, ale zawsze będzie to tylko przybliżenie. Na przykład:

0.33≈0.0101012 (przybliżenie do 6 miejsc po przecinku)

W celu ujednolicenia sposobu zapisu liczb zmiennoprzecinkowych został wprowadzony standard IEEE754. Standard IEEE 754 to zestaw specyfikacji definiujących sposób reprezentacji oraz operacji na liczbach zmiennoprzecinkowych w komputerach. Opracowany przez Institute of Electrical and Electronics Engineers (IEEE), standard ten jest powszechnie używany we współczesnych systemach komputerowych i językach programowania, zapewniając jednolitą metodę manipulacji liczbami zmiennoprzecinkowymi, co umożliwia przenośność kodu między różnymi platformami.

Liczby zmiennoprzecinkowe w standardzie IEEE 754 są reprezentowane w postaci znormalizowanej za pomocą trzech głównych części:

  1. Znak (1 bit): Określa, czy liczba jest dodatnia (0) czy ujemna (1).
  2. Cecha (eksponenta): Przechowuje przesuniętą wartość wykładnika. Wartość ta jest zapisywana w postaci przesuniętej o wartość zwaną biasem.
  3. Mantysa (część ułamkowa): Przechowuje znaczące cyfry liczby.

W języku C++ możemy więc wyróżnić następujące typy liczb zmiennoprzecinkowych:

  • Typ float to pojedyncza precyzja zmiennoprzecinkowa. Zwykle jest przechowywana na 32 bitach (4 bajty). Zakres liczb wynosi od 1.2E-38 do 3.4E+38, natomiast precyzja jest do 7 cyfr znaczących.
Przykład definicji zmiennej typu float:

float zmienna_rzeczywista;
  • Typ double to podwójna precyzja zmiennoprzecinkowa. Zwykle jest przechowywana na 64 bitach (8 bajtów). Zakres liczb wynosi od 2.2E-308 do 1.8E+308, natomiast precyzja jest do 15 cyfr znaczących.
Przykład definicji zmiennej typu double:

double zmienna_rzeczywista;
  • Typ long double może oferować jeszcze większą precyzję. Jego rozmiar zależy od implementacji, ale zazwyczaj jest przechowywany na co najmniej 80 bitach (10 bajtów) lub więcej. 
Przykład definicji zmiennej typu long double:

long double zmienna_rzeczywista;

2.5. Typy void

Typ void w języku C++ jest specjalnym typem danych, który reprezentuje "brak wartości". Może być używany w różnych kontekstach. Typ void* może być używany jako wskaźnik ogólny, który może przechowywać adres dowolnego typu danych. Jednakże, wskaźnik ten nie zawiera informacji o typie danych, na który wskazuje. Zazwyczaj wymaga to rzutowania na odpowiedni typ przed użyciem. Do czego taki typ danych może służyć wrócimy przy omawianiu wskaźników w dalszej części podręcznika.

3. Typ napisowy

W języku C++ nie ma wbudowanego standardowego typu do przechowywania napisów (czyli zmiennych łańcuchowych). W praktyce często istnieje potrzeba operowania na tego typu danych. W języku C do operowania na łańcuchach znaków wykorzystywane były tablice. W języku C++ wykorzystywana jest w tym celu biblioteka string. Zmienna typu string jest często używana do przechowywania i manipulowania tekstami, takimi jak nazwy, zdania, adresy email czy informacje pobrane ze strumieni wejścia. Aby korzystać ze zmiennych typu string, należy dodać nagłówek <string> do kodu źródłowego programu. 

Umowny typ string w języku C++ (ale nie w C) określa napis (inaczej: łańcuch, tekst), czyli dowolny ciąg znaków ujęty w cudzysłowy.
Zmienna typu string oferuje wiele przydatnych funkcji i metod do manipulacji tekstem, takich jak dodawanie, usuwanie, porównywanie, przeszukiwanie itp. Jest to potężne narzędzie przy tworzeniu aplikacji, które wymagają pracy z tekstem. 
Przykład definicji zmiennej string:

string napis; 
Przy pomocy zmiennej typu string możliwe jest wykonywanie podstawowych operacje na łańcuch znaków. Do poszczególnych znaków odwołujemy się za pomocą indeksów. Analogicznie jak do tablicy znaków. Tablice zostaną omówione w dalszej części podręcznika. Dodatkowo po kropce umieszczonej za nazwą zmiennej mamy dostęp do szeregu operacji, które możemy wykonać. Możemy np. pobrać rozmiar zmiennej, a więc liczbę znaków. Do tego służy operacja size() , która zwraca liczbę całkowitą będącą liczbą znaków. 
Jeżeli więc np.  zdefiniujemy  imie  jako zmienną typu string, a następnie wczytamy ten napis, to imie.size() oznacza długość (Iiczbę znaków) wczytanego napisu.
Dodatkowo możliwe jest dodawanie zmiennych typu string.
Wynikiem wyrażenia "zadanie1" + ".cpp" jest napis "zadanie1.cpp".
Inną operacją, którą będziemy wykorzystywać jest porównywanie zmiennych  typu string.
Ze względu na uporządkowanie znaków w kodzie ASCII obowiązuje reguła, którą najłatwiej podać na przykładzie: "Jan" < "Janina" oraz "Jan" < "jan". To pozwala na sortowanie napisów w taki sam sposób, jak to się robi z liczbami - przynajmniej dla języka angielskiego.

4. Typ wyliczeniowy

Typ wyliczeniowy w języku C++ (ang. enumeration, enum) to  typ danych, który umożliwia definiowanie zmiennych, które mogą przyjmować jedną z wcześniej zdefiniowanych wartości. Wartości te są reprezentowane przez identyfikatory. 

Typ wyliczeniowy definiuje się za pomocą słowa kluczowego enum. Składnia wygląda następująco:

enum DniTygodnia {
    Poniedzialek,
    Wtorek,
    Sroda,
    Czwartek,
    Piatek,
    Sobota,
    Niedziela
};

Domyślnie wartości wyliczeniowe są reprezentowane jako kolejne liczby całkowite zaczynając od zera. Można jednak przypisać im konkretne wartości:

enum DniTygodnia {
    Poniedzialek=1,
    Wtorek,
    Sroda,
    Czwartek,
    Piatek,
    Sobota,
    Niedziela
};
W tym przypadku Poniedzialek ma wartość 1, Wtorek 2, Sroda 3, itd. 

5. Instrukcje czytaj i pisz

W języku C++, operacje wejścia/wyjścia (I/O) są kluczowym elementem programowania, pozwalającym na interakcję z użytkownikiem i systemem plików. Do obsługi operacji wejścia/wyjścia wykorzystywana jest biblioteka <iostream>. Do operacji wypisywania na ekranie oraz odczytywania danych od użytkownika wykorzystujemy strumienie 'cout' (wyjścia) oraz 'cin' (wejścia). 

Przy pomocy strumienia 'cout' możemy wyświetlić na ekranie komunikat lub wartość zmiennej.

cout << "Przykladowy napis";
cout << "Inny przyklad" << endl;

W przedstawionym przykładzie poleceniem 'cout' wypisywany jest na ekranie przykładowy napis. Jest także możliwość kontynuowania strumieniowego wyświetlania danych na ekranie przez kolejne wykorzystanie '<<'. W przykładzie pokazano także w jaki sposób wymusić przejście do nowej linii za pomocą 'endl'. Możliwe jest także przejście do nowej linii za pomocą polecenia '\n' umieszczonego bezpośrednio w tekście:

cout << "Przykladowy napis \n";
Możliwe jest także wstawienie innych znaków specjalnych np. znak tabulacji '\t'. 

Oprócz wypisywania na ekranie stałych napisów można także wyświetlać wartości zmiennych. W przypadku podania w strumieniu nazwy zmiennej, na ekranie zostanie wyświetlona jej wartość,  a nie jej nazwa. Na przykładzie poniżej przedstawiono sposób wyświetlania wartości zmiennych na ekranie.
int x,y;
x = 1;
y = 4;
cout << "Wartosc x: " << x << endl;

cout << "Wartosc y: ";
cout << y << endl;

cout << "Wartosc sumy x+y: "; << x + y << endl;
Możliwe jest wyświetlanie wartości zmiennych lub wyników działań na zmiennych. Oprócz wyświetlania na ekranie informacji oraz wyników działania aplikacji bardzo często istnieje także konieczność pobierania danych od użytkowników. Do tego celu możemy wykorzystać polecenie 'cin'. Instrukcję możemy wykorzystać do wczytania wartości jednej lub wielu zmiennych dowolnego typu z poziomu klawiatury lub pliku. Przykład poniżej demonstruje odczyt przykładowych danych różnych typów z klawiatury.

string napis;
cout << "Podaj przykladowy napis." << x << endl;
cin >> napis;

int x,y;
cout >> "Podaj wartosc x." << x << endl;
cin >> x >> y;
Wczytywanie danych przez użytkownika poprzedzone jest wyświetleniem prośby o wczytanie danych. W przykładowym kodzie podano także przykład wczytywania wielu zmiennych przy wykorzystaniu jednego polecenia 'cin'. W taki sposób możliwe jest wczytywanie zmiennych różnych typów.

W trakcie wczytywania danych z klawiatury oraz pliku nie jest sprawdzana poprawność wprowadzanych typów zmiennych. Programista musi o to zadbać, aby typy były zgodne lub odpowiednio obsłużyć oraz zasygnalizować błędy.
W przedstawiony sposób możliwe jest wczytywanie jedynie tekstów niezawierających spacji. Aby wczytać tekst zawierający spacje należy wykorzystać funkcję 'getline'. Dzieje się tak dlatego, że odczyt danych ze strumienia w C++ odbywa się w sposób niezachłanny - czyli zawsze czytane jest najmniej znaków jak można. A ponieważ w przypadku słów separatorem jest tzw. biały znak (czyli znak spacji, tabulacji, nowego wiersza itp.), to odczyt kończy się po napotkaniu pierwszego separatora.

Funkcja posiada dwa parametry:
  • polecenie np. cin
  • zmienna typu string.
Wywołanie funkcji przedstawione jest w przykładzie poniżej.
string dane;
cout << "Podaj imie i nazwisko i wcisnij Enter\n ";
getline (cin, dane);

cout << " Witaj, " << dane << endl; // Wydrukuje się imie i nazwisko
Funkcja jest także wykorzystywana w przypadku wczytywania danych z pliku. Takie wykorzystanie funkcji będzie zademonstrowane w kolejnych rozdziałach, które będą dotyczyć obsługi plików tekstowych.

6. Instrukcje warunkowe

W celu implementacji bardziej rozbudowanej aplikacji, oprócz wypisywania na ekranie oraz pobierania danych od użytkownika, konieczne jest podejmowanie różnych decyzji w aplikacji. Decyzje prowadzą do selektywnego wywoływania operacji w zależności od spełnienia lub nie określonego warunku. 
Ogólna postać instrukcji warunkowej jest następująca:

if (warunek) 
     instrukcja_1;    // wykonywana, jeśli warunek jest spełniony
 else 
     instrukcja_2;  // wykonywana w przeciwnym przypadku
Zdefiniowana instrukcja warunkowa oznacza, że w przypadku gdy warunek zostanie spełniony powinna być wykonana instrukcja_1. Jeżeli warunek nie zostanie spełniony zostanie wykonana instrukcja_2.

Poniżej przedstawiono prostą symulację działania instrukcji warunkowej, która umożliwia sprawdzenie działania operacji warunkowej dla wybranej wartości całkowitej. Po prawej stronie wyświetlane jest wyjście (wynik działania aplikacji).
Wybierz wartość zmiennej x i zobacz, jak działa warunek if w C++ z animacją.
int x = ...;  // Aktualna wartość: ...
if (x > 0) {
    cout << "x jest większe od 0" << endl;
} else {
    cout << "x nie jest większe od 0" << endl;
}
Instrukcja else nie jest obligatoryjna możliwe jest zastosowanie skróconej wersji instrukcji warunkowej if.
Skrócona wersja instrukcji warunkowej ma postać:

if (warunek) 
     instrukcja_1    // wykonywana, jeśli warunek jest spełniony
Każda z instrukcji występujących w konstrukcji if musi być jedną instrukcją (chociaż taka jedna instrukcja może być bardzo długą i skomplikowaną instrukcją strukturalną).
Przedstawiona uwaga oznacza, że do instrukcji warunkowej if możemy przypisać tylko jedną instrukcję, która znajduje się bezpośrednio pod nią. To samo dotyczy instrukcji else. Możemy to zobrazować za pomoca kodu umieszczonego poniżej.
if (a>0) cout << a;
else
    cout << "zmieniamy znak";
    cout << -a;
W przedstawionym przykładzie do instrukcji if należy cout << a; natomiast do instrukcji else należy cout << "zmieniamy znak";. Instrukcja cout << -a; nie należy do instrukcji else. Jest więc niezależna od warunku (a>0) i wykona się zawsze.

Jeżeli chcemy wywołać więcej instrukcji niż jedną należy zastosować instrukcję blokową albo instrukcję złożoną. Instrukcja oznacza wykorzystanie nawiasów klamrowych {}, pomiędzy którymi możemy umieścić wiele instrukcji. Instrukcja blokowa jest traktowana jak jedna instrukcja, więc możemy ją przypisać do instrukcji warunkowej if lub else. Możemy więc tworzyć instrukcje warunkowe typu:
if (warunek)
{
   ...
   ...
}
    
lub
if (warunek)
{
    ...
} else {
    ...
}
    
Instrukcja blokowa może być dowolnie zagnieżdżana.
Każda z instrukcji należących do instrukcji if także może być instrukcją if. Wtedy instrukcję taką nazywamy zagnieżdżoną.
Poniżej przedstawiony jest przykład zagnieżdżonej instrukcji if.
if (...)
   ...
else if (...)
    ...
else
    ...
Przyjmuje się, że else zawsze należy wiązać z najbliższym mu wstecz if-em. Możemy więc zapisać:

if (...)
    if (...)
       ...
    else
       ...
    
Jeżeli chcielibyśmy, aby else w tej konstrukcji należał do pierwszego if-a, musimy zastosować instrukcję blokową:
if (...)
{
    if (...)
        ...
} else
    ...
Oczywiście nie jest konieczne stosowanie zawsze instrukcji blokowej. Należy jednak pamiętać, że bez stosowania nawiasów klamrowych tylko jedna instrukcja znajdująca się za instrukcją if lub else zostanie do niej przypisana. Jeżeli chcemy mieć większą kontrolę nad wywołaniem kodu możemy stosować nawiasy klamrowe. Zwłaszcza w wypadku gdy nie jesteśmy pewni w jaki sposób zostanie zinterpretowany kod.