Строковые функции sprintf(), atoi(), atol(), atoll() и atof()

Практический курс по C/C++: https://stepik.org/course/193691

Это занятие начнем с рассмотрения довольно популярной функции:

int sprintf(char* buffer, const char* format, ...);

которая определена в заголовочном файле stdio.h. Эта функция работает также как и известная нам функция printf(), только результат заносит не в выходной поток stdout, а в указанную строку buffer. И, как вы понимаете, с ее помощью очень удобно формировать строки по заданному шаблону. Например, имеются габаритные размеры (width, height, depth) и нам нужно их представить в формате:

(width x height x depth)

Вот пример программы, которая это делает:

#include <stdio.h>
 
int main(void) 
{
         double width = 2.4, height = 0.76, depth = 3.14;
         char info[100];
         const char format[] = "(%.2f x %.2f x %.2f)";
         
         sprintf(info, format, width, height, depth);
         puts(info);
 
         return 0;
}

После ее запуска на экран будет выведена строка:

(2.40 x 0.76 x 3.14)

Как видите, все достаточно просто и удобно. Единственный важный нюанс – это размер массива info. Его должно быть достаточно, чтобы целиком помещалась сформированная строка. В представленном примере довольно просто определить максимальную длину итоговой строки. Соответственно, можно заранее объявить массив нужного размера. Но так бывает не всегда. Если форматная строка предполагает вставку других строк, то итоговый размер строки может быть сколь угодно большим. Например:

#include <stdio.h>
 
int main(void) 
{
         double width = 2.4, height = 0.76, depth = 3.14;
         char name[] = "Chair";
         char info[100];
         
         sprintf(info, "(%s: %.2f x %.2f x %.2f)", name, width, height, depth);
         puts(info);
 
         return 0;
}

Здесь в шаблон вывода добавлено название предмета. И, теоретически, максимальная длина имени может быть очень большой, больше размера массива info. В результате приходим к проблеме выхода за пределы массива и записи данных в произвольные ячейки памяти. Выходом из этой ситуации может стать указание максимального числа символов в подставляемой строке. Например, так:

sprintf(info, "(%.3s: %.2f x %.2f x %.2f)", name, width, height, depth);

В итоге из массива name будут взяты только первые 3 символа. Так мы снова сможем контролировать максимальную длину, но при этом некоторые данные могут быть представлены в усеченном виде, а это не всегда допустимо.

Другой выход из этой ситуации, вычислить длины подставляемых строк и сформировать массив нужной длины. Сделать это лучше всего с использованием функций malloc() и free(), о которых речь пойдет в будущих занятиях. При этом не стоит для решения такой задачи использовать массивы переменной длины, то есть, массивы, объявленные через переменные, например, так:

const size_t size = strlen(name) + 100;
char info[size];   // плохое решение

Как я уже отмечал, программа, использующая массивы переменной длины, может неэффективно переводиться в машинные коды.  Забегая вперед, приемлемое решение выглядит следующим образом:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 
int main(void) 
{
         double width = 2.4, height = 0.76, depth = 3.14;
         char name[] = "Chair";
         const size_t size = strlen(name) + 100;
         char* info = (char *)malloc(size);
         
         sprintf(info, "(%.30s: %.2f x %.2f x %.2f)", name, width, height, depth);
         puts(info);
 
         free(info);
 
         return 0;
}

Функция malloc() выделяет size+100 байт в памяти, в нее заносится строка и в конце программы выделенная память освобождается с помощью функции free(). Фактически, получили тот же массив переменной длины, но лучшим способом.

Преобразование чисел в строки

Довольно часто функцию sprintf() используют для преобразования чисел в строки. Например, у нас имеется целочисленная переменная var_i со значением -123:

int var_i = -123;

и нам бы хотелось получить ее строковое представление в массиве:

char str_var[10];

в виде пяти символов:

Как вы уже догадались, сделать это можно следующим образом:

sprintf(str_var, "%d", var_i);

с последующим выводом результата на экран:

puts(str_var);

Довольно удобная и универсальная конструкция. Так можно выполнять преобразование любой числовой информации в текстовый вид. Например, для вещественного значения можно записать функцию sprint() следующим образом:

double var_d = 35.7895;
sprintf(str_var, "%.2f", var_d);

Получим число в строке с точностью до сотых «35.79». И так далее.

Преобразование из строк в числа

Конечно, в практике нередко приходится выполнять и обратные задачи: преобразовывать числовые значения из строк в соответствующий числовой тип данных. Для этого в заголовочном файле stdlib.h имеется набор следующих функций:

  • int atoi(const char* str);
    для преобразования целых чисел из строк в тип int;

  • long atol(const char* str);
    для преобразования целых чисел из строк в тип long;

  • long long atoll(const char* str);
    для преобразования целых чисел из строк в тип long long;

  • double atof(const char* str);
    для преобразования вещественных чисел из строк в тип double.

Использовать их достаточно просто и очевидно. Например:

#include <stdio.h>
#include <stdlib.h>
 
int main(void) 
{
         int a = atoi("123");
         long b = atol("234235354");
         long long c = atoll("23423535456456");
         double d = atof("4564.4545");
 
         printf("a = %d\nb = %ld\nc = %lld\nd = %f\n", a, b, c, d);
 
         return 0;
}

После запуска программы увидим результат:

a = 123
b = 234235354
c = 23423535456456
d = 4564.454500

Единственный нюанс работы этих функций, связан с неверным представлением числовой информации в строке или переполнением. Например, если прописать следующие строки в функциях atoi(), atol(), atoll() и atof():

int a = atoi("1a23");
long b = atol("-234235354564564564");
long long c = atoll("234d23535456456");
double d = atof("f4564.4545");

то получим следующие результаты:

a = 1
b = -1588106836
c = 234
d = 0.000000

Отсюда хорошо видно, что преобразование выполняется либо до первого нечислового символа, либо выдается 0, если строка изначально не может быть интерпретирована как число. Также некорректный результат может получаться, если число не умещается в типе данных, к которому оно приводится.

На этом мы завершим рассмотрение основных функций работы со строками. Конечно, их гораздо больше того, что мы охватили на последних занятиях. Но этой информации вам будет вполне достаточно, чтобы сделать первые шаги в работе со строками языка Си.

Практический курс по C/C++: https://stepik.org/course/193691

Видео по теме