Menu

Konwersja string na int w C: atoi, strtol i zamiana liczb na tekst

Konwersja między tekstem a liczbami w C: dlaczego atoi nie potrafi zgłosić błędu, jak poprawnie używać strtol i strtod z endptr i errno, zamiana liczb na napisy przez snprintf i sztuczki z '0' dla pojedynczych cyfr.

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

Tekst przychodzi od użytkowników, z plików i z wiersza poleceń jako znaki. "42" to trzy bajty, '4', '2' i '\0', i nic w nim nie jest liczbą 42, dopóki go nie przekonwertujesz. C oferuje dwie generacje funkcji konwertujących: krótkie, stare, które nie potrafią zgłaszać błędów, i dłuższe, które potrafią. Ta strona pokazuje obie i wyjaśnia, dlaczego prawdziwe programy używają drugiej.

Stara rodzina: atoi, atol, atof

Czyta się je jak nazwy: ASCII na int, na long, na double. Jeden argument, jeden wynik.

Początkowe białe znaki są pomijane, opcjonalny znak jest odczytywany, cyfry są pobierane, a wszystko za nimi jest ignorowane. To wygodne, dopóki wejście nie jest błędne:

Wszystkie trzy wypisują 0. Nie ma kodu powrotu, flagi ani sposobu, by zapytać „czy się udało?”, a wartość, która przepełnia int, to niezdefiniowane zachowanie, a nie zgłoszony błąd. Przy wpisanym na sztywno literale w programie zabawce da się to znieść. Przy wszystkim, co może dostarczyć człowiek lub plik, to generator cichych błędów.

Właściwe narzędzie: strtol

strtol (string to long) to ta sama konwersja z dwoma kanałami informowania o tym, co się stało.

long strtol(const char *text, char **endptr, int base);

endptr jest ustawiany na pierwszy znak, którego funkcja nie pobrała. base to podstawa systemu liczbowego: użyj 10 albo 0, aby o systemie zdecydował prefiks literału (0x dla szesnastkowego, początkowe 0 dla ósemkowego).

Odczyt endptr odpowiada na pytania, na które atoi odpowiedzieć nie potrafi:

  • end == input oznacza, że nic nie zostało przekonwertowane: tekst nie zaczynał się od liczby.
  • *end != '\0' oznacza, że za liczbą coś było. Czy to błąd, zależy od ciebie: przy przetwarzaniu "42 jablka" to w porządku, ale jest to problem, gdy cała linia miała być liczbą.

Przepełnienie jest zgłaszane osobno przez errno. Pełny, poprawny idiom warto napisać raz i używać wielokrotnie:

Trzy szczegóły sprawiają, że to jest poprawne. errno = 0 przed wywołaniem ma znaczenie, bo biblioteka tylko ustawia errno i nigdy go nie czyści, więc stara wartość z wcześniejszego wywołania zostałaby odczytana jako błąd tego wywołania. strtol zwraca long, który może być szerszy niż int, więc sprawdzenie zakresu względem INT_MIN/INT_MAX jest osobne od ERANGE. A zwracanie sukcesu jako statusu i przekazywanie wartości przez wskaźnik to sposób, w jaki funkcje w C zgłaszają „albo wartość, albo błąd”, bo wartość zwracana nie może pełnić obu ról.

strtoul to odpowiednik bez znaku, a strtoll/strtoull obsługują long long.

Inne systemy liczbowe

Podstawa 16 czyta liczby szesnastkowe, a podstawa 0 sprawdza prefiks i sama decyduje.

Podstawa 0 to pułapka w jednym konkretnym przypadku: liczba dziesiętna z zerami na początku, taka jak "012", zostanie odczytana jako ósemkowa i da 10. Jeśli wejście jest dziesiętne, podaj 10 wprost.

Liczby zmiennoprzecinkowe: strtod

strtod ma ten sam kształt dla double, a atof ma tę samą wadę co atoi.

strtod akceptuje też notację naukową ("1.5e3"), szesnastkowe liczby zmiennoprzecinkowe oraz słowa "inf" i "nan". Ustawienia regionalne wpływają na separator dziesiętny, co zaskakuje przy przetwarzaniu plików zapisanych gdzie indziej. W domyślnych ustawieniach „C” separatorem jest zawsze kropka.

Liczby na tekst: snprintf

W drugą stronę standardową odpowiedzią jest snprintf. Zapisuje co najwyżej tyle, ile podasz, zawsze kończy napis znakiem końca i zwraca długość, jakiej potrzebowałby pełny wynik.

Wartość zwracana służy do sprawdzenia obcięcia. Jeśli jest większa lub równa rozmiarowi bufora, tekst się nie zmieścił:

Po dwie rzeczy nie warto sięgać: itoa nie należy do standardowego C, więc kod, który go używa, nie wszędzie się skompiluje, a sprintf to snprintf bez limitu rozmiaru, czyli funkcja, która bez słowa skargi zapisze za końcem bufora.

Pojedyncze znaki i '0'

Znaki cyfr od '0' do '9' mają gwarancję, że są kolejne, więc zwykłe odejmowanie zamienia znak cyfry na jej wartość i odwrotnie.

Tak zachowują się tylko cyfry: litery nie muszą być kolejne, więc c - 'a' nie jest przenośne przy arytmetyce na alfabecie. Zanim zaufasz odejmowaniu, zabezpiecz je przez isdigit:

Rzutowanie na unsigned char jest wymagane z tego samego powodu co przy napisach: funkcje z <ctype.h> są niezdefiniowane dla ujemnych argumentów, a zwykły char może mieć znak.

Której funkcji użyć

  • Tekst od użytkownika, z pliku lub z argv → strtol / strtod z pełnym sprawdzeniem.
  • Literał wpisany przez ciebie w kodzie i pod twoją kontrolą → atoi jest do przyjęcia, choć nic szczególnego.
  • Liczba do bufora → snprintf i sprawdzenie wartości zwracanej.
  • Jeden znak cyfry → odejmij '0' po sprawdzeniu isdigit.
  • Nigdy → gets, sprintf, itoa.

Najczęściej zadawane pytania

Jak zamienić string na int w C?

Poprawna odpowiedź to strtol(text, &end, 10): zwraca wartość, ustawia end na pierwszy znak, którego nie pobrała, i zgłasza przepełnienie przez errno. atoi(text) jest krótsze, ale zwraca 0 zarówno dla "0", jak i dla "hello", bez możliwości odróżnienia tych przypadków.

Co jest nie tak z atoi w C?

Nie potrafi zgłosić błędu. atoi("abc") zwraca 0, czego nie da się odróżnić od prawdziwego zera, a przepełnienie to niezdefiniowane zachowanie, a nie zgłoszony błąd. Używaj strtol zawsze, gdy tekst pochodzi od użytkownika, z pliku albo z argumentu wiersza poleceń.

Jak zamienić int na string w C?

snprintf(buf, sizeof buf, "%d", n). To funkcja standardowa, ograniczona rozmiarem bufora, która zwraca liczbę znaków potrzebnych na pełny wynik, więc wartość zwracana równa sizeof buf lub większa oznacza, że tekst został obcięty. itoa nie należy do standardowego C.

Jak zamienić znak '7' na liczbę 7 w C?

Odejmij '0': int digit = c - '0';. Znaki cyfr są kolejne w każdym zestawie znaków C, więc odejmowanie daje od 0 do 9. Najpierw zabezpiecz je przez isdigit((unsigned char)c), bo dla innych znaków ta arytmetyka nie ma sensu.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ