Tak jak w Javie czy Pascalu, obowiązuje w C/C++ zasada ścisłej kontroli typu. Dla wszystkich występujących w programie zmiennych ich typ musi być określony (zadeklarowany) zanim mogą być użyte. Samą zmienną możemy uważać za nazwane miejsce w pamięci, w którym przechowywana jest wartość o znanym kompilatorowi typie. Kompilator potrzebuje tej informacji, aby zarezerwować na zmienną odpowiednią ilość pamięci i aby wiedzieć, jak interpretować różne operacje na tej zmiennej.
Zasady poprawności identyfikatorów (nazw) zmiennych są w C/C++ podobne jak w innych językach: identyfikatory mogą składać się z liter, cyfr i znaku podkreślenia; nie mogą rozpoczynać się cyfrą (w odróżnieniu od niektórych innych języków, znaki waluty nie są w identyfikatorach dozwolone).
Tak więc nazwy (identyfikatory) A_book i a_book będą traktowane jako różne.
Wbudowane typy podstawowe są podobne do tych, jakie być może Czytelnik zna z Javy. Występują jednak pewne różnice. W szczególności, nie jest gwarantowana stała długość (w bajtach) reprezentacji maszynowej zmiennych poszczególnych typów. Na przykład, zmienne typu int mogą mieć rozmiar, w zależności od implementacji, 2, 4 lub 8 bajtów (obecnie jednak prawie zawsze są to cztery bajty). W związku z tym istnieje przydatny, wbudowany operator sizeof, który zwraca długość reprezentacji binarnej zmiennej danego typu na danej platformie (w bajtach). Użyć tego operatora możemy tak jak funkcji, której jedynym argumentem jest albo dowolna zmienna typu, dla którego chcemy poznać długość reprezentacji, albo nazwa samego typu (w pierwszym z tych przypadków nawias jest opcjonalny). Podanie nazwy typu wystarczy, gdyż
Jak powiedzieliśmy, wszystkie zmienne — nazwane
obszary pamięci o określonym adresie i typie — muszą być przed
pierwszym użyciem zadeklarowane i zdefiniowane. Jak to zrobić, pokażmy
na przykładzie zmiennych typu
int:
1. #include <iostream> 2. using namespace std; 3. 4. int main() { 5. int k1; 6. int k2(1); 7. int k3{}; 8. int k4{1}; 9. int n=1, m = n, i{1}, j{i}; 10. }
Jak widać, podajemy najpierw nazwę typu, a potem nazwę definiowanej zmiennej. Nie musimy, ale raczej powinniśmy, nadać nowowprowadzonej zmiennej jakąś sensowną wartość. Robimy to poprzez zainicjowanie zmiennej od razu w miejscu definicji. Na przykład w powyższym programie:
int n = 1; int m = n; int i{1}; int j{i};z czego widać, że, na przykład, definiując m możemy traktować n jako już istniejącą zmienną. Pierwsze dwie linijki pokazują tu „klasyczny” sposób inicjowania definiowanych zmiennych, poprzez użycie znaku równości (normalnie znak równości oznacza przypisanie, ale jeśli obiekt po lewej stronie nie istniał wcześniej i jest właśnie tworzony, to nie jest to przypisanie, ale właśnie inicjalizacja).
Poniższy program ilustruje definiowanie zmiennych oraz
operator
sizeof:
1. #include <iostream> 2. #include <string> 3. using namespace std; 4. 5. int main() { 6. long double ld = 0; 7. string st = "Hermenegilda"; 8. short sh = 0; 9. long *lo = nullptr; 10. cout << "long double: " << sizeof ld << endl 11. << "double : " << sizeof(double) << endl 12. << "float : " << sizeof(float) << endl 13. << "long long : " << sizeof(long long) << endl 14. << "long : " << sizeof(long) << endl 15. << "int : " << sizeof(int) << endl 16. << "short : " << sizeof sh << endl 17. << "char : " << sizeof(char) << endl 18. << "wchar_t : " << sizeof(wchar_t) << endl 19. << "char16_t : " << sizeof(char16_t) << endl 20. << "char32_t : " << sizeof(char32_t) << endl 21. << "bool : " << sizeof(bool) << endl 22. << "string : " << sizeof st << endl 23. << "long* : " << sizeof lo << endl; 24. }
dał wyniki następujące ma linuksowej maszynie 64-bitowej
long double: 16 double : 8 float : 4 long long : 8 long : 8 int : 4 short : 2 char : 1 wchar_t : 4 char16_t : 2 char32_t : 4 bool : 1 string : 32 long* : 8Jak widać, w tym systemie typ long ma tę samą reprezentację co long long; na maszynach 32-bitowych long ma zwykle ten sam wymiar co int (ale jest traktowany jako osobny typ). Zauważmy też, że wymiar obiektów typu std::string zależy od implementacji i może być bardzo różny dla różnych kompilatorów.
Jak widać, deklaracja typu ma postać
Typ zmienna;choć może to być też
auto zmienna = wartość;lub
decltype(wyrażenie) zmienna;Słowo kluczowe auto znaczy tu, że kompilator sam ma się domyślić, patrząc na wartość inicjującą, jaki ma być typ deklarowanej/definiowanej zmiennej (oczywiście typ ten będzie ściśle ustalony i nie może być potem zmieniony). Z kolei decltype znaczy, że zmienna ma być tego typu, jakiego jest typu wyrażenie podane w nawiasie. Wyrażenie to nie będzie obliczane, kompilator sprawdzi tylko jaki byłby typ wyniku. Przykład powinien wyjaśnić sposób, w jaki sposób obie konstrukcje mogą być zastosowane:
1. #include <iostream> 2. using namespace std; 3. 4. int main() { 5. auto k = 7; // k jest typu int 6. auto x = 1.; // x jest typu double 7. decltype(x) y = 7; // y jest typu double, choć 8. // '7' jest literałem typu int 9. decltype(k*x) z = 7; // iloczyn k*x jest typu double 10. cout << "k/2=" << k/2 << ", y/2=" << y/2 11. << ", z/2=" << z/2 << endl; 12. }
Programik ten drukuje
k/2=3, y/2=3.5, z/2=3.5co pokazuje, że rzeczywiście y i z są typu double — gdyby były typu int, to dzielenie przez 2 dałoby wynik dokładnie 3 (tak jak to jest w przypadku k) Inne aspekty użycia auto i decltype rozpatrzymy później. Przydatność tych konstrukcji może się na tym etapie wydawać wątpliwa, ale przekonamy się, że są one niezwykle przydatne!
T.R. Werner, 23 lutego 2022; 19:40