Текст приходит от пользователей, из файлов и из командной строки в виде символов. "42" — это три байта: '4', '2', '\0', — и ничто в них не является числом 42, пока вы не выполните преобразование. C предлагает два поколения функций преобразования: короткие старые, которые не умеют сообщать об ошибках, и более длинные, которые умеют. Здесь показаны оба и объяснено, почему настоящие программы пользуются вторым набором.
Старое семейство: atoi, atol, atof
Они читаются как их имена: ASCII в int, в long, в double. Один аргумент, один результат.
Ведущие пробельные символы пропускаются, читается необязательный знак, поглощаются цифры, а всё, что дальше, игнорируется. Это удобно ровно до тех пор, пока ввод не окажется неверным:
Все три печатают 0. Нет ни кода возврата, ни флага, ни способа спросить «получилось ли?», а значение, переполняющее int, — неопределённое поведение, а не сообщённая ошибка. Для жёстко вписанного литерала в учебной программе это терпимо. Для всего, что может подать человек или файл, это генератор тихих багов.
Правильный инструмент: strtol
strtol (string to long) — то же преобразование, но с двумя каналами для отчёта о произошедшем.
long strtol(const char *text, char **endptr, int base);
endptr устанавливается на первый символ, который функция не поглотила. base — основание системы счисления: используйте 10 или 0, чтобы решение принимал сам префикс литерала (0x для шестнадцатеричного, ведущий 0 для восьмеричного).
Чтение endptr отвечает на вопросы, на которые atoi ответить не может:
end == inputозначает, что не преобразовано ничего — текст не начинался с числа.*end != '\0'означает, что дальше был ещё текст. Ошибка это или нет — решать вам: при разборе"42 apples"всё нормально, а вот если числом должна была быть вся строка, это проблема.
О переполнении сообщается отдельно, через errno. Полную правильную идиому стоит написать один раз и переиспользовать:
Правильным этот код делают три детали. errno = 0 перед вызовом важен, потому что библиотека только устанавливает errno и никогда его не сбрасывает, так что устаревшее значение от прежнего вызова было бы прочитано как неудача текущего. strtol возвращает long, который может быть шире int, поэтому проверка диапазона по INT_MIN/INT_MAX отделена от ERANGE. А возврат признака успеха с выдачей самого значения через указатель — это то, как функции C сообщают «либо значение, либо неудача»: возвращаемое значение не может делать обе работы сразу.
strtoul — беззнаковый аналог, а strtoll/strtoull работают с long long.
Другие основания
Основание 16 читает шестнадцатеричные числа; основание 0 смотрит на префикс и решает само.
Основание 0 — ловушка в одном конкретном случае: десятичное число с ведущим нулём вроде "012" читается как восьмеричное и даёт 10. Если ваш ввод десятичный, указывайте 10 явно.
Числа с плавающей точкой: strtod
strtod устроен так же, но для double, а у atof тот же изъян, что и у atoi.
strtod также принимает научную запись ("1.5e3"), шестнадцатеричные числа с плавающей точкой и слова "inf" и "nan". На десятичный разделитель влияет локаль, что удивляет тех, кто разбирает файлы, написанные в другом месте: в локали «C» по умолчанию разделитель всегда точка.
Числа в текст: snprintf
В обратную сторону стандартный ответ — snprintf. Он пишет не больше указанного вами размера, всегда завершает строку нулём и возвращает длину, которая потребовалась бы полному результату.
Возвращаемое значение и есть проверка на обрезание. Если оно больше либо равно размеру буфера, текст не поместился:
Чего не стоит брать: itoa не входит в стандартный C, поэтому использующий его код соберётся не везде; а sprintf — это snprintf без ограничения по размеру, то есть он без возражений запишет за конец вашего буфера.
Одиночные символы и '0'
Символы цифр от '0' до '9' гарантированно идут подряд, поэтому обычное вычитание переводит символ цифры в её значение и обратно.
Так ведут себя только цифры: буквы не обязаны идти подряд, поэтому c - 'a' для алфавитной арифметики непереносимо. Прикрывайте вычитание проверкой isdigit, прежде чем ему доверять:
Приведение к unsigned char необходимо по той же причине, что и в статье строки: функции из <ctype.h> не определены для отрицательных аргументов, а обычный char может быть знаковым.
Какую функцию выбрать
- Текст от пользователя, из файла или из
argv→strtol/strtodс полной проверкой. - Литерал, который вы написали сами и контролируете →
atoiдопустим, хотя и ничем не примечателен. - Число в буфер →
snprintf, и проверяйте возвращаемое значение. - Один символ цифры → вычесть
'0'послеisdigit. - Никогда →
gets,sprintf,itoa.
Часто задаваемые вопросы
Как преобразовать строку в int в C?
Правильный ответ — strtol(text, &end, 10): он возвращает значение, устанавливает end на первый непрочитанный символ и сообщает о переполнении через errno. atoi(text) короче, но возвращает 0 и для "0", и для "hello", и отличить одно от другого нельзя.
Чем плох atoi в C?
Он не умеет сообщать о неудаче. atoi("abc") возвращает 0, неотличимый от настоящего нуля, а переполнение — это неопределённое поведение, а не сообщённая ошибка. Используйте strtol всякий раз, когда текст пришёл от пользователя, из файла или из аргументов командной строки.
Как преобразовать int в строку в C?
snprintf(buf, sizeof buf, "%d", n). Это стандартно, ограничено размером буфера и возвращает число символов, которое потребовалось бы полному результату, — поэтому возвращаемое значение, равное sizeof buf или больше, означает, что текст обрезан. itoa в стандартный C не входит.
Как превратить символ '7' в число 7 в C?
Вычесть '0': int digit = c - '0';. Символы цифр идут подряд в любой кодировке C, поэтому вычитание даёт от 0 до 9. Сначала проверьте символ через isdigit((unsigned char)c) — для всего остального такая арифметика бессмысленна.