Символы и строки
Методы Join и Split
Методы Join и Split выполняют над строкой текста взаимно обратные преобразования. Динамический метод Split позволяет осуществить разбор текста на элементы. Статический метод Join выполняет обратную операцию, собирая строку из элементов.
Заданный строкой текст зачастую представляет собой совокупность структурированных элементов - абзацев, предложений, слов, скобочных выражений и т.д. При работе с таким текстом необходимо разделить его на элементы, пользуясь специальными разделителями элементов, - это могут быть пробелы, скобки, знаки препинания. Практически подобные задачи возникают постоянно при работе со структурированными текстами. Методы Split и Join облегчают решение этих задач.
Динамический метод Split, как обычно, перегружен. Наиболее часто используемая реализация имеет следующий синтаксис:
public string[] Split(params char[])
На вход методу Split передается один или несколько символов, интерпретируемых как разделители. Объект string, вызвавший метод, разделяется на подстроки, ограниченные этими разделителями. Из этих подстрок создается массив, возвращаемый в качестве результата метода.
Синтаксис статического метода Join таков:
public static string Join(string delimiters, string[] items )
В качестве результата метод возвращает строку, полученную конкатенацией элементов массива items, между которыми вставляется строка разделителей delimiters. Как правило, строка delimiters состоит из одного символа, который и разделяет в результирующей строке элементы массива items ; но в отдельных случаях ограничителем может быть строка из нескольких символов, например, запятая и следующий за ней пробел.
Рассмотрим примеры применения этих методов. В первом из них строка представляет сложноподчиненное предложение, которое разбивается на простые предложения. Во втором предложение разделяется на слова. Затем производится обратная сборка разобранного текста. Вот код соответствующей процедуры:
/// <summary> /// Разборка и сборка текстов /// </summary> public void TestSplitAndJoin() { string txt = "А это пшеница, которая в темном чулане хранится," + " в доме, который построил Джек!"; Console.WriteLine("txt={0}", txt); Console.WriteLine("Разделение текста на простые предложения:"); string[] SimpleSentences, Words; //размерность массивов SimpleSentences и Words устанавливается // автоматически в соответствии с размерностью массива, //возвращаемого методом Split SimpleSentences = txt.Split(','); for (int i = 0; i < SimpleSentences.Length; i++) Console.WriteLine("SimpleSentences[{0}]= {1}", i, SimpleSentences[i]); string txtjoin = string.Join(",", SimpleSentences); Console.WriteLine("txtjoin={0}", txtjoin); Words = txt.Split(' '); for (int i = 0; i < Words.Length; i++) Console.WriteLine("Words[{0}]= {1}", i, Words[i]); txtjoin = string.Join(" ", Words); Console.WriteLine("txtjoin={0}", txtjoin); }//TestSplitAndJoin
Взгляните на результаты выполнения этой процедуры.
Обратите внимание, что методы Split и Join хорошо работают, когда при разборе используется только один разделитель. В этом случае сборка действительно является обратной операцией и позволяет восстановить исходную строку. Если же при разборе задается некоторое множество разделителей, то возникают две проблемы:
- невозможно при сборке восстановить строку в прежнем виде, поскольку не сохраняется информация о том, какой из разделителей был использован при разборе строки. Поэтому при сборке между элементами вставляется один разделитель, возможно, состоящий из нескольких символов;
- при разборе двух подряд идущих разделителей предполагается, что между ними находится пустое слово. Если при разборе предложения на слова использовать в качестве разделителей пробел и запятую, то запятая бы исчезла как часть слова, но взамен появились бы пустые слова.
Как всегда, есть несколько способов справиться с проблемой. Один из них состоит в том, чтобы написать собственную реализацию этих функций, другой - в корректировке полученных результатов, третий - в использовании мощного аппарата регулярных выражений.
Динамические методы класса string
Операции, разрешенные над строками в C#, разнообразны. Методы этого класса позволяют выполнять основные типичные операции - вставку, удаление, замену строк, поиск вхождения подстроки в строку. Класс string наследует методы класса object, частично их переопределяя. Класс string наследует и, следовательно, реализует методы четырех интерфейсов: ICompareable, ICloneable, IConvertible, IEnumerable.
Рассмотрим наиболее характерные методы при работе со строками.
Метод | Описание |
---|---|
Insert | Вставляет подстроку в заданную позицию. |
Remove | Удаляет подстроку в заданной позиции. |
Replace | Заменяет подстроку в заданной позиции на новую подстроку. |
Substring | Выделяет подстроку в заданной позиции. |
IndexOf, IndexOfAny, LastIndexOf, LastIndexOfAny |
Определяются индексы первого и последнего вхождения заданной подстроки или любого символа из заданного набора |
StartsWith, EndsWith | Возвращается true или false, в зависимости от того, начинается или заканчивается строка заданной подстрокой. |
PadLeft, PadRight | Выполняет набивку нужным числом пробелов в начале и в конце строки. |
Trim, TrimStart, TrimEnd | Обратные операции к методам Pad. Удаляются пробелы в начале и в конце строки, или только с одного ее конца. |
ToCharArray | Преобразование строки в массив символов. |
Сводка методов, приведенная в таблице, дает достаточно полную картину широких возможностей, имеющихся при работе со строками в C#. Следует помнить, что класс string является неизменяемым. Поэтому Replace, Insert и другие методы, изменяющие строку, представляют собой функции, возвращающие в качестве результата новую строку.
Класс StringBuilder - построитель строк
Класс string не разрешает изменять существующие объекты. Строковый класс StringBuilder позволяет компенсировать этот недостаток. Этот класс принадлежит к изменяемым классам, и его можно найти в пространстве имен System.Text. Рассмотрим класс StringBuilder подробнее.
Объявление строк. Конструкторы класса StringBuilder
Объекты этого класса объявляются с явным вызовом конструктора класса. Поскольку специальных констант этого типа не существует, вызов конструктора для создания и инициализации объекта просто необходим. Конструктор класса перегружен, и наряду с конструктором без параметров, создающим пустую строку, имеется набор конструкторов, которым можно передать две группы параметров. Первая группа позволяет задать строку или подстроку, значением которой будет инициализироваться создаваемый объект класса StringBuilder. Вторая группа параметров позволяет задать емкость объекта - объем памяти, отводимой данному экземпляру класса StringBuilder. Каждая из этих групп не является обязательной и может быть опущена. Примером может служить конструктор без параметров, который создает объект, инициализированный пустой строкой, и с некоторой емкостью, заданной по умолчанию, значение которой зависит от реализации. Приведу в качестве примера синтаксис трех конструкторов:
- public StringBuilder(string str, int cap); Параметр str задает строку инициализации, cap - емкость объекта;
- public StringBuilder(int curcap, int maxcap); Параметры curcap и maxcap задают начальную и максимальную емкость объекта;
- public StringBuilder(string str, int start, int len, int cap); Параметры str, start, len задают строку инициализации, cap - емкость объекта.
Операции над строками
Над строками этого класса определены практически те же операции, что и над строками класса string:
- присваивание (=);
- две операции проверки эквивалентности (= =) и (!=);
- взятие индекса ([]).
Операция конкатенации (+) не определена над строками класса StringBuilder, ее роль играет метод Append, дописывающий новую строку в хвост уже существующей. Семантика операций частично изменилась. Присваивание для строк этого класса является полноценным ссылочным присваиванием, так что изменение значения строки сказывается на всех экземплярах, ссылающихся на строку в динамической памяти. Эквивалентность теперь является проверкой ссылок, а не значений. Со строкой этого класса можно работать как с массивом, но, в отличие от класса string, здесь уже все делается как надо: допускается не только чтение отдельного символа, но и его изменение. Рассмотрим уже знакомый пример работы со строками, используя теперь строки класса StringBuilder:
/// <summary> /// Операции над строками StringBuilder /// </summary> public void TestStringBuilder() { string DEL = "->"; StringBuilder s1 = new StringBuilder("ABC"), s2 = new StringBuilder("CDE"); StringBuilder s3 = s2.Insert(0,s1.ToString()); s3.Remove(3, 3); bool b1 = (s1 == s3); char ch1 = s1[2]; string s = s1.ToString() + DEL + s2.ToString() + DEL + s3.ToString() + DEL + b1.ToString() + DEL + ch1.ToString(); Console.WriteLine(s); s2.Replace("ABC", "Zenon"); s1 = s2; s2[0] = 'L'; s1.Append(" - это музыкант!"); Console.WriteLine(s1.ToString() + " -> " + s2.ToString()); }
Результаты работы этого метода показаны на рис. 7.6
Этот пример демонстрирует возможность выполнения над строками класса StringBuilder тех же операций, что и над строками класса string. Обратите внимание, теперь методы, изменяющие строку, Replace, Insert, Remove, Append реализованы как процедуры, а не как функции. Они изменяют значение строки непосредственно в буфере, отводимом для хранения строки. Появляется новая возможность - изменять отдельные символы строки.
Основные методы
У класса StringBuilder методов значительно меньше, чем у класса string. Это и понятно: класс создавался с целью дать возможность изменять значение строки. По этой причине у класса есть основные методы, позволяющие выполнять такие операции над строкой, как вставка, удаление и замена подстрок, но нет методов, подобных поиску вхождения, которые можно выполнять над обычными строками. Технология работы обычно такова: создается обычная строка; из нее конструируется строка класса StringBuilder ; выполняются операции, требующие изменение значения; полученная строка преобразуется в строку класса string ; над этой строкой выполняются операции, не требующие изменения значения строки.
Давайте чуть более подробно рассмотрим основные методы класса StringBuilder:
- public StringBuilder Append(<объект>); К строке, вызвавшей метод, присоединяется строка, полученная из объекта, который передан методу в качестве параметра. Метод перегружен и может принимать на входе объекты всех простых типов, начиная от char и bool до string и long. Поскольку объекты всех этих типов имеют метод ToString, всегда есть возможность преобразовать объект в строку, которая и присоединяется к исходной строке. В качестве результата возвращается ссылка на объект, вызвавший метод. Поскольку возвращаемую ссылку ничему присваивать не нужно, то правильнее считать, что метод изменяет значение строки;
- public StringBuilder Insert(int location,<объект>); Метод вставляет строку, полученную из объекта, в позицию, указанную параметром location. Метод Append является частным случаем метода Insert ;
- public StringBuilder Remove(int start, int len); Метод удаляет подстроку длины len, начинающуюся с позиции start ;
- public StringBuilder Replace(string str1,string str2); Все вхождения подстроки str1 заменяются на строку str2 ;
- public StringBuilder AppendFormat(<строка форматов>, <объекты>); Метод является комбинацией метода Format класса string и метода Append. Строка форматов, переданная методу, содержит только спецификации форматов. В соответствии с этими спецификациями находятся и форматируются объекты. Полученные в результате форматирования строки присоединяются в конец исходной строки.
За исключением метода Remove, все рассмотренные методы являются перегруженными. В представленном описании приведен основной вариант вызова метода, не отражающий точный синтаксис всех перегруженных реализаций.
Емкость буфера
Каждый экземпляр строки класса StringBuilder имеет буфер, в котором хранится строка. Объем буфера - его емкость - может меняться в процессе работы со строкой. Объекты класса имеют две характеристики емкости - текущую и максимальную. В процессе работы текущая емкость изменяется, естественно, в пределах максимальной емкости, которая реально достаточно высока. Если размер строки увеличивается, то соответственно автоматически растет и текущая емкость. Если же размер строки уменьшается, то емкость буфера остается на том же уровне. По этой причине иногда разумно уменьшать емкость. Следует помнить, что попытка уменьшить емкость до величины, меньшей длины строки, приведет к ошибке.
У класса StringBuilder имеется 2 свойства и один метод, позволяющие анализировать и управлять емкостными свойствами буфера. Напомню, что этими характеристиками можно управлять также еще на этапе создания объекта, - для этого имеется соответствующий конструктор. Рассмотрим свойства и метод класса, связанные с емкостью буфера:
- свойство Capacity - возвращает или устанавливает текущую емкость буфера;
- свойство MaxCapacity - возвращает максимальную емкость буфера. Результат один и тот же для всех экземпляров класса;
- метод int EnsureCapacity(int capacity) - позволяет убедиться, что емкость буфера не меньше емкости, заданной параметром capacity ; если текущая емкость меньше, то она увеличивается до значения capacity, иначе не изменяется. Максимум текущей емкости и capacity возвращается в качестве результата работы метода.
Приведу код, в котором проводятся различные эксперименты с емкостью буфера:
/// <summary> /// Анализ емкости буфера /// </summary> public void TestCapacity() { string txt = "А это пшеница, которая в темном чулане хранится," + " в доме, который построил Джек!"; string str = "А роза упала на лапу Азора"; StringBuilder strbuild = new StringBuilder(100, 1000); StringBuilder txtbuild = new StringBuilder(txt); strbuild.Append(str); //Емкость буфера Console.WriteLine("strbuild: емкость буфера = {0}, " + "максимальная емкость = {1}", strbuild.Capacity, strbuild.MaxCapacity); Console.WriteLine("txtbuild: емкость буфера = {0}, " + "максимальная емкость = {1}", txtbuild.Capacity, txtbuild.MaxCapacity); //Изменение емкости //Ошибка периода выполнения! //попытка установить емкость меньше длины строки //txtbuild.Capacity = 75; int sure = txtbuild.EnsureCapacity(75); Console.WriteLine("sure= {0}", sure); // увеличим строку за пределы буфера // емкость автоматически увеличится! txtbuild.Append(txtbuild.ToString()); Console.WriteLine("txtbuild: емкость буфера = {0}", txtbuild.Capacity); }
В этом фрагменте кода анализируются и изменяются емкостные свойства буфера двух объектов. Демонстрируется, как меняется емкость при работе с объектами. Результаты работы этого фрагмента кода показаны на рис. 7.7.