В C# есть символьный класс char, основанный на классе System.Char и использующий двухбайтную кодировку Unicode представления символов. Для этого типа в языке определены символьные константы - символьные литералы. Константу можно задавать:
символом, заключенным в одинарные кавычки;
escape-последовательностью;
Unicode-последовательностью, задающей Unicode код символа.
Вот несколько примеров объявления символьных переменных и работы с ними:
/// <summary>/// Символы, коды, строки/// </summary>public void TestChar() { char ch1='A', ch2 ='\x5A', ch3='\u0058'; char ch = new Char(); int code; string s; ch = ch1; //преобразование символьного типа в тип int code = ch; ch1=(char) (code +1); //преобразование символьного типа в строку //s = ch; s = ch1.ToString()+ch2.ToString()+ch3.ToString(); Console.WriteLine("s= {0}, ch= {1}, code = {2}", s, ch, code); }//TestChar
Три символьные переменные инициализированы константами, значения которых заданы тремя разными способами. Переменная ch объявляется в объектном стиле, используя new и вызов конструктора класса. Тип char, как и все типы C#, является классом. Этот класс наследует свойства и методы класса object и имеет большое число собственных методов.
Существуют ли преобразования между классом char и другими классами? Явные или неявные преобразования между классами char и string отсутствуют, но, благодаря методу ToString, переменные типа char стандартным образом преобразуются в тип string. Поскольку у каждого символа есть свой код, существуют неявные преобразования типа char в целочисленные типы, начиная с типа ushort. Обратные преобразования целочисленных типов в тип char также существуют, но они уже явные.
В результате работы процедуры TestChar строка s, полученная сцеплением трех символов, преобразованных в строки, имеет значение BZX, переменная ch равна A в латинском алфавите, а ее код - переменная code - 65. Хотя преобразования символа в код и обратно просты, полезно иметь процедуры, выполняющие взаимно-обратные операции, - получение по коду символа и получение символа по его коду:
/// <summary> /// Код символа /// </summary> /// <param name="sym">символ</param> /// <returns>его код</returns> public static int SayCode(char sym) { return sym; }//SayCode /// <summary> /// Символ /// </summary> /// <param name="code">Код символа</param> /// <returns>символ</returns> public static char SaySym(int code) { return (char)code; }// SaySym
В первой процедуре преобразование к целому типу выполняется неявно. Во второй - преобразование явное.
Говоря о символах и их кодировке, следует помнить, что для символов алфавитов естественных языков (латиницы, кириллицы) применяется плотная кодировка. Это означает, что поскольку буква z в латинице следует за буквой y, код z на единицу больше кода y. Только буква "Ё" в кириллице не подчиняется этому правилу. Для цифр также используется плотная кодировка, и их коды предшествуют кодам букв. Заглавные буквы в кодировке предшествуют строчным. Ряд символов воспринимаются как управляющие, выполняя при их появлении определенное действие. К подобным относятся такие символы, как "перевод строки" (new line), "возврат каретки" (carriage return), "звонок". Эти символы не имеют видимого образа, а их коды задаются escape последовательностями ('\n', '\r'). Поскольку алфавит, задаваемый Unicode-кодировкой, содержит более 65000 символов, большинство кодов зарезервировано и им пока не соответствуют реальные символы. Рассмотрим пример, демонстрирующий коды некоторых символов.
Процедуры печати PrintCode и PrintSym достаточно просты, так что код их не приводится. Результат работы этого метода показан на рис. 7.1.
Рис. 7.1. Символы и их коды
Класс char, как и все классы в C#, наследует свойства и методы родительского класса object. Но у него есть и собственные методы и свойства, и их немало. Приведу сводку этих методов.
Таблица 7.1. Статические методы и свойства класса char
Метод
Описание
GetNumericValue
Возвращает численное значение символа, если он является цифрой, и (-1) в противном случае.
GetUnicodeCategory
Все символы разделены на категории. Метод возвращает Unicode категорию символа. Ниже приведен пример.
IsControl
Возвращает true, если символ является управляющим.
IsDigit
Возвращает true, если символ является десятичной цифрой.
IsLetter
Возвращает true, если символ является буквой.
IsLetterOrDigit
Возвращает true, если символ является буквой или цифрой.
IsLower
Возвращает true, если символ задан в нижнем регистре.
IsNumber
Возвращает true, если символ является числом (десятичной или шестнадцатеричной цифрой).
IsPunctuation
Возвращает true, если символ является знаком препинания.
IsSeparator
Возвращает true, если символ является разделителем.
IsSurrogate
Некоторые символы Unicode с кодом в интервале [0x1000, 0x10FFF] представляются двумя 16-битными "суррогатными" символами. Метод возвращает true, если символ является суррогатным.
IsUpper
Возвращает true, если символ задан в верхнем регистре.
IsWhiteSpace
Возвращает true, если символ является "белым пробелом". К белым пробелам, помимо пробела, относятся и другие символы, например, символ конца строки и символ перевода каретки.
Parse
Преобразует строку в символ. Естественно, строка должна состоять из одного символа, иначе возникнет ошибка.
ToLower
Приводит символ к нижнему регистру.
ToUpper
Приводит символ к верхнему регистру.
MaxValue, MinValue
Свойства, возвращающие символы с максимальным и минимальным кодом. Возвращаемые символы не имеют видимого образа.
Большинство статических методов перегружены. Они могут применяться как к отдельному символу, так и к строке, для которой указывается номер символа для применения метода. Основную группу составляют методы Is, крайне полезные при разборе строки. Приведу примеры, в которых используются многие из перечисленных методов:
Вот как выглядят результаты консольного вывода, порожденного выполнением метода.
Рис. 7.2. Свойства символов
Обратите внимание: буквенными символами являются как символы латиницы, так и кириллицы, символ возврата каретки относится к белым пробелам и к управляющим символам, а символ точки с запятой к разделителям не относится.
Кроме статических методов, у класса char есть и динамические методы. Большинство из них - это методы родительского класса object, унаследованные и переопределенные в классе char. Из собственных динамических методов стоит отметить метод CompareTo, позволяющий проводить сравнение символов. Он отличается от метода Equal тем, что для несовпадающих символов выдает "расстояние" между символами в соответствии с их упорядоченностью в кодировке Unicode.
2.1. Класс char[] - массив символов
В языке C# определен класс char[], и его можно использовать для представления строк постоянной длины, как это делается в С++. Более того, поскольку массивы в C# динамические, расширяется класс задач, в которых можно использовать массивы символов для представления строк. Так что имеет смысл разобраться, насколько хорошо C# поддерживает работу с таким представлением строк.
Массив char[] - это обычный массив, элементы которого являются символами. Массив символов можно преобразовать в строку, можно выполнить и обратное преобразование. У класса string есть конструктор, которому в качестве аргументов можно передать массив символов. У класса string есть динамический метод ToCharArray, преобразующий строку в массив символов.
Класс char[], как и всякий класс-массив в C#, является наследником не только класса object, но и класса Array. Некоторые методы класса Array можно рассматривать как операции над строками. Например, метод Copy дает возможность выделять и заменять подстроку в теле строки. Методы IndexOf, LastIndexOf позволяют определить индексы первого и последнего вхождения в строку некоторого символа. К сожалению, их нельзя использовать для более интересной операции - нахождения индекса вхождения подстроки в строку. При необходимости такую процедуру можно написать самому. Вот как она выглядит:
int IndexOfStr( char[]s1, char[] s2) { //возвращает индекс первого вхождения подстроки s2 в строку s1 int i =0, j=0, n=s1.Length-s2.Length; bool found = false; while( (i<=n) && !found) { j = Array.IndexOf(s1,s2[0],i); if (j <= n) { found=true; int k = 0; while ((k < s2.Length)&& found) { found =char.Equals(s1[k+j],s2[k]); k++; } } i=j+1; } if(found) return(j); else return(-1); }//IndexOfStr
В реализации используется метод IndexOf класса Array, позволяющий найти начало совпадения строк, после чего проверяется совпадение остальных символов. Реализованный здесь алгоритм является самым очевидным, но не самым эффективным.
А теперь рассмотрим метод, тестирующий преобразования строк и массивов символов.
/// <summary> /// Строки и массивы символов /// </summary> public void TestCharArray() { const string STROKA = "Строка "; const string HAS = " содержит подстроку "; const string NO = "не "; string source = "Петроград", pattern = "рад"; char[] sour = source.ToCharArray(); char[] pat = pattern.ToCharArray(); int first = SymAndStr.IndexOfStr(sour, pat); if ( first >= 0) Console.WriteLine(STROKA + source + HAS + pattern); else Console.WriteLine(STROKA + source + NO + HAS + pattern); string word = new string(sour, first - 1, 4); Console.WriteLine(word); }
2.2. Существует ли в C# строки типа char*
В языке C# указатели допускаются в блоках, отмеченных как небезопасные. Теоретически в таких блоках можно объявить переменную типа char*, рассматривая ее как строку. В C# строки типа char* использовать не рекомендуется.
Основным типом при работе со строками является тип string, задающий строки переменной длины. Класс string в языке C# относится к ссылочным типам. Над строками - объектами этого класса - определен широкий набор операций, соответствующий современному представлению о том, как должен быть устроен строковый тип.
Объекты класса string объявляются как все прочие объекты простых типов - с явной или отложенной инициализацией, с явным или неявным вызовом конструктора класса. Чаще всего, при объявлении строковой переменной конструктор явно не вызывается, а инициализация задается строковой константой. Но у класса string достаточно много конструкторов. Они позволяют сконструировать строку из:
символа, повторенного заданное число раз;
массива символов char[];
части массива символов.
Некоторым конструкторам в качестве параметра инициализации можно передать строку, заданную типом char*. Но все это небезопасно, и подобные примеры приводиться и обсуждаться не будут. Приведу примеры объявления строк с вызовом разных конструкторов:
public void TestDeclStrings() { //конструкторы string world = "Мир"; //string s1 = new string("s1"); //string s2 = new string(); string sssss = new string('s',5); char[] yes = "Yes".ToCharArray(); string stryes = new string(yes); string strye = new string(yes,0,2); Console.WriteLine("world = {0}; sssss={1}; stryes={2};"+ " strye= {3}", world, sssss, stryes, strye); }
Объект world создан без явного вызова конструктора, а объекты sssss, stryes, strye созданы разными конструкторами класса string. Заметьте, не допускается явный вызов конструктора по умолчанию - конструктора без параметров. Нет также конструктора, которому в качестве аргумента можно передать обычную строковую константу. Соответствующие операторы в тексте закомментированы.
Над строками определены следующие операции:
присваивание (=);
две операции проверки эквивалентности (= =) и (!=);
конкатенация или сцепление строк (+);
взятие индекса ([]).
Начну с присваивания. Поскольку string - это ссылочный тип, в результате присваивания создается ссылка на константную строку, хранимую в "куче". С одной и той же строкой в "куче" может быть связано несколько переменных строкового типа. Эти переменные являются синонимами - разными именами одного и того же объекта, разделяя общую память.
В отличие от других ссылочных типов операции, проверяющие эквивалентность, сравнивают значения строк, а не ссылки. Эти операции выполняются так же, как над значимыми типами.
Бинарная операция "+" сцепляет две строки, приписывая вторую строку к хвосту первой.
Возможность взятия индекса при работе со строками отражает тот приятный факт, что строку можно рассматривать как массив и получать без труда каждый ее символ. Каждый символ строки имеет тип char, он доступен только для чтения, но не для записи.
Вот пример, в котором над строками выполняются данные операции:
/// <summary> /// Операции над строками /// </summary> public void TestOpers() { const string DEL = "->"; string s1 = "ABC", s2 = "CDE"; string s3 = s1 + s2; string s4 = s3.Substring(0, 3); bool b1 = (s1 == s4); char ch1 = s1[2]; Console.WriteLine(s1 + DEL + s2 + DEL + s3 + DEL + b1.ToString() + DEL + ch1.ToString()); }