2. Typy danych

2.1. Wprowadzenie

Zanim przejdziemy do dokładnego opisywania typów pora na jeszcze jedną uwagę, która& nie jest oczywista na pierwszy rzut oka, oraz jest cechą charakterystyczną C / C++, lecz już niekoniecznie w przypadku innych języków programowania.

W C++ prawie wszystko jest wyrażeniem które zwraca jakąś wartość. 
Nie to końca jest to zawsze zrozumiałe na pierwszy rzut oka. O ile dla prawie każdego jest jasne, że wykonanie operacji dodawania x+y+15 zwraca wynik, podobnie jak wywołanie funkcji sin(x), o tyle nie wszyscy od razu wiedzą, że również wykonanie przypisania z = x zwraca wartość, podobnie jak przykładowo . . . drukowanie tekstu na ekranie cout << ”witaj”; . . . i wiele, wiele innych. 
W C++ nie istnieje możliwość zdefiniowania  podprogramu czy operatora który nie zwraca jakiejkowiek wartości, natomiast możliwe są dwie sytuacje:

  • zwracana wartość może być tzw. wartością która oznacza brak wartości ( void),
  • zwracaną wartość można pominąć (nie „przechwycić” jej) – co jest niemożliwe w wielu innych językach. Dlatego poprawne jest napisanie po prostu instrukcji 2+2; (pomijając kompletny brak jej sensu).
Skoro wiadomo, że prawie wszystko w C++ ma wartość, i wartość ta może być odrzucona, to co się dzieje z wartościami które nie mają być są odrzucone?

Zmienne, stałe i L-wartości

To, co jest charakterystyczne zarówno dla zmiennych, jak i stałych występujących w C++, to wspomniana na początku silna kontrola typów. W praktyce oznacza to, że zmienna czy stała musi mieć raz przypisany w momencie definicji, i potem do końca jej czasu życia niezmienny – typ danych które może przechowywać. Typ jest niezmienny – lecz od tego czy może się zmieniać wartość, czy nie – zależy czy mamy do czynienia ze zmienną czy ze stałą. W składni C++ to rozróżnienie jest zaznaczone, lecz często nie do końca poważnie brane przez kompilator.

Ogólnie stałe definiuje się i deklaruje tak jak zmienne – jedyną różnicą jest dodanie przedrostka const przed wyspecyfikowaniem typu. Podanie const w założeniu uniemożliwia zmianę wartości tak oznaczonej zmiennej w zasięgu jej widoczności bez jawnego rzutowania const cast. No właśnie . . . bez jawnego rzutowana . . . to co to za stała którą można zmienić w zmienną? Dlatego wspomniałem, że const nie jest brany poważnie przez kompilator. Jeśli włączona jest optymalizacja, kompilator sprawdza, czy istnieje w programie choćby teoretyczna możliwość zmiany wartości oznaczonej jako stała – i jeśli tak, to ignoruje przyrostek const tworząc zamiast tego zmienną, której wartości nie można prosto zmienić. W przeciwnym wypadku często wartości stałych są bezpośrednio rozwijane w kodzie programu, i nie jest im w ogóle przydzielana pamięć z obszaru pamięci danych.

Z pojęciami zmiennej i stałej ściśle związane jest pojęcie L-wartości. Ogólnie można przyjąć, że:

L-wartością możemy nazwać wszystko co może stać po lewej stronie przypisania – czemu można przypisać wartość.

W praktyce L-wartościami najczęściej są zmienne bez modyfikatora const, ale także – np. parametry funkcji z tym modyfikatorem. Od powyższej definicji jest wyjątek – w przypadku definiowania stałej połączonego z jej inicjacją, stała stoi po lewej stronie równania – lecz nie jest ona L-wartością . . . wartość jest wyliczana na etapie kompilacji, a operator przypisania jest wtedy traktowany jako operator inicjacji. Dlatego też w nowych wersjach standardu języka raczej nastawiamy się na inicjację przy stosowaniu nawiasów klamrowych - a nie przy wykorzystaniu składni ze znakiem przypisania. 

Na razie parametrami funkcji i innymi skomplikowanymi zagadnieniami nie zajmujmy się, natomiast sama definicja L-wartości mówi nam, dlaczego poprawny jest poniższy kod:


double r = 12.3;
double x; 
x = 2.0*M_PI*r;

x jest zmienną – więc jest L-wartością, natomiast niepoprawny jest ten kod:


double x;
2.0*M_PI*r = x;

Wartość wyrażenia 2*M PI*r nie jest L-wartością.

Ogólny podział typów danych

W pierwszym przybliżeniu wszelkie dostępne typy danych w języku C++ można podzielić na następujące grupy:

  • Typy podstawowe. Wśród nich wyróżniamy typy ściśle powiązane z architekturą sprzętową komputera (logiczne, znakowe, całkowite, zmiennoprzecinkowe), typ wyliczeniowy i typ oznaczający brak wartości
  • Typy pochodne dla typów podstawowych, czyli funkcje, wskaźniki i referencje
  • Typy złożone, czyli tablice, struktury i klasy.

Typy - zależnośic

Wspomniany podział nie jest jedynym możliwym, wśród wspomnianych typów można na przykład wydzielić typy przeliczalne:

Typem przeliczalnym (porządkowym) nazywamy każdy typ, który charakteryzuje się skończonym, uporządkowanym zbiorem wartości, w którym można wyznaczyć wartość największą i najmniejszą oraz dla którego dla każdej wartości z wyjątkiem wartości brzegowych możemy jawnie wskazać wartość następną i poprzednią.

Do typów przeliczalnych zaliczamy typ logiczny, znakowy, całkowitoliczbowy i wyliczeniowy. Wyróżnienie typów przeliczalnych jest istotne z tego względu, że w niektórych instrukcjach (np. switch) zmienna sterująca musi być przeliczalna. Istnieje także wydzielona grupa typów arytmetycznych:

Typem arytmetycznym nazwiemy każdy typ, dla którego da się w sposób naturalny stosować operatory arytmetyczne. Tutaj zaliczymy wszystkie typy liczbowe (logiczny, całkowitoliczbowy i zmiennoprzecinkowy) oraz ... typ znakowy czy wskaźniki.