Każda zmienna w C ma typ, wybrany przy deklaracji i niezmienny przez cały jej czas życia. Typ decyduje o trzech rzeczach: ile bajtów zajmuje zmienna, jak te bajty są interpretowane i jakie operacje mają na niej sens.
Lista typów w C jest krótka. Cała złożoność kryje się w modyfikatorach.
Cztery typy bazowe
charprzechowuje jeden bajt. Służy do pojedynczych znaków ('A'), bajtów stringa, a czasem jako malutka liczba całkowita.intto podstawowy, uniwersalny typ całkowity. Liczniki pętli, rozmiary, identyfikatory.floatidoubleprzechowują liczby rzeczywiste.doublema mniej więcej dwa razy większą precyzję.voidjest w pewnym sensie czwartym typem, ale oznacza "brak wartości": nie da się zadeklarować zmiennejvoid, można go użyć tylko jako typu zwracanego przez funkcję, pustej listy parametrów albo wskaźnika bez typu.
Zwróć uwagę na przyrostek f w 3.14f. Bez niego literał jest typu double, a przypisanie go do float po cichu traci precyzję. Niektóre kompilatory o tym ostrzegają.
Modyfikatory: short, long, unsigned
Bazowe typy całkowite mają modyfikatory, które zmieniają rozmiar albo znak.
Każdy typ całkowity potrzebuje własnego specyfikatora formatu: %hd dla short, %d dla int, %ld dla long, %lld dla long long, %u dla unsigned. Użycie złego to niezdefiniowane zachowanie, a nie błąd zaokrąglenia.
unsigned usuwa bit znaku i podwaja dodatni zakres. unsigned int przechowuje wartości od 0 do około 4,3 miliarda zamiast od -2,1 do +2,1 miliarda. To właściwy wybór dla rzeczy, które naprawdę nie mogą być ujemne (rozmiary, liczby bajtów, wzorce bitowe), i pułapka dla wszystkiego, od czego można odjąć coś poniżej zera.
signed jest domyślne dla int, short, long i long long, więc prawie nigdy go nie piszesz. Jedyne miejsce, gdzie ma to znaczenie, to char: to, czy zwykły char ma znak, czy nie, jest zależne od implementacji, więc pisz signed char albo unsigned char, gdy znak ma znaczenie.
Rozmiary: co naprawdę dostajesz
Standard C określa minima, a nie dokładne rozmiary. Na każdej współczesnej 64-bitowej maszynie z Linuksem, macOS albo Windows zobaczysz to:
| Typ | Typowy rozmiar | Typowy zakres |
|---|---|---|
char | 1 bajt | od -128 do 127 (albo od 0 do 255) |
short | 2 bajty | od -32 768 do 32 767 |
int | 4 bajty | od -2 147 483 648 do 2 147 483 647 |
long | 8 bajtów (4 w Windows) | mniej więcej ±9,2 tryliona |
long long | 8 bajtów | mniej więcej ±9,2 tryliona |
float | 4 bajty | ~7 cyfr znaczących |
double | 8 bajtów | ~15 cyfr znaczących |
long double | 16 bajtów (bywa różnie) | więcej niż double |
Wiersz z long to ten, który zaskakuje ludzi: ma 8 bajtów w Linuksie i macOS, a 4 bajty w 64-bitowym Windows. Kod, który zakłada, że long pomieści wartość 64-bitową, nie jest przenośny. Używaj long long albo typów o dokładnej szerokości ze stdint.h (int32_t, uint64_t), gdy rozmiar jest częścią wymagań.
sizeof: zapytaj kompilator
Nigdy nie zgaduj rozmiaru, tylko go zmierz:
sizeof to operator, a nie funkcja, i jest obliczany w czasie kompilacji. Zwraca wartość typu size_t, którą wypisuje się przez %zu.
sizeof(char) na pewno wynosi dokładnie 1: tak w C definiuje się bajt. Wszystko inne mierzy się względem niego.
limits.h i float.h
Dokładne zakresy dla twojego kompilatora są dostępne jako nazwane stałe:
To z tymi wartościami porównujesz, gdy musisz wiedzieć, czy operacja się przepełni. Sprawdzenie if (a > INT_MAX - b) przed obliczeniem a + b to sposób na wykrycie przepełnienia zanim nastąpi, a to ważne, bo przepełnienia liczb ze znakiem nie da się wykryć po fakcie.
Przepełnienie liczb całkowitych
To, co się dzieje, gdy wartość przekroczy zakres swojego typu, zależy wyłącznie od znaku.
Przepełnienie bez znaku jest zdefiniowane: wartość przekręca się modulo 2^N.
Ten drugi przypadek to prawdziwe źródło błędów. Pętla typu for (unsigned i = n - 1; i >= 0; i--) nigdy się nie kończy, bo wartość bez znaku jest zawsze >= 0.
Przepełnienie ze znakiem to niezdefiniowane zachowanie. Nie "przekręcenie", tylko niezdefiniowane. Kompilator może założyć, że nigdy się nie zdarza, i optymalizować na tej podstawie, co oznacza, że sprawdzenie przepełnienia napisane po fakcie może zostać usunięte:
int sum = a + b;
if (sum < a) { /* kompilator może to całkowicie usunąć */ }
Sprawdzaj wcześniej, używając limitów:
if (b > 0 && a > INT_MAX - b) {
/* a + b by się przepełniło: obsłuż to */
}
Precyzja liczb zmiennoprzecinkowych
float i double przechowują liczby w systemie dwójkowym, a większość ułamków dziesiętnych nie ma dokładnej postaci dwójkowej, tak samo jak 1/3 nie ma dokładnej postaci dziesiętnej.
Wynika z tego zasada: nigdy nie porównuj wartości zmiennoprzecinkowych przez ==. Zamiast tego porównuj wartość bezwzględną różnicy z małą tolerancją.
I nigdy nie używaj liczb zmiennoprzecinkowych do pieniędzy. Przechowuj grosze jako liczbę całkowitą; long long z groszami jest dokładne tam, gdzie double ze złotówkami nie jest.
Wybór typu
Krótka lista decyzji, która obejmuje większość kodu:
- Liczby całkowite:
int, chyba że masz powód, by wybrać coś innego. To typ, który procesor obsługuje najwydajniej, i typ, wokół którego zbudowane są wszystkie reguły arytmetyki. - Wszystko większe niż 2 miliardy:
long longalboint64_tzestdint.h. - Rozmiary, długości, indeksy tablic pochodzące z
sizeofalbostrlen:size_t. Jest bez znaku i na pewno wystarczająco duży dla każdego obiektu. - Liczby dziesiętne:
double. Używajfloattylko po to, żeby zmniejszyć o połowę zużycie pamięci w dużych tablicach albo na sprzęcie wbudowanym bez jednostki podwójnej precyzji. - Pojedyncze znaki i surowe bajty:
chardla tekstu,unsigned chardla danych binarnych. - Prawda/fałsz:
boolzestdbool.h; zobacz wartości logiczne w C. - Dokładne szerokości bitowe (formaty plików, protokoły sieciowe, rejestry sprzętowe):
stdint.h, czyliuint8_t,int16_t,uint32_ti tak dalej.
Mieszanie typów
Gdy łączysz dwa różne typy w jednym wyrażeniu, C po cichu je konwertuje, zanim wykona obliczenia. Zwykle to pomaga, a czasem prowadzi do katastrofy:
Pierwszy przypadek to dzielenie całkowite: oba argumenty są typu int, więc wynik jest int, a część ułamkowa zostaje odrzucona. Trzeci jest gorszy: porównanie wartości ze znakiem z wartością bez znaku zamienia tę ze znakiem na bez znaku, przez co -1 staje się ogromną liczbą dodatnią.
Te reguły konwersji i sposób przejęcia nad nimi kontroli za pomocą jawnego rzutowania to temat artykułu o rzutowaniu typów w C.
Najczęściej zadawane pytania
Jakie są podstawowe typy danych w C?
Cztery typy bazowe: char dla pojedynczych znaków i bajtów, int dla liczb całkowitych oraz float i double dla liczb dziesiętnych. Modyfikatory (short, long, long long, signed i unsigned) zmieniają ich rozmiar i znak, tworząc pełny zestaw.
Ile bajtów ma int w C?
Prawie zawsze 4 bajty (32 bity) na współczesnych komputerach stacjonarnych i serwerach, co daje zakres od około -2,1 miliarda do 2,1 miliarda. Standard gwarantuje tylko co najmniej 2 bajty, a 16-bitowe mikrokontrolery rzeczywiście używają 2. Jeśli musisz wiedzieć na pewno, użyj sizeof(int).
Jaka jest różnica między float a double w C?
float ma 4 bajty i około 7 cyfr znaczących; double ma 8 bajtów i około 15. Double to domyślny typ literałów zmiennoprzecinkowych i funkcji matematycznych, więc jeśli nie przechowujesz milionów wartości ani nie celujesz w układ wbudowany, double jest właściwym wyborem.
Co się dzieje, gdy int się przepełni w C?
Dla int ze znakiem przepełnienie to niezdefiniowane zachowanie: kompilator może przekręcić wartość, nasycić ją albo całkowicie usunąć sprawdzenie podczas optymalizacji. Dla int bez znaku jest ono w pełni zdefiniowane: wartość przekręca się modulo 2^N, więc UINT_MAX + 1 to 0. Nigdy nie polegaj na przepełnieniu liczb ze znakiem.