Menu

Typy danych w C: int, float, char, double i inne

Każda zmienna w C ma typ, który ustala jej rozmiar i to, co może przechowywać. Oto wszystkie podstawowe typy, ich rzeczywiste rozmiary, typy ze znakiem i bez znaku, sizeof, limits.h i to, co dzieje się przy przepełnieniu.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

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

  • char przechowuje jeden bajt. Służy do pojedynczych znaków ('A'), bajtów stringa, a czasem jako malutka liczba całkowita.
  • int to podstawowy, uniwersalny typ całkowity. Liczniki pętli, rozmiary, identyfikatory.
  • float i double przechowują liczby rzeczywiste. double ma mniej więcej dwa razy większą precyzję.
  • void jest w pewnym sensie czwartym typem, ale oznacza "brak wartości": nie da się zadeklarować zmiennej void, 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:

TypTypowy rozmiarTypowy zakres
char1 bajtod -128 do 127 (albo od 0 do 255)
short2 bajtyod -32 768 do 32 767
int4 bajtyod -2 147 483 648 do 2 147 483 647
long8 bajtów (4 w Windows)mniej więcej ±9,2 tryliona
long long8 bajtówmniej więcej ±9,2 tryliona
float4 bajty~7 cyfr znaczących
double8 bajtów~15 cyfr znaczących
long double16 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 long albo int64_t ze stdint.h.
  • Rozmiary, długości, indeksy tablic pochodzące z sizeof albo strlen: size_t. Jest bez znaku i na pewno wystarczająco duży dla każdego obiektu.
  • Liczby dziesiętne: double. Używaj float tylko 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: char dla tekstu, unsigned char dla danych binarnych.
  • Prawda/fałsz: bool ze stdbool.h; zobacz wartości logiczne w C.
  • Dokładne szerokości bitowe (formaty plików, protokoły sieciowe, rejestry sprzętowe): stdint.h, czyli uint8_t, int16_t, uint32_t i 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.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ