Опубликован: 02.12.2009 | Уровень: для всех | Доступ: свободно | ВУЗ: Тверской государственный университет
Лекция 7:

Символы и строки

< Лекция 6 || Лекция 7: 1234567
Аннотация: Эта лекция посвящена работе с текстовой информацией. Рассматриваются различные типы данных, применяемые при работе с текстами - char, string, stringBuilder. Подробно обсуждаются классические алгоритмы поиска и сортировки строковых данных.

Проект к данной лекции Вы можете скачать здесь.

Общий взгляд

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

  • отдельные символы, чаще всего его называют типом char;
  • строки постоянной длины, часто они представляются массивом символов;
  • строки переменной длины - это, как правило, тип string, соответствующий современному представлению о строковом типе.

Символьный тип char, представляющий частный случай строк длиной 1, полезен во многих задачах. Основные операции над строками - это разбор и сборка. При их выполнении приходится чаще всего доходить до каждого символа строки. В языке Паскаль, где был введен тип char, сам строковый тип рассматривался, как char[]-массив символов. При таком подходе получение i-го символа строки становится такой же простой операцией, как и получение i-го элемента массива, следовательно, эффективно реализуются обычные операции над строками - определение вхождения одной строки в другую, выделение подстроки, замена символов строки. Однако заметьте, представление строки массивом символов хорошо только для строк постоянной длины. Массив не приспособлен к изменению его размеров, вставки или удалению символов (подстрок).

Наиболее часто используемым строковым типом является тип, обычно называемый string, который задает строки переменной длины. Над этим типом допускаются операции поиска вхождения одной строки в другую, операции вставки, замены и удаления подстрок.

Тип string в языке C# допускает двойственную интерпретацию. С одной стороны, значения переменной типа string можно рассматривать, как неделимое значение - скаляр - строку текста. С другой стороны, это значение можно интерпретировать, как массив из n элементов, где n - это длина строки. Каждый такой элемент задает отдельный символ и принадлежит символьному типу char.

string s1 = "рок", s2 = "око",;
char ch1, ch2, ch3;
ch1 = s1[0];	ch2 = s1[1]; 	ch3 = s1[2];
string s3 = s1 + s2;

В этом примере показано, как можно работать с отдельными символами строки и как можно работать со скалярным представлением строки.

Класс char

В 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 символов, большинство кодов зарезервировано и им пока не соответствуют реальные символы. Рассмотрим пример, демонстрирующий коды некоторых символов.

// <summary>
   /// Преобразования код <-> символ
   /// </summary>
   public void SymToFromCode()
   {
       char sym1 = '0', sym2 = 'a', 
      sym3 = 'A', sym4 = '\r',
      sym5 = 'а', sym6 = 'А';
       PrintCode(sym1); PrintCode(sym2);
       PrintCode(sym3); PrintCode(sym4);
       PrintCode(sym5); PrintCode(sym6);
       int code1 = 13, code2 = 122, 
      code3 = 1071, code4 = 70000;
       PrintSym(code1); PrintSym(code2);
       PrintSym(code3); PrintSym(code4); 
   }

Процедуры печати 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, крайне полезные при разборе строки. Приведу примеры, в которых используются многие из перечисленных методов:

/// <summary>
  /// Свойства символов
  /// </summary>
  public void TestCharMethods()
  {     
      Console.WriteLine("Метод GetUnicodeCategory:");
      System.Globalization.UnicodeCategory cat1, cat2;
      cat1 = char.GetUnicodeCategory('A');
      cat2 = char.GetUnicodeCategory(';');      
      Console.WriteLine("'A' - category {0}", cat1);
      Console.WriteLine("';' - category {0}", cat2);
      Console.WriteLine("Метод IsLetter:");
      Console.WriteLine("'z' - IsLetter - {0}",
        char.IsLetter('z'));
      Console.WriteLine("'Я' - IsLetter - {0}",
         char.IsLetter('Я'));
      Console.WriteLine("Метод IsLetterOrDigit:");
      Console.WriteLine("'7' - IsLetterOrDigit - {0}",
         char.IsLetterOrDigit('7'));
      Console.WriteLine("'Я' - IsLetterOrDigit - {0}",
         char.IsLetterOrDigit('Я'));
      Console.WriteLine("Метод IsControl:");
      Console.WriteLine("';' - IsControl - {0}",
         char.IsControl(';'));
      Console.WriteLine(@"'\r' - IsControl - {0}",
         char.IsControl('\r'));
      Console.WriteLine("Метод IsSeparator:");
      Console.WriteLine("' ' - IsSeparator - {0}",
         char.IsSeparator(' '));
      Console.WriteLine("';' - IsSeparator - {0}",
         char.IsSeparator(';'));
      Console.WriteLine("Метод IsWhiteSpace:");
      Console.WriteLine("' ' - IsWhiteSpace - {0}",
        char.IsWhiteSpace(' '));
      Console.WriteLine(@"'\r' - IsWhiteSpace - {0}",
    char.IsWhiteSpace('\r'));     
  }//TestCharMethods

Вот как выглядят результаты консольного вывода, порожденного выполнением метода.

Свойства символов

Рис. 7.2. Свойства символов

Обратите внимание: буквенными символами являются как символы латиницы, так и кириллицы, символ возврата каретки относится к белым пробелам и к управляющим символам, а символ точки с запятой к разделителям не относится.

Кроме статических методов, у класса char есть и динамические методы. Большинство из них - это методы родительского класса object, унаследованные и переопределенные в классе char. Из собственных динамических методов стоит отметить метод CompareTo, позволяющий проводить сравнение символов. Он отличается от метода Equal тем, что для несовпадающих символов выдает "расстояние" между символами в соответствии с их упорядоченностью в кодировке Unicode.

Класс 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);
  }

Существует ли в C# строки типа char*

В языке C# указатели допускаются в блоках, отмеченных как небезопасные. Теоретически в таких блоках можно объявить переменную типа char*, рассматривая ее как строку. В C# строки типа char* использовать не рекомендуется.

< Лекция 6 || Лекция 7: 1234567
Гулжанат Ергалиева
Гулжанат Ергалиева
Федор Антонов
Федор Антонов

Здравствуйте!

Записался на ваш курс, но не понимаю как произвести оплату.

Надо ли писать заявление и, если да, то куда отправлять?

как я получу диплом о профессиональной переподготовке?