Как разделить строки с помощью String.Split в C#
Метод String.Split создает массив подстрок, разбивая входную строку по одному или нескольким разделителям. Этот метод зачастую является самым простым способом разделить строку по границам слов. Он также используется для разбиения строк по другим конкретным символам или строкам.
Примеры C# в этой статье выполняются во встроенном средстве выполнения кода и на площадке Try.NET. Нажмите на кнопку Выполнить, чтобы выполнить пример в интерактивном окне. После выполнения кода вы можете изменить его и выполнить измененный код, снова нажав на кнопку Выполнить. Либо в интерактивном окне выполняется измененный код, либо, если компиляция завершается с ошибкой, в интерактивном окне отображаются все сообщения об ошибках компилятора C#.
Следующий код разбивает обычную фразу на массив строк для каждого слова.
string phrase = "The quick brown fox jumps over the lazy dog."; string[] words = phrase.Split(' '); foreach (var word in words) < System.Console.WriteLine($">"); >
Каждый экземпляр знака разделения создает значение в возвращаемом массиве. Последовательные знаки разделения создают пустую строку в виде значения в возвращаемом массиве. В следующем примере показано создание пустой строки с использованием символа пробела в качестве разделителя.
string phrase = "The quick brown fox jumps over the lazy dog."; string[] words = phrase.Split(' '); foreach (var word in words) < System.Console.WriteLine($">"); >
Такое поведение упрощает работу с такими форматами, как файл данных с разделителями-запятыми (CSV), которые представляют табличные данные. Идущие подряд запятые представляют пустой столбец.
Чтобы исключить из возвращаемого массива все пустые строки, можно передать необязательный параметр StringSplitOptions.RemoveEmptyEntries. Для более сложной обработки возвращенной коллекции можно использовать LINQ, чтобы управлять результирующей последовательностью.
String.Split может использовать несколько знаков разделения. В следующем примере используются пробелы, запятые, точки, двоеточия и вкладки в качестве разделительных символов, которые передаются Split в массив. Цикл в конце кода отображает каждое из слов в возвращенном массиве.
char[] delimiterChars = < ' ', ',', '.', ':', '\t' >; string text = "one\ttwo three:four,five six seven"; System.Console.WriteLine($"Original text: ''"); string[] words = text.Split(delimiterChars); System.Console.WriteLine($" words in text:"); foreach (var word in words) < System.Console.WriteLine($">"); >
Последовательные экземпляры любого разделителя создают пустую строку в выходном массиве:
char[] delimiterChars = < ' ', ',', '.', ':', '\t' >; string text = "one\ttwo :,five six seven"; System.Console.WriteLine($"Original text: ''"); string[] words = text.Split(delimiterChars); System.Console.WriteLine($" words in text:"); foreach (var word in words) < System.Console.WriteLine($">"); >
Метод String.Split может принимать массив строк (в этом случае в качестве разделителей при анализе целевой строки используются последовательности символов, а не отдельные символы).
string[] separatingStrings = < "; string text = "one'"); string[] words = text.Split(separatingStrings, System.StringSplitOptions.RemoveEmptyEntries); System.Console.WriteLine($" substrings in text:"); foreach (var word in words)
См. также
- Извлечение элементов из строки
- Руководство по программированию на C#
- Строки
- Регулярные выражения .NET
Совместная работа с нами на GitHub
Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.
Разделение строки в С++
Функция strtok предназначена для разбиения на части заданной строки.
#include #include char * tmp = strtok(char *string, char *delimiters);
- string — разбиваемая строка
- delimiters — разделитель. Символ которые будет считаться началом/концом строки
Функция возвращает параметр типа char, который содержит первый «кусок» строки. Для получения дальнейших «кусков» необходимо вызвать функцию с первым параметром NULL
char *tmp = strtok(NULL, char *delimiters);
Выглядит это грозно, но на примере вам станет всё понятно.
Рассмотрим функцию, которая разделит заданную строку по пробелам.
#include #include #include int main () < char buff[] = "Строка для разделения по пробелам"; char *tmp_char; tmp_char = strtok(buff," "); // вызовем функцию strtok для разделения строки в buff по пробелам while (tmp_char != NULL) < ///выводим части пока они существуют std::cout return 0; >
Как видите всё не так сложно…
Как разбить строку на слова и записать в масив
Не могу разобраться как разбить строку на слова и сохранить в массив как отдельные слова в языке Си. Пробую через strtok, но к сожалению отделает только первое слово. upd. Получаю от пользователя строку и записываю в массив Пример: input = cd .. argc[0] = cd argc[1] = .. Вот код:
char split(char *commadnForSpilt) < //FIX to strtok int i = 0; char *p = strtok (commadnForSpilt, " "); char *array[3]; while (p != NULL) < array[i++] = p; p = strtok (NULL, " "); >for (i = 0; i
Разбиваем строку на подстроки по разделяющим символам своими руками
Приветствую вас, дорогие читатели. В данной статье описана разработка функции разделения строк. Возможно, эта функция может стать для вас хорошей альтернативой, вместо функции strtok из стандартной библиотеки языка Си.
Для разработки использовался компилятор gcc. Код писался под стандарт C99. Он несовместим с С90 из-за наличия объявлений с присваиванием (вида var a = val; а не var a;), а также объявлений в конце функций. Используемые флаги компилятора:
-Wall -Werror -pedantic -std=c99
Вообще говоря, сама задача разбиения строк на подстроки, каждая из которых отделена в исходной строке определённым символом, является довольно распространённой. Очень часто необходимо извлечь из строки слова, разделённые пробелами. Конечно, в стандартной библиотеке языка Си уже есть функция strtok (заголовочный файл ), но она имеет свои побочные эффекты, перечисленные ниже.
- Функция модифицирует исходную строку, разбивая её на лексемы. Она возвращает указатель на саму лексему, или NULL, в случае, когда нет больше лексем. Подробнее о ней можно прочитать здесь.
- Так как функция модифицирует строку, то при передачи ей строчного литерала, будет получено SEGV, поскольку память для таких литеральных строк выделяется в сегменте кода, доступного только для чтения.
- Для последующих вызовов, функции необходимо передавать нулевой указатель (литерал NULL), чтобы она могла продолжить сканирование с последней распознанной лексемы (предыдущего вызова).
- Она не учитывает экранирование символов разделителей.
В виду вышеуказанных причин, мной было принято решение написать свой вариант функции strtok. Новая функция должна выполнять задачу старой, но со следующими ограничениями:
- Не менять оригинальную строку, в которой ищутся лексемы.
- Для каждой найденной лексемы создавать новую строку.
- Сохранять свою текущую позицию, а именно - указатель на подстроку, которая ещё не разбиралась.
- Иметь однородную последовательность вызовов.
- Иметь возможность экранировать символы разделители, при сложных лексемах.
- Иметь возможность работать со строковыми литералами (константами).
Основные шаги при разделении строк
При определении подстрок разделённых между собой каким-либо символом, прежде всего необходимо иметь возможность определять его наличие в строке.
Также необходимо устранить последовательность символов разделителей в начале и в конце строки, для корректной работы функции разбиения.
Наконец, для выделения памяти необходимо будет написать функцию, учитывающая исключительную ситуацию при работе с памятью (ошибки вида SEGV), а также макрос, позволяющий кратко писать вызов такой функции.
Разработка функции
Приступим к разработке. Для начала определим заголовочный файл "str_utils.h", содержащий все прототипы необходимых функций. Реализации функций положим в файл "str_utils.c".
Начнём с функции нахождения символов в строке. Библиотечная функция strchr могла бы решить эту задачу. Но проблема в том, что она не допускает в качестве аргумента строки, в которой надо искать символ, значения NULL. При попытке компилировать с флагами -Wall -Werror , файл с таким аргументом не скомпилируется. Хотя, такую ситуацию можно было бы обработать, вернув NULL. Поэтому был определён свой вариант данной функции с именем contains_symbol. Её прототип выглядит следующим образом:
size_t contains_symbol(char *symbols, char symbol);
Её реализация определена следующим образом (файл "str_utils.c"):
size_t contains_symbol(char *symbols, char symbol) < size_t pos = 1; if(symbols == NULL) return 0; while(*symbols != '\0')< if(*symbols++ == symbol) return pos; pos++; >return 0; >
Данная функция возвращает позицию символа в строке, увеличенную на единицу. Она не учитывает нулевой символ. Если символ не был найден или ей передали NULL,, функция вернёт 0. Её удобно использовать в цикле while, при проверке текущего символа строки на его наличие в другой строке.
Для инкапсуляции работы с памятью был определён отдельный заголовочный файл "mem.h", содержащий следующие прототипы:
void *alloc_mem(size_t nbytes); void *calloc_mem(size_t nelems, size_t elem_size); #define alloc_str(x) ((char *) alloc_mem(x + 1))
Соответствующие функции реализованы в отдельном файле "mem.c":
#include #include void *alloc_mem(size_t nbytes) < char *buf = (char *)malloc(nbytes); if(buf != NULL)< memset(buf, '\0', nbytes); return buf; >exit(-1); > void *calloc_mem(size_t nelems, size_t elem_size) < void *buf = calloc(nelems, elem_size); if(buf != NULL)< return buf; >exit(-1); >
Они выделяют блок памяти в куче, содержащий указанное количество байт, а также дополнительно его обнуляют (функция alloc_mem).
Функция обрезки разделителей строки trim_separators выглядит следующим образом:
/* trims symbols from separators at src string */ /* returns new trimmed string */ char *trim_separators(char *src, char *separators);
char *trim_separators(char *src, char *separators) < if(src == NULL || separators == NULL) return NULL; char *sp = src; while(contains_symbol(separators, *sp)) sp++; /* if it contains only symbols from separators =>NULL */ if(sp - s == strlen(s)) return NULL; char *sp2 = s + strlen(s) - 1; /* last char at src */ while(contains_symbol(separators, *sp2)) sp2--; /* if it contains only symbols from separators => NULL */ if(sp2 < s) return NULL; size_t sz = 0; if(sp2 - sp == 0 && *sp == '\0') return NULL; /* zero byte is not a character */ else if(sp2 - sp == 0)< sz = 1; >else < sz = (sp2 - sp) + 1; >char *res = alloc_mem(sz); memcpy(res, sp, sz);/* copy all chars except last zero byte */ return res; >
В начале мы проверяем на NULL аргументы функции. Если они нулевые, то возвращаем NULL.
Далее, через указатель sp, проходим строку слева направо, пока мы встречаем символы из строки separators. Если мы прошли всю строку, значит она целиком и полностью состоит из сепараторов, следовательно надо удалить все символы, или же просто вернуть NULL.
char *sp = src; while(contains_symbol(separators, *sp)) sp++; /* if it contains only symbols from separators => NULL */ if(sp - s == strlen(s)) return NULL;
Аналогично, далее через указатель sp2, проходим строку справа налево, проверяя, находится ли текущий символ в массиве separators. Если это не так, то мы прерываем цикл, а указатели будут содержать ссылку на первые символы, не являющимися разделителями. Если мы опять прошли всю строку, значит снова придётся удалять всю строку, следовательно, возвращаем NULL.
char *sp2 = s + strlen(s) - 1; /* last char at src */ while(contains_symbol(separators, *sp2)) sp2--; /* if it contains only symbols from separators => NULL */ if(sp2 < s) return NULL;
Наконец, вычисляем длину строки. Если указатели ссылаются на одно и то же место, то в строке был лишь один символ, не являющийся разделителем, а потому размер результата будет равным 1 байту (один лишний байт для нулевого символа учтён в макросе alloc_str). Если же этот единственный символ является нулевым (маркером конца), то возвращаем NULL. Иначе берём разницу между адресами указателями, прибавляем к ней единицу, и получаем длину новой строки. Затем мы просто выделяем память для новой строки и копируем в неё строку, начинающуюся с указателя sp.
Теперь, объединим работу выше написанных функции, в единую функцию get_token().
Код функции get_token дан ниже:
char *get_token(char *src, char *delims, char **next) < if(src == NULL || delims == NULL) return NULL; char *delims_p = delims; /* the end of lexem (points to symbol that follows right after lexem */ char *src_p = trim_separators(src, delims); /* the begining of the lexem */ char *lex_begin = src_p; if(src_p == NULL)< *next = NULL; return NULL; >/* flag that indicates reaching of delimeter */ int flag = 0; while(*src_p != '\0') < flag = 0; while(*delims_p != '\0')< if(*delims_p == *src_p)< flag = 1; break; >delims_p++; > if(flag == 1) break; delims_p = delims; src_p++; > /* now src_p points to the symbol right after lexem */ /* compute lexem size and reset pointers (from trimmed to the original src) */ char *offset; size_t tok_size; offset = (src + strspn(src, delims)); tok_size = (src_p - lex_begin); free(lex_begin); lex_begin = offset; src_p = offset + tok_size; if(*src_p == '\0') *next = NULL; else *next = src_p; /* result token */ char *res = alloc_str(tok_size); memcpy(res, lex_begin, tok_size); return res; >
В ней используется функция обрезки trim_separators(). Функция обрезки возвращает новую строку, и далее сканирование ведётся по ней. В цикле лишь проверяется, не равен ли текущий символ какому-либо символу разделителю из массива символов delims, и если равен, то выйти из цикла. Указатель src_p проходит по сканируемой строке. После цикла он будет указывать на символ, следующий за лексемой (конец лексемы). А начало лексемы сохраняется в указателе lex_begin, который изначально указывает на начало обрезанной, сканируемой строки. После обнаружения границ лексемы, вычисляется её размер (её число символом), а затем сканируемая строка удаляется из динамической кучи. Затем указатели переустанавливаются на позиции в оригинальной строке (первый аргумент функции get_token()), а часть строки, которая ещё не была разобрана, присваивается в качестве содержимого двойному указателю next. Обратите внимание, что next является ссылкой на другой указатель (в данном случае, на указатель строки). Двойной указатель позволяет менять значение переменной типа char *, записывая новый адрес в next. Для первого вызова данной функции, next должен хранить адрес переменной указателя, которая указывает на строку и хранит адрес первой ячейки строки. Однако, при работе с двойным указателем возможна серьёзная и незаметная ошибка, если в качестве начального значения next передать адрес переменной, которая непосредственно указывает на строку, а не адрес переменной копии, которая содержит копию адреса строки. В следующем разделе подробно описана данная ситуация, и показан пример работы данной функции.
Пример работы get_token()
Ниже дан простой рабочий пример функции get_token(). Оригинальная строка с лексемами хранится в указателе test, копия адреса строки (копия переменной test) хранится в переменной copytest. Указатель tok хранит текущую распознанную лексему, а next - сканируемую часть строки. Данная программа разделяет строку test по пробелу и символу табуляции на подстроки, и выводит их. Также она выводит саму строку test до и после работы функции. Как можно убедиться по выводу, оригинальная строка не меняется.
#include #include #include #include "mem.h" #include "str_utils.h" int main(int argc, char **argv) < char *test = " They have a cat.\n \0"; char *copytest = test; char **next = ©test; /* has side effect on copytest */ char *tok = NULL; printf("src:%s\n", test); printf("copytest:%s\n", copytest); while(*next != NULL)< tok = get_token(*next, " \t\0", next); if(tok == NULL) break; printf("%s\n", tok); free(tok); >printf("src after:%s\n", test); printf("copytest after:%s\n", copytest); return 0; >
Вывод данной программы:
src: They have a cat. copytest: They have a cat. They have a cat. src after: They have a cat. copytest:(null)
Обратите внимание, что в цикле есть дополнительная проверка на NULL указателя tok. Дело в том, что при получении последнего слова в строке (а именно "cat.\n"), указатель next будет указывать на подстроку, состоящую лишь из одних пробелов (плюс нулевой символ). Функция trim_separators() для таких строк возвращает NULL, так как по логике придётся урезать все символы в строке. В итоге get_token() также вернёт NULL, поскольку уже ничего не осталось для сканирования. Поэтому переменная tok сохранит значение NULL, на последнем шаге.
Теперь снова по поводу двойного указателя next. Как вы могли заметить, в вышеприведённом коде ему передаётся адрес переменной copytest, а не переменной test. Дело в том, что мы можем нечаянно затереть значение переменной test (именно переменной, а не самой строки). Для примера, изменим код следующим образом. Передадим адрес test в указатель next. В итоге мы получим следующий вывод.
src: They have a cat. copytest: They have a cat. They have a cat. src after:(null) copytest: They have a cat.
Как видите, сама строка не меняется, но изменилось значение переменной test. Теперь она содержит NULL, поскольку на последнем шаге, функция присваивает ей соответствующее значение. Отсюда следует, что операции вида:
*next = addr; *next = NULL;
с двойными указателями (указатель на указатель), тройными, и какой-либо сколь угодно длинной цепочкой указателей создают побочный эффект.
Модификация функции get_token(). Экранирование разделителей
Функция get_token() умеет возвращать новые подстроки (токены) из исходной строки, не меняя её. Однако она совершенно не умеет их экранировать, в случае, когда лексемы представляют собой более сложные объекты. Например, а что если лексема содержит символ, который мы выбрали в качестве разделителя?
Например, вам необходимо выделять значения из формата CSV , где, каждая строка имеет следующий вид:
1233,"John Cenna","Male",4.22,"2004, 2005, 2006",1 1234,"John Doe","Male",4.24,"2001, 2004, 2007",0 1235,"Maria Laws","Female",4.23,"2003, 2006, 2008",1
Данные значения формируют следующую таблицу: