Tamkovich.com: Телеком/VoIP блог
Современные технологии: Asterisk, SIP, Kamailio, Linux, Cisco, Linksys
Как разделить строку в Си
19 февраля, 2010 by Сергей Тамкович
Программирование, РазноеРазделение строки на элементы — стандартная задача при обработке текста. Многие высокоуровневые языки предоставляют удобные операторы для решения этой задачи. Например язык Perl позволяет разбить строку используя в качестве разделителя другую строку или регулярное выражение с помощью функции split. Результат разбиения возвращается в виде массива:
@elements = split(/\s/, "very simple example"); |
В PHP аналогичную роль выполняют функции explode (для деления по текстовому разделителю) и preg_split для деления по регулярному выражению:
$elements1 = explode(" ", "very simple example"); $elements2 = preg_split("/[\s,]+/", "very simple example"); |
В Си разделение строки несколько сложнее. Многие программисты, в цикле, ищут разделители с помощью таких функций как index или strstr, а затем меняют его на нулевой байт. Данный подход — громоздкий и неудобный. Гораздо проще воспользоваться функцией strtok. Функция strtok позволяет разбить текстовую строку на токены, используя указанные разделители. Пример использования strtok:
/* strtok usage example */ #include <stdio.h> #include <string.h> void main () { char str[] = "Very simple example,test"; char *token, *last; printf ("Splitting string \"%s\" into tokens:\n", str); token = strtok_r(str, " ,", &last); while (token != NULL) { printf ("%s\n", token); token = strtok_r(NULL, " ,.-", &last); } } |
Почему strtok_r, а не strtok и что за третий параметр &last? Функция strtok_r является потоко-безопасной (thread-safe) аналогом функции strtok, третий параметр (**lasts) используется для сохранения текущего положения в оригинальной строке. Поскольку сегодня многопоточные приложения стали нормой, рекомендую использовать именно strtok_r().
char * strtok_r(char *newstring, const char *delimiters, char **save_ptr) |
Функция strtok_r работает следующем образом: В случае если переданный указатель newstring отличен от NULL, считается что передана строка (последовательность ненулевых байт завершающаяся нулевым). При первом вызове функции strtok_r, значение сохранённое в save_ptr — игнорируется. Функция strtok_r, находит в строке newstring первый из разделителей, заменяет его на нулевой байт (‘\0’), сохраняет позицию, следующую за нулевым байтом в save_ptr, и возвращает указатель на найденный токен (для первого вызова функции, указатель на найденный токен будет совпадать с указателем на начало строки). Последующие вызовы выглядят следующим образом:
token = strtok_r(NULL, " ,.-", &last); |
В случае, если указатель newstring равен NULL, обработка строки начинается с указателя сохранённого в save_ptr, в остальном, алгоритм идентичен первому вызову функции.
Программирование, Разное

Спасибо за статью. Про strtok знал, про strtok_r — не знал.
Скажите, а strtok_r описан в стандарте Си? Если да, то в какой его версии.
Меня интересует кросплатформенность приложения, написанного с использованием именно этой функции.
inst, strtok_r() определена в POSIX.1-2001 в современных FreeBSD и MacOS она тоже есть. Вероятно эти ОС, уже соответствуют стандарту (POSIX.1-2001).
Кстати, strtok_r() — хороший вариант для начала, но не панацея. К сожалению данная функция не видит пустых полей. Т.е. строка ‘1,2,,4’ будет отпарсена в 3 поля, третье поле будет иметь значение 4.
Спасибо за статью. Только вот в C++ Builder 6 я не увидел функции strtok_r. Тоже самое можно и с strtok проделать, если заменить strtok_r на strtok и убрать 3 параметр.
Кстати, что значит, функция strtok является потоко-безопасной? Какие могут возникнут ошибки при использовании функции strtok в многопоточном приложении?
kzru_hunter, наверно невнимательно читал статью :) strtok_r сохраняет текущее положение в третий параметр, у strtok этого параметра нет и он сохраняет это положение в стек — это потенциальная проблема для многопоточных програм.
эээ… извините что встреваю в ваш диалог, но зачем вообще что-то парсить на сях?
IMHO: для решения нужной задачи всегда можно найти свой язык, подходящий для решения данной задачи.
Если нужно что-то парсить регекспами и работать с текстом — это однозначно перл, ибо быстрее и профессиональнее него никто не справится с обработкой текста.
Если нужны быстрейшие массивы для веба, то явно PHP, но никак не asm. Ибо пхп изначально заточен под работу с массивами. И даже костыли для ООП реализованы через массивы, как бы это смешно не выглядело.
Если ООП, то питон, а не PHP и не перловка.
Если драйвер, то асм или си, но явно не питон =)
Подбирайте подходящий язык под решение задачи, а не решения под язык.
Чуть не забыл, Java — для денег (и не для чего больше). =)
Ben-Ja, извиняем. Видишь ли, «парсить» это не самоцель, это лишь метод достижения цели — например обработать конфигурационный файл, или какой нибудь протокол — например SIP. Пытаться для этого пределать внешнюю программу на другом языке — редкостное извращение.
Про Perl — при всей моей любви к нему, у него лишь одно преимущество перед Си — простота и скорость создания маленьких проектов. По скорости работы, по масштабируемости (привет тредам) и по многим другим параметрам — ни перл ни другие супер языки всерьёз рассматривать нельзя, увы.