Как разбить строку на символы c
Перейти к содержимому

Как разбить строку на символы c

  • автор:

Разбить строку на символы

Можно ли в языке C# когда вводишь строку «string», например: «Собака» — разделить его на буквы «C», «о», «б», «а», «к», «а». Я знаю по функцию «Split», но там надо задавать место разделение, а мне нужно прям каждый символ и пробел тоже!

Отслеживать
28.7k 4 4 золотых знака 40 40 серебряных знаков 81 81 бронзовый знак
задан 21 фев 2018 в 18:06
117 1 1 золотой знак 2 2 серебряных знака 12 12 бронзовых знаков

2 ответа 2

Сортировка: Сброс на вариант по умолчанию

В классе string есть специальный метод ToCharArray , который возвращает массив символов. Также string реализует IEnumerable , поэтому мы можем воспользоваться методом расширения из Linq ToArray . Выбирайте какой нравится (но подозреваю, что первый может работать быстрее, т.к. строке известна ее длина, а второй более универсальный метод и работает для любых последовательностей):

string s = "string"; char[] a = s.ToCharArray(); char[] b = s.ToArray(); 

Ну а если вам не нужно изменять данные, а только читать — можно воспользоваться индексатором: Console.WriteLine(s[0]);

Если вы хотите посимвольно отредактировать строку, то получите массив символов, как написано выше, отредактируйте любые элементы в нем и получите строку из конечного массива (у string есть конструктор, принимающий массив символов):

string s2 = new string(a); 

Вместо массива можно использовать класс StringBuilder — он представляет изменяемую строку, индексатор у него поддерживает как чтение, так и запись:

string s1 = "string"; StringBuilder sb = new StringBuilder(s1); sb[0] = 'b'; sb[1] = 'o'; string s2 = sb.ToString(); Console.WriteLine(s2); 

Как разделить строку на символы java

Чтобы разбить строку на символы в Java , вы можете использовать метод toCharArray() класса String . Этот метод возвращает массив символов, которые составляют данную строку.

String str = "Hello, world!"; char[] chars = str.toCharArray(); for (char c : chars)  System.out.println(c); > 

Этот код разобьет строку «Hello, world!» на массив символов и напечатает каждый символ в консоли:

Разбиваем строку на подстроки по разделяющим символам своими руками

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

Для разработки использовался компилятор gcc. Код писался под стандарт C99. Он несовместим с С90 из-за наличия объявлений с присваиванием (вида var a = val; а не var a;), а также объявлений в конце функций. Используемые флаги компилятора:

-Wall -Werror -pedantic -std=c99

Вообще говоря, сама задача разбиения строк на подстроки, каждая из которых отделена в исходной строке определённым символом, является довольно распространённой. Очень часто необходимо извлечь из строки слова, разделённые пробелами. Конечно, в стандартной библиотеке языка Си уже есть функция strtok (заголовочный файл ), но она имеет свои побочные эффекты, перечисленные ниже.

  1. Функция модифицирует исходную строку, разбивая её на лексемы. Она возвращает указатель на саму лексему, или NULL, в случае, когда нет больше лексем. Подробнее о ней можно прочитать здесь.
  2. Так как функция модифицирует строку, то при передачи ей строчного литерала, будет получено SEGV, поскольку память для таких литеральных строк выделяется в сегменте кода, доступного только для чтения.
  3. Для последующих вызовов, функции необходимо передавать нулевой указатель (литерал NULL), чтобы она могла продолжить сканирование с последней распознанной лексемы (предыдущего вызова).
  4. Она не учитывает экранирование символов разделителей.

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

  1. Не менять оригинальную строку, в которой ищутся лексемы.
  2. Для каждой найденной лексемы создавать новую строку.
  3. Сохранять свою текущую позицию, а именно — указатель на подстроку, которая ещё не разбиралась.
  4. Иметь однородную последовательность вызовов.
  5. Иметь возможность экранировать символы разделители, при сложных лексемах.
  6. Иметь возможность работать со строковыми литералами (константами).

Основные шаги при разделении строк

При определении подстрок разделённых между собой каким-либо символом, прежде всего необходимо иметь возможность определять его наличие в строке.

Также необходимо устранить последовательность символов разделителей в начале и в конце строки, для корректной работы функции разбиения.

Наконец, для выделения памяти необходимо будет написать функцию, учитывающая исключительную ситуацию при работе с памятью (ошибки вида 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

Данные значения формируют следующую таблицу:

Разделение строки в С++

Функция 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; >

Как видите всё не так сложно…

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *