Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

ооп теория

.pdf
Скачиваний:
20
Добавлен:
14.02.2015
Размер:
3.58 Mб
Скачать

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

Console.WriteLine("кок и кук"); strpat="(т|к).(т|к)";

str="кок тот кук тут как кот";

FindMatches(str, strpat);

Вот результаты работы этого фрагмента кода.

Рис. 15.4. Регулярные выражения. Пример "кок и кук"

Пример "обратные ссылки"

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

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

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

RegularExpression, тем не менее, позволяет решить эту задачу. Причина в том, что расширение стандартных регулярных выражений в Net Framework

является не только синтаксическим. Содержательные расширения связаны с введением понятия группы, которой отводится память и дается имя. Это и дает возможность ссылаться на уже созданные группы, что и делает грамматику языка контекстно-зависимой. Ссылка на ранее полученную группу называется обратной ссылкой. Признаком обратной ссылки является пара символов "\k", после которой идет имя группы. Приведу пример:

Console.WriteLine("Ссылка назад - второе вхождение слова");

271

strpat = @"\s(?<word>\w+)\s\k'word'";

str = "I know know that, You know that!"; FindMatches(str, strpat);

Рассмотрим более подробно регулярное выражение, заданное строкой strpat. В группе, заданной скобочным выражением, после знака вопроса идет имя группы "word", взятое в угловые скобки. После имени группы идет шаблон, описывающий данную группу, в нашем примере шаблон задается произвольным идентификатором "\w+". В дальнейшем описании шаблона задается ссылка на группу с именем "word". Здесь имя группы заключено в одинарные кавычки. Поиск успешно справился с поставленной задачей,

подтверждением чему являются результаты работы этого фрагмента кода.

Рис. 15.5. Регулярные выражения. Пример "обратные ссылки"

Пример "Дом Джека"

Давайте вернемся к задаче разбора предложения на элементы. В классе string для этого имеется метод Split, который и решает поставленную задачу.

Однако у этого метода есть существенный недостаток, - он не справляется с идущими подряд разделителями и создает для таких пар пустые слова. Метод

Split класса Regex лишен этих недостатков, в качестве разделителей можно задавать любую пару символов, произвольное число пробелов и другие комбинации символов. Повторим наш прежний пример:

public void TestParsing()

{

string str,strpat;

 

//разбор

предложения - создание массива слов

str =

это пшеница, которая в темном чулане

 

хранится," +" в доме, который

построил Джек!";

strpat =" +|, +";

 

Regex

pat = new Regex(strpat);

 

string[]

words;

 

words

= pat.Split(str);

 

272

int i=1;

foreach(string word in words) Console.WriteLine("{0}: {1}",i++,word);

}//TestParsing

Регулярное выражение, заданное строкой strpat, определяет множество разделителей. Заметьте, в качестве разделителя задан пробел, повторенный сколь угодно много раз, либо пара символов - запятая и пробел. Разделители задаются регулярными выражениями. Метод Split применяется к объекту pat

класса Regex. В качестве аргумента методу передается текст, подлежащий расщеплению. Вот как выглядит массив слов после применения метода Split.

Рис. 15.6. Регулярные выражения. Пример "Дом Джека"

ПРИМЕР "АТРИБУТЫ"

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

XML-документы, разбором которых приходится заниматься. В качестве заключительного примера рассмотрим структурированный документ, строки которого содержат некоторые атрибуты, например, телефон, адрес и e-mail.

Структуру документа можно задавать по-разному; будем предполагать, что каждый атрибут задается парой "имя: Значение" Наша задача состоит в том,

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

соответствует одному атрибуту. Приведу начальный фрагмент кода

273

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

public void TestAttributes()

{

string s1 = "tel: (831-2) 94-20-55 ";

string s2 = "Адрес: 117926, Москва, 5-й Донской проезд,

стр.10,кв.7 ";

string s3 = "e-mail: Valentin.Berestov@tverorg.ru "; string s4 = s1+ s2 + s3;

string s5 = s2 + s1 + s3;

string pat1 = @"tel:\s(?<tel>\((\d|-)*\)\s(\d|-)+)\s"; string pat2= @"Адрес:\s(?<addr>[0-9А-Яа-я \-\,\.]+)\s"; string pat3 =@"e-mail:\s(?<em>[a-zA-Z.@]+)\s";

string compat = pat1+pat2+pat3; string tel="", addr = "", em = "";

Строки s4 и s5 представляют строку разбираемого документа. Их две,

для того чтобы можно было проводить эксперименты, когда атрибуты в документе представлены в произвольном порядке. Каждая из строк pat1, pat2, pat3 задает одну именованную группу в регулярном выражении, имена групп

- tel, Адрес, e-mail - даются в соответствии со смыслом атрибутов. Сами шаблоны подробно описывать не буду - сделаю лишь одно замечание.

Например, шаблон телефона исходит из того, что номеру предшествует код,

заключенный в круглые скобки. Поскольку сами скобки играют особую роль,

то для задания скобки как символа используется пара - "\(". Это же касается и многих других символов, используемых в шаблонах, - точки, дефиса и т.п.

Строка compat представляет составное регулярное выражение, содержащее все три группы. Строки tel, addr и em нам понадобятся для размещения в них результатов разбора. Применим вначале к строкам s4 и s5 каждый из шаблонов pat1, pat2, pat3 в отдельности и выделим соответствующий атрибут из строки. Вот код, выполняющий эти операции:

Regex reg1 = new Regex(pat1);

Match match1= reg1.Match(s4); Console.WriteLine("Value =" + match1.Value); tel= match1.Groups["tel"].Value; Console.WriteLine(tel);

Regex reg2 = new Regex(pat2); Match match2= reg2.Match(s5);

Console.WriteLine("Value =" + match2.Value);

274

addr= match2.Groups["addr"].Value; Console.WriteLine(addr);

Regex reg3 = new Regex(pat3); Match match3= reg3.Match(s5);

Console.WriteLine("Value =" + match3.Value); em= match3.Groups["em"].Value; Console.WriteLine(em);

Все выполняется нужным образом - создаются именованные группы, к ним можно получить доступ и извлечь найденный значения атрибутов. А теперь попробуем решить ту же задачу одним махом, используя составной шаблон compat:

Regex comreg = new Regex(compat);

Match commatch= comreg.Match(s4); tel= commatch.Groups["tel"].Value; Console.WriteLine(tel);

addr= commatch.Groups["addr"].Value; Console.WriteLine(addr);

em= commatch.Groups["em"].Value; Console.WriteLine(em);

}// TestAttributes

И эта задача успешно решается. Взгляните на результаты разбора текста.

Рис. 15.7. Регулярные выражения. Пример "Атрибуты"

На этом и завершим рассмотрение регулярных выражений а также лекции,

посвященные работе с текстами в C#.

275

Тема 16. КЛАССЫ СОДЕРЖАНИЕ ЛЕКЦИИ:

КЛАССЫ И ООП

o ДВЕ РОЛИ КЛАССОВ СИНТАКСИС КЛАССА

o ПОЛЯ КЛАССА

ДОСТУП К ПОЛЯМ

oМЕТОДЫ КЛАССА

ДОСТУП К МЕТОДАМ

МЕТОДЫ-СВОЙСТВА

ИНДЕКСАТОРЫ

ОПЕРАЦИИ

o СТАТИЧЕСКИЕ ПОЛЯ И МЕТОДЫ КЛАССА o КОНСТАНТЫ

oКОНСТРУКТОРЫ КЛАССА

ДЕСТРУКТОРЫ КЛАССА ПРОЕКТИРОВАНИЕ КЛАССА RATIONAL

o СВОЙСТВА КЛАССА RATIONAL

o КОНСТРУКТОРЫ КЛАССА RATIONAL

oМЕТОДЫ КЛАССА RATIONAL

ЗАКРЫТЫЙ МЕТОД НОД

ПЕЧАТЬ РАЦИОНАЛЬНЫХ ЧИСЕЛ

ТЕСТИРОВАНИЕ СОЗДАНИЯ РАЦИОНАЛЬНЫХ

ЧИСЕЛ

o ОПЕРАЦИИ НАД РАЦИОНАЛЬНЫМИ ЧИСЛАМИ

o КОНСТАНТЫ КЛАССА RATIONAL

276

КЛАССЫ И ООП

Объектно-ориентированное программирование и проектирование построено на классах. Любую программную систему, выстроенную в объектном стиле, можно рассматривать как совокупность классов, возможно,

объединенных в проекты, пространства имен, решения, как это делается при программировании в Visual Studio .Net.

ДВЕ РОЛИ КЛАССОВ

У класса две различные роли: модуля и типа данных. Класс - это модуль, архитектурная единица построения программной системы.

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

она может решать ту же задачу, что и система, состоящая из многих модулей.

Вторая роль класса не менее важна. Класс - это тип данных, задающий реализацию некоторой абстракции данных, характерной для задачи, в

интересах которой создается программная система. С этих позиций классы -

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

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

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

класс. Если вы создаете класс Account, реализующий такую абстракцию как

277

банковский счет, то в этот класс нельзя добавить поля из класса Car,

задающего автомобиль.

Объектно-ориентированная разработка программной системы основана на стиле, называемом проектированием от данных. Проектирование системы сводится к поиску абстракций данных, подходящих для конкретной задачи.

Каждая из таких абстракций реализуется в виде класса, которые и становятся модулями - архитектурными единицами построения нашей системы. В

основе класса лежит абстрактный тип данных.

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

Синтаксис класса

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

[атрибуты][модификаторы]class имя_класса[:список_родителей] {тело_класса}

Атрибутам будет посвящена отдельная лекция. Возможными модификаторами в объявлении класса могут быть модификаторы new, abstract, sealed, о которых подробно будет говориться при рассмотрении наследования, и четыре модификатора доступа, два из которых - private и protected - могут быть заданы только для вложенных классов. Обычно класс

278

имеет атрибут доступа public, являющийся значением по умолчанию. Так что в простых случаях объявление класса выглядит так:

public class Rational {тело_класса}

В теле класса могут быть объявлены:

константы;

поля;

конструкторы и деструкторы;

методы;

события;

делегаты;

классы (структуры, интерфейсы, перечисления).

О событиях и делегатах предстоит подробный разговор в последующих лекциях. Из синтаксиса следует, что классы могут быть вложенными. Такая ситуация - довольно редкая. Ее стоит использовать, когда некоторый класс носит вспомогательный характер, разрабатывается в интересах другого класса, и есть полная уверенность, что внутренний класс никому не понадобится, кроме класса, в который он вложен. Как уже упоминалось,

внутренние классы обычно имеют модификатор доступа, отличный от public.

Основу любого класса составляют его конструкторы, поля и методы.

Поля класса

Поля класса синтаксически являются обычными переменными

(объектами) языка. Их описание удовлетворяет обычным правилам объявления переменных, о чем подробно говорилось в лекции 5.

Содержательно поля задают представление той самой абстракции данных,

которую реализует класс. Поля характеризуют свойства объектов класса.

279

Напомню, что, когда создается новый объект класса (в динамической памяти или в стеке), то этот объект представляет собой набор полей класса. Два объекта одного класса имеют один и тот же набор полей, но разнятся значениями, хранимыми в этих полях. Все объекты класса Person могут иметь поле, характеризующее рост персоны, но один объект может быть высокого роста, другой - низкого, а третий - среднего роста.

Доступ к полям

Каждое поле имеет модификатор доступа, принимающий одно из четырех значений: public, private, protected, internal. Атрибутом доступа по умолчанию является атрибут private. Независимо от значения атрибута доступа, все поля доступны для всех методов класса. Они являются для методов класса глобальной информацией, с которой работают все методы,

извлекая из полей нужные им данные и изменяя их значения в ходе работы.

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

но часто желательно, чтобы некоторые из них были доступны в более широком контексте. Если некоторые поля класса A должны быть доступны для методов класса B, являющегося потомком класса A, то эти поля следует снабдить атрибутом protected. Такие поля называются защищенными. Если некоторые поля должны быть доступны для методов классов B1, B2 и так далее, дружественных по отношению к классу A, то эти поля следует снабдить атрибутом internal, а все дружественные классы B поместить в один проект (assembly). Такие поля называются дружественными. Наконец, если некоторые поля должны быть доступны для методов любого класса B,

которому доступен сам класс A, то эти поля следует снабдить атрибутом public. Такие поля называются общедоступными или открытыми.

Методы класса

280