ПРОГРАММИСТУ
Дж. Бишоп, Н. Хорспул
в кратком изложении Перевод с английского К.Г.Финогенова
Москва БИНОМ. Лаборатория знаний 2005
УДК 004.65 ББК 32.973 Б59
Б59
Бишоп Дж. С# в кратком изложении / Дж. Бишоп, Н. Хорспул; Пер. с англ. — М.: БИНОМ. Лаборатория знаний, 2005. — 472 с , ил. ISBN 5-94774-211-Х (русск.) ISBN 0-321-15418-5 (англ.) Книга предназначена для обучения основам объектно-ориентированного программирования с использованием языка С# и затрагивает почти все основные средства языка, включая пространства имен, использование коллекций и программирование сетевых задач. Особенное внимание уделяется концепциям полиморфизма и расширяемости. Книга изобилует многочисленными примерами, представляющими собой функционирующие программы, и сводными таблицами с компактным описанием основных языковых средств. Ориентированная прежде всего на студентов первого года обучения, книга в равной степени адресована студентам всех уровней, для которых она будет служить прекрасным пособием, а также всем, кто работает на других языках и желает перейти на С#. УДК 004.65 ББК 32.973
Учебное издание Бишоп Джудит, Хорспул Найджел С# в кратком изложении Ведущий редактор И. Маховая. Художник Ф. Инфантэ Художественный редактор О. Лапко. Корректор И. Королёва Компьютерная верстка Л. Катуркина Подписано в печать 27.12.04. Формат 70x100'/ . Гарнитура Школьная. Бумага офсетная. Печать офсетная. Усл. печ. л. 38,2. Тираж 3000 экз. Заказ 2047 Издательство «БИНОМ. Лаборатория знаний». Адрес для переписки: 119071, Москва а/я 32. Телефон (095)955-0398. E-mail:
[email protected]. http://www.lbz.ru. Отпечатано с готовых диапозитивов в полиграфической фирме «Полиграфист». 160001, г. Вологда, ул. Челюскинцев, 3. © Pearson Education Limited 2004 This translation of C# Concisely, First Edition is published by arrangement with Pearson Education
ISBN 5-94774-211-Х (русск.) ISBN 0-321-15418-5 (англ.)
^ д н а р у с с к и й ЯЗЫК) БИНОМ. Лаборатория знаний, 2005
@ п
К читателям
Многие книги, посвященные компьютерам, имеют такой объем, что, поднимая их, вы рискуете заработать грыжу, однако материала в них не больше, чем в бульварной газете. Академические учебники зачастую так скучны, что у вас плавятся мозги, хотя они не учат вас ничему, что могло бы пригодиться в реальной жизни. Но только не эта книга! Этот краткий курс аккумулирует в себе многолетний опыт и знания авторов. В каждую главу включены многочисленные примеры и упражнения, но что мне лично нравится больше всего —так это волшебная россыпь компактных справочных «форм», щедро разбросанная по всей книге. Широкое использование не зависящего от платформы пространства имен Views, специально разработанного для этой книги и позволяющего создавать элементы графического интерфейса пользователя, облегчает изучение материала и способствует приобретению практического опыта. Еще одним достоинством книги является включение специальной главы, посвященной отладке. Даже в невероятном предположении, что вы при создании программы никогда не вносите в нее ошибки, отладка представляет собой идеальное средство для понимания динамики работы программы; ну а для нас, простых людей, это дополнительное мощное средство локализации наших ошибок. Имея на своем столе эту книгу, вы превратите каждый рабочий день в праздничный. Эрик Мейер руководитель группы технической поддержки Microsoft WebData Team 31 июля 2003 г.
Предисловие Уилльяму и Майклу, как и всегда, а также Джанет, без любви и поддержки которых эта книга не была бы написана Дж. Бишоп Микаэле, которая в течение подготовки этой книги щедро помогала и ободряла нас; мы и представить себе не могли, что эта работа займет столько времени Н. Хорспул Написание этой книги заняло у нас целый год. Нам хотелось бы объяснить вам, будь вы преподаватель, студент или просто любознательный читатель, зачем мы взяли на себя этот труд и что вы почерпнете из этой книги.
С# — откуда и куда С# — это новый язык, разработанный Эндерсом Хейлсбергом (Anders Hejlsberg), Скоттом Уилтамутом (Scott Wiltamuth) и Питером Гоулдом (Peter Golde) в корпорации Microsoft в качестве основной среды разработки для .Net Framework и всех будущих продуктов Microsoft. C# берет свое начало в других языках, в основном, в C++, Java, Delphi, Modula-2 и Smalltalk. Про Хейлсберга следует сказать, что он был главным архитектором Turbo Pascal и Borland Delphi, и его огромный опыт способствовал весьма тщательной проработке нового языка. С одной стороны, для С# в еще большей степени, чем для упомянутых выше языков, характерна внутренняя объектная ориентация; с другой стороны, в нем реализована новая концепция упрощения объектов, что существенно облегчает освоение мира объектно-ориентированного программирования. В настоящее время С# утверждается среди языков, используемых как для разработки нового программного обеспечения, так и для обучения. Поскольку эта книга является прежде всего учебником, мы хотели бы подчеркнуть, что, по нашему мнению, С# является идеальным языком для начального обучения программированию. Некоторым препятствием здесь является консервативность: в большинстве организаций твердые позиции занял язык Java и такое его положение поддерживается новыми разработками и всей инфрастуктурой. С# также удобен для более специализированных курсов. Ввиду того что этот язык поддерживает такие современные концепции, как делегирование и перегрузку операторов, и даже делаются предложения о реализации родовых классов, С# представляется превосходной средой для таких, в частности, курсов, как «Современные методы программирования», «Структуры данных», «Сетецентрические модели вычислений» и «Распределенные системы». Использование С# может также оказаться полезным для курсов повышения квалификации с производственным
Предисловие
уклоном, к каковым относятся многие программы обучения Microsoft, поскольку обучающиеся сразу обеспечиваются реальной средой программирования. В отличие от Java, C# можно использовать только при определенных обстоятельствах. Если академическая организация принадлежит объединению Microsoft Academic Alliance (вступить куда можно за минимальную плату), то программное обеспечение будет доступно как сотрудникам, так и студентам. Язык С# (но не все его библиотеки) утвержден в качестве стандарта Европейской ассоциацией ЕСМА (в декабре 2001 г.) в результате чего появились и другие (также бесплатные) компиляторы для Windows и других платформ. Сама корпорация Microsoft выпустила три таких продукта под кодовым именем Rotor для платформ Windows, FreeBSD и Macintosh OSX. Продукты Microsoft обычно отличаются интенсивным потреблением ресурсов и требуют таких компьютеров, которые находятся за пределами возможностей многих студенческих лабораторий. В силу этого бесплатные компиляторы могут оказаться предпочтительнее. Однако в стандарте ЕСМА имеется серьезное упущение — отсутствует прикладной интерфейс Windows.Form, используемый для программирования графических интерфейсов пользователя. Мы работаем с корпорацией Microsoft над восполнением этого пробела с апреля 2002 г., и результатом этой работы явился продукт Views, небольшая экономная система графического интерфейса пользователя (GUI), основанная на языке XML. Разработаны варианты Views как для стандартных Windows-систем, так и для других платформ, воспринимающих систему Rotor. Совокупность системы Views и бесплатных компиляторов С# дает вполне жизнеспособную и технологически современную альтернативу комбинации лицензионных С# и Visual Studio корпорации Microsoft, обеспечивая при этом еще и независимость от платформы. С# несомненно имеет большое будущее как в обучении и исследованиях, так и в системных разработках. Целью этой книги является приближение этого будущего путем максимально широкого распространения языка С#.
Обзор книги Для современных учебников по программированию стала общим местом декларация использования подхода «объекты в первую очередь». Внимательное изучение учебников и докладов на образовательных конференциях по компьютерным направлениям показывает, что смысл выражения «объекты в первую очередь» довольно туманен. Более того, в этих работах редко указывается, что же понимается под второй или третьей очередью. Несомненно, настоящая книга использует подход «объекты в первую очередь», при этом, используя новаторскую методику изложения, нам удалось, надеемся, вложить в это выражение более четкий смысл. В гл. 2 мы вводим объекты действительно в первую очередь путем исполь-
8
Предисловие
зования их, а не определения для них типов. Мы основываем изложение на двух встроенных структурных типах, DateTime (структура) и Random (класс), и развиваем концепции реализации, переменных, методов и присваивания значительно более естественным путем, чем было бы возможно в случае требования первоначального определения типов. Далее в гл. 3 мы рассматриваем вопрос определения типа, возвращаясь назад и обобщая уже знакомые читателю концепции, а также развивая формализм таких понятий, как параметры и свойства. Мы приняли твердое решение оставаться на этой стадии в мире значений переменных, используя для создания объектов структуры и упоминая ссылки лишь как средство передачи параметров. Мы уверены, что лучшим педагогическим принципом является строго последовательное рассмотрение концепций, и, откладывая до гл. 7 детальное рассмотрение аппарата ссылок, мы тем самым устраняем часто возникающие недоумения по поводу использования значений переменных и ссылок на них. Ввиду такого решения обсуждение классов начинается лишь в гл. 8. С# в большей степени, чем другие современные объектно-ориентированные языки, опирается на понятие типа, причем это понятие интерпретируется одинаково, независимо от того, идет ли речь о скалярной переменной типа int, структуре или классе. Мы придерживаемся того же принципа, и откладывание обсуждения классов на более поздний этап не приводит к каким-либо неестественным программным конструкциям. На вопрос «если объекты в первую очередь, то что во вторую?» мы отвечаем в гл. 4: управляющие структуры, строки и массивы. Все они представляют собой базовые строительные блоки, используемые при разработке программ. Без этих блоков нельзя полноценно использовать более мощные объектно-ориентированные средства — коллекции и полиморфизм. Перед тем, однако, как перейти к этим полезным предметам, мы потратим две главы на то, чтобы заложить основы целенаправленного программирования. Глава 5 описывает систему Views, которую мы разработали в качестве дополнения, облегчающего обслуживание графических пользовательских интерфейсов в любой С#-программе. Имея такое инструментальное средство, вы получаете возможность быстрее разрабатывать программы, взаимодействующие с пользователем, причем независимо от платформы. В гл. 7 мы завершаем исследование ввода и вывода, рассмотрев хранение данных в файлах, а также представление файлов в С# в виде потоков данных. Гл. 6, посвященная ошибкам и процедурам отладки, почти уникальна для учебников по программированию. В этой главе описываются как стандартные, так и новые методики отладки, от выводимых на экран сообщений до контрольных вызовов. Большая часть этих методик не требует никакого дополнительного программного обеспечения кроме компилятора С#. В отдельном разделе описывается использование отладчика, а в Приложении Ж приводятся дополнительные сведения об отладочных возможностях Microsoft Visual Studio.
Предисловие
К этому моменту программисты смогут справиться с любой задачей, доступной решению на более старых языках, но в объектно-ориентированном стиле. Главы 8 и 9 завершают тему объектов рассмотрением коллекций (с которыми особенно удобно работать на С#), а также расширяемости и полиморфизма. В гл. 9 мы проводим обсуждение принципов, а затем показываем, как они могут быть реализованы с помощью любой из трех имеющихся методик — наследования, интерфейсов и абстрактных классов. В девяти первых главах книги мы рассмотрели большую часть языка С#, хотя, разумеется, остался еще целый пласт функций прикладного интерфейса пользователя (API). Какие из них особенно важны? Какие следует тщательно изучить? Книга не может считаться завершенной, если в ней на рассказывается, как выполнять рисование различных изображений. Поэтому мы посвятили гл. 10 рассмотрению API пространства имен System.Drawing, показав использование этих возможностей как независимо, так и интегрированными в систему View. Далее, неотъемлемой частью современной жизни является распределенная обработка данных, и мы посвятили несколько страниц способам, с помощью которых компьютеры могут взаимодействовать в С#. Мы показываем, как эти способы позволяют с легкостью реализовывать даже весьма сложные программы.
Наш подход Настоящая книга выполняет свои обучающие функции с помощью смеси равных долей формализма и примеров. Каждый новый структурный компонент или встроенный тип описывается сначала в виде «формы» — таблицы, в которой даются синтаксис и семантика объекта, затем приводятся простые примеры и, наконец, рассматривается полный текст работоспособной программы. Таких программ насчитывается в книге более 50; некоторые из них развиваются по мере перехода от главы к главе, демонстрируя, как новые средства позволяют получить более полное решение исходной задачи. Важными аспектами программирования являются графический интерфейс пользователя (GUI) и тестирование программ. Везде, где можно, для получения тестовых данных используется генератор случайных чисел, что позволяет должным образом испытать программы и получить разумный объем выходных данных. Никакой язык нельзя представить в виде набора изолированных друг от друга средств, и С# не является исключением. При рассмотрении некоторых средств, таких как вывод, строки, циклы и массивы, мы использовали «спиральный» подход, сначала вводя новые понятия в элементарной форме, и лишь в дальнейшем завершая их полное описание. Хороший учебник должен быть максимально интерактивным, и мы предусмотрели ряд дополнительных средств обучения как в самой книге, так и на соответствующем Web-сайте. Эти средства включают в себя:
1_0
Предисловие
• ряд приложений с перечнем ключевых прикладных интерфейсов и средств языка, объединенных в группы для облегчения поиска; • контрольные вопросники с альтернативными ответами в конце каждой главы 1 ; • разнообразные упражнения разного уровня сложности в конце большинства глав; • все примеры, доступные в сети по адресам http://www.cs.up.ac.za/ csharp или http://www.booksites.net/bishop; • полная система Views, со справочником и примерами; • дискуссионный форум для вопросов, касающихся С#, книги и Views; • набор слайдов и ответов для преподавателя.
Система Views Система Views представляет собой специальный класс для создания прикладных GUI независящим от поставщика способом с использованием спецификации XML. Система была разработана авторами в качестве проекта в рамках инициативы Rotor, организованной корпорацией Microsoft в апреле 2002 г. Система тестировалась студентами первого и второго года обучения, которые с энтузиазмом использовали ее в качестве альтернативы обычному программированию посредством API Windows.Form или Visual Studio. Последнюю версию Views можно переписать по сети с Web-сайта этой книги по адресу http://www.cs.up.ac.za/csharp или http:// www.booksites.net/bishop.
Благодарности Мы хотели бы поблагодарить за значительный практический вклад в эту книгу и в систему Views студентов Polelo Group в Университете Претории — Бэзила Уорралла (Basil Worrall), Катрину Берг (Kathrin Berg), Джони Ло (Johny Lo), Дейвида-Джона Миллера (David-John Miller), Мэнди ван Шалквик (Mandy van Schalkwyk) и Сфисо Щабалала (Sfiso Tshabalala). Глубокая благодарность Эдвину Пиру (Edwin Peer), который разработал и обслуживал Web-репозиторий, столь хорошо поддерживающий эту книгу. В Университете Виктории Джонатан Мейсон (Jonathan Mason) помог найти и устранить ошибки в программах View, Раджвиндер Пейнсар-Уэйлаведж (Rajwinder Panesar-Walawedge) отвечал за реализацию варианта View для системы Rotor, а Бернард Шольц (Bernhard Scholz) (в творческом отпуске, предоставленном Венским Техническим Университетом) Издательство сочло целесообразным дополнить русское издание книги списком правильных ответов. — Прим. ред.
Предисловие
1_1_
работал над важными программами клиентской части Views, которые будут выпущены позже. Нам хотелось бы поблагодарить корпорацию Microsoft за их щедрый дар в поддержку этой книги и системы Views, а Дж. Бишоп особо отмечает инициативу и поддержку со стороны Лианн Скотт-Уилльямс (Leanne Scott-Williams) из корпорации Microsoft (Южная Африка). Эта работа также поддерживалась грантом THRIP из южноафриканского фонда National Research Foundation. H. Хорспул также благодарит Памелу Лоз (Pamela Lauz) из Microsoft Canada за поддержку. Выполняя совместную работу при наличии 10-часовой разницы во времени, мы не могли бы выжить без превосходной интернет-поддержки, предоставленной нашими кафедрами в Университетах Претории и Виктории, а также Microsoft Research Laboratory в Кембридже и Венском Техническом Университете, где эта книга была завершена. Дж. Бишоп особо благодарит д-ра Пола Мира (Paul Meyer) и штат Адденбрукского госпиталя в Кембридже за то, что они поставили ее на ноги в тот момент, когда работа над книгой дошла до завершающих стадий. Поскольку значительная часть работы над книгой выполнялась в необычное время суток, Н. Хорспул хотел бы поблагодарить Torrefazione Italia за весьма необходимый и прекрасно приготовленный кофе, a KBSG Seattle за предоставление удачной фоновой музыки. Дж. Бишоп также ценит вклад винной индустрии Кейптауна и Late Late Show от Classic FM. Наконец, наша общая признательность нашим семьям за их терпение в течение многих часов, проведенных нами дома в работе над этим проектом, а также за стимулирующие обсуждения, в которых они, такие же компьютерно-ориентированные, как и мы сами, могли принимать участие. Мы надеемся, что наши семьи будут так же довольны получившимся результатом, как и мы сами. Джудит Бишоп Претория, Южная Африка и Кембридж, Англия Найджел Хорспул Виктория, Канада и Вена, Австрия Август 2003 г.
Введение
Изучение техники программирования — увлекательнейшее занятие. Столько надо понять перед тем, как удастся написать хотя бы простейшую программу! В первой главе мы постараемся дать именно эти начальные знания. Мы обсудим роль языков программирования и их развитие во времени. Общее направление их совершенствования — позволить программисту меньше думать о деталях и сосредоточиться на решении поставленной перед ним задачи. Для ввода программы в компьютер и ее запуска должны быть предусмотрены какие-то средства, и мы рассмотрим две имеющиеся возможности. Некоторое внимание будет также уделено разработке программного обеспечения.
1.1 Преамбула Любая достаточно развитая технология неотличима от магии. Артур К. Кларк Любой пользователь компьютера, наверное, полностью согласится с этой цитатой знаменитого писателя-фантаста. (Приведенное высказывание известно также под именем Третьего Закона Кларка.) С момента появления электронных компьютеров в начале 40-х гг. управляющее их работой программное обеспечение становилось все более сложным и изощренным. На сегодня оно достигло такой степени сложности, что его функционирование представляется недалеким от магии. Программное обеспечение компьютера приобрело такой объем, что никакой человек не может охватить его целиком. Не следует даже пытаться разобраться во всех деталях его работы. Рассмотрим, однако, некоторую аналогию. Допустим, что мы изучаем юриспруденцию. Количество законов, принятых в стране, и сложность этих законов также превышают возможности их понимания конкретным человеком. Студент-юрист и не пытается запомнить все законы. Вместо этого он постарается изучить основные принципы права, построит некоторую систему знаний, касающихся юриспруденции в целом, освоит специфический язык, используемый в текстах законов, научится рассуждать и делать заключения на основе принципов права и, возмож-
1.2 Роль языков программирования
КЗ
но, постарается приобрести глубокие знания законодательства в одной конкретной области юриспруденции (уголовное право, право собственности, налоговое право и т. д.). Изучая программное обеспечение компьютера, мы следуем тому же подходу. Мы рассматриваем базовые принципы организации программ и работы компьютера. Мы знакомимся с языком, на котором составляют программы, учимся применять принципы на практике и, возможно, становимся специалистами в одной узкой области использования программного обеспечения. В этой книге мы концентрируем наше внимание на единственном языке программирования, называемом С# (произносится «си шарп»). Мы научимся использовать язык С# для разработки простых прикладных программ. По ходу дела мы также познакомимся с некоторыми сторонами функционирования компьютеров.
1.2 Роль языков программирования Компьютер Компьютер представляет собой электронное устройство, состоящее из нескольких основных узлов. Одним из таких компонентов является память, которая хранит комбинации единиц и нолей. Эти единицы и ноли могут физически реализовываться в виде крошечных электронных устройств (в которых протекание тока в одном направлении означает единицу, а в другом — ноль, или устройство может содержать конденсатор, заряженное состояние которого означает единицу, а разряженное — ноль) или микроскопических магнитных полей (направление магнитного потока по часовой стрелке может означать единицу, а против часовой стрелки — ноль), или отражающей способности миниатюрных углублений в металлическом слое вращающегося компакт-диска, или различных состояний любого физического объекта. Возможности здесь неисчерпаемы. Компьютер содержит блоки памяти разных видов, работающие по-разному и предназначенные для выполнения различных задач. К счастью, детали функционирования памяти компьютера обычно не важны для программиста. Программа манипулирует с комбинациями единиц и нолей безотносительно к их физической реализации. Программа использует эти комбинации для обозначения чисел в двоичной форме или букв алфавита, или любой другой информации в соответствии с нашей схемой ее представления. Другим стандартным компонентом компьютера является центральный процессорный узел (central processor unit, CPU) или просто процессор. Процессор может выполнять элементарные действия над комбинациями единиц и нолей в памяти компьютера. Например, процессор может заглянуть в две ячейки компьютерной памяти, извлечь из них комбинации, представляющие два числа, сложить эти числа и сохранить результат в виде новой комбинации единиц и нолей в другой ячейке памяти.
14
Глава 1. Введение
Невероятные способности и кажущееся волшебство функционирования компьютера проистекают из способа управления процессором. Процессору необходимо сообщить, из каких ячеек памяти ему надо извлечь числа; ему следует указать, что числа надо сложить (или, возможно, умножить или вычесть, или выполнить какую-либо другую арифметическую операцию); наконец, процессор должен знать, в какую ячейку памяти отправить результат. После того, как процессор выполнит описанные операции, ему следует сообщить, что делать дальше. Последовательность действий, которые мы хотим получить от процессора, определяется длинной последовательностью единиц и нолей в памяти компьютера. На рис. 1.1 изображена реальная комбинация единиц и нолей, которая заставляет компьютер с процессором Intel Pentium извлечь два числа из конкретных ячеек компьютерной памяти, сложить эти числа и сохранить их сумму в третьей ячейке. Приведенная комбинация не очень наглядна, но это именно то, что понимает компьютер. Комбинации на рисунке изображены в виде трех строк, потому что в них заключено описание трех различных команд, которые последовательно, друг за другом, говорят процессору, что ему делать. 10100001 00000011 10100011
01101000 00000101 01110000
10111100 01101100 10111100
01000001 10111100 01000001
00000000 01000001 00000000
00000000
Рис. 1 . 1 . Двоичная программа сложения двух чисел для процессора Intel Pentium
Пропуски между группами из восьмерок единиц и нолей соответствуют организации памяти в компьютере — в компьютерах с процессором Intel Pentium (как, впрочем, и почти во всех остальных) память разделена на группы таких восьмерок, которые называются байтами или, реже, октетами. Индивидуальные единицы и ноли известны под названием битов; этот термин (bit) является сокращением от слов binary digits (двоичные разряды). Комбинация из 16 байт, показанная на рисунке, является, между прочим, маленьким фрагментом значительно более длинной компьютерной программы. Ведь крайне маловероятно, что мы захотим написать программу, которая выполнит всего лишь одно сложение и после этого завершится.
Представление данных Память компьютера организована в виде групп битов. Почти во всех используемых сегодня компьютерах эти группы состоят из 8, 16, 32, часто 64 и иногда 128 бит. С помощью двоичной системы счисления группы битов можно использовать для представления целых чисел. Например, 8-битовая группа 01101011 представляет целое число 0х2 7 + 1х26 + 1х25 + 0х2 4 + 1х23 + 0х2 2 + 1x2* + 1x2°,
1.2 Роль языков программирования
15
которое равно 0+64+32+0+8+0+2+1, или 107. Процессор компьютера с легкостью может манипулировать с числами, записанными в двоичной системе. Однако в действительности этот вопрос оказывается не таким простым, так как компьютеры должны уметь обрабатывать и отрицательные числа. Кроме того, с помощью специальной системы представления, называемой плавающей точкой, компьютеры могут обрабатывать числа, имеющие очень большую величину, например, 6,023x1023. Различные формы представления чисел будут подробнее рассмотрены в гл. 3. Хотя первые компьютеры разрабатывались исключительно для выполнения операций над числами, компьютерные программисты быстро поняли, что компьютеры могут с таким же успехом обрабатывать тексты, написанные, например, на английском языке. Достаточно назначить каждому алфавитному символу некоторое число, и память компьютера сможет хранить фрагменты текста. Обычно для кодирования символов используется таблица ASCII (American Standard Code for Information Interchange). В соответствии с этой таблицей каждому символу назначается код в диапазоне от 0 до 127; этот код удобно размещается в байте, причем остается еще один неиспользуемый бит. Согласно таблице ASCII, буква «А» (латинская) имеет код 65, буква «В» — 66 и т. д. Слово «BANANA» запишется в виде такой последовательности из 6 целых чисел: 66 65 78 65 78 65 и займет в памяти компьютера 6 последовательных байтов. Компьютеру редко приходится выполнять арифметические операции над числами, представляющими текст (пожалуй, разумным примером является прибавление 1 для получения следующей буквы алфавита). Однако символы можно перемещать с одного места на другое, а также сравнивать друг с другом. Например, поскольку компьютер может определить, что 65
«
:^
»n:J
с 23
« O r *
RtoOH (МЦНМХ ou 8
"
rsfS'lit:
Л.
•' I (.«TttfV "
; B SJ
;:,
i
tV,*? |1СС#вЯ
;•
;
J
Orir*}
"
iv.tllm Stv.rt.--i
;:.
"
-::
. - . ,1 ,. - M V ! « - I
i2L
M ndBv|
V
'f> static
void »
:i I
Sain
Tryi)>0e
:)
jjj
Jj
» Я ;;
1*' j i 1' { tM i -i ^
.... о ^
.'.;*„'.
Рис. 1.6. IDE Visual Studio .NET
3
Под именем Unix мы понимаем все варианты этой системы, включая Linux, FreeBSD и Solaris.
26
Глава 1. Введение
Естественно было ожидать, что программисты создадут инструментальные программные средства, облегчающие труд программиста.Visual Studio является лишь одним примером таких средств. Интерактивная среда разработки типично включает в себя следующие элементы: • интерактивный текстовый редактор, обычно содержащий средства форматирования и облагораживания внешнего вида программы; • интерактивный справочник, предоставляющий информацию о языке программирования и сопутствующем программном обеспечении; • средства разработки графического интерфейса пользователя (GUI); • компилятор; • отладчик, позволяющий выполнять пробные прогоны программы с контролем ее выполнения. Среда IDE вроде Visual Studio .Net предназначена для профессиональных программистов. Хотя она сильно способствует сокращению времени, требуемого для разработки программ, сама она является довольно сложным в использовании программным средством. Перед тем, как приступить к использованию этой IDE, пользователь должен достаточно глубоко разобраться в принципах функционирования компьютера и в особенностях использования конкретной IDE. В этой книге мы будем предполагать, что для создания исходного текста программы используется простой текстовый редактор, а для ее компиляции и прогона — командное окно.
1.5 Начинаем работать на С# Как уже отмечалось, в этой книге предполагается, что вы будете компилировать и запускать программы с командной строки. Рассмотрение и использование интерактивной среды разработки является предметом более профессионального учебного курса или уделом особо любознательных и отважных читателей. Повсюду в этом разделе мы полагаем, что для разработки и запуска С#-программ используется компьютер, управляемый операционной системой Windows (имея в виду под этим названием различные варианты операционной системы, включая Windows ME, Windows NT, Windows 2000 и Windows XP).
Создание и редактирование файлов с исходным текстом Разрабатывая С#-программу, мы создаем ее в виде одного или нескольких текстовых файлов. Рассмотрим простейший случай, когда программа состоит из единственного текстового файла. Этот файл можно создать с помощью любого текстового редактора. В системах Windows стандартный текстовый редактор носит имя Notepad (Блокнот). Можно также использовать программу WordPad. Несложный поиск с помощью Web-браузера позволит обнаружить еще несколько альтернативных программ, имеющих более дружественный интерфейс. Одним из таких достойных всяческого одобрения и, к тому же, бесплатных продуктов является программа
1.5
Начинаем работать на С #
27
EditPadLite, которую можно найти по адресу http://www.EditPadLite.com. С ее помощью были созданы многие программы из этой книги. На рис. 1.7 показан кадр EditPadLite с исходным текстом простейшей Сопрограммы. Файлу следует дать имя (любое) с расширением .cs, которое является сокращением от «CSharp».
class Hello { s t a t i c void Hain() { Console.WriteLine("Surprise!
9: 1 Рис. 1.7. Редактор EditPadLite использован для создания Сопрограммы
Компиляция С#-программы При работе в системе Windows можно использовать программу Command Prompt. Обычно ее можно найти в разделе Start/Programs/Accessories (Пуск>Программы>Сеанс MS-DOS) главного меню. Запуск этой программы приводит к выводу на рабочий стол Windows окна DOS; в это окно можно вводить команды. К этим командам относятся: Команды командной строки (выборка) cd cd путь cd . . d: dir path help help команда
//Вывод имени текущего каталога //Смена текущего каталога на каталог путь //Смена текущего каталога на вышележащий каталог //Смена текущего диска на диск d: //Вывод имен файлов текущего каталога //Вывод значения переменной окружения Path //Вывод списка доступных команд //Получение информации об использовании //указанной команды
Окно Command Prompt в системе Windows позволяет пользователю вводить с клавиатуры команды, напоминающие команды Unix. Многие из этих команд устарели (поскольку восходят к старой операционной системе MS-DOS).
28
Глава 1. Введение
Для командного окна существует понятие текущего каталога (текущей папки). По умолчанию имя текущего каталога выводится в начале каждого нового системного запроса в командном окне. Например, текст C:\Documents
and Settings\thomas>
говорит о том, что текущим является каталог C:\Documents and Settings\ thomas. Если подлежащий компиляции файл с исходным текстом С#-программы расположен в другом каталоге, имеет смысл именно его назначить текущим с помощью команды cd. Заметьте, что если каталог, в который вы хотите перейти, принадлежит другой файловой системе (другому логическому диску), то вы должны прежде всего перейти в эту файловую систему. Например, для перехода в каталог D:\My Work\Files надо использовать такую последовательность команд: d: cd "my. work" cd f i l e s Как видно из этого примера, Windows не различает в именах файлов прописные и строчные буквы. (Однако для систем Unix и MacOS они будут различающимися символами.) Если текущим является каталог с исходным текстом С#-программы, она должна откомпилироваться, если ввести такую команду: esc
/debug:full
hello.cs
где hello.cs является именем файла с исходным текстом. Здесь требуется сделать два пояснения. Во-первых, что собой представляет команда esc, и, во-вторых, что обозначает ключ /debug:full? Команда esc. Мнемоническое обозначение esc не является встроенной в программу Command Prompt командой, как, например, команда cd. Наша задача — активизировать компилятор С#, который представляет собой программу с именем csc.exe; именно это и попытается для нас сделать программа Command Prompt. Если имя, введенное в командной строке, не является встроенной командой, программа Command Prompt ищет на диске выполнимый файл с именем (без суффикса) esc. Но где она ищет этот файл? Выполнимые файлы ищутся только в текущем каталоге, а также в тех каталогах, которые перечислены в строке, являющейся значением переменной окружения Path. Если в ответ на нашу команду программа Command Prompt выведет такое сообщение ' e s c ' i s not recognized as an i n t e r n a l or external command, operable program or batch f i l e , ['esc' не распознано, как внутренняя или внешняя команда, выполнимая программа или командный файл]
1.5 Начинаем работать на С#
-
29
это означает, что компилятор С#, который представляет собой программу с именем csc.exe, не был обнаружен ни в одном из каталогов, перечисленных в строке, являющейся значением переменной Path. Если такое случится в компьютере в системе коллективного доступа (например, в компьютерном классе колледжа), придется поискать системного администратора и попросить его устранить возникшее затруднение. Для этого потребуется проверить правильность установки компилятора С# и если с ним все в порядке, проверить значение переменной Path. Текущую установку этой переменной можно получить с помощью команды path
введенной в командном окне (строчными или прописными буквами). Если дело в переменной Path, то обойти возникшую трудность можно с помощью указания полного пути к файлу компилятора С#. Пусть этот путь таков: С:\Program
Files\Microsoft.NET\urtinstallpath
Тогда мы можем ввести команду С:\Program
Files\Microsoft.NET\urtinstallpath\csс.exe /debug:full hello.cs
(всю на одной строке), и программа Command Prompt активизирует компилятор С#, не выполняя предварительный поиск этой программы в каталогах списка переменной Path. Ключ /debug. Вторая особенность использования компилятора С# заключается в том, что процессом компиляции можно в какой-то степени управлять путем указания соответствующих ключей в командной строке. Полный список этих ключей можно получить, введя команду esc
/help
К счастью, значения ключей, действующие по умолчанию, во всех случаях разумны, и мы, реализуя программы, описанные в этой книге, можем спокойно игнорировать все возможности настройки компилятора. Единственной настройкой, для которой можно посоветовать изменить значение, действующее по умолчанию, является режим отладки, определяемый ключом debug. Указав для ключа /debug значение full, мы требуем от компилятора С# сгенерировать дополнительную информацию, которая приводит к выводу на экран более детальных сообщений об ошибках, если они обнаруживаются в программе (что, к сожалению, является самым обычным делом). Имеется и более короткая форма приведенной выше команды:
Этот путь не является стандартным, поэтому на конкретном компьютере, возможно, придется поискать, на каком именно диске находится файл csc.exe.
30
Глава 1. Введение
esc /debug+ hello.cs
Больп1ие по объему программы обычно собираются из нескольких исходных файлов. Если наша программа состоит из файлов one.cs, two.cs и three.cs, то для получения выполнимого файла следует ввести команду esc
/debug:full
one.cs two.cs three.cs
Результирующий выполнимый файл будет назван one.cs, two.cs или three.cs, в зависимости от того, какой исходный файл содержит стартовую точку программы (которой обычно является фрагмент программы с именем Main — как будет детально объяснено в гл. 2). Если для создания программы необходимо компилировать много исходных файлов, лучше использовать IDE Visual Studio. Ввод на командной строке всех этих имен может оказаться утомительным делом, сопряженным с возможными ошибками. Если же программа состоит всего лишь из нескольких файлов, то вполне можно обойтись компиляцией с командной строки.
Запуск и выполнение откомпилированной программы Если компилятор С# не обнаружил в исходном тексте С#-программы каких-либо ошибок, он создаст выполнимый файл. В системах Windows имя по умолчанию для выполнимого файла совпадает с именем исходного файла, но суффикс .cs заменяется на .ехе. Если, например, исходный файл назван hello.cs, то выполнимый файл получит имя hello.exe. Для запуска полученной программы достаточно ввести ее имя на командной строке. Это имя можно ввести как без суффикса .ехе hello
так и с суффиксом hello.exe
Если в состав программы входили несколько исходных файлов, то запустить надо тот, в котором находится метод Main. В следующей главе мы детальнее познакомимся с методом Main и с другими частями С#-программы.
Основные понятия, рассмотренные в гл. 1 В этой вводной главе были затронуты некоторые аспекты истории языков программирования, идея языка ассемблера и собственно языков программирования, а также понятие компилятора, назначением которого является трансляция программы в двоичный код, исполняемый компьютером. Была также рассмотрена процедура подготовки выполнимой программы с помощью программ текстового редактора и компилятора, вызываемых в командном окне.
Контрольные вопросы
31
Понятия, затронутые в гл.1: программирование
язык ассемблера
двоичная система
шестнадцатеричная система
кодирование символов
оператор присваивания
интерактивная среда разработки
запуск программы
Рассмотренные операторы:
Определения и синтаксические формы: языки программирования
определение компьютера
команда esc
команды командной строки (выборка)
разработка программ
цикл while
Контрольные вопросы Контрольные вопросы в этой книге снабжены альтернативными ответами. Приводимые ниже пять вопросов позволят вам проверить усвоение вами основных понятий этой главы. 1.1. Сколько может быть различных комбинаций битов в одном байте? (а) 64
(б) 16
(в) 8
(г) 256
1.2. Если в регистре еах содержится число 7, то что, по вашему мнению, будет содержаться в этом регистре после выполнения приведенной ниже команды Intel Pentium? add
еах,еах
(а) невозможно определить
(б) 7
(в) 14
(г) 8
1.3. Какое десятичное число представляет двоичное число 0011111? (а) 63
(б) 31
(в) 111111
(г) 6
1.4. Если код ASCII буквы «А» равен 65, код «В» — 66 и т. д., какая строка текста представлена приведенной ниже последовательностью чисел:? 72
69
76
76
79
32
• (a) hello (в) OLLEH
Глава 1. Введение (б) GEKKO (г) HELLO
1.5. Если в системе Windows текущим является каталог C:\a\b\c\d, в какой каталог мы попадем после выполнения в командном окне приведенной ниже цепочки команд? cd cd cd cd
. . . . с flub
(a) C:\a\b\c\d\flub
(6) C:\a\b\c\flub
(в) C:\a\c\flub
(г) C:\c\flub
Упражнения Упражнения заключаются в том, что вы должны что-то сделать. В последующих главах упражнения потребуют от вас составления или модификации С#-программ. Поскольку мы пока еще не дошли до практического программирования, предлагаемые ниже упражнения попроще. 1.1. Для каких языков программирования есть компиляторы на вашем компьютере? Перечислите все, которые вам удастся найти. 1.2. Является ли программа Command Prompt на компьютере с системой Windows компилятором? Обоснуйте ваш ответ. 1.3. Каждый символ (буква, цифра, знак препинания и т. д.) требуют в кодировке Unicode двух байтов памяти. Оцените, сколько потребуется байтов для хранения символов, из которых состоит вся эта книга. 1.4. Распознает ли ваш текстовый редактор синтаксис языков программирования с окраской различных элементов языка в разные цвета? Можете ли вы установить такой режим для С#? 1.5. Определите, сколько памяти имеет ваш компьютер и какой объем памяти занимают ваш компилятор С#, редактор и IDE.
Использование объектов 2
Наше путешествие в мир программирования мы начнем с обзора принципов объектной ориентации, после чего перейдем к реализации этих принципов на С # . Для создания объектов мы будем использовать типы, уже существующие в языке и служащие для описания дат, изображений, строк, целых чисел и случайных последовательностей. Типы включают в себя данные и функции, и мы прежде всего остановимся на четырех видах функций — конструкторах, свойствах, операторах и методах. Будут рассмотрены ввод и вывод на уровне строк и изображений, а также базовые принципы форматирования выводимой на экран информации. С помощью нескольких примеров использования объектов в программах мы проиллюстрируем основы объектной ориентации.
2.1 Введение в объекты Что такое объект? Такой вопрос часто задается, и весьма существенно получить на него ответ. Объекты являются краеугольными камнями программ, написанных на современном языке типа С#. Такие языки предназначены для разработки объектов, взаимодействующих друг с другом предопределенным образом. В этом разделе мы рассмотрим объекты в общем виде, вместе с присущими им типами, а в следующем разделе перейдем собственно к языку С#. Неформально объект является представлением чего-то, существующего в реальном мире, с отображением и того, чем он является, и того, что он делает. Рассмотрим рис. 2.1. На этой фотографии мы можем идентифицировать многие объекты реального мира, например, суда, деревья, строения и людей. Сами эти объекты, очевидно, не могут существовать в компьютере. Функцией компьютера является обработка информации. Информация представляет собой абстрактное понятие, в то время как объекты на фотографии являются физическими объектами реального мира. Программа же сохраняет и обрабатывает информацию об объектах реального мира, причем она может это делать весьма аккуратно и чрезвычайно быстро. Для объекта на фотографии, например, судна на переднем плане, компьютер может записать и затем обновлять данные о таких деталях, 2—2047
34
Глава 2. Использование объектов
Рис. 2 . 1 . Объекты
как регистрационный номер судна, количество перевозимых им пассажиров или имя владельца. Эта информация, скорее всего, будет собрана в программе в одном месте в виде связной совокупности, и эта совокупность информации для конкретного судна может рассматриваться, как объект. Объект судна в программе является проекцией объекта реального мира, и данные в этом объекте отражают лишь незначительную долю всех деталей, которые мы обычно связываем с реальным судном. Манипуляции с объектом судна в программе могут включать в себя, например, смену владельца судна в случае его продажи. Очевидно, что объект в компьютерной программе не является объектом в физическом смысле слова. В самом деле, некоторые объекты в программе могут и не соответствовать никаким объектам реального мира, а лишь некоторым неосязаемым или абстрактным концепциям. Например, мы можем создать объект, который будет помнить, как индивидуальный пользователь предпочитает организовать изображение на компьютерном экране: будут ли значки программ расположены столбиком с левой стороны экрана или вдоль его верхней кромки, какие значки следует вывести на экран, и какой вид будет иметь курсор? Возвращаясь к рис. 2.1, представление изображенной на рисунке фотографии может храниться в памяти компьютера в виде единиц и нолей (их будет очень много!). Самой фотографии в компьютере не будет; только ее представление и некоторая дополнительная информация, необходимая для обеспечения возможности манипуляций с изображением. Эта дополнительная информация обычно включает в себя ширину, высоту и разрешение. Совокупность такой информации образует объект
2.1 Введение в объекты
35
изображения. Программа может осуществлять манипуляции с изображением, изменяя его размер, поворачивая или отражая зеркально, а также посылая его на принтер для вывода на бумагу.
Формализм объектов Рассмотрим более формальное определение объектов, поскольку оно используется в программировании. Объект Объект в программе представляет собой реализацию определенного понятия. Объект представляется совокупностью данных, связанных с понятием, и функций, к которым он имеет доступ с целью установки, изменения или удаления его собственных данных, а также данных, входящих в другие объекты.
Термины данные и функции будут далее рассмотрены во всем объеме. Для нашего объекта изображения данными будут фактические пикселы изображения, плюс его ширина, высота и разрешение, а функции, приложимые к этому объекту, будут включать в себя изменение размеров, зеркальное отражение, вращение и т. д. В качестве другого примера объекта рассмотрим дату рождения. Данными для такого объекта будут значения года, месяца и дня. В качестве функций объекта даты рождения можно предложить следующие действия: • способность вычитать его из другого объекта даты (скажем, из текущей даты), чтобы получить число лет (возраст); • сравнение с другим объектом даты с целью выяснения их равенства; • передача объекта другому объекту, который имеет возможность выводить значения данных и, таким образом, отображать их на экране. Во всех этих трех случаях объект может взаимодействовать с другими объектами через свои функции.
Типы Обычно программы должны манипулировать многими схожими объектами. Например, мы может иметь программу, осуществляющую манипуляции с сотнями разных изображений различающихся размеров. Однако все эти различные изображения схожи в том отношении, что любое из них мы можем поворачивать, зеркально отражать, изменять размеры или выводить на печать. Объекты изображений оказываются почти взаимозаменяемыми. В этом случае естественно рассматривать все эти объекты изображений, как принадлежащие типу изображения. В программировании каждый объект должен принадлежать определенному типу. В программе может быть определено несколько типов, и такая программа может содержать несколько разных видов объектов.
36
Глава 2. Использование объектов
Тип Тип предоставляет определение данных и функций, требуемых для: •
описания некоторой группы объектов или
•
выполнения специфической задачи.
Типы, содержащие и данные, и функции, могут описывать группы объектов. Примеры таких типов: • тип даты (Date на рис. 2.2), данные которого содержат значения дня (day), месяца (month) и года (year) конкретной даты, и который предоставляет функции для сложения дней (AddDays), сравнения дат (Compare) и т. д.; • тип числа (Number на рис. 2.2), данные которого содержат требуемое значение (Value), и который предоставляет обычные функции для действий над числами: сложения, вычитания, умножения, сравнения и т. д. Некоторые типы предоставляют только функции, и тогда мы не может объявлять объекты таких типов, а должны лишь использовать их функции. Примерами могут служить типы, осуществляющие: • математические операции (Math на рис. 2.2), например, извлечения квадратного корня или вычисления синуса или косинуса; • организацию ввода и вывода данных (InputOutput на рис. 2.2) для взаимодействия программы с внешним миром. Рис. 2.2 показывает в общем виде, как могут выглядеть эти типы. Каждая рамка изображает определенный тип и начинается с его имени, за которым следуют сначала данные, а затем функции. В следующем разделе мы рассмотрим, как такие типы определяются и используются конкретно в С#. Мы также увидим, почему данное «value» для типа Number выделено курсивом. Date
Number
day month year
Value
AddDays Compare
*
И
+
И
Т.Д.
Math
InputOutput
Sgrt Sin Cos
Read Write
И
И
Т.Д.
Т.Д.
Т.Д.
Рис. 2.2. Примеры типов
Теперь, чтобы получить всю картину, посмотрим, как могут выглядеть объекты, созданные для первых двух типов (рис. 2.3). Объекты типа Date имеют имена — birth [дата рождения] и marriage [дата брако-
2.1 Введение в объекты
37
сочетания], а также значения каждого данного, указанного в определении типа. Функции в объектах не показаны, потому что они используются совместно всеми объектами данного типа; следовательно, их уместнее показать в самих типах. Созданные таким образом объекты носят название экземпляров данного типа. В С# обычно имена типов начинаются с прописных букв, а имена объектов — со строчных. Date
Number
day month year
Value +
*
AddDays Compare и т.д. /
ч
я* :birth 1970 11 14
и
\
/ :marriage
-19
т.д.
4 3.14159
2000 2 25
Рис. 2.З. Объекты, созданные из типов
Объекты Number выглядят несколько иначе. У них нет имен, и на рисунке показаны лишь их значения. Дело в том, что в большинстве языков (и в С#) предусмотрены встроенные типы данных, соответствующие типам самого компьютера, в частности, числовые типы, например, целые. Такие типы называют простыми типами. Поэтому, например, тип для числа позволяет указать одно числовое значение (скажем, -19); этот тип соответствует одному из числовых типов компьютера, однако его значение может быть указано в обычной форме десятичного целого числа. Функции простого типа представлены встроенными операторами + , — и др. В гл. 3 мы рассмотрим числа в С# более подробно. Простой тип Простой тип обычно: • • • •
представляет данное, имеющее одно значение; непосредственно соответствует встроенному типу компьютера; имеет предопределенный набор значений в языке; использует встроенные операторы языка в качестве своих функций.
38
Глава 2. Использование объектов
К другим простым типам, имеющим эквиваленты в самом компьютере, относятся: • булев (или логический) тип, который принимает два значения, true (истина) и false (ложь), записываемые в виде слов, а не чисел, и которому соответствуют логические операторы вида and (И), or (ИЛИ) и др. Внутри компьютера булевы значения могут представляться одним битом; • символьный тип, который позволяет представить буквы, цифры и другие символы, в основном те, которые имеются на клавиатуре. Большая часть этих символов вводится в программу в апострофах, например, ' К ' или '8'. Внутри компьютера каждый символ занимает 1 байт, как это уже отмечалось в гл. 1. Вернувшись к рис. 2.3, мы можем заметить, что Date, очевидно, не является простым типом, поскольку содержит три значения, а также и потому, что нет предопределенного способа записи даты; в самом деле, одну и ту же дату можно записать множеством разных способов. Например, значения 14 November 1970 14/11/70 November 14, 1970 1970.11.14 являются одинаково правильными способами представления даты в зависимости от ваших потребностей или страны проживания. Поэтому в противоположность простым типам дату можно назвать структурным типом. Простые типы включены в язык, и для них предусматриваются специальные наборы символических обозначений, однако в С# имеется также и целый ряд структурных типов, например, для записи дат. Другим структурным типом является строка символов, заключаемая в двойные кавычки, например, «London» или «John Smith». Этот тип входит в группу структурных типов, потому что он включает не одно, а несколько простых значений. Однако языки обычно предоставляют операторы для объединения строки, например, + или &. Значения строк могут иметь различные представления, однако символы строки всегда хранятся в памяти в одном месте. Программисты имеют возможность определять новые структурные типы, не соответствующие ни компьютерным типам, ни каким-либо предопределенным символическим обозначениям. При этом структурный тип может иметь несколько значений, в противоположность простому типу, для которого допустимо только одно значение. Подытожим сказанное, приведя определение структурного типа.
Использование объектов
39
Структурный тип Структурный тип •
содержит данные для одного или нескольких значений, каждое из которых соответствует своему типу;
•
включает определенные пользователем данные и функции.
Приведенные выше определения типов умышленно несколько расширены по сравнению с теми, что используются в С#, поскольку важно изучать принципы программирования, а не просто язык.
2.2 Члены объектов Как мы уже видели, типы, а, следовательно, и их объекты, включают данные и функции, причем функции иногда выступают в виде операторов. Как они описываются? В этом разделе мы начнем использовать истинные обозначения С# и введем понятие формы, которая показывает, как записываются в С# те или иные конструкции, и что они обозначают. То, как они записываются, называется синтаксисом, а что они обозначают — семантикой. Форма, следовательно, будет выглядеть приблизительно таким образом: Форма для конструкции С# Синтаксис конструкции Семантика конструкции
Шрифты, используемые в формах, уже обсуждались в гл. 1.
Обращение к членам Данные и функции, входящие в тип, носят название членов, или элементов типа. Для обращения к членам типов или объектов, имеющих имена, предусмотрены специальные обозначения. К членам, обозначаемым символами операторов (например, +), обращение осуществляется иначе, в соответствии с принятой практикой написания арифметических выражений. Члены типов, используемые всеми объектами данного типа (а также другими объектами) имеют общее название статических членов. Обращение к ним осуществляется с помощью оператора «точка» (.) с указанием имени типа. Члены, которые предназначены для использования в конкретном объекте, представляющем собой экземпляр типа, называются членами экземпляра, и обращение к ним осуществляется с указанием имени объекта. Таким образом, мы имеем следующую форму:
40
Глава 2. Использование объектов
Обращение к членам тип.член объект.член тип. метод (список__параметров) объект.метод(список_параметров) объект оператор объект оператор объект
//Инфиксный (бинарный) //оператор //Префиксный (унарный) //оператор
Два первых варианта приложимы ко всем данным, а также к большинству функций. Если член является членом экземпляра, указывается объект; если член является статическим, указывается тип. Членам, являющимся методами (см. ниже), могут передаваться параметры. Два последних варианта используются в тех случаях, когда операторы (например, + или -) определены для данного типа. Детали семантики для каждого вида обращения обсуждаются ниже.
Воспользовавшись, например, рисунками 2.2 и 2.3, мы можем записать члены таким образом: birth.day marriage.year birth.AddDays(20) Math.Sqrt(x) InputOutput.Read() 6+ 3
//Дата рождения //Год бракосочетания //Добавить 20 дней к дате рождения //Вычислить квадратный корень из х //Операция чтения //Арифметическое действие над числами
Перед точкой мы указываем тип или объект, а после точки пишем имя члена. Теперь перейдем к обсуждению способов описания членов-данных и членов-функций.
Данные Данные д л я типа состоят из одного или более полей. Каждое поле специфицируется своим собственным типом, который называется объявлением поля. Поля могут объявляться по-разному, что определяет способы их использования, но пока мы ограничимся простой формой объявления: Объявление поля (упрощено) тип поле; тип поле = выражение; тип поле!, поле2, . . . Первый вариант объявляет поле данного типа. Второй вариант объявляет поле и инициализирует его выражением, которое должно давать результат того же типа. Третий вариант показывает, что допустимо объявить список полей одного типа.
2.2 Члены объектов
41
(Термин выражение, использованный в приведенной выше форме, будет объяснен позже. Здесь мы полагаем, что выражение включает значения соответствующих типов.) Какие типы можно использовать? В предыдущих разделах мы упоминали, среди прочих, типы для чисел, дат, изображений и строк. В С# эти типы являются встроенными, и им присвоены имена int, DateTime, Image и string, соответственно. Поэтому примеры объявлений могут выглядеть таким образом: int temperature; Image photol; DateTime birth, graduation, marriage; int century = 100; string taxEnd = "February";
Принято имена простых типов вроде int начинать со строчной буквы, а имена структурных типов, таких как Image — с прописной. Исключением является предопределенный тип string, хотя для него имеется синоним String. Программисты на С# предпочитают использовать обозначение string. Структуры и классы. В С# используются структурные типы двух видов — структуры (struct) и классы (class). DateTime является структурой, a Image — классом. На первых порах мы будем иметь дело в основном со структурами. Во многих отношениях обе конструкции схожи, однако классам свойственны дополнительные черты, расширяющие их возможности, именно, наследование и полиморфизм. Эти черты не используются в простых программах, и о них можно не думать при изучении основ объектно-ориентированного программирования, поэтому мы отложим знакомство с ними до следующих глав. Здесь же мы остановимся на различиях между структурами и классами, проявляющихся в способах их инициализации. Инициализация. В приведенном выше примере полей данных двум последним данным, century и taxEnd, присвоены определенные значения. А что можно сказать об остальных полях? В С# все целочисленные поля при их объявлении инициализируются нолями. Когда объявляется структура, например, DateTime, создаются ее поля, и все они инициализируются в соответствии с их типами. Поэтому если DateTime включает в себя поля дня, месяца и года (а, скорее всего, это так и есть), и все эти поля представляют собой целые числа (а это точно так), то все они будут инициализированы нулевыми значениями. Поля Image, какими бы они ни были, не могут иметь разумных значений до тех пор, пока не будет загружено конкретное изображение. Image, будучи классом, а не структурой, инициализирует свои объекты специальным значением, называемым null. Именно таким будет значение photol, пока мы не предпримем шаги к его изменению.
42
Глава 2. Использование объектов
Функции В С# используются несколько видов функций; пока мы ограничимся рассмотрением конструкторов, методов, свойств и операторов. В соответствии с темой этой главы, мы здесь остановимся на том, как эти функции используются в Сопрограммах; в гл. 3 мы обсудим, как они определяются. Конструкторы. Конструктор представляет собой функцию специального вида, которая используется для создания объекта данного структурного типа. Конструктору передаются некоторые значения, которые он вставляет в поля нового объекта. Таким образом, если мы хотим создать несколько объектов даты, мы можем написать: D a t e T i m e b i r t h = new D a t e T i m e ( 1 9 7 0 , D a t e T i m e m a r r i a g e = new D a t e T i m e ( 2 0 0 0 ,
11, 14),-//Дата 2, 2 5 ) ; / / Д а т а
рождения бракосочетания
Значения в скобках называются параметрами, и они должны перечисляться в том порядке, в каком они ожидаются конструктором. В случае DateTime этот порядок таков: год, месяц, день. Форма создания объекта выглядит таким образом: Создание объекта ТИП obj
= new ТИП (список_параметров)
Создается объект данного типа с именем, указанным полем obj и полями, инициализированными в соответствии со списком параметров.
Использование знака равенства указывает, что содержимое правой части назначается имени объекта, указанного в левой части. Назначение является очень важным понятием программирования, и мы еще им займемся. Объекты можно также создавать посредством их методов, как будет показано ниже для класса Image. Методы. Метод типа может быть использован для изменения значений полей, для выполнения внешних действий, например, вывода чего-то на экран, а также для вычисления результата. Например, в типе DateTime предусмотрен метод для сложения числа лет. В качестве примера использования этого метода мы можем написать: marriage.AddYears(25);
//Прибавить
25 лет
к дате
бракосочетания
Метод носит имя AddYears. Он использован применительно к объекту marriage, и ему передается значение 25, с которым он может работать. Число 25 является параметром метода AddYears. To, что этот метод применяется к объекту, указывается с помощью оператора «точка». В результате этой операции будет изменено значение поля, принадлежащего объекту marriage.
2.2 Члены объектов
43
В разделе 2.1 и на рис. 2.2 было упоминание типа для ввода и вывода. В С# этот тип представляет собой класс с именем Console. Одним из его методов является WriteLine; он используется для вывода значений на экран. Для вывода на экран значения объекта birth мы можем написать Console.WriteLine (birth);
Это предложение активизирует метод WriteLine класса Console с параметром birth. Другая категория методов создает объект для объявленного поля. В приведенном ниже предложении метод FromFile считывает изображение из файла в программу. Image photol = Image.FromFile("Photo.jpg"); Построение предложения здесь такое же, как и в предыдущем примере (WriteLine); в нем используется вариант формата обращения тип.член, обсуждавшийся в начале этого раздела. Свойства. Свойства позволяют нам отыскивать значения полей данных управляемым способом и, возможно, настраивать их. В С# поля по умолчанию объявляются частными (закрытыми) по отношению к объекту. Часто нам надо сделать их значения доступными, но в то же время контролировать вносимые изменения. Например, дню месяца нельзя присвоить значение 32. Если, однако, поле связано со свойством, тогда свойство может ограничить действия, выполняемые над полем. Например, тип DateTime не предоставляет нам непосредственный доступ к каким-либо полям, но имеет много свойств, позволяющих нам обращаться к этим полям. Примерами таких свойств являются Day, DayOfWeek, Year и др. Свойства, как и методы, применяются к объектам с помощью оператора «точка», а их имена начинаются с прописной буквы; параметров у них нет, поэтому нет и скобок. Используя приведенный выше пример создания объекта, можно с помощью свойства Year получить значение поля year: Console.WriteLine(birth.Year) ;
которое будет равно 1970. Другим интересным свойством DateTime является Now, которое позволяет получить текущие дату и время. Написав Console.WriteLine(DateTime.Now);
мы выведем на экран текущие дату и время, например, 2002/07/13 08:55:20 РМ
44
Глава 2. Использование объектов
Для изображений предусмотрены свойства, отображающие размеры, и их можно вывести на экран следующим образом: Console.WriteLine(photol.Width); Console.WriteLine(photol.Height) ;
Операторы. Операторы обозначаются с помощью символов, и приводят к вызову метода, который выполняет действие над двумя объектами, указываемое оператором. Для каждого типа может быть определено много операторов, в том числе и с вполне очевидным смыслом. Например, можно написать: temperature + 5 birth.Year - graduaiton.Year graduation - b i r t h photol.Width < photol.Height
+ 1
Здесь мы видим примеры операторов плюс (+), минус (-), меньше чем ( ///Программа Time Reading
Bishop & Horspool April 2002
/ // ill " ///По-прежнему вычисляет время в конкретном городе по ///заданной разнице во времени по отношению к другому ///городу, но использует операцию ввода, что позволяет задать ///второй город во время выполнения ///Демонстрирует ввод переменных string и int /// void Go () { Console.WriteLine ("The Time Reading Program"); 3—2047
66
Глава 2. Использование объектов string city;//Город int offset;//Разница во времени int startTime • 14;г//14.00 или 2 часа дня DateTime now = DateTime.UtcNow; //Введем характеристики второго города Console.Write("City? " ) ; city = Console.ReadLine(); Console.Write("Offset from London? " ) ; offset = int.Parse(Console.ReadLine()); DateTime meeting = new DateTime (now.Year,now.Month,now.Day,startTime, 0,0) ; Console.WriteLine("The meeting in London is " + " scheduled for " + meeting) ; Console.WriteLine(city + " is " + offset + " hours different"); DateTime offsetMeeting = meeting.AddHours(offset); Console.WriteLine("The meeting in "+city+" will be at " + offsetMeeting.ToShortTimeString() ) ; } static void Main() { new TimeReading().Go();
Для демонстрации работы программы ее следует запустить несколько раз. В приведенном ниже выводе данные, введенные пользователем, показаны более светлым шрифтом. The Time Reading Program City? NewYork Offset from London? -5 The meeting in London is scheduled for 2002/08/05 New York is -5 hours different The meeting in New York will be at 09:00 AM The Time Reading Program City? Berlin Offset from London? 1 The meeting in London is scheduled for 2002/08/05 Berlin 1 hours different The meeting in Berlin will be at 03:00 PM The Time Reading Program City? Perth Offset from London? 7 The meeting in London is scheduled for 2002/08/05 Perth is 7 hours different The meeting in Perth will be at 09:00 PM
14:00:00
14:00:00
14:00:00
2.6 Основы API C#
67
The Time Reading Program City? San Francisco Offset from London? -8 The meeting in London i s scheduled for 2002/08/05 14:00:00 San Francisco i s -8 hours d i f f e r e n t The meeting in San Francisco w i l l be a t 06:00 AM Программа, безусловно, полезна, но запускать программу заново для получения каждого нового набора данных представляется довольно неудобным. Хотелось бы сделать так, чтобы программа сама могла воспринимать и обрабатывать многократные запросы. Как этого добиться, будет показано в последующих главах.
2.6 Основы API С# Аббревиатура API является сокращением от Application Programming Interface, интерфейс прикладного программирования, и используется, как и эквивалентный ему термин «пространство имен», для описания группы связанных классов, структур и т. д. В этой главе мы имели дело со следующими API: System System.Drawing System.Windows.Forms
Два последних интерфейса использовались только в примере 2.4 для вывода фотографии, и мы ими заниматься пока не будем. Однако мы использовали ряд важных классов, включенных в System, и теперь мы рассмотрим некоторые из них, не всесторонне, но достаточно детально, чтобы ими можно было пользоваться в следующих главах. DateTime Форма для структуры DateTime приведена ниже. Из нее можно получить более точную информацию о данных и функциях, которые мы уже использовали. Весьма полезными могут быть методы DaysInMonth и IsLeapYear. Далее мы видим, что в DateTime описаны два оператора вычитания; второй определяет разность двух дат и возвращает значение типа TimeSpan. Мы с этим типом пока не сталкивались. TimeSpan хранит время в днях, часах и еще меньших единицах. Если нам нужен ответ в годах, нам придется самим разделить результат на 365 (или 366).
Глава 2. Использование объектов
68 Структура DateTime (сокращено) //******* ***конструкторы** **********
DateTime(int год, int месяц, int день); DateTime (int год, int месяц, int день, int час, int минута, int секунда) ; //**********Статические свойства********** static DateTime Now;
//Текущие дата и время
static DateTime Today;
//Текущие дата и время //00:00:00)
(время равно
static DateTime UtcNow; //Текущие дата и время по Гринвичу (UTC) //***********Свойства экземпляра************ int Day int Month int Year int Hour int Minute int Second int Millisecond int DayOfYear //************Статические методы************* static
int
static
bool
static
int
Compare(DateTime tl,
DateTime t2)
Equals(DateTime tl,
DateTime t2)
DaysInMonth(int год,
static
DateTime Parse(string
static
bool
IsLeapYear(int
int месяц)
s)
год)
static DateTime operator - (DateTime d, TimeSpan t) static TimeSpan operator -(DateTime dl, DateTime o\2) static DateTime operator +( DateTime d, TimeSpan t) static bool operator ==(DateTime dl, DateTime d2) //************* *Методы экземпляра* * ************ int CompareTo(object значение) override bool Equals(object значение) string ToStringO string ToString(string формат) string ToShortDateString() ; string ToLongDateString() ; Во всех перечисленных категориях имеются много других членов. Для получения полной информации обратитесь к интерактивному справочнику или полному учебнику по API.
2.6 Основы API C #
69
В приведенной форме указаны четыре метода, выполняющие сравнение в общем виде и сравнение на равенство. Если d l и d2 являются экземплярами типа DateTime, эти методы можно вызвать следующим образом: DateTime.Equals(dl,d2) ; dl.Equals(d2); d2.Equals(dl);
//Вызов статического метода " //Вызов метода для экземпляра dl //Вызов метода для экземпляра d2
Здесь все три выражения дают один и тот же результат. Однако в типе DateTime определены также операторы для сравнения на равенство, которые облегчают чтение программы. Таким образом, вместо любого из приведенных выше выражений можно просто написать dl
== d2
Тип DateTime предоставляет также операторы сравнения, и мы можем использовать выражение вида dl
< d2
которое вернет true или false в зависимости от соотношения величин d l и d2, или применить один из методов Compare или CompareTo, действие которых слегка различается. Они возвращают отрицательное целое число, если первый операнд меньше второго, 0, если они равны, и положительное целое число, если первый операнд больше. Например, можно написать if
(dl.CompareTo(d2) < 0) { //dl описывает более раннюю дату,
чем d2
Очевидно, что операторы использовать проще, если, конечно, они определены. Операторы сравнения будут рассмотрены подробнее в разделах 3.4 и 4.1.
string и int В API отсутствует определение string. Это связано с тем, что почти все типы в С#, имена которых начинаются со строчных букв, имеют псевдонимы, т. е. другие типы с теми же именами. Реальный класс для string называется String; поскольку он входит в пространство имен System, он также называется System.String. Форма этого типа будет приведена в гл. 4. Если, однако, мы поищем в API определение int, то окажется, что, в отличие от string, типу int соответствует структура-псевдоним с другим именем — Int32. При этом Int32 (или int) предоставляет поля и методы, перечисленные в приведенной ниже форме.
Глава 2. Использование объектов
70
Структура int или int32 (сокращено) //Статические поля const i n t MinValue //-2 147 483 648 const i n t MaxValue // 2 147 483 647 //Статические методы s t a t i c i n t Parse(string s ) ; //Методы экземпляра s t r i n g ToStringO; Во всех перечисленных категориях имеется много других членов. Для получения полной информации обратитесь к интерактивному справочнику или полному учебнику по API. В дополнение к перечисленному, для целых чисел определены все обычные операторы. Random
Мы сначала ввели понятие типов неформальным образом, а затем, в этом разделе, начали описывать их спецификацию. Сделаем наоборот — сначала специфицируем тип, а затем приведем пример его использования. Рассмотрим класс случайных чисел. Его форма приведена ниже. Мы можем представить себе использование класса Random для моделирования выбрасывания игральной кости: Random throw = new Random();//Создаем экземпляр throw i n t d i c e = throw.Next(1,б);//Бросаем кость и получаем число //очков в dice Класс Random (сокращено) //Конструкторы Random () Random(int затравка); //Методы экземпляра int Next () ; i n t Next(int макс_значение) ; i n t Next(int мин_значение, int макс_значение) ; double NextDouble() ; Конструкторы возвращают случайные объекты, которые образуют последовательность псевдослучайных чисел. Методы Next возвращают следующее число в последовательности, возможно, между заданными значениями. NextDouble возвращает число в диапазоне от 0.0 до 1.0.
В приводимом ниже примере случайные числа типа Random используются в качестве объектов вместе с всеми остальными рассмотренными к настоящему моменту типами API.
2.6 Основы API C#
7f
Пример 2.8. Ваш счастливый день Предположим, что мы хотим получить от программы дату счастливого дня. Чтобы выбрать этот день случайным образом, мы просим пользователя ввести счастливое число. Введенное число дает начало индивидуальной последовательности случайных чисел. Затем с помощью этой последовательности мы генерируем номера дня и месяца. На первый взгляд, это можно сделать следующим образом: int int
luckyDay = г.Next (1,31); luckyMonth = r.Next (1,12);
Однако в этом случае мы можем получить неправильную пару чисел, например, 31 и 4 (31 апреля) или 29 и 2 (29 февраля). Последнее иногда допустимо, а иногда — нет. Поскольку все это просто игра, мы можем устранить ошибки, начав с марта и получая даты, не превышающие 30. Позже, в гл. 4, мы рассмотрим способы обработки специальных случаев и сможем модифицировать эту программу, чтобы она покрывала весь год. Итак, программа Lucky Day [Счастливый день]. Файл Lucky.cs using System; class Lucky { ///<summary> ///Программа Lucky Day
Bishop & Horspool Aug 2002
iii
///Определяет счастливый день ///Демонстрирует использование случайных чисел /// void Go () { Console.WriteLine("The Lucky Day Program\n"); Console.Write("What is your lucky number? " ) ; int luckyNumber = int.Parse(Console.ReadLine()); Random r = new Random(luckyNumber); int luckyDay = r.Next(1,30); int luckyMonth = r.Next(3,12); DateTime luckyDate = new DateTime (DateTime.Now.Year,luckyMonth,luckyDay); Console.WriteLine("Your lucky day will be " + luckyDate.DayOfWeek + " " + luckyDate.ToString("M" ) ) ; } static void Main() { new Lucky () .Go () ;
Возможный вывод программы будет выглядеть следующим образом: The Lucky Day Program What is your lucky number? 7 6 Your lucky day will be Saturday 13 April
72
Глава 2. Использование объектов
Весьма полезно проанализировать программу, обращая внимание на типы и данные. Мы используем пять различных типов: Lucky (класс, образующий программу), Console, int, Random и DateTime. В программу входят, как обычно, два метода Go и Main, а также ряд общедоступных методов, именно, WriteLine, Write, ReadLine, Parse, Next и ToString. К полям относятся luckyNumber, luckyDay, luckyMonth, luckyDate и г. Поля объявляются по мере возникновения в них необходимости; сможете ли вы переписать программу, перенеся объявление полей в начало класса? В программе также используются несколько свойств и конструкторов. Сможете ли вы их найти? Наконец, следует заметить, что работу программы нельзя проверить при однократном запуске. Запустите программу несколько раз и убедитесь в том, что она генерирует различные даты, хотя, разумеется, не в январе и феврале, поскольку мы исключили эти месяцы из рассмотрения.
Основные понятия, рассмотренные в гл. 2 В этой главе были рассмотрены следующие понятия (некоторые из них повторно): программа
структура программы
класс
метод
параметр
предложение
комментарий
сцепление (конкатенация)
ввод и вывод
структура
свойство
простое выражение
идентификатор
тип
переменная
объявление переменной
инициализация
оператор
присваивание (назначение)
член (элемент)
Рассмотрены следующие определения, синтаксические формы и API: объект
тип
простой тип
структурный тип
обращение к членам
объявление поля (простое)
создание экземпляра объекта
класс простой программы
простые предложения ввода
структура DateTime (сокращенно)
структуры int или Int32 (сокращенно)
класс Random (сокращенно)
Контрольные вопросы
73
Рассмотрены следующие операторы и знаки пунктуации:
Явным образом использовались следующие ключевые слова С # : using string int
.
class new
Демонстрировались следующие формы комментариев в программе:
Контрольные вопросы 2.1. Вспомните определения, данные в этой главе. Тип: (а) определяет данные (б) представляет числовое и функции значение (в) состоит из объектов (г) является либо структурой, либо классом 2.2. Вспомнив определения и принятые правила использования строчных и прописных букв, приведенные в этой главе, ответьте, обращением к чему является конструкция Part.Member: (а) к свойству объекта (б) к методу класса (в) к свойству класса (г) к полю объекта 2.3. Для сцепления двух строк мы используем: (a) concat (в) &
(б) + (г) ничего
2.4. Прочитать целое число с клавиатуры можно с помощью: (а) Console.ReadLine() (б) int.Parse(Console.ReadLine()) (в) int.Parse.Console.ReadLine() (г) Parse(Console.ReadLine())
2.5. Если требуется прочитать с клавиатуры две строки, они должны: (а) разделяться точкой с запятой (в) быть заключены в кавычки
(б) разделяться пробелами (г) вводиться на отдельных строках
74
Глава 2. Использование объектов
2.6. В конструкции DateTime.Today Today является: (а) свойством
(б) методом
(в) конструктором
(г) полем
2.7. Если endOfYear endOfYear-now?
и now являются переменными, каков тип
(a) DateTime (в) TimeSpan
(б) int (г) невозможно определить
2.8. Если test является объектом Random, какое выражение вернет значение между 0 и 100? (a) Next.test (100) (в) t e s t (0,100)
(б) test.Next(100) (г) t e s t (next (100) )
2.9. Чтобы получить доступ к классу Console, с какой директивы мы начинаем программу? (a) access Console; (в) using Console;
(б) import Console; (r) using System;
2.10. Для создания и запуска объекта, представляющего собой программу с именем Test, как описано в этой книге, какое предложение мы используем? (a) new Test (). Go ();
(б) new Test();
(в) Go (new Test ());
(г) Go .Test () .Main () ;
Упражнения Выполните эти упражнения на основе подхода, описанного в разделе 2.2, т. е. используя не конкретные операторы и конструкции С # , а общие принципы объектного ориентирования. Представьте свои ответы в виде связного текста или простых блок-схем. 2.1. Игра «Морские гонки». Фирма UPUV Games Inc. решила разработать компьютерную игру с использованием в качестве рабочего поля рис. 2.1, где игроки выступают в качестве капитанов морских судов. Составьте список типов, которые необходимо определить в игре, вместе с входящими в них данными и функциями. Для некоторых типов покажите, как будут выглядеть о дин-два объекта этих типов. 2.2. Даты. Считая, что у нас есть один структурный тип Date и два простых типа Integer и String, сконструируйте новый структурный тип для студента, записавшегося на прослушивание четырех курсов в университете. Продумайте состав данных этого типа с произвольной степенью подробности и включите в состав типа по крайней мере две функции, обслуживающие объекты студентов. Начертите диаграмму типа и изобразите два объекта времени, заполнив их значениями.
Упражнения
75
2.3. Видеокамеры. Разработайте тип для объекта киносъемки, включающий полезную информацию, которую способны фиксировать современные видеокамеры, например дату и скорость фильма. 2.4. Детский сад. У ребенка в детском саду имеются такие характеристики, как имя, фамилия, группа (например, кролики, собачки, рыбки и голуби), а также дата рождения. Создайте тип ребенка Child, содержащий всю эту информацию о ребенке и разработайте методы, полезные для обслуживания этой информации. Одним из методов должна быть функция, возвращающая год выпуска ребенка из детского сада, считая, например, что ребенок находится в детском саду до возраста 6 лет. Начертите диаграмму типа и изобразите два объекта этого типа с их значениями. 2.5. Главы книги. В следующих нескольких главах мы займемся разработкой программы, которая содержит информацию об учебнике. Начните с определения типа главы книги Chapter, в котором хранятся название главы и число страниц в ней. Включите в тип функцию, возвращающую первую страницу главы. Можете ли вы на основании проработанного материала придумать такую функцию? Используя настоящую книгу в качестве тестовых данных, создайте объекты для первых четырех частей. Следующие упражнения требуют анализа и модификации программ этой главы. 2.6. Анализ программы Lucky. Рассмотрев программу примера 2.8, выделите в ней составляющие элементы, как это было сделано для примера 2.4. 2.7. Правильное время. Программа из примера 2.3 выводит дату конца года с временем, соответствующим моменту запуска программы. Модифицируйте ее так, чтобы переменная endOfYear содержала в качестве времени 23:59. Подсказка: вам придется создать дополнительный конструктор, показанный в форме DateTime (раздел 2.6). 2.8. Расширение возможностей программы вычисления времени начала совещания. На основании перечня недостатков программы, приведенного в конце примера 2.6, разработайте модификацию этого примера. Опишите новые возможности в комментариях к программе и измените текст программы соответственно. 2.9. Другое изображение. Найдите какое-нибудь изображение и запустите программу Fetchlmage для вывода его на экран. Подгоняется ли размер окна на экране под размер вашего изображения? А теперь напишите с самого начала несколько новых программ. 2.10. Получение книг из библиотеки. Книги можно брать из библиотеки на 14 дней. Составьте программу, которая считывает имя абонента и выводит короткую квитанцию с указанием текущей даты и даты,
76
Глава 2. Использование объектов
когда книга должна быть возвращена. Текущую дату получите с помощью соответствующего свойства DateTime. 2.11. Проверка программы получения книг из библиотеки. Измените программу из упражнения 2.10, чтобы она вводила текущую дату (вместо того, чтобы получать ее с помощью свойства DateTime), а затем выполните тестирование вашей программы, вводя различные даты и проверяя, дает ли программа правильный ответ. Проверить следует те даты, которые повлекут переход в следующий месяц и даже год. 2.12. Конечный срок. Конечный срок выполнения задания указывается в виде даты и времени. Составьте программу, которая вводит все элементы конечного срока и выводит информацию о том, сколько часов осталось для выполнения задания. Вашей программе понадобится использование класса TimeSpan, который был упомянут в спецификации DateTime в разделе 2.6. 2.13. Часы зала заседания. Банк установил четверо часов в своем зале заседаний в Лондоне, которые показывают время в местах нахождения крупнейших бирж, именно, в Лондоне, Нью-Йорке, Токио и Гонконге. Разница зимнего времени в этих точках по отношению к времени по Гринвичу составляет 0, 5, -9 и - 8 . Составьте программу, которая читает время в Лондоне (в любом удобном для вас формате), создает по одному объекту времени для каждого города и выводит их вместе с названиями соответствующих городов. Объясните, почему вы не можете выполнить эти действия в таком порядке: создать объекты, прочитать время, изменить время в объектах, вывести результаты. 2.14. Часы зала заседания для любого момента времени. Измените программу из упражнения 2.13 так, чтобы время в Лондоне выбиралось с помощью датчика случайных чисел.
Внутри объектов 3
В главе 2 мы знакомились с основами программирования на примерах использования объектов, а теперь займемся вопросами определения новых типов объектов. Мы рассмотрим последовательно каждый компонент типа, начав с полей, конструкторов и свойств, и перейдя затем к предложениям, выражениям и простым типам, из которых составляются методы и операторы. Использование нового для нас вида предложения — простого цикла позволит привести и обсудить интересные примеры с большим объемом вывода.
3.1 Структура типа Объекты объявляются с помощью типов. В гл. 2 мы использовали предопределенные типы, такие как int, а также типы, принадлежащие пространствам имен, например, DateTime. В этой главе мы научимся создавать собственные структурные типы. При наличии такой возможности язык программирования становится открытым, приобретая способность бесконечно наращиваться. Действительно, в дополнение к сотням встроенных в API типов (вроде DateTime, Console и Random), мы можем определить неограниченное количество собственных типов. Давайте посмотрим на элементы, составляющие тип, с точки зрения создания нового типа.
Данные Данные новых типов состоят из полей существующих типов. Эти типы могут быть как типами API, например, int или string, так и ранее объявленными нами типами. Предложим, например, тип для описания информации об экзаменах, включая их даты. Состав данных такого типа может выглядеть следующим образом: struct Exam {//Экзамен s t r i n g course;//Предмет int weight;//Процентный вклад в суммарную оценку s t r i n g lecturer;//Лектор DateTime date;//Дата экзамена s t r i n g venue;//Место проведения экзамена s t a t i c int totalNoOfExams=0;//Общее число экзаменов
78
Глава 3. Внутри объектов
Мы здесь показали данные для простоты в виде полей; ниже мы увидим, как можно сделать их доступными через свойства. Теперь переменную типа Exam можно объявить следующим образом: Exam COSExam = new Exam( "COS110", 10, "Dr B r a i n " , new D a t a T i m e ( 2 0 0 3 , 4 / 2 0 ) , "ELB4.2");
Одно из полей типа Exam объявлено с атрибутом static, что означает, что оно будет существовать в единственном экземпляре для всех объектов типа Exam. В переменной totalNoOfExams мы записываем общее число экзаменов в системе с учетом всех объектов. Более того, мы в процессе создания не назначаем этому полю какого-либо значения, поскольку его вычисляет сам тип.
Функции Функции-члены новых типов значительно более интересны, так как с их помощью мы задаем фактические правила поведения объектов. Поведение описывается посредством предложений, и до сих пор мы использовали лишь очень ограниченный набор предложений. Наиболее важными были следующие типы предложений, с которыми мы имели дело: •
ввода;
•
вывода;
• вызова метода; • присваивания. В этой главе мы детально обсудим перечисленные предложения, а также познакомимся с предложением простого цикла. Остальные предложения будут рассмотрены в гл. 4. Теперь обратимся к тому, каким образом функции определяются, а также как проектировать и группировать функции с целью получения связного и практически полезного набора при разработке нового типа. Ранее мы определили четыре-вида функций — конструкторы, свойства, методы и операторы. Каждый из этих видов играет определенную роль в конструировании и использовании типа.
Конструкторы Каждый тип должен иметь конструктор. Конструктор представляет собой последовательность предложений с тем же именем, что и сам тип; конструктор вызывается, когда создаются объекты этого типа. Например, предложение
3.1 Структура типа DateTime
meeting
= new
DateTime(now.Year, now.Month, now.Day, s t a r t T i m e ,
0,
0);
вызывает конструктор DateTime. Тип может иметь несколько конструкторов, однако у них обязательно должны различаться списки параметров (список параметров функции называется ее сигнатурой). Другой конструктор DateTime используется в предложении DateTime
endOfYear
= new
DateTime(now.Year,
12,
31);
Для того чтобы познакомиться с внутренним устройством конструктора, рассмотрим конструктор для типа Exam, определенного выше. Exam(string с, i n t w, string course = c; weight = w; l e c t u r e r = 1; date = d; venue = v; t o t a l += 1;
1, DateTime d, string
v)
{
Для конструктора типично копирование предоставляемых ему параметров в поля объекта; этим могут ограничиваться его функции. Поскольку параметры и поля относятся к одним и тем же данным, принято именовать параметры начальными буквами названий полей (если, конечно, при этом не возникает конфликтов имен). Если конструкторы инициализируют поля объекта, то как это соотносится с инициализацией, которая может иметь место при объявлении полей? Оказывается, существуют определенные правила относительно структур и инициализации полей, сведенные в табл. 3.1. Табл. 3 . 1 . Инициализация полей структуры Понятие
Структуры
Поля экземпляра
Инициализировать при объявлении нельзя
Статические поля
Инициализировать при объявлении можно
Если есть конструктор
Должны инициализироваться все поля (а не некоторые)
Если конструктора нет
Полям придаются значения по умолчанию для типа, например, 0 для чисел и null для строк
Другими словами, значения полей экземпляров структуры должны устанавливаться внутри конструктора, а не там, где эти поля объявляются. Продолжая наш пример, предположим, что имеется, ряд экзаменов, место проведения которых (venue) к моменту истечения срока неизвестно. Тогда можно предусмотреть второй конструктор такого рода:
80
Глава 3. Внутри объектов
Exam (string с, i n t w, string 1, DateTime d) { course = c; weight = w; lecturer = 1; date = d; venue = "To be announced";//Будет объявлено позже t o t a l += I ;
Между конструкторами не будет конфликта, так как один требует пять параметров, а другой только четыре. Соответствующие предложения создания объектов могут выглядеть таким образом: Exam c o m p u t e r s Exam m a t h s
= new Exam("COS110", 5 0 , " P r o f Watson", new D a t e T i m e ( 2 0 0 3 , 1 1 , 1 3 ) ) ; = new Exam("MATH152", 8 0 , " D r J a m e s " , new D a t e T i m e ( 2 0 0 3 , 1 1 , 1 8 ) , "OldHall");
Другой структурный тип С#, класс, предоставляет конструктор по умолчанию для классов, который также оставляет все поля в неинициализированном виде. Это по-прежнему означает 0 или 0.0 для чисел и null для большинства других объектов.
Методы и свойства Рассматривая методы в принципе, мы можем завершить наш пример Exam, добавив несколько разумных методов пока без их содержимого: public TimeSpan DaysToExam() {...} public .override string ToStringO {...}
Первый метод прочитает поле date объекта и вычислит, сколько дней осталось до даты экзамена. Второй метод будет использоваться для вывода значений на печать. Он замещает другой метод с тем же именем, предоставляемый классом System по умолчанию, и вызывается непосредственно из предложений Console.WriteLine. В методе требуется указать модификатор overide [заместить], потому что он замещает существующий метод. Модификатор public требуется для обоих методов, чтобы они были видны и, соответственно, могли быть вызваны из программных строк, находящихся вне данной структуры. Почему только два метода? Дело в том, что большинство полезных функций в этом типе, как и во многих других, будут предоставлены свойствами. Свойства в первую очередь возвращают значения полей, однако они могут также использоваться для их установки. В результате мы можем постулировать наличие в нашем типе следующих свойств, хотя мы еще даже не умеем объявлять свойства: public public
string Course int Weight
3.2 Поля и свойства public public public public
81
string Lecturer DateTime Date string Venue s t a t i c int Total
Доступность. Заметьте, что для обоих методов, а также для всех свойств мы использовали модификатор public. Этот модификатор обеспечивает видимость определяемых элементов вне типа. Если не указать public при объявлении поля, оно будет рассматриваться как private для данного типа, т. е. закрытое для внешнего мира. Данные, объявленные в структуре (course, weight и т. д.) не имеют модификатора public и, следовательно, будут по умолчанию закрыты. Обычно так и поступают: данные объявляют закрытыми (private), а методы и свойства — открытыми (public).
План объявления типа Все элементы типа могут быть объявлены в любом порядке, однако по этому поводу имеются некоторые соглашения. Обычно мы сначала описываем данные, затем конструкторы, далее методы и операторы. Поэтому общий план структуры выглядит так, как это показано в приведенной ниже форме. Форма для определения класса будет такой же, только ключевое слово struct следует заменить на class. Определение структуры модификаторы struct идентификатор_класса { объявления_полей конструкторы объявления_свойств объявления_методов объявления_ опера торов другие_объявления
Элементы типа могут следовать в любом порядке, хотя обычно следуют плану, приведенному в этой форме. Другие объявления включают события и индексаторы, о которых речь будет идти позже.
3.2 Поля и свойства Важнейшей частью любого типа являются его данные, которые представляются в виде объявленных для них полей. Как было видно из общего обсуждения типов в гл. 2, поля могут содержать переменные (чаще всего) или константы (довольно редко). Отличием языка С# от большинства других языков является наличие понятия свойства. Свойство отражает
82
Глава 3. Внутри объектов
ту или иную черту (аспект) класса. Часто, хотя не всегда, это просто значение поля с почти идентичным именем. Если бы мы сделали поле доступным для всех остальных объектов, тогда они могли бы читать это поле и записывать в него. Введя в класс свойство, мы защищаем поле, контролируя доступ к нему извне. Такая методика носит название инкапсуляции. Синтаксис определения свойств приведен ниже в виде формы. Определение свойства public
ТИП идентификатор_свойства {
get
{предложения,
включающие
return
имя; }
set
{предложения,
включающие присваивание для члена
имя;}
Тело метода get определяет, что происходит, когда используется идентификатор свойства, например, с правой стороны операции присваивания или в качестве аргумента метода. Тело метода set определяет, что происходит, когда используется идентификатор свойства, например, с левой стороны операции присваивания. В определении свойства должен присутствовать хотя бы один из методов (или оба).
Ключевые слова get и set показывают, что для этого поля возможна операция присваивания. После того, как свойство объявлено, его можно использовать вместо поля, которое оно защищает. Мы видели уже много примеров таких действий в гл. 2 применительно к встроенным типам. Теперь рассмотрим пример в предположении, что мы уже определили тип Exam. Свойство для изменения поля venue будет определено таким образом: public get set
s t r i n g Venue {//Место проведения экзамена { return venue; }//Получить место проведения экзамена { venue = value; }//Установить место проведения экзамена
} •
Слово value [значение] имеет в этом контексте вполне определенный смысл. Оно соответствует значению выражения с правой стороны операции присваивания для свойства Venue. Примеры использования этого свойства: //Распечатаем место проведения экзамена по компьютерам Console.WriteLine("The exam i s in " + COSExam.Venue); //Теперь изменим место проведения экзамена venue COSExam.Venue - "HSB3.10";
Если мы определяем свойство без компонента set, то изменения соответствующей этому свойству переменной запрещаются. Пусть, например, мы желаем предотвратить изменение названия экзамена. Тогда мы определим соответствующее свойство таким образом: public string Course { get {return course;}//Получить название курса
3.2 Поля и свойства
83
Теперь воспользуемся всеми полученными нами знаниями относительно данных и функций для создания нового типа. Пример 3.1. Тип StarLord
Важнейшим этапом составления компьютерной игры является определение персонажей, которые будут бороться друг с другом за верховную власть. Предположим, что мы пишем программу для такой игры. Мы начнем с определения типа ее персонажей. Назовем этот тип StarLord [Повелитель звезд] и представим его в виде такой структуры С#: Файл Star Lords, cs using System; ///<summary> ///Персонажи StarLord // /
Bishop & Horspool May 2002
til
///Персонажам свойственны определенные характеристики ///и возможность бороться с другими Повелителями звезд /// public struct StarLord { string name; //Имя int strength, //Сила int points; //Баллы static Random r = new Random();//Случайное число public StarLord(string n, int s) { name = n; strength = s; points = s; } public string Name { get {return name;} } public int Points { get {return points;} set {points = value;} } public int Strength { get {return strength;} } public void Attack(StarLord opponent) { int damage - r.Next(strength/3+points/2);//Повреждение opponent.Points -= damage;//Уменьшение баллов оппонента } public override string ToStringO { return name + " at " + points;
Мы уже достаточно изучили типы, чтобы понимать, что можно объявить два объекта StarLord следующим образом:
84
Глава 3. Внутри объектов
StarLord
lordl,
Iord2;
после чего инициализировать их предложениями: lordl Iord2
= new S t a r L o r d ( " D a r t h " , 1 4 ) ; = new S t a r L o r d ( " B i l b o " , 1 0 ) ;
Заметьте, что поле points инициализируется конструктором, хотя для этого поля в конструкторе нет отдельного параметра. Два объекта могут затем взаимодействовать посредством определенного в StarLord метода Attack: lordl.Attack(Iord2);
В методе Attack выполняются следующие предложения: int damage = r.Next (strength/3+points/2); opponent.Points -= damage;
Степень повреждения вычисляется исходя из текущей силы персонажа и его текущего числа баллов. Полученная величина затем вычитается из числа баллов оппонента. В нашем примере объектом является lordl (с именем Darth), а оппонентом, указанным в качестве параметра метода Attack — Iord2 (с именем Bilbo). В классе StarLord предусмотрены три свойства: Name, Points и Strength. Свойства защищают переменные, так что name и strength можно только прочитать (get), однако points можно как читать, так и изменять (get и set).
Статические свойства и свойства экземпляра Мы уже видели в описаниях API в разделе 2.6, что некоторые свойства объявляются, как статические (static). Это означает, что они приложимы ко всем объектам данного типа. В типе Exam поле totalNoOfExams объявлено статическим и, скорее всего, будет иметь соответствующее ему статическое свойство. Другой пример: предположим, что мы хотим вести учет количества Повелителей звезд во вселенной. Тогда мы добавим к типу StarLord следующую переменную вместе с соответствующим ей свойством: static public get
int count = 0; static int Count { {return count;}
}
которое будет вызываться так:
Внутри объектов
85
Console.WriteLine("There are "+StarLord.Count+" StarLords now"); Разумеется, и конструктор следует изменить так, чтобы при создании каждого нового Повелителя звезд выполнялся инкремент переменной count. Пример 3.2. Тип Time Хотя мы в гл. 2 интенсивно использовали тип DateTime, нетрудно предугадать, что в ряде случаев более удобным может оказаться облегченный вариант этого типа, например, для работы только со временем (без даты). Получив представление о конструкторах и свойствах и проработав пример StarLord, мы вполне способны определить такой тип. Этот пример показывает, что могут существовать различные модели, приводящие к нескольким типам с различными способами обращения к данным. Прежде всего, определим данные: int
hour,
min;//4ac,
минута
По умолчанию эти поля закрыты (pivate). Чтобы открыть к ним доступ, их следовало объявить таким образом: public
int
hour, min;
Однако это разрешит неконтролируемый и, возможно, противоречивый доступ к переменным hour и min. Альтернативой будет объявление объекта постоянным, чтобы после создания объекта его нельзя было изменить. Результатом изменений будут новые объекты. Это приводит к такому конструктору: public Time (int h, i n t m) { hour = h; min = m; и двум свойствам: public Hour { get {return hour; } public Min { get {return min;}
которые допускают доступ к соответствующим полям только для чтения. В чем еще нуждается тип? Ему нужна описывающая его структура и почти всегда метод ToString. В результате мы имеем такой полный вариант простого типа для времени:
86
Глава 3. Внутри объектов
Файл Times.cs struct Time { ///<summary> ///Простой класс Time // / ///Сохраняет часы и минуты ///Не проверяет задаваемое время ///Объекты, будучи созданы, не могут быть /// int hour, min //Час, минута public Time (int h, int m) { hour = h; min = m; } public int Hour { get {return hour;} } public int Min { get {return min;) } public override string ToStringO { return hour + ":" + min;
изменены
Пусть мы хотим создать время, изменить час на 12 и вывести оба времени. Нельзя написать: Time a = new Time (someHour, Console.WriteLine(a); a.Hour = 12; Console.WriteLine(a);
someMin);
потому что Hour — это свойство без компонента set. Вместо этого напишем так: Time a = new Time (someHour, someMin); Console.WriteLine(a); a = new Time(12, a.Min); Console.WriteLine(a);
или даже еще лучше так: Time a = new Time(someHour, someMin); Console.WriteLine (a); Time b = new Time (12, a.Min); Console.WriteLine(b);
где а и b теперь существуют независимо и могут изменяться как угодно.
3.3 Числовые типы
87
3.3 Числовые типы Из семейства языков С язык С# унаследовал 11 числовых типов. Их можно сгруппировать следующим образом: • целые со знаком (sbyte, short, int, long), • целые без знака (byte, ushort, uint, ulong), • действительные с плавающей точкой (float, double), • действительные с фиксированной точкой (decimal). Из всех этих типов в каждодневном программировании чаще других используются int и double, наиболее удобные представители целых и действительных чисел. Числовые типы имеют некоторые общие характеристики: 1. Каждая из четырех групп допускает различные способы представления чисел в компьютере, в смысле расположения битов. Расположение битов влияет на значения, которые мы можем или, наоборот, не можем использовать, и на ошибки, возникающие из-за недостаточной точности представления числа. 2. Каждый тип характеризуется размером и использует для хранения числа определенное количество битов от 8 до 96. Размер влияет также на диапазон хранимых чисел. 3. Каждый тип прямо соответствует структурному типу в пространстве имен System; следствия этого обсуждаются в гл. 8. 4. Значения одного типа обычно могут быть преобразованы в другой, если только второй тип может содержать в себе все значения первого. Так, короткое (short) значение может сохраняться в длинной (long) переменной, но не наоборот, если только не использовать явно указанное преобразование; в последнем случае возможны ошибки. См. ниже о преобразованиях. В табл. 3.2 приведены характеристики пяти типов с примерами. В таблице содержится значительный объем информации. Рассмотрим ее более подробно.
Целочисленные типы Мы обсудим здесь три целочисленных типа — byte, int и long. Тип byte описывает числа без знака, а типы int и long — со знаком. Число типа byte сохраняется в 8 битах и не имеет знака (т. е. все его Q возможные значения считаются положительными), давая 2 = 2 5 6 комбинаций нулей и единиц. Поскольку одна из этих комбинаций характеризует 0, максимальное значение равно 255. Значения типа byte используются в тех случаях, когда мы пишем системную программу, манипулирующую с числами в компьютере на уровне отдельных байтов.
88
Глава 3. Внутри объектов
Таблица 3.2. Характеристики некоторых числовых типов
Тип
Число битов
Диапазон значений
Приблизительное число десятичных разрядов
byte
8
0 ... 255
3
int
32
-2147483648 ... 2147483647
10
long
64
±9 х 10'
double
64
±5 х Ю
decimal
96
8
324
18 ... ±1.7 х 10™ 26
±1.0 х 10' ... ±7.9 х Ю
26
15 ... 16 28
Примеры 128 255 10654 -18765
10000000 -1234567890 -16.56 8Е-06 49.99М -9999.9999М
В большинстве программ, работающих с целыми числами, используется тип int. Этот тип позволяет задать в целом числе до 10 десятичных разрядов, что довольно много. Из-за того, что числа могут иметь как положительные, так и отрицательные значения, максимальное абсолютное О
31
значение числа составляет I , что как раз и соответствует значениям, приведенным в табл. 3.2. Количество положительных значений на 1 меньше количества отрицательных, так как одна из комбинаций битов отводится под 0. Если сравнить тип int с типом sbyte (signed byte, байт со знаком), то мы увидим, в типе sbyte используется та же система представления. Его 8 бит дают 256 различных комбинаций, и они используются для представления чисел в диапазоне от —128 до 127 плюс еще 0. Тип long находится в той же группе целых чисел со знаком, что и int, но в таблице для наглядности диапазон этих чисел указан приблизительно в виде степени десяти. Наличие в числе 64 битов дает 2 различных значений (помните, что половина этих чисел отрицательна, а другая половина — положительна, включая 0), а это приблизительно составляет 10 1 8 .
Типы с плавающей точкой Мы включили в нашу таблицу тип long, потому что поучительно его сравнение с типом double. Оба эти типа содержат одно и то же число битов, однако дают сильно различающиеся диапазоны значений и число десятичных разрядов. Так происходит потому, что числа типа double представляются в памяти совершенно не так, как long. Их представление состоит из двух компонентов со знаком, мантиссы и показателя степени (порядка). Мантиссой называется число, начинающееся с цифры, за которой идет десятичная точка, а за ней — оставшаяся часть числа. Показатель степени указывает, где в действительности среди представленных цифр числа должна стоять десятичная точка. Такое представление носит название представления с плавающей точкой. Хотя в компьютере все числа записываются
3.3
Числовые типы
89
в двоичной форме, формат с плавающей точкой в огромной степени расширяет возможный диапазон представляемых чисел. Это можно увидеть из табл. 3.3, где приведено несколько примеров таких чисел с указанием их десятичного представления. Т а б л и ц а 3 . 3 . Примеры чисел с плавающей точкой Число (а) 5.0 (b) 0.005 (с) -0.005 (d) 5000.0 (е) 500300.0 (f) 50030000000000000000.0 (g) 0.00000000000000000005 (h) 5003000000000.0000077 (i) 5.0E60 (i) 5.0E-60
Мантисса 0.5 0.5 -0.5 0.5 0.5003 0.5003 0.5 0.5003 0.5 0.5
Порядок 1 _о -2 4 6 20 -19 13 61 -59
Пример (а) совсем простой, однако заметьте, что если вы хотите записать число типа double, оно должно содержать десятичную точку; числа без десятичной точки рассматриваются, как целые. Примеры (b)-(g) таблицы иллюстрируют преимущества представления с плавающей точкой: нули в начале или в конце числа не записываются в мантиссу, а учитываются в значении порядка. В результате в мантиссе оказывается больше места для значащих цифр, независимо от того, каков порядок всего числа. Это проиллюстрировано в примере (f), где показано число с 20 десятичными цифрами. В табл. 3.2 указано, что числа double могут содержать лишь 15-16 разрядов, но это относится только к значащим цифрам. Такое же число, как в примере (f), представлено в примере (е), но с другим значением порядка. В примере (g) единственная значащая цифра 5 не смогла бы попасть в 16-разрядное число, но в формате с плавающей точкой она прекрасно помещается. Тем не менее, и плавающая точка не всесильна, и иногда число теряет в точности. В примере (h) все 20 разрядов числа являются значащими, однако реально в памяти могут быть представлены лишь 15 или 16 разрядов. Последние несколько разрядов пропадают, и все число попадет к пользователю с погрешностью по отношению к тому числу, которое было реально введено. Примеры (i) и (j) демонстрируют формат Е (сокращение от слова exponent, показатель степени), с помощью которого мы можем записать число с указанием его порядка. Этот формат удобен в тех случаях, когда справа или слева от десятичной точки имеется много нолей. Например, число из примера (f) можно записать в программе, как 5.003Е20. Его же можно записать и иначе, например, 50.03Е19, если такая запись для пользователя более наглядна. В конце главы читатель найдет несколько контрольных вопросов по поводу формата с плавающей точкой.
90
Глава 3. Внутри объектов
Типы с фиксированной точкой Последний тип, показанный в табл. 3.2, decimal, интересен в том отношении, что, как и типы с плавающей точкой, он представляет действительные числа, но, в отличие от них, позволяет делать это абсолютно точно, без погрешности. В этом типе всегда 28 десятичных разрядов. Однако диапазон возможных значений значительно уже. Числа типа decimal должны записываться в программах с завершающей буквой «М». Такие числа используются в основном в финансовых вычислениях. Например, можно написать decimal taxRate = 12.50М;//Ставка налога
Некоторые другие типы также требуют указания при числе различных букв, чтобы отличить их от более распространенных типов. Например, при записи числа, которое должно иметь тип float (а не double), необходимо использовать букву F вместо Е, например, 3.145F0. С другой стороны, буква L, обозначающая тип long, не является обязательной.
3.4 Выражения Мы подошли к одному из наиболее существенных разделов программирования: как записывать выражения. Выражения уже упоминались вскользь в конце раздела 2.4. Здесь мы рассмотрим этот вопрос более подробно. Выражения являются представлением на компьютерном языке математических формул, однако между ними имеются несколько различий. Правила, касающиеся того, какие типы переменных можно комбинировать, и какие при этом получаются результаты, сильнее связаны с фактическим представлением данных в компьютере, чем с математикой, и эти правила необходимо понимать и помнить. Далее, в дополнение к числовым выражениям, имеются и другие их виды, например, выражения отношения, строковые и битовые. Наконец, имеются предложения выражений, к которым, в частности, принадлежит операция присваивания.
Числовые операторы Операторы используются для построения выражений, в которых участвуют числа, поля и функции. С# предоставляет как операторы, соответствующие обычным арифметическим обозначениям, так и некоторые новые. К первой группе относятся операторы -\— * и /. Оператор * используется для обозначения умножения. Поскольку выражения всегда пишутся на одной строке, оператор / используется для обозначения деления. Часто для обеспечения правильного порядка выполнения действий в выражении используются скобки. Примеры выражений: c o s t * exchangeRate temp * 9.0 / 5 - 32 (b * b - 4 * а * с)
/
(2
* a)
3.4 Выражения
Для каждого из типов данных можно использовать каждый из перечисленных выше операторов. Что произойдет, если мы смешаем в одном выражении данные разного типа? Простой ответ заключается в том, что результат повышается до типа с большим диапазоном значений. Для типов int, long и double правила повышения сведены в следующую таблицу: Повышение типа (сокращено) Если один из операндов имеет тип double, результат будет типа double, в противном случае, если один из операндов имеет тип long, результат будет типа long, в противном случае результат будет типа int.
Для операций над типом byte результат всегда преобразуется в int. Для данных типа decimal результат будет иметь тип decimal, за исключением того, что числа с плавающей точкой не могут смешиваться с числами типа decimal без преобразования (преобразование будет описано ниже/. Важный сопутствующий результат этих правил заключается в том, что деление целых чисел дает целый результат. Поэтому, например, получается: 7 / 3 3 / 4
= 2 = 0
Таким образом, если мы включим в программу дробь, равную одной второй, в виде 1/2, фактически будет использовано значение 0. Наконец, оператор % вычисляет остаток от деления (операция деления по модулю). Предположим, что нам надо преобразов'ать число минут в часы и минуты. Это можно сделать таким образом: int totalMins, hours, mins;//Полное число минут, часы, минуты totalMins = int.Parse(Console.ReadLine());//Введем полное число //минут hours = totalMins / 60;//Целочисленное деление mins = totalMins % 60;//Получим остаток Console.WriteLine(totalMins + " minutes = " + hours + " hours, " + mins
+ " minutes");
Введя значение 429, м ы получим: 429 minutes
= 7 hours,
9 minutes
а введя значение -429, получим 1
Полный перечень этих правил приведен в спецификации ЕСМА С# Language Specification, разд. 14.2.6.
92
Глава 3. Внутри объектов
-429 minutes = -7 hours, -9 minutes Заметьте, что 7x60+9 = 429, а -7x60-9 = -429. В общем виде, если х и у — два числа, тогда операции деления и получения остатка удовлетворяют условию: (х/у) ху +
(х%у) =
х
Завершая обсуждение оператора %, заметим, что 7% -2 дает 1, а -7%-2 дает - 1 . Другими словами, остаток всегда имеет знак левого операнда. Обратите внимание на то, что оператор % можно использовать со всеми числовыми типами, не только с целыми. Пример 3.3. Автобус-челнок Автобус-челнок, перевозящий пассажиров между городом и аэропортом, покидает аэропорт каждые полчаса. Указав время в часах и минутах, мы хотели бы узнать время отправления следующего автобуса. Наша привычка оперировать с часами и минутами настолько развита, что эту задачу ничего не стоит решить в уме, однако для компьютера она потребует некоторого объема вычислений. Задача разбивается на две части: представление времени и выполнение вычислений для следующего автобуса. Поскольку в примере 3.2 мы уже разработали простой тип для описания времени, начнем с него. Мы можем создать объект типа времени: Time
now
= new
Time (h,
m) ;
где h и m вводятся с клавиатуры и соответствуют свойствам now.Hour и now.Min. Вычисления оказываются довольно запутанными: int nextBusInMins = (now.TimeInMins+30)/30*30;
Здесь требуются некоторые объяснения. Если N — целое число, то N/30 дает целочисленный результат деления на 30. Тогда N/30*30 дает другое целое число, кратное 30, но, вероятно, меньшее, чем N, поскольку при делении обычно выполняется округление в меньшую сторону. Выражение будет равно N только когда N кратно 30. Выражение (N+30)/30*30 даст следующее число, кратное 30. Теперь рассмотрим пример с единицами времени. Пусть время равно 9:45. Мы можем получить от объекта это время, выраженное в минутах (для выполнения этой операции к типу Time было добавлено новое свойство), т. е. 9x60+45 = 585. Добавление к числу минут числа 30 даст 615. Целочисленное деление на 30 даст 20. Умножение этого результата на 30 даст 600 минут, или 10 часов, т. е. время 10:00. Из этого времени мы конструируем новый объект с помощью второго конструктора:
3.4
Выражения
93
public Time(int t) { hour = t / 60; min = t % 60;
Тогда следующим предложением программы будет: Time nextBus = new Time(nextBusInMins); Мы можем внести в программу усовершенствование, позволяющее обобщить ее на любой интервал движения автобуса, не только 30 минут. Предположим, что летом автобусы отправляются с интервалами 20 минут. Нам не хотелось бы просматривать весь текст программы и отыскивать места, в которых встречается число 30. Вместо этого мы заменяем константу 30 на имя поля, объявляя это поле константой. В приводимом ниже примере эта константа имеет имя interval. Имея два объекта времени, мы можем вывести на экран требуемую информацию. Текст программы приведен ниже. Файл Shuttle.cs using System; class ShuttleBus { ///<summary> ///Программа Shuttle Bus Bishop & Horspool Aug 2002 /// == ========== ///Вычисляет время подхода следующего автобуса в начале или ///середине часа ///Демонстрирует определение структур, а также использование ///свойств и арифметических операторов, включая деление по ///модулю /// const int interval = 30; void Go() { Console.WriteLine("What is the time? " ) ; Console.Write("Hour: " ) ; int hours = int.Parse(Console.ReadLineО);//Введем часы Console.Write("Min: " ); int mins » int.Parse(Console.ReadLine());//Введем минуты Time now = new Time(hours,mins) ; int nextBusInMins = (now.TimelnMins + interval) /interval*interval; Time nextBus = new Time(nextBusInMins); Console.WriteLine("Time is " + now + " and the next bus is at " + nextBus) ; } static void Main() { new ShuttleBus () .Go () ;
94
Глава 3. Внутри объектов
struct Time { ///<summary> ///Простой класс Time III
///Сохраняет часы и минуты; не проверяет введенное время ///Объекты, будучи созданы, не могут быть изменены /// int hour, min;//4acbi, минуты public Time (int h, int m) { hour = h; min • m; } public Time(int t) { hour = t / 60; min = t % 60; } public int Hour { get {return hour;} } public int Min { get {return min;} } public int TimelnMins { get {return hour * 60 + min;} } public override string ToStringO { return hour + ":" + min;
Чтобы проверить программу, мы вводим, например, 9:14. Вывод программы будет таким: What i s t h e time? Hour: 9 Min: 14 Time i s 9:14 and t h e
next bus
is
at
9:30
Для исчерпывающего тестирования программы мы должны вводить разные значения времени, и среди них те, которые приведут к пересечению характерных временных границ. Набор тестовых данных может иметь следующий состав: 9 9 9 9 9 10 23
14 29 30 45 59 00 35
3.4 Выражения
95
Что случится в последнем случае? Если мы прочитаем описание типа Time, мы увидим, что в нем фактически не выполняется никаких проверок значений, поэтому в этом случае мы получим результат 24:00.
Выражения отношения Следующей полезной операцией над числами является их сравнение. С этой целью большинство языков предоставляют целый набор операторов сравнения, именно, = = != < > =
равно не равно меньше чем больше чем меньше чем или равно больше чем или равно
Результатом такого сравнения двух значения будет значение типа bool, причем это значение может быть либо true [истина], либо false [ложь]. Подробное обсуждение типа bool, имеющего собственный набор операторов, проводится в гл. 4. Здесь мы только отметим, что с помощью перечисленных выше операций можно сравнивать числовые значения. Такого рода сравнения будут использованы ниже для управления циклами. Для каждого из числовых типов существует каждый из перечисленных операторов. Если в одном предложении сравнения смешиваются разные числовые типы, тогда используется оператор более «высокого» типа: Типы результата сравнения Если один из операндов имеет тип double, выполняется сравнение типов double, в противном случае, если один из операндов имеет тип long, выполняется сравнение типов long, в противном случае выполняется сравнение типов int.
Учитывая, что типы с плавающей точкой могут представлять данные неточно, лучше избегать сравнения чисел этих типов на равенство. Обычно оказывается достаточным применить сравнение на неравенство. Примеры условных выражений: i < 10 t.Year == 2003 DateTime.Now != first x >= у Для числовых типов предусмотрены два специальных оператора, ++ и — . Их можно использовать для прибавления или вычитания единицы из переменной, участвующей в выражении. Очень часто, однако, они исполь-
96
Глава 3. Внутри объектов
зуются не внутри выражений, а в качестве самостоятельных предложений, например, так:
mass--;
Результат таких записей заключается в том, что единица вычитается из значения переменной (или прибавляется к нему). Приведенные выше предложения эквивалентны следующим: i = i+1; mass = mass-1; Фактически имеются две формы операторов ++ и — . Можно написать как х++ (постфиксная форма), так и ++х (префиксная форма), и то же самое для оператора — . Если эти операторы используются в качестве самостоятельных предложений, нет никакой разницы между префиксной и постфиксной формой этих операторов. (В программах этой книги операторы инкремента и декремента всегда используются в виде самостоятельных предложений.) Ниже мы увидим примеры использования этих операторов в циклах.
Операторы присваивания Наряду с простым оператором присваивания =, имеется возможность комбинирования этого оператора с каждым из других арифметических операторов с образованием составных операторов присваивания. Для чисел это будут следующие операторы: +=
Во всех случаях смысл такой записи будет в выполнении указанной арифметической операции над числами, стоящими в правой и левой частях выражения и в присваивании результата этой операции левому операнду выражения. Таким образом, в предложениях i += 2; mass -= 5;
выполняется прибавление 2 к i и вычитание 5 из mass, соответственно. Эти операторы являются просто сокращенными записями, но они экономят время на ввод программы и весьма популярны среди программистов на С#.
3.4 Выражения
97
Другим расширением присваивания является множественное присваивание. Допустимо написать в одной строке September = a p r i l
= June = november = 30;
Оператор присваивания, как будет объяснено ниже, является правоассоциативным, поэтому указанные в примере операции присваивания будут выполняться справа налево.
Приоритет и ассоциативность Наконец, мы должны рассмотреть понятие приоритета (старшинства) применительно к рассмотренным операторам. Приоритет определяет, в каком порядке выполняются операторы. В обычных математических выражениях умножение всегда выполняется перед вычитанием, в каком бы порядке эти действия не были указаны в выражении, поэтому 10-3*2 дает в результате 4, а не 14. К счастью, в С# операторы действуют точно таким же образом. В табл. 3.4 перечислены операторы формально принятых в С# групп. Первая группа имеет наивысший приоритет; далее приоритет снижается. Т а б л и ц а 3 . 4 . Приоритет операторов и ассоциативность Группа
Операторы 1 х.у
f(x)
Ассоциативность
Первичные
(х)
++ —
new
Справа налево
Унарный
-
Мультипликативные
*
Аддитивные
+ -
Слева направо
Отношения
< > < = > =
Слева направо
Равенства
==
Слева направо
Присваивания
= *=
Справа налево /
%
Слева направо
!• /=
%= += - =
Справа налево
Здесь х обозначает переменную или выражение; у обозначает поле или функцию; f обозначает функцию.
Ассоциативность определяет порядок выполнения операций по отношению к операторам одной группы. Все бинарные операторы левоассоциативные (за исключением присваивания). Унарные операторы, например, точка или new, правоассоциативные. Поэтому выражения, включающие несколько операторов плюс, выполняются слева направо (левоассоциативный оператор). То же относится и к оператору минус, что соответствует нашим привычкам. Например, 1 0 - 4 - 1 + 3 4—2047
98
Глава 3. Внутри объектов
даст 8. Мы и не ожидаем, что это выражение будет вычисляться, как 10
-
(4
-
(1
+ 3)
что даст 10. На практике арифметические операторы на вызывают особых затруднений, однако следует проявлять осторожность, когда эти операторы взаимодействуют с группами первичных операторов или операторов отношения. В табл. 3.5 приведены некоторые примеры, которые помогут яснее представить себе рассмотренные понятия. Таблица 3.5. Примеры действия приоритетов Вычисляется, как
Выражение 1 + 2 / 3 + 4
1
х+у > p+q
(х
today.Hour
+ (2 + у)
/ 3) >
(р + q)
(today.Hour)
+ 10
+ 4
+ 10
Преобразования Мы уже несколько раз упоминали преобразования типов. Понять принцип преобразований в С# очень легко. Значение может быть неявным образом повышено к следующему старшему типу. Такое неявное преобразование выполняется при смешивании в выражении различных значений. Правила преобразования были перечислены в разделе 3.4. Желая выполнить обратное преобразование, старшего типа в младший, мы должны использовать явное преобразование. Правила для него таковы: Явное преобразование (идентификатор_ типа) checked
выражение
({идентификатор типа)
выражение)
Выражение вычисляется и преобразуется в указанный тип, возможно, с потерей информации. Действительные числа при преобразовании в целые значения округляются вниз (в сторону ноля). Если тип не может содержать результирующее значение и включена проверка (модификатор checked), возникает ошибка, называемая OverflowException [исключение переполнения]. Помещение модификатора checked перед преобразованием приводит к возникновению исключения даже если режим проверки выключен в компиляторе.
Поэтому если в программе описаны следующие переменные: i n t i = 6; double x e 3.14; double у - 5.003000000008Е+15; double z;
3.5 Простые циклы
99
то приведенные ниже операции присваивания приведут к результатам, описанным в комментариях: z i i i i
= = = = =
i;//Всегда правильно — значение равно б. О х;//Ошибка компиляции — требуется явное преобразование у;//Ошибка компиляции — требуется явное преобразование (int) х;//Правильно — значение равно 3 (int) у;//Ошибка выполнения, если включен режим проверки //(checked), неопределенное значение в противном //случае
Значение i в последнем случае при выключенной проверке будет равно —1244672188, что не имеет никакого отношения к значению у, за исключением того, что некоторые биты величины у будут рассматриваться, как целое число. Такого же рода результат получится, например, при смешивании целых и байтов, или любых двух числовых типов, требующих указания явного преобразования . Очевидно, что проверка преобразований числовых типов является важной операцией, и вам следует убедиться, что режим проверки включен в той среде, в которой вы работаете. Альтернативой этому является явное указание модификатора checked, как это показано в приведенной выше форме. Операция явного преобразования и модификатор checked используют круглые скобки вокруг выражения, к которому они применяются и, как было показано в табл. 3.4, рассматриваются как первичные операторы.
3.5 Простые циклы Выражения составляют основную часть любой программы, однако они становятся особенно полезны, если повторяются многократно. Именно в этом случае особенно ярко проявляется истинное могущество такой машины, как компьютер. Люди не любят выполнять нудные повторяющиеся операции, компьютер справляется с ними гораздо лучше. Поэтому мы приступаем к рассмотрению очень простой структуры, выполняющей циклическую работу. Простой цикл управляется переменной-счетчиком, начальное значение которой показывает, сколько раз нам надо выполнить заданную операцию. Есть и более сложные циклы; мы рассмотрим их в следующей главе. Ключевом словом, позволяющим организовать циклическое выполнение, является слово for, поэтому такие циклы известны под названием «циклы for». Ниже приводится форма для таких циклов.
Полный перечень этих правил приведен в спецификации ЕСМАС# Language Specification, разд. 14.2.6.
100
Глава 3. Внутри объектов
Цикл for (простой)
for
(int счетчик = начало; счетчик ///Программа ComputeGrade
(вариант 2) Bishop
/ I /
=
=
& Horspool
2002
=
ill
///Преобразует оценку в буквенное обозначение. ///Демонстрирует использование массивов и выход из цикла ///посредством оператора return /// doublet] boundary = { 90.0, 85.0, 80.0, 75.0, 70.0, 65.0, 60.0, 50.0, 40.0 }; string[] grade - { "A+", "A", "A-", "B+", "В", "В-", "C", "D", "E"}; string DetermineGrade(double m) { for (int i = 0; i< boundary .Length; i++) { if (m >= boundary[i]) return grade[i]; } return "F"; } void Go() { double mark; Console.WriteLine("Enter -1 to quit"); Console.WriteLine("Mark Grade"); do { mark = double.Parse(Console.ReadLine()); if (mark >= 0) {0}", DetermineGrade(mark)); Console.WriteLine } while (mark >= 0 ) ; static void Main() { new ComputeGrade2() .Go()
Типичный прогон программы дает следующий результат: Enter Mark
-1 t o q u i t Grade
77
B+
65
B-
64
c
90
A+
-1
152
Глава 4. Управление и массивы
Цикл foreach В дополнение к формам циклов, рассмотренным уже в разделе 4.3, в С# имеется еще одна особая конструкция, называемая циклом foreach. Этот цикл разработан специально для коллекций, собраний данных, которые будут рассмотрены в гл. 8. С другой стороны, массив является простым видом коллекции значений, и цикл foreach вполне можно использовать с массивами. Если, например, у нас есть массив с перечислением названий месяцев string[]
MonthNames = { "January", "April", "May", "June", "October", "November",
"February", "March", "July", "August", "September", "December" };
то следующий цикл выведет список месяцев в том же порядке, как они указаны в объявлении массива и по одному имени на строке: foreach ( s t r i n g s i n MonthNames) Console.WriteLine ( s ) ;
{
}
Приведем форму для цикла foreach. Цикл foreach foreach
(тип идентификатор in идентификатор-массива {
. . . тело цикла, может ссылаться на идентификатор
Идентификатору присваиваются последовательные значения из массива. Для каждого значения тело цикла повторяется.
4.5 Строки и символы Хотя эта глава посвящена, главным образом, управлению программой, мы ввели в нее описание массивов, что дало нам возможность предложить более наглядные примеры на циклы. Строки обладают многими характеристиками массивов, а символы представляют собой элементы строк. Рассмотрим теперь эти понятия более детально.
Тип string [строка] На протяжении всей книги мы использовали строковые константы. Встречались нам также и операции сцепления строк. Например, предложение Console.WriteLine("Result
= "+n);
4.5 Строки и символы
153
преобразует значение п в строку (если только тип п не является уже строкой) и сцепляет эту строку со строковой константой "Result = ", в результате чего образуется новая более длинная строка, передаваемая затем методу WriteLine класса Console. Строковый тип string является структурным типом, имеющим много общего с другими типами С#, но имеющим и присущие только ему особенности поведения. Со строками можно выполнять следующие операции: • назначение строковых значений строковым переменным, • сравнение двух строк, • сцепление (конкатенацию) двух строк, • доступ к свойствам строки и, • вызов различных методов, определенных для типа string. Строка схожа с массивом элементов, которые мы можем читать, но не изменять. Предположим, что мы объявляем и инициализируем строку следующим образом: string
s = "abcdefghij";
Мы можем получить доступ к каждому символу строки и напечатать его с помощью такого цикла: for
( i n t i = 0 ; i < s .L e n g t h ; i + + ) { C o n s o l e . W r i t e L i n e ( " E l e m e n t {0} = { 1 } " , i ,
s[i]);
который выводит 10 строк текста вроде следующих: Element 0 = а Element 1 = Ь Element
9 = j
Однако строки не считаются массивами и назначение s[3]
= ' х ' ; //Неверная
попытка
заменить
букву
d на букву х
является неверным, потому что переменные строкового типа являются неизменяемыми — их нельзя модифицировать. Для того чтобы создать новое строковое значение, необходимо создавать новый экземпляр строкового типа .
Изменяемые строки должны реализовываться, как экземпляры класса StringBuilder.
154
Глава 4. Управление и массивы
Операции со строками Базовые операции со строками перечислены в приведенной ниже форме. Объявление и использование строк string svarl; string svar2 = "строка"; svarl = svar2; svarl = "строка"; svarl = svar2 + "строка"; int len = svarl.Length;
//Объявляем строковую переменную //Объявляем и инициализируем строку //Присваиваем значение строке //Присваиваем значение строке //Сцепляем две строки //Получаем длину строки
Переменная svarl создается с начальным значением null, в то время как переменная svar2 создается и инициализируется значением "строка".
Важно подчеркнуть разницу между понятиями null (т. е. отсутствие значения) и нулевая, или пустая строка. Здесь может помочь пример. Приведенные ниже строки С# приведут к ошибке времени выполнения: string s i ; si += "abc";
//Создается строка со значением null //Неверно, выполнено не будет!!
потому что левый операнд в операции сцепления строк не имеет значения. Если, однако, переписать строки следующим образом: string s2 • " " ; s2 += "abc"; то они будут успешно выполнены и переменная s2 получит в итоге значение "abc". Строковые константы начинаются и заканчиваются символами двойных кавычек « " », причем эти константы могут иметь любую длину, начиная от 0. Иногда нам нужно включить в строковую константу символ двойной кавычки. Для этого символ кавычки необходимо предварить символом обратной косой черты «\». Однако иногда требуется включить в строку сам символ «\». Эта проблема решается тем же способом — включением перед выводимым символом «\» еще одного символа «\». Ниже приведено несколько примеров такого рода. Console.WriteLine ( " " ) ; Console.WriteLine ( " \ " " ) ; Console.WriteLine("\\\\"); Console.WriteLine ( " \ \ \ " a \ \ \ " " ) ;
//Выводит //Выводит //Выводит //Выводит
пустую строку строку: " строку: \\ строку: \ " а \ "
Вот еще полезный пример: string s • " \ \ \ " " ; Console.WriteLine(s.Length) ; //Выводит 2 Важно отметить, что иногда включение в текст программы двух символов приводит к тому, что только один (специальный) символ помещается в
4.5 Строки и символы
155
строковую константу. Все эти специальные комбинации символов в С# имеют в качестве первого символа пары символ обратной косой черты. Некоторые дополнительные специальные комбинации символов перечислены в следующем разделе, посвященном символьному типу char. Все комбинации, допустимые в качестве символьных констант, могут быть использованы и внутри строковых констант.
Методы типа string Тип string включает целый ряд предопределенных методов. Выборка наиболее полезных методов приведена в форме для класса string. Полную информацию о методах string можно найти в описании класса System.String (потому что string в языке С# является синонимом System. String). Класс string (сокращено) bool si.StartsWith(string s2) //true, если si начинается с s2 bool si.EndsWith(string s2) //true, если si заканчивается строкой s2 int si.IndexOf(char ch) //Ищет символ ch внутри si int si. IndexOf (char ch, int pos) //To же, но начиная с позиции pos int si.IndexOf(string s2) //Ищет строку s2 внутри si int si. IndexOf (string s2, int pos) //To же, но начиная с //позиции pos string si.Substring(int pos) //Извлекает подстроку из si string si. Substring (int pos, int len) //To же string si.ToLowerf) //Копирует si, преобразуя буквы в строчные string si.ToUpper () //Копирует si, преобразуя буквы в прописные string si.Trim () //Копирует si, убирая лидирующие и //завершающие пробелы string si.TrimStart() //Копирует si, убирая лидирующие пробелы string si.TrimEnd() //Копирует si, убирая завершающие пробелы string [] si.Split () //Расщепляет si на слова, по одному на //элемент массива char si. [int pos]; //Обращается к элементу в позиции pos StartsWith возвращает true только если строка s1 начинается со строки s2; EndWith выполняет аналогичную проверку в конце строки. IndexOf возвращает позицию первого вхождения в строку s1, начиная от ее левого конца, символа ch или подстроки s2. Первая позиция строки имеет номер 0. Если поиск не дает результата, возвращается - 1 . Два варианта с аргументом pos начинают поиск от позиции pos. Методы Substring возвращают подстроки строки s1. Первый вариант возвращает всю подстроку, начинающуюся с позиции pos; второй вариант возвращает подстроку длиной len, начинающуюся с позиции pos. ToUpper и ToLower преобразуют все алфавитные символы в строке s1 в прописные или строчные, соответственно. TrimStart возвращает копию строки s1, из которой убраны лидирующие пробелы; TrimEnd возвращает ту же строку, из которой убраны завершающие пробелы; Trim возвращает копию без тех и других пробелов. Split разбивает строку s/ на массив подстрок, где в качестве подстрок рассматриваются части строки s1, ограниченные пробельными символами (пробелами или символами табуляции). s1[pos] выполняет обращение к символу в позиции pos.
156
i
Глава 4. Управление и массивы
Тип char Если символьная строка схожа с массивом, то к какому типу принадлежит элемент этого массива? Ответ — к типу char. Переменная типа char может содержать значение, представляющее собой числовой код одного символа. Каждый символ, например, буква 'а' или знак '&', имеет закрепленный за ним числовой код. Соответствие кодов и символов до некоторой степени произвольно, однако все классы и методы в библиотеке С#, работающие со строками и символами, должны использовать одну и ту же кодировку. Библиотека С# использует метод кодирования, носящий название Unicode. Каждое значение Unicode лежит в диапазоне от 0 до 65535 и занимает, следовательно, 16 бит памяти. В Unicode 26 строчных букв латинского алфавита от 'а' до V нумеруются по порядку, так же, как и 26 прописных букв от 'А' до Z' и 10 цифровых символов от '0' до '9'. Составляя программу, вы всегда можете считать, что символы в этих группах нумеруются последовательно (см. Приложение Г, где приведены дополнительные сведения относительно кодировки Unicode). Объявление и использование символов char char
chl;
//Объявление
ch2 = ' X ' ;
символьной переменной
//Объявление и инициализация символьной переменной
chl
= ch2;
//Присваивание символьного
значения
chl
= 'У;
//Присваивание символьного
значения
s
= s+chl;
//Сцепление
строки и символа
s
= chl+s;
//Сцепление
символа и строки
Объект c h l создается и инициализируется символом с кодом 0, a ch2 создается и инициализируется значением 'X'.
В качестве примера приведем фрагмент программы, который позволяет увидеть числовые значения некоторого диапазона символов: for
( c h a r ch = ' A ' ; ch ? @ А В D E F G H I J K L M N O P Q R S T U V W X Y [ \ ] _ a b c d e f g h i j k l m n o p q r s t v w y z { | } - | ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 7 ? ? ? ? ? ? ? ? ? ? ? ? i Ф £ a V | S " с
I a u
I D f t 0 0 0 0 6 x 0 U U U D Y _ f i & i a ft 4 Q u
* у
9
ft у
i
ft
ft
£
£
£
£
d
fi
d
б
б
о
б
• о
ft
Полученный вывод не вполне точен, так как не все коды могут быть отображены в виде символов на экране.
Escape-последовательности Символьная константа записывается в форме 'X', где одиночные кавычки (апострофы) используются в качестве ограничительных символов как слева, так и справа. Учитывая, что в кодировке Unicode могут существовать 65535 различных 16-битовых кодов, а типичная клавиатура компьютера имеет лишь около 50 алфавитно-цифровых клавиш, и принимая еще во внимание, что некоторые символы выводятся специфическим образом (например, символ табуляции), необходимо предусмотреть специальные обозначения для записи большинства символов типа char. Эти специальные обозначения начинаются с обратной косой черты; некоторые из них перечислены в приводимой ниже форме. Они носят название Escape-последовательностей (escape — переход), а символ обратной черты в данном контексте называют строковым escape-символом (не путать с клавишей Esc на компьютерной клавиатуре). Этот символ указывает на переход к альтернативным обозначениям. Символьные escape-последовательности (сокращено) \п \t \0 V \" \\ \uXXXX
Символ Символ Символ Символ Символ Символ Символ
перевода строки горизонтальной табуляции null одиночного апострофа кавычки обратной косой черты UNICODE с кодом ХХХХ (шестнадцатеричное)
Все комбинации могут использоваться внутри строковой константы или константы типа char и обозначают один 16-битовый символ. В комбинации \uXXXX символ X обозначает одну шестнадцатеричную цифру (от 0 до 9 или от А до F); так, \uOO41 обозначает символ с кодом 4x16+1, или 65, а это есть код буквы 'А'
158
Глава 4. Управление и массивы
Символ перевода строки осуществляет переход на следующую строку; символ табуляции смещает вывод к следующей предопределенной позиции в текущей строке. Ноль-символ часто используется для обозначения отсутствия символа — он обычно игнорируется при выводе. Поэтому его можно использовать как способ удаления символов из строки. Таким образом, мы имеем целый ряд символьных и строковых значений со схожими названиями, но имеющими разный смысл: •
ноль-символ ' \ 0 ' ;
•
null;
•
пустая строка " " ;
•
строка, содержащая ноль-символ " \ 0 " .
Эти значения могут быть созданы следующим образом: char cl = string si string s2 s t r i n g s3
'\0'; = n u l l ; //si не имеет значения = " " ; //s2 имеет строковое значения; длина строки = О = "\0"; //s3 имеет строковое значения с длиной = 1
Программа может выполнять лишь ограниченный набор операций над символьными переменными, именно, задавать им новые значения, сравнивать их и сцеплять со строками. Однако эти значения исключительно полезны при вводе и выводе текста. Unicode Имеется много тысяч символов, отсутствующих на клавиатуре. Это акцентированные символы, символы-умляуты, специальные символы денежных единиц, символы алфавитов других языков (иврита, греческого) и т. д. Однако, как уже отмечалось, имеется международное соглашение относительно числового представления всех этих символов, носящее название Unicode. Информацию об этой кодировке можно найти по адресу http://www.unicode.org. Некоторые наиболее полезные коды приведены в табл. 4.4. Таблица 4.4. Некоторые символы Unicode Символ
Unicode
ё
00 F9
U
OOFC
0
00F8
Ф
00А2
£
00A3
¥
00А5
€ ©
20АС 263А
4.5 Строки и символы
159
Для того чтобы вставить символ Unicode в строку, мы используем escape-код \ucccc, где сссс — код Unicode. Необходимо указывать полный 16-битовый код. Например, можно написать Console.WriteLine("Dr
M\uOOFCller's salary \u00A5200.000");
i n Japan w i l l
be " +
что приведет к выводу на экран строки: Dr Muller's salary in Japan will be ¥200.000
Если символы Unicode выводятся неправильно, вы должны установить на вашем компьютере необходимые шрифты. Инструкции, касающиеся этих действий, содержаться на Web-сайте Unicode. Возможности консольного окна по части вывода символов Unicode могут оказаться ограниченными (не все символы будут отображаться на экране), но все-таки попробуйте выполнить приведенный выше фрагмент.
Методы типа char Тип char, являющийся синонимом типа System.Char, предоставляет ряд статических методов, в основном связанных с классификацией символьных значений и распределением их по различным категориям символов. Некоторые методы анализа символов приведены ниже. Тип char (сокращено) bool
char.IsDigit{ch)
//true,
если
ch
цифра
bool
char.IsLetter(ch)
//true,
если
ch
буква
bool
char.IsLetterOrDigit(ch)
//true,
если ch
bool
char.IsLower(ch)
буква или цифра
//true,
если ch
строчная буква
bool char.IsUpper(ch)
//true,
если ch
прописная буква
bool
//true, если ch //табуляция
char.IsWhiteSpace (ch)
пробел или
Под ch подразумевается значение типа char. Если проверка дает отрицательный результат, метод возвращает значение false.
Помимо букв, цифр и пробельных символов, имеются много других категорий. Все детали можно найти в документации к типу System.Globalization. UnicodeCategory. Проверки на категорию обычно используются для просмотра вводимого текста и поиска в нем определенных частей. Например, для нахождения следующего идентификатора (имени переменной) в строке исходного текста программы, можно использовать такой метод: string GetNextldentifier(string line, int idStart = - 1 ;
int pos) {
160
Глава 4. Управление и массивы for
(int i=pos; i 0. Если это условие выполняется, тело цикла (предложения, заключенные в фигурные скобки) выполняется. Затем снова вычисляется условное выражение. Если результат опять равен true, тело цикла снова выполняется, и этот процесс повторяется до тех пор, пока вычисление условного выражения не даст false. Поскольку тело цикла в каждой итерации делит к на 2, значение к должно становиться все меньше и меньше. В конце концов к должно стать равным нолю, и условное выражение дает false. Второй цикл в программе, расположенный внутри метода Go, написан как бесконечный цикл. Обычно следует избегать бесконечных циклов. Конкретно в этой программе цикл for содержит условное выражение, которое приводит к выходу из цикла не как обычно, а с помощью предложения return. Файл ShowBinary.cs
using System; class ShowBinary { ///<summary> ///Программа ShowBinary / //
Bishop & Horspool 2002
ill
///Выводит число в двоичной системе обозначений ///Демонстрирует использование цикла while /// void PrintBinary(int k) { if (k — 0) { Console.WriteLine("0"); return; } string s = ""; while (k > 0) { if (k%2 == 0) s = ' 0' + s ; 6—2047
Глава 4. Управление и массивы
162 else s = ' 1' + s ;
к /= 2; } Console.WriteLine(s); } void Go () { Console.WriteLine("Note: enter -1 to exit the program."); for ( ; ; ) { Console.Write("Enter number to display in binary: " ) ; int num = int.Parse(Console.ReadLine()); if (num < 0) return; PrintBinary(num);
static void Main() { new ShowBunary() .Go();
Типичный прогон программы может выглядеть следующим образом: Note: enter -I to exit Enter nubmer to display 1101 Enter nubmer to display 100 Enter nubmer to display 1100100 Enter nubmer to display
the program. in binary: 13 in binary: 4 in binary: 100 in binary: -1
4.6 Дополнительные предложения выбора Для реализации требуемого алгоритма передачи управления в программе от одного предложения к другому достаточно предложений if и циклов while. Однако программы становятся более наглядными, если в них использовать более выразительные конструкции передачи управления. К таким конструкциям можно причислить предложения break и continue. Предложение break может быть использовано для внеочередного выхода из цикла, а предложение continue позволяет вне очереди начать выполнение следующей итерации цикла.
Предложение break Рассмотрим внутренний цикл в программе Primes.cs (пример 4.4). Как только мы находим делитель для переменной i, мы должны завершить цикл. Другой способ программирования такого цикла будет выглядеть следующим образом:
4.6 Дополнительные предложения выбора prime
=
true;
163
#*~
for (int j = 3; j*j ///Программа Roman2Arabic
Bishop & Horspool May 2002
///Преобразует в десятичную систему и выводит на экран числа, ///вводимые пользователем в латинской системе счисления ///Выполняется лишь минимальная проверка правильности ///вводимых латинских чисел ///Демонстрирует использование предложений switch /// void Arabic(string s) { int units, tens, hundreds, thousands; units = tens = hundreds = thousands = 0; for (int = 0; i < s. Length; i++) { char с = s[i]; switch (c) { case ' I' : case ' i' : units++; break; case ' V : case ' v' : units = 5 - units; break; case ' X' : case ' x' : tens += 10 - units; units = 0; break; case ' I/ : case ' 1' : tens = 50 - tens - units; units = 0; break; case ' С : case ' c' : hundreds += 100 - tens - units; tens = units =0;
168
Глава 4. Управление и массивы break; case ' D' : case ' d' : hundreds • 500 - hundreds - tens - units; tens = units =0; break; case ' M' : case ' m' : thousands += 1000 - hundreds - tens - units; hundreds = tens = units = 0; break; default: Console.WriteLine( "Error: {0} is not a Roman digit", c) ; break;
return thousands + hundreds + tens + units; } void Go() { for ( ; ; ) { Console.Write ( "Enter Roman number (or empty line to exit) : ") ; string s = Console.ReadLine() ; s = s .Trim() ; if (s. Length == 0) break; Console.WriteLine ( "{0} is the Roman notation for {1}", s, Arabic (s.))
static void Main() { new Roman2Arabic ().Go();
Обратите внимание на то, что конструкции case 'метка1 используются парами, 'X' вместе с 'х' и т. д. Таким простым способом м ы расширили программу на случаи использования как строчных, так и прописных латинских букв. После запуска программы вы получите результат вроде следующего: Enter Roman XIX is the Enter Roman XIX is the Enter Roman XIX is the Enter Roman XIX is the Enter Roman
number (or empty line to Roman notation for 19 number (or empty line to Roman notation for 1900 number (or empty line to Roman notation for 1919 number (or empty line to Roman notation for 1977 number (or empty line to
exit) : XIX exit) : MCM exit) : MCMXIX exit) : MCMLXXVII exit) :
4.7 Проект 2. Игра «Камень—ножницы—бумага»
169
4.7 Проект 2. Игра «Камень—ножницы—бумага» Введение В игру «Камень—ножницы—бумага» обычно играют два человека в несколько раундов. В каждом раунде оба игрока прячут руки за спиной. Затем, по счету три, оба игрока быстро выбрасывают руки вперед. Если рука сжата в кулак, она обозначает камень. Если выбрасывается ладонь со сжатыми вместе пальцами, это обозначает бумагу. Наконец, если указательный и средний палец образуют букву V, это обозначает ножницы. Если оба игрока выбрасывают одинаковые фигуры, раунд считается закончившимся вничью. В противном случае ножницы побеждают бумагу (потому что ножницы могут резать бумагу), бумага побеждает камень (потому что лист бумаги можно обернуть вокруг камня), а камень побеждает ножницы (потому что ударив камнем по ножницам, можно их повредить). Введя слова «rock paper scissors» в поисковой машине Web, вы найдете многие тысячи Web-сайтов посвященных этой игре . (Если даже вы не относитесь к этой игре серьезно, многие не разделяют вашего мнения.) В табл. 4.5 приведена матрица правил этой игры, где +1 обозначает победу игрока А, - 1 победу игрока Б, а 0 — ничью. Таблица 4.5. Возможные исходы игры «Камень—ножницы—бумага» Фигура игрока В Rock [Камень]
Фигура игрока А
Paper [Бумага]
Scissors [Ножницы]
Rock
0
-1
1
Paper
1
0
-1
Scissors
-1
1
0
Когда в эту игру играют два человека, она является борьбой психологии — каждый игрок старается предугадать, какую фигуру выкинет его соперник. Когда человек играет с компьютером, стратегия уж точно не основана на психологии, особенно, если игроку известен исходный текст программы. Наша программа, чтобы затруднить предугадывание, использует случайные числа.
Структура программы Программа разбита на два класса, сохраняемые в двух разных файлах. Один, класс DriveRPSGameConsole, отвечает за все взаимодействия с пользователем. Он также ведет учет для игрока-человека числа выигранОсобенно рекомендуем сайт http://www.worldrps.com
170
Глава 4. Управление и массивы
ных, проигранных и ничейных раундов. Другой класс, RPSGame, фактически ведет игру — выбирает фигуру, выбрасываемую компьютером, и, получив информацию о фигуре оппонента, сообщает, кто выиграл данный раунд. Исходный текст программы приведен ниже. Управляющая структура программы DriveRPSGameConsole использует внешний цикл, который в каждой итерации запрашивает у пользователя его фигуру и отвечает выбором своей. Для удобства пользователя задание фигуры осуществляется вводом одной буквы: «г» для камня, «р» для бумаги и «s» для ножниц. Для завершения игры пользователь должен ввести «q» (сокращение от quit). Разделение программы на два класса потребовало добавления ключевого слова public [открытый] к конструктору и двум методам класса RPSGame. Без ключевого слова public и конструктор, и оба метода были бы по умолчанию закрытыми (private). Другими словами, они были бы скрыты и их нельзя было бы вызвать из другого класса DriveRPSGameConsole. В противоположность этому, метод, названный Result, вызывается только из фрагмента внутри класса и, следовательно, не нуждается в объявлении его открытым. Подробное обсуждение всех модификаторов доступа С# проводится в гл. 9. Файл DriveRPSGameConsole.cs using System; c l a s s DriveRPSGameConsole ///<summary> ///Программа RPSGame
{ Bishop
& Horspool
August
2002
///Играет с пользователем в игру "Камень—ножницы—бумага", ///используя для ввода-вывода консоль /// void Go () { ///<summary> ///Управляет игрой "Камень—ножницы—бумага" /// RPSGame game = new RPSGame ( ) ; int noOfWins, noOfDraws, noOfLosses, round; noOfWins = noOfDraws = noOfLosses = round = 0; string computersChoice; string result; for ( ; ; ) {
computersChoice = game.ComputersChoice;//Ход компьютера string players.Choice = null; do { Console.Write( "Enter R (Rock), P (Paper, "+ "S (Scissors), or Q (Quit): " ) ; string b = Console.ReadLineO .ToLower () ;//Ход пользователя switch (b[0]) {
4.7 Проект 2. Игра «Камень—ножницы—бумага» case ' г' : playersChoice case ' p' : playersChoice case ' s' : playersChoice case ' q' : playersChoice
171
= "Rock"; break; = "Paper"; break; = "Scissors"; break;
= "Quit"; break; } } while (playersChoice == null); if (players.Choice == "Quit") break; result = game.ComparePlays(playersChoice); round++; Console.WriteLine("Round "+round); Console.WriteLine("The computer's choice = "+computersChoice); Console.WriteLine ("The player's choice = "+playersChoice); switch (result) { case "draw": Console.WriteLine( " This round is drawn");//Ничья в этом раунде noOfDraws++; break; case "lose": Console.WriteLine( " Sorry,you lose this round");//Вы проиграли //этот раунд noOfLosses++; break; case ""win": Console.WriteLine( 11 Well done, you win this round");//Вы //выиграли этот раунд noOfWins++; break; } Console.WriteLine("Status: {0} wins, {1} draws,"+ "{2} losses", noOfWins, noOfDraws, noOfLosses);
static void Main() { new DriveRPSGameConsole() .Go ();
Второй файл называется RPSGame.cs и содержит программную реализацию класса RPSGame. В нем определяется свойство, обеспечивающее доступ к следующей фигуре, выбранной компьютером. Метод ComparePlay вычисляет и выводит результат раунда.
172
Глава 4. Управление и массивы
Файл RPSGame.cs using System; class RPSGame { ///<summary> ///Ведет учет состояния игры "Камень—ножницы—бумага" /// string[] MoveNames = {"Rock", "Paper", "Scissors"}; string computersChoice; Random r; public RPSGame() { r = new Random () ; public string ComputersChoice { get { //генерирует случайные числа 0, 1 или 2, преобразует //их в строку //и сохраняет для дальнейшего использования на этапе //сравнения computersChoice = MoveNames[r.Next(3) ]; return computersChoice; } public string ComparePlays(string playersChoice) { ///<summary> ///Определяет, выиграл ли игрок у компьютера ///Вызывает функцию Result, конструирующую сообщение ///Функция Result вызывается со следующими параметрами: /// — ход игрока /// - ход компьютера, при котором игрок выигрывает /// - ход компьютера, при котором выигрывает компьютер /// switch (playersChoice) { case "Rock" // игрок выигрывает проигрывает return Result("Rock" , "Scissors", "Paper"); case "Paper" : // игрок выигрывает проигрывает return Result("Paper", "Rock", "Scissors"); case "Scissors" : // игрок выигрывает проигрывает return Result("Scissors", "Paper", "Rock",); default: return null; string Result(string player, string Pwin, string Plose) { if computersChoice == Pwin) return "win"; else if computersChoice == Plose) return "lose"; else return "draw";
Основные понятия, рассмотренные в гл. 4
173
Если вы запустите программу, ее вывод может выглядеть примерно так: Enter R (Rock) , P (Paper) , S (Scissors) , or Q (Quit) : R Round 1 The computer's choice = Scissors The player's coice = Rock Well done, you win this round Status: 1 wins, 0 draws, 0 losses Enter R (Rock) , P (Paper) , S (Scissors) , or Q Round 2 The computer's choice = Rock The player's coice = Rock This round is drawn Status: 1 wins, 1 draws, 0 losses Enter R (Rock) , P (Paper) , S (Scissors) , or Q Round 3 The computer's choice = Paper The player's coice = Rock sorry, you lose this round Status: 1 wins, 1 draws, 1 losses Enter R (Rock) , P (Paper) , S (Scissors) , or Q
(Quit) : R
(Quit) : R
(Quit) : Q
Поскольку компьютер выбирает свои фигуры, используя генератор случайных чисел, доли выигрышей, проигрышей и ничьих должны составлять приблизительно по 3 3 % в достаточно долгом сеансе. Хотя интерфейс ввода весьма прост (что может быть проще для игрока, чем ввести одну букву в каждом раунде), выход программы нельзя назвать наглядным. Вариант этой игры, использующий графический интерфейс (GUI), представлен в гл. 5.
Основные понятия, рассмотренные в гл. 4 Понятия, рассмотренные в этой главе: массивы
индексация массивов
различные циклы
булевы выражения
предложение break
предложение continue
предложение if
предложение switch
цикл while
цикл do-while
тип bool
тип string
тип char
escape-последовательности и символы Unicode
Глава 4. Управление и массивы
174
Рассмотренные операторы: & &&
I!
Ключевые слова: for
while
do
if
else
break
continue
switch
case
default
foreach
char
string
bool
Синтаксические формы: операторы сравнения
булевы логические операторы
булевы условные операторы
предложение if
цикл while
цикл do-while
цикл for
объявление массива (упрощено)
цикл foreach для массива
объявление и использование типа string
класс string (сокращено)
объявление и использование типа char
символьные escapeпоследовательности (выборка) предложение switch
тип char (сокращено)
Контрольные вопросы 4.1. Если мы хотим установить булеву переменную freePass [бесплатный билет] для детей [children] до 15 лет или для студентов [student] до 25 лет (возраст содержится в переменной age), какое из приведенных ниже предложений будет правильным?
Контрольные вопросы
175
(а) freePass
= age < 15
(б) freePass
= age
(в) freePass (r) freePass
= age < 15 | | (age < 25 && s t u d e n t ) ; = student | | age < 15;
| |
age < 25
| |
student;
< 25 && student;
4.2. Какой из приведенных ниже фрагментов соответствует такому алгоритму: цикл повторяется до тех пор, пока пользователь на введет «q»? (а) bool more = false; while (!more) { ...предложения more = Console.ReadLine()
!= "q";
(б) bool quit = true; while (!quit) { ...предложения quit = Console.ReadLine()
!=
"q");
(в) bool quit = true; while (!quit) { ...предложения more = Console.ReadLine () ==
"q");
(r) bool more • true; while (more) { ...предложения more = Console.ReadLine()
"q");
!=
4.3. Какую последовательность значений выведет приведенный ниже фрагмент? int i while
= 17; (i !=
1)
{
Console.Write("{0}", i ) ; i = 3*i + 1; while (i%2 == 0) i /= 2;
(a) 17 13 5 1 (в) 17 13 5
(6) 17 15 13 11 9 7 5 3 1 (г) 17 13 5 4 1
4.4. Что важно включить в цикл с неоднозначным выходом? (а) предложение break (б) предложение if в конце цикла (в) предложение continue
(г) предложение return
176
Глава 4. Управление и массивы
4.5. Что выведут приведенные предложения? string s = "V'hello! \"\n" ; Console.WriteLine("{0},s.Length
(a) 9
(6) 8
(в) 6
(г) 12
4.6. Если массив объявлен следующим образом: DateTime[] myExamDays = {new DateTime(2003,11,4), new DateTime(2003,11,7), new DateTime(2003,11,12), new DateTime(2003,11,19)}
тогда день моего второго экзамена выражается: (a) myExamDays[2]
(б) myExamDays[I].Day
(в) myExamDays.Day[1]
(г) myExamDays[2].Day
4.7. Если предложение switch не включает выбор по умолчанию, результатом этого будет: (а) ошибка компиляции (б) если значение выражения выбора не соответствует ни одной из меток, то выбирается ветвь case с меткой, наиболее близкой по значению к значению выражения выбора (в) если значение выражения выбора не соответствует ни одной из меток, то выполнение продолжается с предложения, стоящего после конструкции switch (г) ошибка выполнения 4.8. Правильным является следующее предложение switch для задания числа дней в месяцах года: (a)
int
Daysln(int month) switch case
(month) 9:
{
{
case
4:
case
break; case
2:
return
break; else
return
31;
break; }
else }
return
31;
28;
6:
case
11:
return
30;
Контрольные вопросы (б) int
177
Daysln(int
month) {
switch (month) { case 9, 4, 6, 11: return break; case 2: return 28;
30;
break; default: return 31; break;
(в) int Daysln(int month) { switch (month) { case 9: case 4: case break; case 2: return 28; default: return 3 1 ;
(r) int Daysln(int month) { int days; switch (month) { case 9: case 4: case break; case 2: days = 28; break; else days = 3 1 ; break;
6: case
11: return 3 0 ;
6: case
11: days
= 30;
4.9. В программе ComputeGrade2 (пример 4.6) как наилучшим образом вывести два массива? Требуемый вывод должен выглядеть следующим образом: 90.0 85.0
А+ А
и т. д. (а) foreach
(double
mark
in boundary)
Console.WriteLine(mark+" (б) for
"+grade[mark]);
(int i=0; K b o u n d a r y . Length; i++) Console.WriteLine(boundary[i]+"
(в) foreach
(double
mark
"+grade[i]);
in boundary)
Console .WriteLine (mark+If
"+grade [boundary [mark] ] ) ;
178
Глава 4. Управление и массивы (г) fоreach
(double mark in boundary,
Console.WriteLine(mark+"
s t r i n g symbol in grade) "+symbol);
4.10. Если у нас есть объявление s=" by A A Milne ", и мы хотим вывести просто имя "A A Milne", какой из приведенных ниже фрагментов выполнит эту операцию? (а) s .TrimStart () . Substring (s . IndexOf ('A' ) ) . T r i m E n d O ; (б) s . Substring (s. IndexOf ('A' ) ) . Trim() ; (в) S.Substring(s.IndexOf('A')); (r) s.TrimStart () .Substring (s . IndexOf ('A') ,s. Length) .TrimEndO ;
Упражнения 4.1. Разнообразие циклов. Сравнительно легко преобразовать цикл for в эквивалентный ему цикл loop и наоборот. Приведенный ниже фрагмент должен вводить строки в массив и затем выводить их на экран: string s; int lineNum = 0; string[] line = new string[10]; for (s=Console.ReadLine(); s!=null; s=Console.ReadLine()) { line[lineNum] = s; lineNum++; for (int i = 0; i < lineNum; Console.WriteLine{"{0,4}: {1}", i+1, lineNum); }
Включите этот фрагмент в законченную программу и проверьте ее работу. После этого замените циклы for циклами while и убедитесь, что программа по-прежнему дает правильные результаты. Еще раз модифицируйте программу, использовав циклы do-while. 4.2. Любые города. Модифицируйте пример 2.7 (Определение времени с чтением), чтобы программа вводила данные до тех пор, пока пользователь не сигнализирует об окончании ввода. Придумайте, каким должен быть этот сигнал. 4.3. Гистограммы. Если у нас есть массив значений вроде того, что создавался в программе распределения оценок (пример 4.5) или схожий с массивом количества осадков (рис. 4.2), мы можем организовать более наглядный вывод, представляя значение в виде строки звездочек. Мы получим, например, следующее (для нескольких первых строк вывода): mark 0 1
occured 24 26
************************ **************************
Упражнения
179
Разработайте метод, который, получив в качестве параметра значение, выводит соответствующее количество звездочек. Включите этот метод в программу Frequencies. 4.4. Гистограмма количества осадков. Если пользоваться методом вывода из упражнения 4.3, то для получения разумного изображения иногда потребуется изменять масштаб данных при определении количества выводимых звездочек. Например, значения из рис. 4.2 можно удвоить перед тем, как передать их методу Histogram [гистограмма]. Разработайте способ определения необходимости и численных характеристик масштабирования и включите соответствующий фрагмент в программу вывода количества осадков. 4.5. Числа различных систем счисления. Модифицируйте программу ShowBinary (пример 4.7), чтобы она выводила число в любой системе счисления до 10 после ввода значения основания системы. 4.6. Шестнадцатеричные числа. Шестнадцатеричная система счисления, использующая основание 16, является общепринятой в вычислительной технике. В ней используются 16 цифр от 0 до 9, а далее буквы от А до F. Так, число 15 записывается как F, а 16 как 10. Модифицируйте программу ShowBinary, чтобы она выводила числа в шестнадцатеричной системе. Подсказка: для выбора старших цифр можно использовать предложение switch, или преобразование сначала из char в int, а затем из int в char. Отладьте оба варианта. 4.7. Преобразование арабских чисел в латинские. Значительно более интересной задачей, хотя и более простой в некоторых отношения, является преобразование арабских чисел (т. е. с основанием 10) в латинские. Принципы преобразования вы найдете в примерах 4.7 и 4.8. 4.8. Печать текста. Текстовые сообщения (например, службы телефонных сообщений) часто печатаются строчными буквами, но многие сотовые телефоны имеют встроенные средства преобразования в прописную первой буквы после такого символа пунктуации, как точка или знак вопроса. Составьте программу, которая будет вводить сообщение в переменную string (на одной строке), а затем обрабатывать его с получением новой строки с прописными буквами в соответствующих местах. 4.9. Символы валюты. Компания учитывает расходы своих служащих в долларах. После того, как они возвращаются из деловой поездки, они могут представить свои расходы в одной из следующих валют: • • • • • •
фунтах стерлингов, например, £2050; евро, например, €5196; канадских долларах, например, C$4987; долларах США, например, $5000; йенах, например, ¥200000; шведских кронах, например, 7000кг.
180
Глава 4. Управление и массивы
Составьте программу, которая читает одну из этих статей расходов и преобразует ее в доллары США. Курсы валют можно непосредственно записать в программу. Получите их с Web-сайта www.xe.net или другого схожего. Обратите внимание на обозначения денежных сумм в валютах, в которых символ валюты пишется после суммы. 4.10. Марафонский забег. В марафонском забеге участвуют пять основных соперников из Англии, Германии, Италии, Швеции и Норвегии. Организаторы забега получили следующую информацию о каждом бегуне: имя, страна, возраст, лучшее достигнутое им марафонское время (в часах и минутах). •
Разработайте класс, представляющий марафонского бегуна. Для записи лучшего достигнутого им времени используйте класс Time. Обычно марафонское время имеет порядок двух часов.
• Напишите небольшую программу, которая позволит проверить правильность создания вами пяти бегунов. •
Теперь напишите программу забега Race, которая выполнит следующие операции: создание бегунов, генерацию случайного финального времени в забеге для каждого бегуна, вывод на экран списка бегунов с указанием их характеристик вместе с финальным временем и лучшим достигнутым временем.
•
Определите победителя.
4.11. Худеющие приятели. Два приятеля решили попробовать похудеть за несколько недель. Первый из них вначале имел вес 100 кг и талию 98 см в окружности, второй — вес 85 кг и талию 95 см. Каждую неделю они записывают новые значения веса и окружности талии и определяют свои успехи по каждому из этих параметров. • Разработайте и запрограммируйте класс для записи хода похудания с данными о текущем весе и окружности талии. •
Протестируйте класс, создавая объекты для нескольких людей, вплоть до шести.
• Теперь составьте программу, которая отобразит четырехнедельный сеанс похудания. Каждую неделю генерируются случайные значения нового веса и окружности талии так, чтобы они грубо соответствовали предыдущим (например, текущий вес ± до 1.5 кг и окружность талии ± до 2 см). Выводите на экран новые значения и сохраняйте их снова в объектах, а также выводите данные о том, кто похудел больше в каждом случае. •
Ведите учет потерям веса и полноты, а в конце сеанса выведите суммарные потери для каждого участника.
•
Определите участника, потерявшего в весе больше других.
Упражнения
181
4.12. Голосование. Совет директоров состоит из трех членов, у каждого из которых имеется переключатель с двумя надписями: «за» и «против». При голосовании в случае большинства голосов «за» зажигается лампочка. Цепь, которая реализует включение лампочки, представляется булевым выражением L
= a & ( b | c )
| b & c
Составьте программу, которая выводит таблицу возможных значений «за» и «против» для участников a, b и с, а также соответствующие значения L. Для определения L разработайте метод Boolean. 4.13. Дни рождения. Вероятность того, что два человека в группе людей будут иметь один и тот же день рождения, вычисляется по следующей формуле: , . , pin) = 1
365 365
х
364 365
х
363 365
х ... х
364 - п + 1 365
Составьте программу, которая оценивает и выводит эту вероятность для групп от 2 до 60 человек. Нарисуйте таблицу значений пжр(п) для значений п от 10 до 50. Если вы справились с упражнением 4.3, выведите значения для 10, 20, ..., 50 в виде гистограммы. 4.14. Ряд Фибоначчи состоит из последовательности чисел, в которых каждое следующее число представляет собой сумму двух предыдущих, например, 1
1
2
3
5
8
13
21
34
5 5
...
Составьте программу для вывода первых 50 членов ряда Фибоначчи. Используя вложенный цикл for, модифицируйте программу так, чтобы она выводила только каждое третье число. Что можно заметить в этих числах? 4.15. Телефонные компании. Усовершенствуйте проект из гл. 3, включив в него данные об интервалах времени, в течение которых действует льгота, а для каждого телефонного разговора — время его начала (для представления времени можете воспользоваться структурой Time из примера 3.2). Замените генерацию случайного числа, указывающего, является ли разговор льготным, на анализ вхождения времени начала разговора в льготный интервал. Завершите программу выводом на экран заключения о том, какая компания в данном сеансе моделирования предоставляет более выгодные условия.
Графические интерфейсы пользователя с применением системы Views
5
В обсуждавшихся выше программных примерах мы использовали текстовый ввод и вывод. В этой главе мы познакомимся с тем, как создавать на экране окна (формы) Windows, посредством которых пользователь может взаимодействовать с программой. Это взаимодействие реализуется с помощью многочисленных стандартных элементов управления, таких как кнопки и окна редактирования, и включает даже средства вывода изображений. Взаимодействие осуществляется через настраиваемое пользователем пространство имен Views, написанное на языке С# и доступное читателю этой книги. Views использует язык XML, сходный с HTML, для определения содержимого формы Windows. Взаимодействие с формой во время выполнения требует использования в программе различных управляющих структур, в частности, циклов и предложений switch, которые были рассмотрены в гл. 4.
5.1 Графические интерфейсы пользователя Программы, рассматриваемые в предыдущих главах этой книги, управлялись текстовым вводом, получаемым либо с клавиатуры, либо из текстового файла, и выводили результаты своей работы тоже в виде текста — либо в файл, либо в консольное окно на экране. Хотя такая форма ввода-вывода вполне достаточна при проведении работ по настройке компьютера, однако для обычного компьютерного пользователя вывод в виде текста представляется малопривлекательным. И не следует рассматривать этот вопрос только с эстетической точки зрения. Можно привести много примеров, когда выбор вариантов с помощью мыши будет значительно удобнее для пользователя, чем альтернативный ввод путем набора текста на клавиатуре. Итак, мы хотели бы, чтобы наши программы создавали на экране окна для взаимодействия с пользователем, и чтобы внешний вид наших программ был бы столь же приятным, как у коммерческих продуктов. На языке С# вполне можно написать программу, которая будет работать не в консольном текстовом окне, а создаст собственное окно, способное выводить различные сообщения, и содержащее различные элементы управления: кнопки, по которым пользователь сможет щелкать мышью,
5.1 Графические интерфейсы пользователя
183
текстовые поля, в которые можно вводить требуемый текст и проч. Такого рода окно, содержащее нетекстовые элементы для выбора режимов и отображения другой информации, и взаимодействие с которым осуществляется главным образом посредством курсора, перемещаемого с помощью движения мыши, называется графическим интерфейсом пользователя, или, для краткости, GUI (от Graphical User Interface) (произносится gooey). Сопрограмма, выполняемая на компьютере с платформой Windows, может использовать классы из пространства имен System.Windows. Forms для отображения на экране элементов GUI и для управления вводом от мыши и клавиатуры. На рис. 5.1 показана простая форма GUI, созданная таким образом.
Рис. 5 . 1 . Простой графический интерфейс, созданный с помощью форм Windows
Возможности этой формы совершенно очевидны: в окно выводится изображение, а две кнопки используются для показа (Show) и сокрытия (Hide) изображения. Пример был разработан с помощью средства Windows, называемого Visual Studio; программа содержала 124 строки, из которых 15 были написаны программистом, а остальные сгенерированы средой Visual Studio. Обучение программированию с использованием классов и различных инструментальных средств является непростым делом в силу большого количества классов, каждого с солидным набором методов и свойств, которые к тому же взаимодействуют друг с другом неочевидным образом. В этой книге мы знакомим читателя прежде всего с языком С#. Поэтому мы стараемся описывать программные примеры и пространства имен С# по возможности независимым от Windows (операционной сие-
184
Глава 5. Графические интерфейсы с применением системы Views
темы) образом. При таком подходе мы можем уделить внимание также проектированию GUI — т. е. наилучшему расположению элементов управления. Например, на рис. 5.1 кнопки расположены с левой стороны окна, хотя внешний вид кадра был бы лучше, если бы кнопки были помещены над изображением в центре кадра. Чтобы не быть зависимым от платформы и одновременно обеспечить простые средства экспериментирования с GUI, мы сначала разработаем программы, которые будут создавать графический интерфейс для ввода-вывода на основе пространства имен Views, которое было специально разработано для этой книги. Это пространство имен можно переписать по сети как в форме исходных текстов, так и в откомпилированной форме и использовать затем в операционной системе Windows. Файлы находятся на Web-сайте, созданном для поддержки этой книги. Справочные данные по Views приводятся в Приложении Е. Система Views соотносится с формами Windows в том смысле, что она использует в точности те же термины и понятия, что и класс System. Windows.Forms. Будучи, однако, подмножеством, Views обеспечивает более удобные средства работы с GUI. В результате то, чему мы научимся, изучая Views, можно будет перенести на формы Windows без всякого труда.
5.2 Элементы GUI Давайте сначала посмотрим на GUI в целом. Мы затронем следующие термины: •
управление;
• форма (т. е. форма Windows); • линейка меню; • планировка интерфейса; •
взаимодействие.
Графический интерфейс пользователя представляет собой окно на экране, содержащее ряд различных компонентов, или элементов управления. В качестве элементов управления могут выступать, например, надписи, кнопки и текстовые поля. В программном отношении графический интерфейс реализуется в виде формы Windows. Интерфейс чаще всего имеет в верхней части формы строку меню с именем программы или окна, а также несколько кнопок. Обычно таких кнопок три — для свертывания окна (со знаком минус), для его развертывания и изменения размеров (два перекрывающихся прямоугольника) и для уничтожения (крестик). Расположение этих кнопок зависит от платформы; например, на рис. 5.1, который получен в системе Windows, кнопки размещены в правой части строки меню.
5.2 Элементы GUI
185
Что именно расположено под строкой меню, всецело определяется программой, создавшей это окно. Говорят, что элементы управления распланированы определенным образом относительно друг друга и границ окна. Выбор как самих элементов управления, так и их планировки определяется программистом. После того, как мы рассмотрим пример и познакомимся поближе с некоторыми компонентами, мы вернемся к вопросу о планировке интерфейса. Получив интерфейс с элементами управления, мы взаимодействуем с некоторыми из них с помощью мыши и клавиатуры. Поместив курсор мыши на такой элемент управления, как кнопка, и нажав клавишу мыши («щелкнув» по экранной кнопке), мы можем инициировать какие-либо действия. В другом случае мы можем находиться в элементе управления, который позволяет вводить текст с клавиатуры с отображением его в элементе управления. Другие элементы управления определяют области для выводимых данных, которые, как мы уже видели, могут иметь текстовую или графическую форму.
Введение в простые элементы управления Составление и использование игровых компьютерных программ — весьма увлекательное занятие, и мы теперь подошли к такому этапу, когда можем создать для игры удобный графический интерфейс. На рис. 5.2 показано окно GUI для рассмотренной ранее игры «Камень—ножницы—бумага» (см. проект 2 в гл. 4). С правилами игры мы уже знакомы; здесь мы рассмотрим процесс создания для нее графического интерфейса. Прежде всего рассмотрим внимательно рис. 5.2. Область окна под строкой меню содержит разнообразные элементы управления, которые можно сгруппировать следующим образом. Кнопки. В нижней части окна расположены три элемента управления типа Button [кнопка] ; на один из них, с надписью «Scissors», указывает стрелка. Пользователь может помесить курсор мыши на кнопку и щелкнуть по ней. Это действие фиксируется операционной системой, которая, в свою очередь, сообщает программе о событии. В окне имеются еще две управляющие кнопки, названные «Rock» и «Paper», которые точно также посылают в программу события. Надписи. Некоторые элементы управления являются просто строками текста; например тот, где написано «Choose your selection...» [Сделайте свой выбор...]. Эти элементы управления относятся к типу Label [надпись] и являются пассивными — если пользователь помещает на них курсор мыши и щелкает по ним, или вводит что-то с клавиатуры, ничего не происходит. Список. В правой части окна имеется большой прямоугольник, в который программа выводит данные о выборе пользователя и последующих собы-
Глава 5. Графические интерфейсы с применением системы Views
186
СПИСОК
надпись
| Bound 1 I The computer's choice • Paper j The player's choice • Paper 1 This round is drawn окно ввода текста
I Round 2 j The computer's choice • Scissors I The player's choice • Rock I Wei! done, you win this round I I Round 3 j T he computer's choice • Paper j The player's choice = Paper 1 This round is drawn
кнопка
Рис. 5.2. Графический интерфейс для игры «Камень—ножницы—бумага
тиях. Этот элемент управления содержит вертикальный список строк и носит название ListBox [список] . Текстовое поле. На рис. 5.2 мы видим три прямоугольные белые рамки, содержащие числа; эти элементы управления представляют собой объекты типа TextBox [текстовое поле]. В игре «Камень-ножницы-бумага» они используются для вывода некоторых результатов игры: в них отображается суммарное число выигрышей, проигрышей и ничьих для игрока. В дальнейшем везде, где это возможно, мы используем обычные слова (кнопка, окно ввода текста) для обозначения элементов управления. Если же речь будет идти о конкретных экземплярах объектов С#, мы будем пользоваться программными обозначениями (Button, TextBox).
5.2 Элементы GUI
187
Программу можно написать таким образом, что она будет принимать данные, вводимые пользователем в этих полях, но в рассматриваемой программе эта возможность не реализована. Программа, создающая графический интерфейс, должна указать, какие ей требуются элементы управления, какой они должны быть величины и в каких местах окна они будут расположены. Эти детали являются обязательной составляющей разработки интерфейса, и явное указание их в программе может оказаться весьма утомительным делом. Если rockButton является экземпляром класса, который создает и выводит изображение кнопки, нам хотелось бы избежать включения в программу предложений вроде rockButton.Location
= new System.Drawing.Point(55, 275);
чтобы поместить кнопку на расстоянии 55 пикселов от левого края и 275 пикселов от верхнего края окна. Такое планирование интерфейса отнимет массу времени, будет приводить к ошибкам и потребует многочисленных пробных прогонов.
Планирование GUI Разрабатывая графический интерфейс, мы можем просто добавлять в окно элементы управления по мере того, как в них возникает нужда, однако такой подход не даст удовлетворительных результатов. Интерфейс будет аккуратно выглядеть лишь в том случае, если схожие элементы управления будут сгруппированы вместе. Например, все кнопки естественно расположить в верхней части окна или, наоборот, в нижней. Мы можем разделить экран пополам, расположив окна ввода с левой стороны, а окна вывода с правой и т. д. Различные варианты планировки интерфейса будут проиллюстрированы в последующих примерах. Вопрос заключается в том, каким образом мы будем манипулировать элементами интерфейса? Здесь имеются три возможности. «Перетащить и оставить». Этот способ предполагает наличие специального инструментального средства, с помощью которого составляется текст программы. Мы перетаскиваем мышью требуемый элемент управления из списка, в котором перечислены все возможности, и оставляем его на том месте окна, где, на наш взгляд, он должен быть. В дальнейшем мы можем изменить расположение элементов управления, перетаскивая их мышью по экрану, и так же просто изменять их размеры. Такие инструментальные средства представляют собой весьма сложные программы, занимающие в компьютере много места. Примером такой среды является Visual Studio, пакет, разработанный специально для С# и других языков на платформе Windows. Visual Studio обеспечивает массу возможностей, и пользоваться им очень непросто, если вы только начинаете изучать программирование. Visual Studio генерирует програм-
188
Глава 5. Графические интерфейсы с применением системы Views
мный код, использующий различные средства повышенной сложности. Однако есть и другие, более «легковесные» инструментальные среды, которые, к тому же, обладают тем преимуществом, что не зависят от платформы. Одну из них можно использовать и для Views, о чем речь будет идти ниже. Абсолютное позиционирование. Не используя метод «Перетащить и оставить», мы можем включать в программу вызовы методов форм Windows, которые с точностью до пиксела позволят расположить в окне требуемые элементы управления. Типичный экран дисплея содержит пикселы, нумеруемые от 0 до по меньшей мере 800 по горизонтальной оси х и от 0 до по меньшей мере 600 по вертикальной оси у, причем оси расположены так, как это показано на рис. 5.3. Там же изображена кнопка, которая будет расположена в позиции х=300, z/=150, т. е. чуть меньше, чем на полдороги по каждой оси.
о0 300Д50
О Рис. 5.3. Оси координат на компьютерном экране
Программные строки, помещающие кнопку в позицию #=300, г/=150, будут выглядеть приблизительно так: b u t t o n = new B u t t o n ( ) ; b u t t o n . L o c a t i o n = new P o i n t ( 3 0 0 ,
150);
что само по себе не сложно для понимания, но потребует тщательного выбора координат для каждого элемента управления с учетом их размеров и будет довольно трудоемким и скучным делом. Другая сложность заключается в том, что компьютерный экран может быть и больше, чем 600x800, и в этом случае абсолютное позиционирование надо выполнять очень тщательно, чтобы результаты выглядели одинаково удовлетворительно на различных компьютерах. Относительное позиционирование. Если мы позволим располагать элементы управления самой системе, передав ей некоторые базовые указания, дело окажется более простым. При таком подходе обычно создаются горизонтальный и вертикальный списки, вложенные друг в друга, как вам заблагорассудится. Например, интерфейс рис. 5.1 соответствует диаграмме, приведенной на рис. 5.4.
5,3 Введение в систему Views
189
два элемента горизонтального списка
два элемента вертикального •* списка
Рис. 5.4. Относительная планировка элементов управления
Спецификация подобной планировки будет примерно следующей (не будем обращать внимание на синтаксис) вертикальная горизонтальная кнопка кнопка конец горизонтальной изображение конец вертикальной Другими словами, мы начинаем вертикальный список. Первым элементом выступает горизонтальный список, состоящий из двух кнопок. Этот элемент завершается; за ним следует изображение, после чего завершается и вертикальный список. Пространство имен Views, используемое в этой книге, рассчитано на относительную планировку, однако имеет и некоторые возможности абсолютной планировки для более точного позиционирования. Разрабатывать интерфейс с помощью Views гораздо проще, чем посредством Visual Studio, и, тем не менее, Views предоставляет достаточные возможности для управления внешним видом и функционированием интерфейса.
5.3 Введение в систему Views Программирование графического интерфейса с помощью Views включает следующие шаги: 1. Задание расположения элементов управления. 2. Создание объекта графического интерфейса. 3. Обеспечение реакции на действия над элементами управления. 4. Взаимодействие с элементами управления. С точки зрения графического интерфейса под реакцией понимается отклик программы на такие действия пользователя («события»), как щелчок по кнопке. Взаимодействие — это более широкий термин; он, в част-
190
Глава 5. Графические интерфейсы с применением системы Views
ности, подразумевает, что мы можем помещать что-то в элемент управления, например, вводить текст в текстовое поле.
Задание расположения элементов управления В системе Views спецификация планировки выполняется в XML. Эта аббревиатура является сокращением от extensible Markup Language (расширенный язык разметки); синтаксис XML основан на HTML (HyperText Markup Language, язык разметки гипертекста), который используется для задания внешнего вида и содержимого Web-страниц. Однако язык XML имеет более упорядоченную структуру и допускает расширения, что делает его чрезвычайно удобным средством для решения самых разнообразных задач. Система обозначений XML основана на тегах и атрибутах. Каждый тег имеет имя и заключается в квадратные скобки или завершается соответствующим ему тегом закрытия. Теги закрытия предваряются символом наклонной черты (деления). Атрибуты являются идентификаторами с присвоенными им значениями. В каждом домене, к которому применяется XML, определяется набор тегов и атрибутов. Так, для Views мы определим набор из приблизительно 15 тегов, каждый с 5-6 необязательными атрибутами. Элементы обозначений XML, используемых в системе Views, показаны в приводимой ниже форме. Обозначения XML для Views < тег
атрибуты>
другие теги или • < тел а трибуты /> где атрибуты — это список определений атрибутов, записанный в виде идентификатор = значение Теги и идентификаторы могут содержать только буквы; значения атрибутов могут принимать разнообразные формы (числа, строки и т. д., как того требует конкретный атрибут). Если заданный тег идентифицирует элемент управления, который может быть отображен на экране средствами Views и используется интерактивно, тогда для него требуется атрибут Name.
Рассмотрим некоторые теги Views XML, которые могут быть полезны для программы Show-Hide, иллюстрируемой рисунками 5.1 и 5.4. <Button Name=Show/> <Button Name=Hide/>
5.3 Введение в систему Views
191
В этом примере — это тег без атрибутов, который содержит другие теги, поэтому его закрывающий тег указан на отдельной строке с повторением имени тега. Теги для кнопок просты, но поскольку они идентифицируют интерактивные элементы управления в графической интерфейсной форме Windows, они должны определять атрибут Name. В качестве пояснения укажем, что спецификация <Button
Name=Show/>
эквивалентна следующей: <Button
Name=Show>
Заметьте, что теги открытия и закрытия в нашем примере должным образом вложены. При обнаружении несоответствия Views сообщит об ошибке. Другим примером из того же интерфейса будет предложение
Здесь атрибутов больше, для имени файла с изображением и для задания высоты. Если мы указываем либо высоту, либо ширину для отображения рисунка, Views автоматически настраивает другое измерение, что очень удобно. Дополнительные детали измерений в атрибутах Views будут затронуты в разделе 5.5.
Создание объекта графического интерфейса Язык XML для GUI системы Views ничего на значит для самого С#. Он имеет смысл только для определенного нами класса Views.Form. Следовательно, для создания GUI мы должны создать объект Views.Form и передать его XML в качестве инициализирующего параметра. В приведенной ниже форме подытожен этот процесс. Создание объекта Views.Form Views.Form
GUIname
спецификация
- new Views.Form(@'
Views XML
Во время выполнения создается объект с именем GUIname. Символ @ начинает многострочный текст, состоящий из спецификаций Views XML.
В любой программе мы можем иметь несколько выполняемых графических интерфейсов, как один после другого, так и действующих одновременно. Например, с помощью одного интерфейса пользователь входит в систему, после чего активизируется другой интерфейс, организующий содержательное взаимодействие с программой. Строка спецификации фор-
192
Глава 5. Графические интерфейсы с применением системы Views
мы Views не обязательно должна указываться непосредственно в качестве параметра конструктора Views.Form; она может быть инициализирована или прочитана из файла в другом месте программы. Например, вполне правильный фрагмент создания GUI может выглядеть таким образом: string spec = @" . . . спецификация Views XML "; Views.Form f = new Views.Form(spec);
Спецификацию можно сохранить и в отдельном файле. В этом случае аргументом конструктора будет имя файла. (Хотя в обоих случаях аргумент будет представлять собой строку, конструктор легко отличит спецификацию XML от имени файла.)
Правила использования в Views прописных букв и кавычек Класс Views.Form не различает строчные и прописные буквы в именах элементов управления; слово «Button» вполне можно написать, как «bUtTon», хотя это вряд ли разумно. Текст «Show», однако, появится в качестве надписи на элементе управления точно с теми же буквами, которые использованы в спецификации. Хотя Views игнорирует форму написания имен элементов управления, мы тщательно следили в этой главе, чтобы имена тегов в точности соответствовали (в плане использования прописных и строчных букв) соответствующим именам классов в пространстве имен System.Windows. Form. Аналогично, имена атрибутов в точности совпадают с именами свойств этих классов. Теги без прописных букв (например, ) не соответствуют именам классов Windows, и то же справедливо для имен атрибутов, написанных строчными буквами (мы с ними столкнемся позже). Заметьте, что кавычки вокруг названий кнопок могут опускаться, если название не включает в себя пробелы или специальные символы. Это правило специально введено в Views.Form для удобства программиста. В стандартном языке XML двойные кавычки обязательно ставятся вокруг названий и любых других текстовых значений; включение символов двойных кавычек внутрь строк С# менее удобно, чем использование символов одиночных кавычек (и уж точно менее удобно, чем полное отсутствие кавычек).
Обеспечение реакции на действия над элементами управления Создание экземпляра класса Views.Form приводит к появлению на экране компьютера изображения графического интерфейса пользователя. После этого программа может ожидать, пока пользователь не сделает что-нибудь, например, щелкнет по кнопке или введет какой-то текст. Эти воздействия обнаруживаются операционной системой и передаются той части
5.3 Введение в систему Views
193
программы, которой они предназначены, в данном случае созданному нами объекту Views.Form. Часть программы, которая принимает и обрабатывает внешнее воздействие, носит общее название обработчика события. Обработчики событий обычно представляют собой методы, которым передается в качестве параметра имя конкретного активизированного элемента управления. Далее обработчик может решить, как он должен отозваться на это воздействие. Активные элементы управления вроде кнопок несомненно должны иметь собственные обработчики событий, в то время как потенциально пассивные, например, надписи или изображения, в обработчиках событий не нуждаются. В системе Views мы ждем поступления события в цикле ожидания события, основанном на специальном методе с именем GetControl. Этот метод передает в программу имя активизированного элемента управления, после чего происходит переход в обработчик события. Стиль программирования показан в приведенной ниже форме. Взаимодействие с элементами управления в системе Views void Go () { SetUp (); //Создание и инициализация GUI for
( ; ; ) {
string cname = f .GetControl(); if
(cname == null) break;
ActionPerformed(cname); f.CloseGUI(); void ActionPerformed(string switch
c) {
(c) {
case "controll": предложения; break; case "control2": предложения; break; и т. д . default: break;
f.GetControl возвращает имя активизированного элемента управления. Щелчок по кнопке закрытия программы в верхнем правом углу интерфейсного окна приводит к возврату из интерфейса значения null; это завершает цикл и закрывает программу. Метод ActionPerformed использует предложение switch для выяснения того, какой именно элемент управления был активизирован, и выполняет затем соответствующие предложения.
7—2047
194
Глава 5. Графические интерфейсы с применением системы Views
Взаимодействие с элементами управления Находясь в обработчике события, мы можем взаимодействовать с элементом управления двумя способами. Первый и предпочтительный способ заключается в вызове одного из методов Views, специально разработанных с этой целью. В разделе 5.6 мы подробно обсудим эти методы, но о двух из них мы скажем здесь, потому что они будут использоваться в последующих примерах. Это методы GetText и PutText, которые позволяют прочитать текст из текстового поля или из списка либо записать текст в эти элементы управления. Эти методы описаны в приведенной ниже форме. Взаимодействие с текстовыми полями и списками в системе Views s t r = GetText(идентификатор_элемента) PutText{идентификатор элемента, str) Для данного элемента управления с именем, хранящемся в виде строки в параметре идентификатор_элемента, GetText вернет строку, выведенную в данный момент элемент управления. PutText, наоборот, поместит в него данную строку. В случае элемента TextBox PutText затирает текущее значение; в случае элемента ListBox к содержимому списка добавляется следующая строчка и в нее заносится данная строка.
Например, в игре «Камень—ножницы—бумага», проиллюстрированной выше на рис. 5.2, мы выводим сообщение в список с именем «results» в конце каждого раунда. Одним из таких сообщений будет form.PutText("results",
"This round is
drawn");
Мы также выводим число в текстовое поле в левой части кадра с помощью следующего предложения: form.PutText("draws", noOfDraws.ToString());
Как мы увидим позже, текстовое поле имеет имя «draws»; целочисленная переменная noOf Draws ведет учет количества ничьих. Поскольку метод PutText принимает только строки, мы сначала преобразуем целое число в строку с помощью ToString. Поскольку элементы управления Views фактически являются действительными элементами управления, поддерживаемыми той частью операционной системы, которая рисует их на экране и следит за приходом от них сигналов в результате, например, щелчка мышью, второй способ взаимодействия с элементами управления заключается в непосредственной установке их атрибутов. Для этого вы должны знать, какие у них атрибуты, и как мы уже упоминали в начале этой главы, приобретение таких детальных знаний требует много времени, а их использование чревато ошибками. Поэтому можно рекомендовать оставаться, насколько это возможно, в мире Views. Однако технически возможно получить до-
5.3 Введение в систему Views
195
ступ к богатому набору классов, предоставляемому Windows.Forms или другими системами, поверх которых выполняется система Views (которые будут очень похожи); как это делается, будет описано в разделе 5.7. Пример 5.1. Игра «Камень—ножницы—бумага» с графическим интерфейсом Игра «Камень—ножницы—бумага» из гл. 4 предоставляет идеальную возможность изучения программирования графического интерфейса. Игры просто органически нуждаются в графических средствах. На рис. 5.2 мы уже видели, что именно нам хотелось бы получить. Но как мы этого достигли? Мы руководствовались последовательностью тех же шагов, которые были описаны выше. Рассмотрим их подробнее. Определение состава элементов управления. Для ввода трех возможных действий нужны три кнопки. На рис. 5.2 показаны кнопки с рисунками, и в дальнейшем мы увидим, как создаются такие кнопки. Однако можно было использовать и пустые кнопки. В качестве результатов мы решили выводить количество выигрышей, проигрышей и ничьих, отсюда и три текстовых поля. Для сообщений о ходе игры мы использовали список с надписью над ним. Разумное группирование элементов управления. Группирование в данном случае очевидно. Нужно собрать вместе все кнопки и все текстовые поля. В остальном расположение элементов может быть каким угодно. Составление эскиза графического интерфейса. Расположить наши элементы в окне интерфейса можно разными способами. На рис. 5.5 показаны два варианта, отличающиеся от рис. 5.2. C D C D C D )
C D C D i C D ;
: б
Рис. 5.5. Варианты интерфейса для игры «Камень—ножницы—бумага»
Перевод эскиза интерфейса в обозначения Views. Какой бы мы не выбрали формат, концепция вертикального и горизонтального списков остается в силе. Для расположения, показанного на рис. 5.5, а, спецификация будет выглядеть следующим образом:
196
Глава 5. Графические интерфейсы с применением системы Views
три кнопки список три текстовых поля Рассматривая эту спецификацию сверху, мы видим, что интерфейс состоит из вертикального списка с тремя элементами — горизонтального списка, элемента управления-списка и второго горизонтального списка. Каждый из двух горизонтальных списков содержит по три элемента. Другое расположение элементов, показанное на рис. 5.5, б, потребует такой спецификации: три кнопки список три текстовых поля Здесь имеется один горизонтальный ряд с тремя вертикальными элементами. Они представляют собой вертикальный список, элемент управления-список и второй вертикальный список. Если теперь вернуться к рис. 5.2, то расположение элементов на нем будет описываться следующ и м образом: три текстовых поля список три кнопки Приведенные варианты спецификаций показывают гибкость техники проектирования, предоставляемой системой Views, в том отношении, что размещение элементов управления отделяется от их программной реализации.
5.3 Введение в систему Views
197
Размещение элементов является, однако, лишь одной стороной разработки внешнего вида интерфейса. Мы должны также принять во внимание расстояния между элементами. Views выберет эти расстояния достаточно разумным образом, но если мы хотим вмешаться в этот процесс, мы можем ввести две настройки. Во-первых, каждый элемент управления имеет атрибуты Width [ширина] и Height [высота]. Эти величины могут быть заданы, в частности, в сантиметрах, и тогда типичный тег Views может быть описан таким образом:
что сделает элемент больше, чем он был бы по умолчанию. Другой способ изменения расстояния между элементами заключается в использовании тега <space> [расстояние], который позволяет установить также атрибуты ширины и высоты для (обычно пустого) прямоугольника: <space Height=lcm/> Если мы зададим для высоты совсем маленькое значение, и установим черный цвет элемента, мы можем даже нарисовать линию: <space Width=10cm Height=0.1cm BackColor=Black halign=center/> Добавление обработчиков событий к активным элементам управления. Три элемента управления, которые активизируются пользователем, представляют собой кнопки. Поэтому метод ActionPerformed должен предоставить обработчик для каждой из этих кнопок. В сущности, требуемое действие во всех трех случаях одно и то же — записать имя нажатой кнопки. После этого программа должна поместить выводимые значения в текстовые поля, как уже отмечалось выше. Вызов PutText входит в раздел взаимодействия с интерфейсом. Программа написана в виде класса DriveRPSGameGUI, который замещает DriveRPSGameConsole.cs. Ее следует компилировать вместе с существующим классом RPSGame.cs из гл. 4. Кроме того, необходимо сообщить системе, что в программе будет использоваться пространство имен Views, что мы и делаем в самом начале программы, вместе с обычным предложением using System. Файл DriveRPSGameGUI.cs
using Views; using System; class DriveRPSGameGUI { ///<summary> ///Программа RPSGame с GUI iii
Bishop & Horspool May 2002
198
Глава 5. Графические интерфейсы с применением системы Views
///Играет с пользователем в игру "Камень—ножницы—бумага" ///Демонстрирует взаимодействие с GUI и использование switch ///при обработке строк ///Использует класс RPSGame из гл. 4 /// string fspec = @" <space Height=lcm/> <space Height=0.5cm/> <space Height=0.5cm/> <space Height=0.5cm/> <space Width=10cm Height=0.1cm BackColor=Black halign=center/> <Button Name=Rock Image='Rock.gif Width=3cm/> <Button Name=Paper Image='Paper.gif Width=3cm/> <Button Name=Scissors Image='Scissors.gif Width=3cm/> "; string playersChoice; void Go() { ///<summary> ///Управляет игрой "Камень—ножницы—бумага" /// RPSGame game = new RPSGame (); int noOfWins, noOfDraws, noOfLosses, round; Views.Form form = new Views.Form(fspec); noOfWins = noOfDraws = noOfLosses = 0; round = 0; form.PutText("drawBox", " 0 " ) ; form.PutText("winBox", " 0 " ) ; form.PutText("lossBox", " 0 " ) ; string computersChoice, c, result;
5.3 Введение в систему Views for
(
;
;
)
199
{
computersChoice = game.ComputersChoice; с = form.GetControl() ; if (с == null) break; ActionPerformed(с) ; //Устанавливает ход игрока result = game.ComparePlays(playersChoice); round++; form.PutText("history", "Round "+round); form.PutText("history", "The computer's choice = "+computersChoice); form.PutText("history", "The player's choice = "+playersChoice); switch (result) { case "draw": form.PutText("history"," This round is drawn"); noOfDraws++; form.PutText("drawBox", noOfDraws.ToString()); break; case "lose": form.PutText("history"," Sorry, you lose this round"); noOfLosses++; form.PutText("lossBox", noOfLosses.ToString() ) ; break; case ""win": form.PutText("history"," Well done, you win this round"); noOfWins++; form.PutText("winBox" , noOfWins.ToString() ) ; break; } form.PutText(history", " " ) ; } form.CloseGUI() ; } void ActionPerformed(string c) { switch (c) { case "Rock": case "Paper": case Scissors": playersChoice = c; break; default: throw new Exception("Unhandled control
" + c) ;
static void Main() { new DriverRPGGameGUI().Go();
Перед запуском этой программы ее следует откомпилировать вместе с уже знакомым нам файлом RPSGame.cs, а также с классами Views. При запуске выполнимого файла на экране компьютера появляется окно, схожее с тем, что изображено на рис. 5.2.
200
Глава 5. Графические интерфейсы с применением системы Views
Сравнивая текст DriveRPSGameGUI.cs с рис. 5.2, мы можем заметить, что строковая константа, используемая для инициализации fspec, содержит спецификацию, описывающую элементы управления. Конструктор класса Views.Form, вызываемый в методе Go, получает строку fspec в качестве своего аргумента и использует ее для определения состава и расположения элементов управления в окне интерфейса. Интерфейс игры «Камень—ножницы—бумага» содержит несколько примеров пар вертикальных и горизонтальных вложений. Ряд кнопок с изображениями трех вариантов руки игрока создается путем заключения их в пару ... , но эта группа сама является элементом вертикального списка. В интерфейсе имеются и другие вложенные группы. Создав объект Views.Form, метод Go входит в цикл и, вызывая метод GetControl, ожидает, когда пользователь щелкнет по кнопке. Программа отображает результаты своей работы выводом текста в одно из текстовых полей, а также в элемент управления-список. В гл. 7 мы рассмотрим дополнения к этой программе, которые сделают ее более интеллектуальной.
5.4 Планировка элементов с помощью системы Views В этом разделе мы рассмотрим более подробно возможности планировки элементов с помощью системы Views. Заметьте, что использованные нами теги не соответствуют элементам управления Windows.Form и поэтому, в соответствии с нашим соглашением, пишутся без прописных букв в именах.
Форма GUI Спецификация для всей формы GUI должна начинаться с тега и заканчиваться парным тегом закрытия . Эти два тега охватывают группу элементов управления, как будет объяснено ниже. Тег может иметь атрибут Text, отвечающий за появление в строке меню названия окна. Если текст содержит несколько слов, его необходимо заключить в одиночные кавычки (апострофы). Например:
...
Группы элементов управления Задать расположение групп элементов управления в Views можно тремя способами — с помощью вертикального или горизонтального списков, а также посредством абсолютного размещения элементов с указанием их координат. Вертикальные списки. Вертикальный список элементов управления использует пару тегов ... , окружающих спецификации элементов. В качестве простого примера предположим, что мы хо-
5.4 Планировка элементов с помощью системы Views
201
тим отобразить три кнопки различных размеров. Для этого можно использовать следующую спецификацию: @" <Button Name = one Width=2.5cm Height=lcm/> <Button Name = two Width=4cm Height=2cm/> <Button Name = three Width=3cm Height=l.5cm/> ";
Эта спецификация создает интерфейсную форму, изображенную на рис. 5.6, а. Vertical L .
Рис. 5.6. Вертикальный (а) и горизонтальный (б) списки элементов
Горизонтальные списки. Горизонтальный список формируется схожим образом, но с помощью пары тегов ... . Горизонтальный список изображен на рис. 5.6, б. Заметьте, что по умолчанию элементы управления вертикального списка выравниваются по левым сторонам, а элементы горизонтального списка выравниваются по верхним сторонам. Для задания способа горизонтального выравнивания элементов вертикального списка используется атрибут halign, который может принимать значения left [лево], right [право] или centre [центр] (или center для предпочитающих американское написание этого слова); вертикальное выравнивание элементов горизонтального списка осуществляется с помощью атрибута valign, принимающего значения top [верх], bottom [низ] и middle [середина]. Интервалы. Для образования интервалов между элементами, а также для вывода линий используется тег <space>. Величина интервала определяется значениями атрибутов Height [высота] и Width [ширина]. В последнем случае с помощью атрибута BackColor можно установить требуемый цвет линии.
202
Глава 5. Графические интерфейсы с применением системы Views
Абсолютное размещение. Абсолютное позиционирование элементов достигается с помощью панелей, которые будут описаны в конце этой главы. Шрифты Помимо возможности произвольно устанавливать расположение элементов управления, Views позволяет также изменять вид выводимых текстов, повышая тем самым наглядность и привлекательность интерфейсной формы. Вид отображаемых на экране букв носит название шрифта. Шрифты могут относиться к разным семействам, а также иметь различные стили и размеры. По умолчанию, Views выводит все тексты одним из шрифтов sans serif (например, Helvetica) с прямым начертанием букв и размером 10. Размер шрифта задается в пунктах: 8 пунктов соответствуют самому мелкому шрифту, а крупный шрифт размером 24 пункта и больше можно использовать, например, для заголовков. Для элементов управления с текстом, например, надписей и кнопок, можно указать атрибут Font [шрифт], значение которого состоит из различных сцепленных друг с другом дескрипторов (описателей) шрифта. Каждый дескриптор, за исключением размера шрифта, представляет собой слово или короткую аббревиатуру. Некоторые из этих слов перечислены в табл. 5.1, а полный перечень приведен в Приложении Е. Т а б л и ц а 5 . 1 . Характеристики шрифтов По умолчанию
Варианты
Семейство
SansSerif
Serif
Жирность
тесПит[средняя]
bold [жирный]
Стиль
upright [прямой]
italic [курсив]
Размер
10 pt
Monospace [равноширинный]
И м я Serif обозначает обобщенный тип шрифтов с засечками (serif), обычно Times Roman; SansSerif обычно обозначает шрифты Arial или Helvetica; Monospace — это обобщенный тип шрифтов с равной шириной всех символов, например, Courier. Размер шрифта задается указанием его высоты в пунктах в виде десятичного числа; число может содержать дробную часть. Дескрипторы шрифтов могут комбинироваться в любом порядке. Некоторые примеры показаны в табл. 5.2. Таблица 5.2. Примеры шрифтов
Описание шрифта Font=Boidi4
Пример Hello there
Pont=ItalicSansSerif 18 Font=courier
Hello t h e r e
5.5 Элементы управления Views
203
5.5 Элементы управления Views Views поддерживает многие элементы управления, включенные в Windows.Form. Упорядоченный по алфавиту список элементов управления представлен в табл. 5.3. Таблица 5.3. Элементы управления Views.Form Элемент управления Views. Form
Краткое описание
<Button/>
Нажимаемая кнопка
Кнопка с флажком, который можно поставить или убрать
Выпадающий список кнопок с флажками
Выпадающий список, из которого можно извлечь отдельный элемент в виде значения
Прямоугольная область, содержащая группу селективных кнопок
В интерфейсное окно выводится надпись
Прямоугольная область, которую можно использовать для ввода и вывода многих строк текста
Кнопка, которая открывает новое окно, в котором можно выбрать существующий файл путем поиска в файловой системе компьютера
<Panel>
Прямоугольная область, в заданных местах которой можно расположить элементы управления
Выводит графическое изображение
Горизонтальная полоска, в которой затененная левая часть показывает, какая часть некоторого действия выполнена
Круглая кнопка, которая при щелчке по ней становится выбранной. Список таких кнопок заключается в теги GroupBox
<SaveFileDialog/>
Кнопка, которая открывает новое окно, в котором можно выбрать существующий файл путем поиска в файловой системе компьютера или указать имя нового файла; в дальнейшем в этот файл можно выполнить запись данных (с затиранием старых данных в существующем файле)
Прямоугольная область, которую можно использовать для ввода или вывода коротких строк текста
Элемент управления с движком, служащий для ввода целого числа; перемещение движка вперед или назад с помощью мыши изменяет значение этого числа
204
Глава 5. Графические интерфейсы с применением системы Views
В Приложении Е приведены дополнительные сведения об этих элементах управления. В этом разделе мы объясняем назначение каждого элемента управления с помощью примеров, останавливаясь на том, как их можно объединять для получения полезных результатов. Если описывать элементы управления с помощью обозначений XML, поддерживаемых классом Views.Form, все атрибуты, кроме атрибута Name [имя], можно оставлять неопределенными. В этом случае Views предоставит значения по умолчанию, хотя эти значения не всегда будут соответствовать требованиям вашей программы. Классы элементов управления, предоставляемые пространством имен System.Window.Form, имеют много дополнительных атрибутов и обеспечивают значительно более широкие функциональные возможности, чем класс Views.Form. Полное обсуждение этого вопроса, однако, не может быть проведено в рамках этой книги. Желая получить более подробную информацию, обратитесь к документации по программированию в системе Windows или воспользуйтесь интерактивным справочником, входящим в систему разработки Visual Studio.
Единицы измерения Атрибуты ширины Width и высоты Height определяют требуемые размеры отображаемого на экране элемента управления. Если мы задаем один из этих атрибутов просто в виде числа, например, Width
-
100
то единицами измерения служат пикселы. Фактический размер элемента на экране будет зависеть от его геометрического разрешения. Типичной величиной разрешения может быть 72 пиксела на дюйм, и именно это разрешение часто используется для отображения Web-страниц, хотя разрешение вашего конкретного дисплея может отличаться от этой величины. Views воспринимает и другие единицы измерения, как это показано в табл. 5.4. Например, Width
=
1.5cm
в спецификации XML выведет на экран элемент управления шириной приблизительно 1.5 см. К другим единицам измерения относятся миллиметры, цицеро и пункты. Пункты чаще всего используются для задания размера шрифта, как это описывалось в разделе 5.4. Таблица 5.4. Единицы измерения Views Название единицы
Значение единицы
in
дюймы (1 дюйм равен 25,4 мм)
cm
сантиметры
mm
миллиметры
рс
цицеро (1 цицеро равен 1/6 дюйма, или 4,23 мм)
pt
пункты (1 пункт равен 1/72 дюйма, или 0,35 мм)
5.5 Элементы управления Views
205
Атрибуты Каждый тег Views может иметь определенные атрибуты. В табл. 5.5 показаны все возможные атрибуты. Точное соответствие атрибутов и тегов дано в Приложении Е. Атрибуты из левого столбца можно использовать почти с каждым элементом управления; атрибуты из правого столбца в большей степени специализированы. Таблица 5.5. Атрибуты, которые можно использовать в Views XML Name=S
Image=F
Text=S
Value=D
Width=M
Minimum=D
Height=M
Maximum=D
ForeColor=C
Checked=D
BackColor=C
Font=S
Элемент управления Label Элемент управления Label [надпись] используется для вывода в интерфейсное окно текстовой строки. Например, для передачи пользователю информации о программе можно использовать следующую спецификацию Views: Views.Form f = new Views.Form( @" ");
Height=50
Результатом этой спецификации будет интерфейсное окно, показанное на рис. 5.7, а. Ш Label Test Version
3.12beta
Рис. 5.7. Примеры элементов управления: а — надпись, б — кнопка и надпись
206
Глава 5. Графические интерфейсы с применением системы Views
Элемент управления Button Элемент управления Button [кнопка] позволяет пользователю послать сигнал в программу, управляющую интерфейсным окном. Мы можем сопоставить с этим сигналом любое действие — выполнение вычислений и вывод результата или, например, закрытие программы. Именно такое действие выполняется в приведенном ниже,фрагменте: Views.Form f = new Views.Form( @" <Button Name=OK halign=centre/> "); f.GetControl ( ) ; f.CloseGUI ( ) ;
//Ожидание щелчка //Закрыть форму
пользователя
по
кнопке OK
Интерфейсное окно, создаваемое этим фрагментом, приведено на рис. 5.7, б. Поместив на кнопки вместо надписей графические изображения, мы получим более наглядные формы. Если у нас есть файл с изображением, сохраненном в одном из стандартных форматов, например, файл GIF или JPEG, это изображение можно отобразить на кнопке посредством спецификации вроде следующей: <Button Name=Tulip Image='tulip.jpg'/> Если опустить и ширину, и высоту, то размер кнопки будет выбран таким, чтобы на ней поместилось выводимое изображение при нормальном разрешении. Часто, однако, элементы управления удобнее размещать более аккуратно, задавая кнопкам определенный размер. Пример программы, которая использует кнопки с изображениями на них, может служить игра «Камень—ножницы—бумага» (рис. 5.2).
Элемент управления TextBox Элемент управления TextBox [текстовое поле] может быть использован как для ввода, так и для вывода коротких строк текста. Данные, вводимые в текстовое поле, могут представлять собой строки или числа, однако для чтения данных в программу имеется только метод Views чтения строки. Поэтому при вводе числа его следует преобразовать из строкового формата в числовой с помощью метода Parse: s t r i n g aText = form.GetText("A"); i n t aVal = i n t . P a r s e (aText);
Данные, выведенные в текстовое поле, будут выровнены влево. Вывод может быть осуществлен путем изначальной установки атрибута Text в спецификации Views:
5.5
Элементы управления Views
с помощью PutText:
form.PutText("A",
"0"
В обоих случаях текстовое поле будет инициализировано значением 0.
Элемент управления CheckBox Элемент управления CheckBox [кнопка-флажок] предоставляет простой способ ввода данных вида «истина» или «ложь». Обычно эти элементы управления объединяются в группы, причем выбор каждой кнопки осуществляется независимо от остальных. Выбор осуществляется щелчком мыши по кнопке, в результате чего на кнопке появляется изображение галочки. Вторичный щелчок удаляет эту галочку. На рис. 5.8. изображен пример интерфейсного окна, в котором с помощью кнопок-флажков устанавливаются характеристики шрифта. Соответствующая этому рисунку спецификация Views приведена ниже. Chec...
•1
v? UpperCa.se Р Bold Г* Italics : ••:• Proceed
Рис.
j
5.8. Пример элемента управления CheckBox
Views.Form f = new Views.Form(@" <Button Name=Proceed/> ");
while (f.GetControl() != "Proceed"); f.CloseGUI();
Для получения доступа к первой из трех кнопок можно воспользоваться следующим предложением: bool caseFlags = f.GetValue("Case")
!= 0;
208
Глава 5. Графические интерфейсы с применением системы Views
GetValue возвращает в качестве результата 0, если рамка не выбрана (в ней нет галочки), и 1, если она выбрана.
Элемент управления PictureBox Элемент управления PictureBox [поле для изображения] служит, как и показывает его название, для вывода изображения в рамке. Изображение обычно читается из дискового файла и может быть в любом из обычных форматов. Эти форматы включают растры Windows (файлы с расширением «.bmp»), форматы JPEG (расширение «Jpg») и GIF (расширение «.gif»). Реализация Views этого элемента управления допускает задание в спецификации ширины и высоты выводимого изображения; изображение будет масштабировано, чтобы уложиться в заданные размеры. Если не указаны ни ширина, ни высота, размер изображения берется из файла, где оно хранится. Простой пример интерфейсного окна, в которое выведено одно и то же изображение с различными значениями размеров, показан на рис. 5.9; XML-спецификация этого интерфейсного окна приведена ниже.
Рис. 5.9. Пример элемента управления PictureBox
@" -
double[] Add{ doublet] a, double[] to } { int len • a,Length; if (len ! - to.Length) throw new Exception("Error: mismatched vector sizes") doublet] result ~ ne» ttcubieflen]"; for ( int. i = 0; i < len; i++ } result, [i] - a[i]+-to [i] ; return result;
Рис. 6.З. Местоположение ошибки, выявленное отладчиком CLR Debugger
Отладчик выделил цветом предложение программы, следующее за тем, которое вызвало исключение. Отладчик всегда выделяет предложение, которое должно начать выполняться или выполнилось частично (что происходит, когда предложение содержит вызов метода, и этот метод еще не завершился).
242
Глава 6. Исключения и отладка
Отладчик позволяет вывести трассировку стека, значения интересующих вас переменных и многое другое. Дополнительные сведения об отладчике CLR Debugger приводятся в разделе 6.4. Мы рассмотрели проверку того, что значения параметров, передаваемых в метод, допустимы. Заметьте, однако, что еще более важной является проверка допустимости данных, читаемых из файла или с клавиатуры. Вполне вероятно, что файл данных будет случайно испорчен другой компьютерной программой, или что пользователь введет с клавиатуры неправильные данные. Если в нашей программе есть предложения вроде следующих: C o n s o l e . W r i t e ( " E n t e r y o u r w e i g h t i n kg int w • int.Parse(Console.ReadLine());
==>
" ) ;
то проверка вводимых данных должна стать нашей второй натурой. Для проверки данных в программу следует включить предложения такого рода: if
(w < MinWeight | | w > MaxWeight) throw new Exception("Unreasonable weight entered:
"+w) ;
Можно также предусмотреть в программе дополнительные фрагменты, в которых пользователя просят подтвердить правильность введенных данных или заново ввести правильные данные. Удобно подобный ввод данных помещать в цикл, который повторно запрашивает данные, например, три раза, и лишь после этого генерирует исключение. Тогда приведенный выше пример модифицируется таким образом: for ( int tries = 1; tries < = 3; tries++) { Console .Write ("Enter your weight in kg ==> " ) ; int w = int.Parse(Console.ReadLine ()); if (w < MinWeight | | w > MaxWeight) { if (tries == 3) throw new Exception("Unreasonable weight entered: "+w) ; else Console.WriteLine("Unreasonable weight. Try again.");
Console.WriteLine(w+"
is
an acceptable
weight");
Возможный вывод при выполнении этого фрагмента выглядит следующим образом: Enter your weight in Unreasonable weight. Enter your weight in 60 i s an acceptable
kg ==> 600 Try again. kg ==> 60 weight
Если вы не предотвратите ввод в программу недопустимых значений, которые затем будут управлять ходом программы, то вы обрекаете себя на большие неприятности.
6.3 Отладка
243
Если данные читаются из файла, который был создан другой программой, то неправильное значение в файле обычно указывает на серьезные неполадки в вашем программном комплексе. Для того чтобы избежать подобных неприятностей, вы должны в случае ввода недопустимых данных обязательно генерировать исключение и останавливать программу с выдачей сообщения об ошибке. Проверка на допустимость значений с использованием метода Assert. Система .NET Framework на платформе Windows содержит в пространстве имен System.Diagnostics класс Debug. В этом классе реализован метод Assert, который предназначен специально для проверки допустимости значений. Метод Debug.Assert using
System.Diagnostics;
Debug.Assert
(проверка,
сообщение)
Если булево выражение проверка дает false, на экран выводится окно, сообщающее об ошибке и включающее дополнительный текст сообщение. В этом случае должен быть указан ключ компилятора /define:DEBUG, например esc
/define:DEBUG myprogram.es
в противном случае вызовы Debug.Assert игнорируются.
В приведенном выше примере в методе Add мы могли вместо предложения if воспользоваться методом Assert: d o u b l e t ] Add(doublet] a, d o u b l e t ] b) { int l e n = a.Length; Debug.Assert(len==b.Length, "mismatched . . . / / Д а л е е как и раньше
vector
sizes");
Если значение не проходит проверку, на экране появляется окно, схожее с тем, что изображено на рис. 6.4. < mismatched vector sfees at Test.Add(Doublep a, Doubte[] b) D:\nigelh\CSharp Book\Programs\Debugging\MyProgram.cs(15) at TestGoO D:\nigelh\CSharp Book\Programs\Debusging\MyPrograrn.c5(26) at Test.MainO D:\rtigelh\CSharp 8ook\Programs\Debugging\MyProgram.cs(35) Abort
Retry
Ignore
Рис. 6.4. Окно сообщения об ошибке при использовании метода Assert
244
Глава 6. Исключения и отладка
Из трех кнопок в нижней части окна полезной будет только кнопка «Abort». Она прекращает выполнение программы. Кнопка «Retry» приведет к повторному вычислению булева выражения. Повторное вычисление имеет смысл лишь в тех случаях, когда проверяемое значение может измениться (например, с клавиатуры вводится другое число); в противном случае проверка даст тот же результат и на экран повторно выведется то же самое окно. Кнопка «Ignore» позволит программе продолжить выполнение со следующего предложения, что скорее всего приведет позже к другим ошибкам. Если программа прошла отладку, и вы думаете, что в ней нет ошибок, ее следует перекомпилировать без ключа /define:DEBUG. В этом случае компилятор будет игнорировать все предложения, которые активизируют метод Debug.Assert. Предложения трассировки. Предложения проверки допустимости значений помогают в тех случаях, когда первые очевидные симптомы ошибки указывают на выход данных за пределы допустимого диапазона значений. Иногда, однако, симптомы неисправности указывают на то, что программа начала выполнять свои фрагменты в непредусмотренной последовательности. Хотя генерация исключения и изучение затем трассировки стека могут пролить какой-то свет на то, каким образом программа дошла до той точки, где возникла ошибка, этой информации может оказаться недостаточно. Трассировка стека показывает только, какие из вызванных методов активны, но ничего не говорит о том, по каким путям в программе управление передавалось на каждом уровне на предложения, вызывающие методы. Иногда бесценной информацией оказываются сведения о том, какие из предложений if выполнялись, сколько раз повторялись циклы и т. д. Общее решение предоставляется использованием программы отладчика (см. раздел 6.4), однако его не просто освоить. Значительно более простбй метод заключается во включении в программу предложений трассировочного вывода. Многие программисты широко используют этот метод, независимо от того, имеются ли по их мнению в программе ошибки, или, как им кажется, программа свободна от них. Предположим, что в нашей программе имеется метод с таким заголовком: int
Calculate(int
n,
double
tolerance)
который заканчивается предложением: return
result;
Включим две строки в начало метода и дополнительную строку перед его завершением, как это показано ниже:
6.3 Отладка
245
int Calculate(int n, double tolerance) { if (Tracing.IsEnabled) Console.WriteLine ( "* Entering Calculate(n={0}, tolerance={1})", n, tolerance); ...//Тело метода Calculate if
(Tracing.IsEnabled) Console.WriteLine ( "* Exiting Calculate, result={0}",result) ; return result;
Подобные модификации претерпевают все методы в программе. Далее, где-то в программе определяется дополнительный класс: class Tracing { public s t a t i c
bool
IsEnabled
= true;
Это дополнительное определение класса может быть включено в самое начало программы сразу после всех предложений using (и после объявления namespace, если таковое присутствует в программе). При выполнении программы все сообщения трассировки появляются в консольном окне. Мы можем увидеть последовательность сообщений вроде той, что приведена на рис. 6.5: * * * * * * * * * * * *
Entering Main Entering Go Entering RepeatCalculations(tries=3) Entering Calculate(n=l, tolerance=0.1) Exiting Calculate, result=37 Entering Calculate(n=2, tolerance=0.01) Exiting Calculate, result=36 Entering Calculate(n=3, tolerance=0.001) Exiting Calculate, result=35 Exiting RepeatCalculations, result = 35 Exiting Go Exiting Main
Рис. 6.5. Пример трассировочного вывода
Изучение трассировочного вывода обычно предоставляет достаточно информации, чтобы выяснить, где именно программа вычислила неправильный результат или встретилась с другой ошибкой. Если полученной информации недостаточно, мы можем включить дополнительные предложения трассировочного вывода в стратегических точках внутри методов. Например, можно генерировать сообщение трассировочного вывода в начале каждого шага цикла.
246
Глава 6. Исключения и отладка
После исправления ошибки все предложения трассировочного вывода можно без труда замаскировать. Для этого достаточно заменить следующую строку в классе Tracing: public
static
bool
IsEnabled = true;
static
bool
IsEnabled = false;
на public
и сообщения трассировочного вывода не будут генерироваться. Некоторым программистам может не понравиться, что из-за включения в программу всех этих дополнительных предложений if и вызовов Console.WriteLine программа увеличивается в размере и становится более медленной. Об этом не следует беспокоиться, так как большинство программ выполняется достаточно быстро, а использование небольшого дополнительного объема памяти вряд ли окажется критичным. Если, однако, память и время выполнения являются существенными характеристиками программы, будет все же большой ошибкой удалять из программы трассировочные предложения. Редкие программы оказываются полностью свободными от ошибок, и эти трассировочные предложения могут однажды пригодиться. Здесь есть два решения. Оба они приводят к тому, что трассировочные предложения полностью игнорируются в окончательном варианте программы. Первое решение использует условную компиляцию: Трассировочные предложения — условная компиляция #if TRACE Console.WriteLine("...трассировочное
сообщение
...");
#endif Предложения трассировочного вывода могут включать любые сообщения, которые помогают проследить ход выполнения программы и изменение значений переменных. Если программа компилируется с ключом /define:TRACE, например: esc
/define:TRACE
filel.cs
file2.cs
тогда все предложения между строками #if TRACE и #endif включаются в текст программы; в противном случае они игнорируются.
Второе решение использует методы, предоставляемые классом Debug в пространстве имен System.Diagnostics.
6.3 Отладка
247
Класс Debug — использование для трассировочного вывода // Это объявление using должно присутствовать using System.Diagnostics; //Эти три предложения следует выполнить при запуске программы Debug.Listeners.Clear(); Debug.Listeners.Add( new TextWriterTraceListener("trace.txt")) ; Debug.AutoFlush = t r u e ; //Это предложение используется для генерации трассировочного сообщения Debug.WriteLine("...трассировочное
сообщение...");
Debug.Indent();//Включает отступ в трассировочные сообщения Debug.Unindent();//Отменяет действие метода Indent() Если трассировочный вывод нужен, при компиляции программы необходимо указать ключ /define:DEBUG; в противном случае все вызовы методов Debug будут игнорироваться. Трассировочный вывод записывается в файл, поименованный в вызове конструктора класса TextWriterTraceListener.
Метод Debug.WriteLine генерирует строку трассировочного вывода. Однако этот вывод исчезает без следа, если нет того, кто будет воспринимать этот вывод (объект приема какого-либо сообщения называют слушателем (listener)). Сначала вызовом метода Debug.Listeners.Clear() удаляются все существующие слушатели (возможно, в этом вызове нет необходимости, однако и вреда в нем нет). Затем вызовом метода Debug.Listeners.Add добавляется новый слушатель, который создается как экземпляр класса TextWriterTraceListener. В качестве аргумента конструктора этого класса обычно выступает имя файла, в который направляется трассировочный вывод. Впрочем, имеется возможность послать трассировочный вывод непосредственно в консольное окно с помощью такого вызова: Debug.Listeners.Add(new
TextWriterTraceListener(Console.Out) ) ;
Этот вызов можно использовать как вместо вызова, создающего слушателя, так и в дополнение к нему. Перед тем, как обращаться к вызовам Debug.WriteLine, полезно выполнить следующее присваивание: Debug.AutoFlush
= true;
которое обеспечит немедленный вывод в выходной файл каждого трассировочного сообщения. В противном случае строки вывода сначала будут накапливаться в буфере, и лишь после его заполнения выводиться в файл. Такая буферизация может привести к тому, что при остановке программы из-за возникновения ошибки несколько последних строк вывода будут потеряны.
248
Глава 6. Исключения и отладка
Пример 6.1. Использование метода Debug
Рассмотрим программу, которая выводит числа Фибоначчи (упоминавшиеся в упражнении 4.14). Вариант этой программы с использованием метода Debug приведен ниже. Файл FibExample.cs using using using class int
System; System.10; System.Diagnostics; FibExample { fibonacci(int n) { Debug.WriteLine("Entering fibonacci, n="+n) ; Debug.Indent; Debug Assert(n>=0, "argument for fibonacci cannot be negative"); int result; if (n < 2) result else
= 1;
result = fibonacci (n-1) + fibonacci (n-2); Debug.Unindent; Debug.Writeline("Exiting fibonacci, result=" + result) return result; void Go() { Debug.WriteLine("Entering Go"); Console.WriteLine("Fibonacci(4) = {0}, fibonacci(4)); Console.WriteLine("Fibonacci(-1) = {0}, fibonacci(-1)) Debug.WriteLine("Exiting Go" ) ; static void Main() { Debug.Listeners.Clear(); Debug.Listeners.Add( new TextWriterTraceListener("trace.txt"); Debug . Autof lash = truernew FibExample().Go();
Трассировочный вывод, поступающий в файл trace.txt в процессе выполнения программы, приведен ниже. Entering Go Entering fibonacci, n=4 Entering fibonacci, n=3 Entering fibonacci, n=2 Entering fibonacci, n=l Exiting fibonacci, result=l Entering fibonacci, n=0
6.3 Отладка
249
Exiting fibonacci, result=l Exiting fibonacci, result=2 Entering fibonacci, n=l Exiting fibonacci, result=l Exiting fibonacci, result=3 Entering fibonacci, n=2 Entering fibonacci, n=l Exiting fibonacci, result=l Entering fibonacci, n=0 Exiting fibonacci, result=l Exiting fibonacci, result=2 Exiting fibonacci, result=5 Entering fibonacci, n=-l Fail: argument for fibonacci cannot be negative Exiting fibonacci, result=l Exiting Go
Отступы в сообщениях использованы для того чтобы выделить вложенные вызовы методов. Если программа содержит рекурсивные методы, как это имеет место в данной программе, трассировочный вывод может оказаться весьма неудобным для интерпретации, если только не использовать какие-то визуальные средства, показывающие, из каких методов поступают те или иные сообщения. Включить в вывод отступы очень просто: как только осуществляется вход в метод, мы вызываем Debug.Indent(), а перед самым выходом из метода вызываем Debug.Unindent(). Заметьте, что в тех случаях, когда значение булева выражения, использованного в вызове Debug.Assert, оказывается равным false, в выходной файл записывается трассировочное сообщение, а программа продолжается.
Разработка методики тестирования Если вы закончили составление программы или даже небольшой ее части, функционирование новых программных строк следует проверить, выполняя тестовые вызовы содержащихся в них методов и наблюдая результаты этих тестов. Обычно не удается протестировать все возможные комбинации функционирования программы. Однако надежность тестирования можно повысить, если разработать методику тестирования, которая проверит работу всех или большей части участков испытуемых программных строк. Если программа принимает значения в некотором диапазоне, стоит испытать ее поведение, передавая ей значения на краях заданного диапазона, а также и несколько значений в его середине. Например, если числовой ввод в программу лежит в пределах от 0 до 100, определенно следует испытать оба крайних значения, 0 и 100. Программисты часто ошибаются, определяя конечные условия, например, создавая массив размером на один элемент меньше, чем нужно, или выполняя цикл на один шаг больше, чем допустимо.
250
Глава 6. Исключения и отладка
6.4 Использование программы отладчика Типичная установка пакетов .NET Framework и Visual Studio в системе Windows дает возможность использовать три различных отлаживающих программы. Системы Unix имеют свои программы отладки, конкретно, dbx, dbxtool и gdb. Отладчики предназначены для решения двух различающихся задач: 1. Если программа остановилась из-за наличия в ней ошибки, отладчик помогает обнаружить то место в программе, где возникла ошибка, и исследовать значения всех переменных к этому моменту. 2. Отладчик можно использовать для трассировки программы. Отладчик позволяет шаг за шагом выполнять программу, фиксируя последовательность выполняемых предложений и анализируя по ходу дела значения программных переменных. Для реализации этих режимов можно иметь два различных отладчика, однако обычно единый отладчик позволяет выполнять обе операции. Для полной реализации возможностей отладчика ему следует заранее передать информацию о работе программного обеспечения на используемой компьютерной системе. В этой книге мы рассматриваем только простые функции отладчика.
Анализ аварийного останова программы с помощью программы CLR Debugger На рис. 6.1 было показано окно сообщения, которое появляется на экране, когда Сопрограмма, работающая в системе Windows, генерирует исключение, которое не перехватывается предложением try-catch. Щелкнув мышью по кнопке «Yes», мы запускаем экземпляр отладчика Microsoft CLR Debugger, который позволяет получить информацию об отлаживаемой программе. Microsoft называет такой режим отладки «just in time debugging» (отладка в последний момент), потому что программа отладчика активизируется в последний момент, лишь когда в ней возникает необходимость. Щелчок по кнопке «Yes» окна сообщения, показанного на рис. 6.1, приводит к выводу на экран интерфейсного окна программы отладчика, поверх которого выводится окно сообщения вроде того, что изображено на рис. 6.2. Щелчок по кнопке «Break» в этом окне передает управление отладчику. Работая в отладчике, мы можем увидеть, в какой точке программы произошла ошибка, и проанализировать значение программных переменных. (Отладчик также предоставляет возможность продолжить выполнение программы, контролируя порядок выполнения программных предложений и значения ее переменных.)
6.4 Использование программы отладчика
251
Пример 6.2. Использование отладчика CLR Debuger Давайте рассмотрим пример с самого начала. Ниже приведен текст программы с ошибкой (файл buggy.cs). Программа должна сообщить кассиру, как сформировать сдачу за покупку. Метод Go запрашивает у пользователя ввод стоимости покупаемого товара, а затем суммы, полученной у покупателя в уплату за товар. Если, например, товар стоит 2 доллара 75 центов ($2.75) и покупатель передал 10 долларов ($10), программа должна выдать такой результат: The
change
1 2 1
will
be
...
5 dollar note 1 dollar notes 25 cent coin
К сожалению, когда текст программы вводился в компьютер, была сделана ошибка: при вводе номиналов банкнот и монет вместо числа 100 было случайно введено число «10,0». Такая ошибка не будет обнаружена на этапе компиляции. Файл buggy.cs //В этой программе имеется ошибка!! using System; class Buggy { int[] unitValues = { 10000, 5000, 2000, 1000, 500, 10,0, 25, 10, 5, 1}; string unitName(int value) { if (value >= 100) return value/100 + "dollar note"; return value " cent coin"); } void MakeChange(int Cost, int AmountOffered) { int change = AmountOf fered - Cost; if (change == 0) { Console.WriteLine("No change required"); return; } >. Console .WriteLine ("The change will be . . • " ) ; for (int i=0; i < unitValues. Length; i++) { int unit = unitValues[i]; int num = change/unit; string plural; if (num !=0) { if (num > 1) plural = "s"; else Л
1
It ••
plural = ""; change -=num*unit; Console.WriteLine (" {0} {1}{2}", num, unitName(unit), plural);
252
Глава 6. Исключения и отладка
void Go () { for (; ;) { Console .WriteLine ("Enter cost of item ==> ") double с =double.Parse(Console.ReadLine()); Console.WriteLine("Enter amount offered ==> " ) ; double a = double.Parse(Console.ReadLine()); MakeChange((int) (c*100) , (int) (a*100));
s t a t i c void Main() { new Buggy () . Go () ;
При выполнении программы м ы прежде всего замечаем, что при вводе $2.75 в качестве стоимости покупки и $10 полученной от покупателя суммы, вывод программы выглядит таким образом: Enter cost of item ==> 2.75 Enter amount offered ==> 10 The change will be . . . 1 5 dollar note
22
10 cent coins
что очень странно, а затем появляется окно сообщения, схожее с тем, что было изображено на рис. 6.1, однако на этот раз тип исключения оказывается System.DivideByZeroException. Щелкнув по кнопке «Yes», чтобы запустить программу отладчика, мы получим его интерфейсное окно, а на нем — второе окно сообщения, выглядящее так, как показано на рис. 6.6. Щелчок по кнопке «Break» снимает с экрана окно сообщения, оставляя нас в интерфейсном окне программы CLR Debugger (рис. 6.7). Microsoft CLR Debueaer An unhanded exception of type "System.DivldeByZeroException1 occurred in Buggy.exe Additional information: Attempted to divide by zero.
Break
Continue
lanore
Help
Рис. 6.6. Окно сообщения в ответ на исключение DivideByZeroException
6.4 Использование программы отладчика
253
euggy.cs
•i t> x
void HakeCha.nge ( int Cost, int AtnountOff «red ) ( m t change • AmountOttered - Cost; if (change »« 0) ( Console, SriteLinef"No change required"}; return; Console.¥riteLine("The change will be . . . " ) ; fa>t( i n t i»0; l < unitValuea.Length; t++ ) ( int unit • uaitValue»[i]; int. плдт «• change/unit; atriiig p l u r a l ; If {num !» 0) { if (пи» > 1) plural • "s"; else p l u r a l • "'•; change - " nu»*unit; Console. VriteLtne С (Oi il)i2}"t aim, unitNajr.e(uiUt) , )
plural),
Рис. 6.7. Панель с исходным текстом программы в отладчике
По умолчанию GUI отладчика состоит из четырех отдельных окон, или панелей. Самая большая панель отображает исходный текст программы, причем в окне сразу появляется та часть программы, в которой встретилась ошибка, как это видно из рис. 6.7. Программная строка, на которой прервалось выполнение программы, выделена желтым цветом и отмечена горизонтальной стрелкой. Если поместить курсор на имя переменной в панели исходного текста, рядом появляется небольшая рамка, в которую выводится текущее значение этой переменной. Например, если мы поместим курсор на идентификатор change в строке, где обнаружена ошибка, появится такая рамка: change=5 а если мы поместим курсор на идентификатор unit, то мы получим: unit=O и это, очевидно, и есть непосредственная причина ошибки. Теперь мы должны задать себе вопрос, как это переменная с именем unit могла получить значение ноль. Посмотрев на предыдущую строку программы и увидев, что в ней используется переменная i, мы можем определить ее значение, поместив на нее курсор. Окажется, что переменная i содержит значение 6. Далее разумно посмотреть, что же находится в позиции 6 массива, названного unitValues. Если мы поместим курсор на этот идентификатор, то получим только:
Глава 6. Исключения и отладка
254 unitValues={Length=1
Если мы теперь хотим посмотреть содержимое этого массива, мы должны прежде всего дважды щелкнуть по идентификатору unitValues (в результате чего это имя будет выделено цветом), а затем развернуть меню Debug в панели инструментов, расположенной в верхней части окна отладчика, и выбрать пункт QuickWatch. На экран выводится окно с линейкой прокрутки, в котором можно увидеть все элементы массива, как это показано на рис. 6.8.
Recalculate
Expression; unitValues
Add Watch Close
Current value; Name F ] unitValues
— to] — [1] ~ [2] — [3] — [4] [5] ™ [&] — [7] — [8] — [9] ь~ ЕЮ]
i Value {Length-11)10000 5000 2000 1000 500 10 0 25 10 5 1
| Type int[] " int int int int int int int int int int int
Рис. 6.8. Окно QuickWatch с содержимым массива unitValues
Как нам узнать, откуда был вызван текущий метод и как определить значения переменных в этом методе? В панели инструментов в верхней части окна отладчика имеется рамка с именем «StackFrame» [кадр стека]. В правой части рамки (она выглядит, как часть рамки) изображена стрелка, указывающая вниз. Щелчок по этой стрелке выводит на экран список ячеек программы. Для каждого активного метода, т. е. метода, в который программа вошла, но из которого не вышла, в списке отведена отдельная строка. Верхняя строка списка соответствует местоположению ошибки, следующая строка соответствует предложению, в котором был вызван текущий метод, и т. д. Выбор строки списка приводит к изменению содержимого панели исходного текста. Если, например, мы вы-
6.4 Использование программы отладчика
255
берем вторую строку, будет отображен фрагмент исходного текста, показанный на рис. 6.9. Помещение курсора на имена переменных показывает их текущие значения. double с • double.Parse{Console.ReadLine(} ) C o n s o l e . W r i t e ( " E n t e r amount o f f e r e d ==> ") ; double a = d o u b l e . P a r s e ( C o n s o l e . H e a d l i n e ( ) )
Рис. 6.9. Исходный текст вызывающего метода
Трассировка программы с помощью отладчика Хотя возможность проследить ход выполнения программы представляется панацеей от всех бед, позволяющей диагностировать ошибку, в действительности жизнь совсем не так проста. Основным качеством отладчика является его способность выполнять программу по одному предложению за раз. Щелкнув мышью или нажав клавишу на клавиатуре, мы можем заставить программу продвинуться на одно предложение. Такой режим называется пошаговым выполнением. Выполнив одно предложение, мы можем при желании проанализировать значения переменных перед тем, как мышью или клавиатурой инициировать выполнение следующего предложения. Наблюдая за ходом выполнения и проверяя значения переменных, мы рано или поздно обнаружим источник ошибки. Очевидная трудность здесь заключается в том, что программа, перед тем, как подойти к интересующей нас части, где, собственно, и проявляется ошибка, могла выполнить десятки миллионов предложений. Отладчики предоставляют различные способы разрешения этого затруднения. Стандартный способ заключается в использовании точек останова. Мы можем с помощью отладчика отметить одно или несколько предложений в программе, как предложения контрольного останова. Затем мы запускаем программу на полной скорости (т. е. мы не используем режим пошагового выполнения). Программа будет выполняться, пока не произойдет одно из следующих событий: 1. Программа нормально завершается. 2. В программе генерируется исключение (т. е. возникает ошибка). 3. Выполнение достигает предложения контрольного останова. В случаях 2 и 3 мы может с помощью отладчика проанализировать значения переменных, после чего продолжить выполнение (хотя это может и не иметь смысла, если программа остановилась в результате генерации исключения).
256
Глава 6. Исключения и отладка
В Приложении Ж приводятся основные сведения по использованию отладчиков для систем Microsoft Windows. Однако использование отладчика, как это было описано выше, следует рассматривать, как последнее прибежище, и по этой причине мы не будем рассматривать этот вопрос более подробно. Обычно значительно более продуктивным использованием своего времени оказывается вдумчивое чтение исходного текста программы и анализ всех предположений, сделанных при ее составлении. Можно рекомендовать такую методику: вы просите приятеля сесть рядом с вами и начинаете объяснять ему предложение за предложением, как ваша программа должна работать. Даже удивительно, как часто ошибки становятся очевидны, если просто иметь рядом с собой пассивного слушателя, который только выслушивает ваши объяснения и не задает никаких вопросов.
Основные понятия, рассмотренные в гл. 6 В этой главе были рассмотрены предложения try-catch и throw, а также несколько классов С# пространства имен System, имеющих дело с исключениями: Exception DivideByZeroException
IndexOutOfRangeException FormatException
IOException
В этой главе были приведены формы для следующих конструкций: предложение try-catch
метод Debug.Assert
трассировочные предложения — условная компиляция
класс Debug — использование для трассировочного вывода
Контрольные вопросы 6.1. Если в программу включить следующие строки, что случиться? 1 2 3 4
double [] а = new d o u b l e [ 1 0 ] ; for ( i n t i = l ; i filename);
Имя файла filename может быть создано или введено любым способом, но оно должно быть полностью определено перед конструированием формы. Следовательно, имя файла не может быть прочитано, например, с использованием того же объекта Views.Form. (Однако вполне допустимо использовать одну форму для выбора файла, а позже использовать ее для конструирования другой формы.) Все три метода выводят изображение одновременно с появлением формы на экране. Однако в Views предусмотрена возможность вывода изображения уже после того, как создана форма. Для этого мы должны использовать метод Putlmage, описанный в табл. 5.6.
7.5 Файлы в системе Views
271
Пример 7.3. Фотоальбом
Цифровые камеры могут создавать фотографические изображения в виде JPEG-файлов, которые можно затем загружать в компьютер. Кроме того, фотомастерские обычно могут преобразовать фотографии, полученные с помощью обычных пленочных камер в JPEG-файлы. Предположим, что у нас есть несколько файлов изображений в форматах JPEG и мы хотели бы показать их в окне программы. . Мы можем создать простую форму Views с рамкой для изображения PictureBox и кнопкой next [следующий]. При щелчке по этой кнопке увеличивается номер файла и метод Putlmage вызывается с новым именем. Единственной сложностью в программе является создание последовательных имен файлов, и эта задача отлично решается с помощью метода String.Format для десятичных чисел, который сохраняет лидирующие ноли. Файл PhotoAlbum.cs
using System; using Views; using System.10; class PhotoAlbum { ///<summary> ///Программа PhotoAlbum
Horspool & Bishop
Jan 2002
/// . iii ///Позволяет выводить на экран изображения из дисковых файлов jpg ///Имена файлов с изображениями имеют форму A:\001.jpg ///Демонстрирует использование метода Putlmage() /// string spec = @" <Button Name=next/> "; void Go() { Views.Form f = new Views.Form(spec); for (int n = 1; ; n++) { string photo • String.Format("{0:D3}",n); string fileName = "A:\\"+photo+".jpg"; f.GetControl(); if (! File.Exists(filename) ) break; f.Putlmage("photo", filename); f.CloseGUI(); static void Main() { new PhotoAlbum () .Go ();
}
Типичный вывод программы показан на рис. 7.1.
272
Глава 7. Файлы и потоки
Рис. 7 . 1 . Вывод программы PhotoAlbum
Файловые диалоги Чаще всего имена файлов не могут быть созданы с помощью формулы, как это делалось в предыдущем примере. Обычно пользователю должна быть предоставлена возможность поиска и выбора нужного файла. С этой целью в Views предусмотрены два диалоговых элемента управления, OpenFileDialog и SaveFileDialog. Каждый из них представляет собой фактически элемент управления — кнопку, которая содержит имя или текст, указанные в теге. При нажатии кнопки активизируется стандартный файловый диалог операционной системы. Этот диалог возвращает строку, которую затем посредством метода GetText можно использовать в качестве имени файла. Использование диалогов проиллюстрировано ниже в виде развития предыдущего примера.
7.5 Файлы в системе Views
273
Пример 7.4. Выбор и переименование фотографий
Если мы имеем возможность просматривать последовательность фотографий, то нам может захотеться обратиться непосредственно к нужной фотографии и затем сохранить ее под более наглядным именем. Программу фотоальбома нетрудно расширить, включив в нее три кнопки и соответствующий цикл обработки событий: Файл PhotoAlbum2.cs using System; using Views; using System.10; class PhotoAlbum { ///<summary> ///Программа PhotoAlbum // /
Horspool & Bishop
Jan 2003
til
///Позволяет выводить на экран изображения из дисковых файлов jpg ///Имена файлов с изображениями имеют форму A:\001.jpg ///или их можно задать с помощью файлового диалога ///Демонстрирует файловые диалоги и использование метода PutImage() /// string spec = @" <Button Name=next/> <SaveFileDialog Name='Save as'/> "; void Go() { Views.Form f = new Views.Form(spec); int i=l; string filename = null; for (;;) { string с = f.GetControl (); if (c == null) break; switch (c) { case "next" : string photo = String.Format("{0:D3}",n); fileName - "A:\\"+photo+".jpg"; if (!File.Exists(filename)) break; f.PutImage("photo", filename);
break; case "Select a photo" : fileName= f . GetText("Select a photo"); f.Putlmage("photo",filename); break;
274
Глава 7. Файлы и потоки case "Save as" : string savename • f.GetText("Save as"); OpenAndSave(filename,savename); break;
f.CloseGUI(); } void OpenAndSave(string source, string target) File.Copy(source, target, true); } static void Main() { new PhotoAlbum() .Go() ;
Пример 7.5. Окно кассира прилавка супермаркета Программа Till, представленная в гл. 5, имеет серьезный недостаток, заключающийся в том, что виды фруктов и их цены фактически записаны в саму программу. Определенно программу надо бы сделать более гибкой, чтобы ее данные, особенно, цены, можно было бы изменять по мере необходимости. С целью повышения гибкости программы и включения в нее возможности изменять цены, список цен хранится и обновляется в файле данных. Наша задача — получить данные в программу еще перед активизацией основного интерфейсного окна. С этой целью мы используем вводное интерфейсное окно. В нем предусмотрено место для ввода имени кассира и имени файла данных. После активизации данные читаются в массивы, предварительно зарезервированные в программе. После завершения начальной установки вводное окно закрывается, и активизируется главное интерфейсное окно. Установочная форма, используемая кассиром, изображена на рис. 7.2. Спецификация Views для этого окна выглядит следующим образом: string
setupForm « @" <Button Name=Continue/> ";
/>
7.5 Файлы в системе Views
275
FruitStop Till Setup L " j | X Till
S e t U p
jJennie Seiect Price Data File 3gramR.epositorysTili\bin\Debug\prices.da
Apples 2 Pears Jy Grapes 5 Oranges 2.5 Lemons 3 Kiwis 7 Avocados
* ||;
10
Continue
Рис. 7 . 2 . Установочная форма, выводимая на экран программой Till
Установка осуществляется методом SetUpTill, который содержит в себе обработчик событий Views. Когда кассир нажимает кнопку с надписью «Select Price Data File», на экране появляется диалоговое окно открытия файла и пользователь может выбрать в нем требуемый файл. Обработчик события выглядит таким образом: bool ReadDataFile(string filename) { StreamReader inStream; try { inStream = new StreamReader(filename) ; } catch(FileNotFoundException) { return false; ...//Чтение строк return true;
из файла
276
Глава 7. Файлы и потоки
Файл данных назван prices.dat; он имеет такое содержимое: Файл Prices .dat Apples Pears Grapes Oranges Lemons Kiwis Avocados Strawberries Bananas Peaches Plums Apricots
2 3 5 2.5 3 7 10 6 2 4 4 4
Это имя возвращается в Views и может быть прочитано с помощью метода GetText. Файл открывается обычным образом, а затем мы должны обработать данные, что требует некоторых несложных манипуляций со строками. Открытие, чтение и обработка выполняются в методе с именем ReadDataFile. void SetUpTheTill() { s t r i n g cashierName = n u l l ; s t r i n g initialName = " * * Cashier name goes here * * " ; s t r i n g fileName; form - new Views.Form(setupForm, initialName); bool gotFile = false; do {
string с = form.GetControl; ()); switch (c) { case "Name": case "DataFile": cashierName = form.GetText("Name"); if(cashierName == initialName) { form.PutText("Message", "First enter your name"); } else { fileName = form.GetText("DataFile"); form.PutText("Message", fileName); gotFile = ReadDataFile(fileName); } break; case null: form.CloseGUI(); return; default: break; } } while (!gotFile); form.GetControl();//Продолжим return;
7.5 Файлы в системе
V
bool ReaderDataFile(string StreamReader inStream; try { }
e
filename)
w
s
2
7
7
{
inStream = new StreamReader(fileName); catch(FileNotFoundException) { return
} int
i
false;
count;
char[] whiteSpaceChars = {' ', '\t', '\r'}; string s, product, price; for(count = 0; count < maxNumProducts; count++) { s = inStream.ReadLine(); if (s == null) break; s = s.Trim() ; int nameEnd = s.IndexOfAny(whiteSpaceChars); product = s.Substring(0,nameEnd); price = s.Substring(nameEnd+1); item[count] = product; formParameters[2*count] = product; formParameters[2*count+l] = product + ".gif"; unitCost[count] = Convert.ToDouble(price); form.PutText("prices", product + "\t"+unitCost[count] } inStream.Close() ; return true;
Полный текст модифицированной программы можно найти на Web-узле. Впрочем, остальные части программы не претерпели никаких изменений. Дополнительные усовершенствования программы предлагаются в виде упражнений. Для проверки программы нам нужен файл с данными. Нужно также позаботиться о наличии файлов с картинками. После запуска программы мы видим на экране первое интерфейсное окно, показанное на рис. 7.2. Мы выбираем имя файла, prices.dat, после чего первое окно закрывается, и открывается основное окно. Затем мы можем работать с программой так, как это было описано в гл. 5.
Хранение спецификаций Views в файле В гл. 5 мы упоминали, что спецификации Views можно хранить в файле. Это предоставляет значительные преимущества, так как возникает возможность, отладив программу, изменять планировку интерфейса, цвета и шрифты и т. д., просто изменяя данные в файле. Программу при этом не нужно перекомпилировать. Любой из приведенных ранее примеров Views можно перепрограммировать так, чтобы их спецификации считывались в строковую переменную из файла. Поскольку строковая переменная может включать в себя много строк текстового файла, проще всего прочитать в нее спецификации с помощью метода ReadToEnd
278
Глава 7. Файлы и потоки
Пример 7.6. «Камень—ножницы—бумага» с файлами Алгоритм работы компьютера, когда он играет с вами в эту игру, довольно примитивен. Программа при каждом своем ходе просто генерирует случайное число, выбирая один из трех возможных вариантов с равной вероятностью. Человеку, играющему с компьютером, такая игра быстро надоест. Более совершенным был бы алгоритм, при котором компьютер обучается на основании анализа ходов игрока-человека и делает свой выбор с учетом особенностей игры своего противника. Предположим, что игрокчеловек сыграл 10 раундов и в этих 10 раундах он 8 раз выбрал камень, а бумагу и ножницы выбрал лишь по одному разу. В этом случае программа может подправить свой алгоритм, и чтобы успешно выступить против выбора человеком камня, должна в следующих 10 раундах в 80% выбрать бумагу, а на две остальные возможности оставить по 10% ходов. Однако человек-игрок может основывать свой следующий ход на основании того, что произошло на предыдущем ходе (именно так обычно ведут себя два игрока-человека, играя друг с другом). Если после того, как компьютер выбирает камень, человек выбирает в следующем ходе в 70% случаев ножницы, в 20% случаев бумагу и в 10% случаев камень, тогда программа, полагая, что эта стратегия будет оставаться неизменной, будет в качестве своего очередного ход с вероятностью 70% выбирать камень, с вероятностью 20% — ножницы, и с вероятностью 10% — бумагу. Распределение вероятностей для игрока-человека может быть совершенно иным после того, как компьютер выберет ножницы и т. д. Наша программа может вести учет того, как часто тот или иной ход игрока-человека встречается после того или иного хода компьютера. Такая таблица носит название статистики первого порядка, потому что для подсчета вероятностей того или иного следующего хода учитывается только один предыдущий ход. Если мы считаем, что и предыдущий ход человека, и ответ на него компьютера влияют на следующий ход человека, то мы можем использовать статистику второго порядка, и именно такая стратегия реализована в нашем улучшенном варианте игры «Камень—ножницы—бумага». Интересной особенностью программы является то, что она фиксирует стратегию человека-игрока и записывает эту информацию в файл. При каждом запуске программы она смотрит, существует ли этот файл и, если файл существует, читает его. Перед самым завершением программы она записывает в тот же файл обновленную информацию. В этой программе были впервые использованы некоторые средства С#: • Метод NextDouble класса Random возвращает случайное значение типа double, лежащее в пределах от 0.0 до 1.0. • Метод Split класса string расщепляет строку на массив строк, ограничиваемых пробельными символами (пробелом или табуляцией). Например, если строка имеет значение « abc def gh», метод s.Split возвращает пятиэлементный массив со значениями элементов {«», «abc», «def», «», «gh». Пустая строка появляется в качестве элемента массива в тех случаях, когда строка начинается или заканчивается пробельными символами, или два пробельных символа соседствуют в строке.
7.5 Файлы в системе Views •
279
Программа вынуждена прибегать к дополнительным действиям, чтобы игнорировать возникающие пустые строки.
Файл RPSGameFile.cs using System; using System.10; public class RPSGame { ///<summary> ///Программа RPSGame (файловая версия) Horspool & Bishop ///December 2002 ///Ведет учет состояния игры "Камень—ножницы—бумага" /// public const int Rock = 0; public const int Paper = 1; public const int Scissors = 2; string[] MoveNames = {"Rock", "Paper", "Scissors"}; const string hFileName = "C:\\WINDOWS\\Temp\\rpsdata.txt" ; Random r; int computerChoice, userChoice; int numWins, numLosses, numDraws; int[,] WinMatrix = { {0,-1,1},{1,0,-1},{-1,1,0 } }; int [,,] Context2 = new int [3,3,3] ; public string ComputersChoice { get { MakeRandomChoice(); return MoveNames[computerChoice];} } public int Wins { get { return numWins; } } public int Losses { get { return numLosses; } } public int Draws { get { return numDraws; } } public RPSGame() { r = new Random () ; LoadHistoryFile(); } public void EndRPSGame(){ SaveHistory File(); } void LoadHistoryFile() { try { StreamReader hfile = new StreamReader(hFileName); //Читаем весь файл истории как одну строку string contents = hfile.ReadToEnd(); hFile.Close() ; //Разделим его на отдельные строки string[] nums = contents.Split(); int ix = 0; for (int i=0; i <Button Name=find Text=Find/> <space height=20/> <Button Name=select Text=Select/> "; void Go() { try { gui = new Views.Form(spec); } catch (Esception e) { Console.WriteLine(e.Message); return;
320
Глава 8. Коллекции
HandleQueries(); } void HandleQuiries() { for ( ; ; ) { string b = gui.GetControl(); if (b == null) break; switch(b) { case "read in": ReadData(gui.GetText("filename") ) ; break; case "/tind" : HandleSelection(gui.GetText("date")); break; case "select": HandleSelection(gui.GetText("list")); break; } gui.CloseGUI();
bool ReadData(string filename) { StreamReader fin = null; try { fin = File.OpenText(fileName); } catch (FileNotFoundException e) { gui.PutText("List", e.Message); return false; } gui.PutText("list",""); gui.PutText("list", "Holidays from "+filename); string line; string[] elements; while ((line = fin.ReadLine ()) != null) { elements = line.Split (' ' ) ; string date = elements[0] + " "+ elements [1]; String name = "" //Подсоединим остальные данные //для праздничного дня, например, "New Year's Day for (int i=2; Kelements. Length; name += ' '+elements[i]; gui.PutText("list",date.ToString()); H[date] = name; } return true; } void HandleSelection(string date) { String text text = (string) Hfdate];
8.4 Упорядоченные списки
321
if
(text == null) text = "Not a public holiday"; gui.PutText ("holiday", t e x t ) ; gui.PutText("date", date.ToString()) ; } static new
void Main() { PublicHolidays().Go();
Другие коллекции C# предоставляет шесть коллекций, из которых мы пока обсудили только две: ArrayList и SortedList. Кроме них, можно пользоваться следующими коллекциями: • BitArray [Массив битов] • HashTable [Хеш-таблица] • Queue [Очередь] • Stack [Стек] Каждая коллекция имеет свои индивидуальные свойства и методы. Подробное обсуждение этих коллекций является предметом курса, посвященного структурам данных.
Коллекции, определяемые пользователем Разумеется, программист может определить свои собственные коллекции. Чтобы остаться жизнеспособными, они должны правильно взаимодействовать с циклом foreach и функцией CompareTo. Возможность извлекать последовательные данные, хранящиеся в коллекции, возникает в том случае, если эти объекты принадлежат к перечислимым типам. Для того чтобы тип стал перечислимым, в нем должен быть реализован интерфейс IEnumerable. Это достигается путем реализации в данном типе интерфейса IEnumerator и получения для определяемого класса встроенных средств перечисления. Вот почему циклическая обработка списков SortedList столь проста — для этих списков уже обеспечено поведение перечислимых объектов. Точно также, если в коллекции предполагается упорядочивание элементов, она должна обладать свойством сравнимости. Эти функциональные возможности включены в интерфейсы IComparable и IComparator. Все эти интерфейсы будут обсуждаться в гл. 9, вместе с определением новой коллекции. 11—2047
322
Глава 8. Коллекции
8.5 Проект 4 — Расписание курсов повышения квалификации Постановка задачи Сотрудники фирмы BlowFish Inc. посещают курсы повышения квалификации, и запись с данными о каждом курсе, посещаемым или планируемым к посещению, содержится в файле. Нам хотелось бы выяснить, на какие курсы записались какие сотрудники, и какие курсы оказались востребованными .
Общая структура программы Программа в целом будет состоять из двух классов — для служащих и для курсов, соответственно, а также управляющего класса, который будет формировать расписания курсов и выводить их на печать. Мы предполагаем, что курсы, посещаемые служащими, определяются их должностями и не могут выбираться произвольным образом. Поэтому пример данных о служащем выглядит так: 123456 Mr GI Jones
5155
1
Первые пять полей описывают такие характеристики служащего, как его код, титул, инициалы, фамилия и номер телефона. Последнее поле обозначает должность. Что обозначает должность 1? В программировании данные, которые имеют содержательное значение, могут кодироваться числами, однако это не очень удобно. В данном случае у нас могут быть шесть различных должностей, например, director [директор], manager [управляющий], researcher [исследователь], technician [техник], support [работник вспомогательной службы] и trainee [практикант]. Тогда нагляднее сформировать данные о служащем таким образом: 123456 Mr GI Jones
5155
manager;
Перечислимые типы Замена должностей номерами ведет к снижению наглядности и вероятным ошибкам. К счастью, С# предоставляет встроенный тип, который позволяет связать имена с номерами. Типы такого рода называются перечислимыми и состоят из списка имен в определенном порядке; эти имена могут затем использоваться в программе и выводиться на печать. Ниже приведена форма, описывающая свойства перечислимых типов.
8.5 Проект 4 — Расписание курсов повышения квалификации
323
Определение и использование перечислимых типов модификаторы enum имя_типа {ид1, ид2, ... идп) или идентификаторы идп могут быть явными числами, как в id = value Ввод посредством: перечислимая_переменная = (имя_типа) Enum. Parse (typeof (имя__типа) , значение) ; Вывод посредством: имя типа.ToString(перечислимая переменная) Идентификаторы идп в порядке их перечисления отображаются на целые числа либо неявным образом, либо явно, и образуют набор значений для типа. Если отображение выполнено неявно, первое используемое число есть ноль. При вводе строка может быть преобразована в значение перечислимого типа; недопустимая строка генерирует исключение. При выводе переменная перечислимого типа может быть преобразована в строку.
Используя концепцию перечислимых типов, мы можем объявить в типе Employee public
enum Ranks = {director, manager, researcher, techician, support, trainee}
Внутри программы director будет 0, manager 1 и т. д. Числа в составе данных могут быть преобразованы в перечислимый тип в процессе чтения: rank
= (Ranks)
Enum.Parse(typeof(Ranks)
,v a l u e s [ 5 ] ) ;
Перечислимые типы могут участвовать в операциях сравнения и в циклах, что делает их действительно ценным приобретением. Однако в целом в С# (в противоположность таким языкам, как C++ или Pascal), перечислимые типы не столь необходимы для написания изящных программ, потому что мы можем избавиться от кодирования целыми числами, используя вместо них строки. Строки удачно используются в хеш-таблицах, в качестве меток выбора в предложениях с оператором switch или при вводе посредством методов Parse. Тип, определенный с помощью enum, как и его значения, имеют область видимости внутри того типа, в котором они были определены; при использовании вне типа их обозначения должны предваряться префиксом типа, например, Employee.Ranks.
Коллекции Какого рода коллекции выбрать? Очень часто решение зависит от операций, выполняемых над коллекциями. Рассмотрим сначала коллекцию для курсов. После того, как список курсов будет сформирован, мы должны будем выполнять набор циклов такого рода:
324
Глава 8, Коллекции
для каждого (foreach) курса для каждого (foreach) из предложенных должностей для каждого (foreach) служащего если (if) служащий имеет эту должность, добавить к курсу Поскольку мы заранее не знаем количество служащих, и нам хотелось бы иметь возможность упорядочивания, мы используем упорядоченные списки (SortedList) для хранения всех объектов, создаваемых в двух коллекция. Далее мы рассматриваем типы этих объектов. Каждый курс имеет название, список посещающих его (этот список создаст программа) и список предлагаемых должностей. Этому списку можно придать фиксированный размер, поскольку мы знаем, что будут только шесть должностей. Представляется, что здесь можно использовать массив. Теперь детали: class Course { string name;//Название курса Employee.Ranks[] suggested;//Перечень SortedList attendees;//Посещающие
должностей
Все три поля будут использоваться в главной программе, поэтому мы должны предусмотреть для них свойства. Однако возможно ли построить свойства для структурных значений типа упорядоченного списка? Несомненно возможно. Приведем пример: public SortedList Attendees { get{return attendees;} Тип Employee проще, так как он содержит только строки и целые числа: struct Employee { public enum Ranks {director, manager, researcher, technician, support, trainee} string title;//Титул string initials;//Инициалы string surname;//Фамилия int extension;//Телефон Ranks rank;//Должность int employeeNumber;//Номер Мы объявили Employee структурой (struct), однако Course является классом, так как он содержит внутри себя члены-ссылки. Управляющие структуры для создания списков посещающих курсы и печати тоже интересны, и мы приводим их в программе.
Тестовые данные Программа работает с двумя файлами данных. В первом перечисляются курсы вместе с должностями, для которых они предназначены. Пример таких данных:
8.5 Проект 4 — Расписание курсов повышения квалификации Business_Management manager researcher Computer_Literacy manager support trainee researcher Advanced_Computer_Literacy technician support Accounting_Skills manager trainee
325 technician
Заметьте, что названия курсов даны слитными фразами с символами подчеркивания, чтобы каждое такое название образовывало одну строку. Другим файлом является файл со списком служащих, и м ы эти данные уже описывали. Н и ж е приводится пример. 123456 513094 876632 847646 747329 821739
Mr GI Jones 5555 manager Miss KL Emm 6464 technician Dr JM Mullins 3000 director Mrs В Aye 9119 support Mr Q Wantiss 3321 researcher Mr 10 Uwing 5435 trainee
Программа Файл StaffTraining.es using System; using System.Collections; using System.10; class StaffTraining { ///<summary> ///Программа Staff Training Courses Bishop & Horspool Sept 2002 ///......................•....„.**. ///Создает список служащих, которым требуется посещать курсы ///в соответствии с их положением в организации ///Демонстрирует использование коллекций и перечислимых типов /// ///Класс персонажей StarLord с определенными характеристиками ///и возможностью бороться с другими объектами StarLord /// public class Character { string name; int points; int strength; Random r = new Random () ; public Character() { //Требуется, потому что класс — производный } public Character(string n, int s) { name • n; strength = s; points = s; } public string Name { get {return name;} } public int Points { get {return points;} set {points = value; } } public int Strength { get {return strength;} set {strength = value;} } public virtual void Attack(Character opponent) { int damage = r.Next(strength/3+points/2); opponent.Points -= damage; } public override string ToStringO { return name + " at " + points;
Файл SuperCharacter.es using System; ///<summary> ///Производный от класса StarLord класс 111 о улучшенными характеристиками /// public class SuperCharacter : Character { Random r = new Random () ; public SuperCharacter(Character c) //Начнем с того же, что и раньше : base (с.Name, с.Strength) { //Увеличим силу Strength += г.Next(10);
365
366
Глава 9. Полиморфизм и наследование
//Новый метод нападения //Сила оппонента снижается в большей степени (1/2, а не 1/3), //но больше риска - имеется случайная отрицательная составляющая public override void Attack(Character opponent) { //было: int damage = r.Next((strength/3+points/2); int damage =. r.Next(strength/2+points/2-r.Next(1)*4; opponent.Points -= damage;
Файл StarLordTwo.es using System; using System.Windows.Forms; using Views; public class StarLordsTwo { ///<summary> ///Программа StarLords ///
Bishop & Horspool Sept 2002
ill
///Моделирует сражение, имеет персонажи двух уровней ///Демонстрирует наследование, параметры ref ///и интенсивное использование системы Views /// Views.Form f; void SetUpForm() { try { f = new Views.Form(@" <space Height=5/> <Button Name=Attack/>
9.4 Проект 5 — Усовершенствование игры StarLord
367
"); } catch ( Exception e ) { Console.WriteLine( "Exception caught, message follows:{0}", e.Message);
void Go() { SetUpForm() ; SetUpCharacters(); DoBattle () ; } void Display(Character lord, string x) { //Выводит данные в блоки элементов управления: // // N f.PutValue(x+("strength"),lord.Strength); f.PutValue(x+("points"),lord.Points); } Character A; Character B; void SetUpCharacters() { Label lab = f["A"]; A = new Character(lab.Text, 80); lab = f["B"]; В = new Character(lab.Text, 74); Display (A,"A"); Display (В,"В"); } void DoBattle() { for ( ; ; ) { string с = f.GetControl (); if (c == null) break; if (A.Points > 0) && B.Points > 0) { ActionPerformed(c); } if (A.Points ///Программа / //
Spotty
Bishop
& Horspool
Feb
2003
I I I
///Рисует пятна разных цветов ///Демонстрирует использование простых классов пространства /// Spotty () {
//Настроим графическую форму this.Width = 450; this.Height = 450; this.Text = "Spotty"; } protected override void OnPaint(PaintEventArgs e) { Graphics g = e.Graphics; //Переменные, описывающие пятна и их положение Random r = new Random (); SolidBrush brush = new SolidBrush(Color.Red); for (int s = 0; s 0) port = int.Parse(args[0]);v new ATMServer(port);
Заметьте, что потокам обработчика передаются ссылки на их сокеты, идентификатор конкретного клиента, который к этому времени будет уже известен серверу, а также ссылки на их формы Views. А разве форм Views несколько? Да, для каждого выполняемого клиента создается своя форма. Поэтому команды для активизации системы будут выглядеть таким образом: @rem Откройте командное окно ATMServer @rem Откройте второе командное окно ATMClient @гега Откройте третье командное окно ATMClient
При такой форме команд будет использован порт по умолчанию 8190. Теперь приведем программу клиента: Файл ATMClient.cs using System; using System.ComponentModel; using System.Threading; using System.Net.Sockets; using System.Windows.Forms; using System.10; public class ATMClient { ///<summary ///Программа ATMClient
Bishop & Horspool Feb 2003
ill
///Обеспечивает связь путем подсоединения к серверу ATM ///Демонстрирует сетевой обмен информацией /// private NetworkStream output; private BinaryWriter writer; private BinaryReader reader; private string message = ""; private Thread readThread; Views.Form view; static int port = 8190; ATMClient () { try { view = new Views . Form (@"
10.6 Проект 6 — система ATM клиент-сервер
407
0) port = int.Parse(args[0]); new ATMClient ();
Основные понятия, рассмотренные в гл. 10 В главе рассмотрены следующие понятия: графические интерфейсы
многопоточность
анимация
события
обработчики
цвет
фигуры, линии и точки
текст и шрифты
изображения, как объекты Классы и методы API, конкретно затронутые в этой главе: System. Windows. Forms. Form
Application. Run
FillEllipse
SolidBrush
System. Drawing
System. Threading
OnPaint
PaintEventArgs
PaintEventHandler
Color
DrawLine
Pen
DrawString
Font
Image
FromFile
Drawlmage
.
SizeMode
В главу включены следующие формы: Шаблон программы с графикой
События и обработчики для графики
Вывод изображений
Вывод изображения в элемент управления
Создание и запуск потока
Анимация с помощью потоков
Закрытие потока
Контрольные вопросы
409
Контрольные вопросы 10.1. OnPaint представляет собой: (а) событие (в) графический объект
(б) метод (г) форму
10.2. Для активизации метода с именем DrawMe, чтобы он вызывался каждый раз, когда форма должна быть перерисована, мы используем: (а) this.Controls.Add(DrawMe) (б) this.Paint
+- OnPaint(DrawMe);
(в) this.Paint
+=
new
FaintEventHandler(DrawMe)
(r) DrawMe.PaintEventHandler.Add()
10.3. FromFile представляет собой: (а) статический метод класса Image (в) команду GDI+ 10.4. Потоки представляют собой: (а) методы экземпляров (в) события
(б) метод экземпляра класса Image (г) графическое событие (б) статические методы (г) объекты
10.5. ThreadStart представляет собой: (а) метод, который выполняет бесконечный цикл до тех пор, пока не возникнет какое-либо событие, завершающее этот поток (б) делегата, который поддерживает реестр рабочего метода потока (в) объект, который запускает поток вызовом его рабочего метода (г) событие, которое активизирует выполнение потока 10.6. Предложение, которое заставит перерисоваться рисунок в окне для изображения PictureBox p, выглядит так: (а) р. Invalidate () / (б) p.Validate (в) OnPaint (p) ; (г) p.ReDraw(); 10.7. Когда (а) (б) (в)
мы выполняем Web-запрос, результатом этого будет: доступ к ресурсу посредством протокола http:// доступ к Web-ответу посредством протокола http:// доступ к WTeb-oTBerry посредством любого из нескольких протоколов (г) URL
15—2047
410
Глава 10. Графика и сети
10.8. Протоколом, используемым для передачи сообщений между сокетами, является: (а) протокол сокета
(б) https://
(в) поток данных
(г) TCP/IP
10.9. Предложение в сервере, которое позволяет серверу ожидать контакта с клиентом, выглядит так: (а) Thread.Sleep(100); (б) listener.AcceptSocket ( ) ; (в) listener.Start ( ) ; (г) client .Start () ;
10.10. Если у нас есть ATM-система банкомата с тремя клиентами, сколько всего в ней будет выполняться потоков? (а) один
(б) три
(в) четыре
(г) невозможно ответить
Упражнения 10.1. Цветные пятна. Модифицируйте программу Spotty так, чтобы она выводила пятна четырех различных цветов, выбираемых случайным образом. 10.2. Пятна с размерами. Заставьте программу Spotty взаимодействовать с пользователем через консольное окно, чтобы в нее можно было перед началом работы ввести размеры пятен. 10.3. Пятна в Views. Перепишите программу Spotty, включив в нее интерфейс Views с элементами управления для ввода нескольких цветов и размеров пятен. (Подсказка: рисуйте пятна в элементе управления PictureBox.) 10.4. Перемещение прямоугольника, выделяющего часть изображения. Поместите пример 10.2 в интерфейс Views и используйте элементы управления ТгаскВаг для изменения размера выделяющего прямоугольника и перемещения его по изображению. Дополнительно выводите текущую позицию прямоугольника в элемент управления TextBox.
Список форм
Приложение
А
Это приложение предназначено для обеспечения поиска кратких описаний конструкций и классов С# в пространстве имен С#. Команды командной строки (выборка) Объект Тип Простой тип Структурный тип Обращение к членам Объявление поля (упрощено) Создание объекта Класс в качестве простой программы Простые предложения ввода Структура DateTime (сокращенно) Структура int или Int32 (сокращенно) Класс Random (сокращенно) Определение структуры Определение свойства Повышение типа (сокращено) Типы результата сравнения Явное преобразование Цикл for (простой) Предложение вывода с форматированием Спецификация формата Класс NumberFormatlnfо (сокращено) Объявление метода (упрощено) Вызов метода (упрощено). Операторы отношения Булевы логические операторы Булевы условные операторы Предложение if Цикл while Цикл do-while Цикл for Объявление массива (упрощено)
27 35 36 37 39 40 40 42 46 64 68 70 70 81 82 91 95 98 100 107 109 110 112 113 130 131 132 133 136 138 140 145
412
Приложение А. Список форм
Цикл foreach Объявление и использование строк Класс string (сокращено) Объявление и использование типа символов Символьные escape-последовательности (сокращено) Тип char (сокращено) Предложение switch Обозначения XML для Views Создание объекта Views.Form Взаимодействие с элементами управления в системе Views . . . Взаимодействие с текстовыми полями и списками в системе Views Предложение try-catch Метод Debug.Assert Трассировочные предложения — условная компиляция Класс Debug — использование для трассировочного вывода . . . Класс StringBuilder (сокращено) Классы StreamReader и StreamWriter (сокращено) Файловые операции (общие) Класс Array (сокращено) Класс ArrayList (сокращено) Объявления массивов — прямоугольных и рваных Коллекция SortedList (сокращено) Цикл foreach для упорядоченного списка SortedList Определение и использование перечислимых типов Интерфейс IComparable Интерфейс для IComparer Определение интерфейса Преобразования и проверки типов Перегрузка операторов Определение класса Шаблоны методов сериализации Шаблон программы с графикой События и обработчики для графики Вывод изображений Вывод изображения в элемент управления Создание и запуск потока Анимация с помощью потоков Завершение потока
152 154 155 156 157 159 165 190 191 193 194 238 243 246 247 266 269 270 302 305 306 312 315 323 339 341 343 345 353 354 369 377 380 382 383 386 389 393
Ключевые слова и операторы
Приложение
Б
Более детальную информацию относительно ключевых слов и операторов можно найти в спецификациях языка С # . Не все они были рассмотрены в этой книге.
Б.1 Ключевые слова Ключевые слова С# для облегчения ссылок распределены здесь по пяти группам. В некоторых случаях ключевое слово может принадлежать нескольким группам. В этих случаях мы поместили его в наиболее используемую группу.
Простые типы bool
byte
char
const
decimal
double
enum
false
fixed
float
int
lock
sbyte
short
true
uint
ulong
ushort
Структурные типы abstract
base
class
delegate
event
interface
namespaces
null
object
readonly
sealed
string
struct
using
volatile
as
base
checked
is
new
sizeof
stackalloc
this
typeof
unchecked
unsafe
Выражения
Приложение Б. Ключевые слова и операторы
414
Управляющие структуры break default
case do
catch else
continue finally
for
foreach
goto
if
in
switch
throw
try
while
Методы explicit
extern
internal
operate
out
override
params
private
protected
public
ref
return
static
virtual
void
Специальные слова В некоторых местах отдельные идентификаторы имеют специфический смысл, хотя и не являются ключевыми словами. Например, в объявлениях свойства слова get, set и value имеют специфический смысл.
Б.2 Операторы и знаки препинания Имеются несколько видов операторов и знаков препинания. Операторы используются в выражениях для описания операций, в которых участвуют один или несколько операндов. Знаки препинания предназначены для группирования и разделения.
Знаки препинания
Операторы присваивания +=
*Name=S
Создает нажимаемую кнопку.
Text=S
Кнопка может быть снабжена надписью (полученной из строкового атрибута Text) или изображением (в этом случае имя файла, содержащего изображение, берется из атрибута Image), или и тем, и другим одновременно.
Image=F Width=M Height=M ForeColor=C BackColor=C Font=FNT
*Name=S Text=S Width=M Height=M
Размер кнопки по умолчанию устанавливается таким, чтобы на ней помещалась надпись или изображение. Щелчок по кнопке приводит к тому, что метод GetControl() возвращает имя элемента управления Создает небольшую квадратную рамку. Щелчок пользователя по ней добавляет или удаляет флажок. Для получения состояния кнопки-флажка можно использовать метод GetValue
ForeColor=C BackColor=C Font=FNT
*Name=S Text=S
Width=M Height=M ForeColor=C BackColor-C Font=FNT
Создает список кнопок-флажков. Для получения состояния любой кнопки-флажка можно использовать метод GetValue
Е.4 Теги элементов управления
439
Таблица Е.З. Элементы управления Views (продолжение) Элемент управления Views. Form
Атрибуты
Описание
*Name=S Text=S
Width=M Height=M ForeColor=C BackColor=C F.ont=FNT
Создает прокручиваемый список, в котором можно выделять любой элемент в качестве текущего значения. С помощью метода GetValue можно получить индекс выбранного элемента
*Name=S Text=S Width=M Height=M ForeColor=C BackColor=C Font=FNT
Выводит прямоугольную рамку, используемую для помещения в нее группы селективных кнопок. Из этих кнопок в любой момент времени может быть выбрана только одна — если пользователь щелкает по кнопке, она выбирается, а выбранная перед этим становится невыбранной. Для получения надписи выбранной в данный момент кнопки можно использовать метод GetText
*Name=S Text=S Width=M Height=M ForeColor=C BackColot=C Font=FNT
Выводит строку текста
*Name=S Text=S Width=M Height=M ForeColor=C BackColor=C Font=FNT
Создает прямоугольную рамку, в которую можно поместить (и из которой извлекать) много строк текста.
*Name=S Text=S Width=M Height=M ForeColor=C BackColor=C Font=FNT
Создает кнопку, щелчок по которой открывает новое окно, позволяющее просмотром файловой системы найти требуемый файл из числа существующих.
*Name=S Text=S Width=M Height=M ForeColor=C BackColor=C Font=FNT
Создает прямоугольную область, называемую панелью, в которой можно разместить требуемые элементы управления. Панель можно снабдить линейками вертикальной и горизонтальной прокрутки, если это требуется для перемещения в поле зрения элементов управления
<Panel>
Выбранную в данный момент строку можно извлечь с помощью метода GetText; метод PutText добавляет новую строку к содержимому списка
Щелчок по кнопке и выбор файла приводит к тому, что метод GetControlO возвращает имя элемента управления; имя выбранного файла можно получить с помощью метода GetText
442
Приложение Е. Класс Views.Form
Имя Bold (и его синоним bf) дает полужирный шрифт; обычный светлый шрифт можно получить, используя имя Medium (или его синоним md). Имя Italic (и его синонимы it, Emphasis или em) дает курсивный ширфт; обычный прямой шрифт можно получить, используя имя Upright (или его синоним up). Размер шрифта можно задать, указав его размер в пунктах в виде десятичного числа; число может также включать дробную часть. Дескрипторы могут комбинировать в любом порядке. Некоторые примеры показаны ниже. Пример
Описание шрифта
Hello there
Bold24
ItalicSansl6
Hello there
Courier9.5
Hello there
Единицы размера. Высота или ширина элемента управления могут быть заданы просто в виде десятичных чисел, и в этом случае за единицу размера по умолчанию принимается пункт. При желании непосредственно вслед за числом можно указать другую единицу. Допустимые единицы размера приведены в табл. Е.4. Таблица Е.4. Единицы размера Имя
Значение
in
дюймы
cm
сантиметры
mm
миллиметры
pt
пункты
рс
пайка
В дюйме (2.54 см) содержится 72 пункта. Views предполагает, что разрешение экрана равно одному пикселу на пункт, т. е. 72 пиксела на дюйм (28.3 пиксела на см). Пример задания атрибутов: Width=64 Height=2.5cm Image='С:\Temp\MyPhotos\picturel.jpg' Name=Start Text='Select the input file'
Е.6 Методы Views.Form
443
Задание выравнивания. Атрибутам valign и halign могут быть присвоены следующие значения: valign: halign:
Top (по умолчанию), Left (no умолчанию),
Middle, Bottom Centre (или Center),
Right
Е.б Методы Views.Form Методы, входящие в класс Views.Form, перечислены в табл. Е.б. Заметьте, что поскольку реализация Windows класса Views.Form наследует от класса System. Windows.Forms.Form, вы можете вызывать любые открытые методы этого родительского класса, если вы компилируете свою Сопрограмму в системе Windows. Таблица. Е.5. Методы Views.Form Метод Views. Form
Описание
Form( string спецификация,
Это конструктор: создается и выводится на экран форма Windows с элементами управления, определенными в XML-спецификации. Первый аргумент может быть либо XML-спецификацией, предоставляемой в виде строковой константы, либо именем файла, содержащего спецификацию. Спецификация может содержать шаблоны { 0 } , {1} которые относятся либо к необязательным аргументам, следующим за XML-спецификацией, либо к элементам необязательного второго аргумента, представляющего собой массив объектов. Если используется шаблон {/}, активизируется метод ToString /-го необязательного аргумента, и результирующая строка подставляется на место шаблона {/} в строке спецификации
void C l o s e G U I ()
Завершает выполнение потока, который ожидает щелчка пользователя по форме, и освобождает другие системные ресурсы. Этот метод должен обязательно вызываться, когда исчезает необходимость в наблюдении формы на экране
string G e t C o n t r o l {)
Ожидает от пользователя выполнения действия с формой (т. е. щелчка по кнопке), после чего возвращает имя элемента управления, по которому щелкнули
string GetText( string имя)
Возвращает текстовое значение, связанное с элементом управления с указанным именем. Если элементом управления является TextBox или ListBox, это тот текст, который был введен пользователем. Если элементом управления является OpenFileFialog или SaveFileDialog, текст представляет собой имя выбранного файла
int GetValue( string имя)
Возвращает целочисленное значение, связанное с элементом управления с указанным именем. Для элементов управления TrackBar или ProgressBar это значение определяет текущую позицию ползунка. Для элемента управления CheckBox результат 1 или 0 указывает, установлен или не установлен флажок в данной кнопке, соответственно. Для элемента управления DomainUpDown результат является индексом выбранного в настоящий момент элемента списка (нумерация элементов списка начинается от 0)
Приложение Е. Класс Views.Form
444
Таблица. Е.5. Методы Views.Form (окончание) Метод Views. Form
Описание
int GetValue( string имя, int индекс)
Для элемента управления CheckedListBox результат показывает состояние кнопки-флажка с указанным индексом в списке кнопок, причем 1 обозначает отмеченную кнопку, а 0 — не отмеченную. Для других элементов управления результат совпадает с результатом, возвращаемым методом СетЛ/а!ие(имя)
void PutText( string имя, string текст)
Устанавливает атрибут Text элемента управления с указанным именем. Может использоваться для вывода текста в элементы управления ТехШох и ListBox
void PutValue( string имя, int значение)
Устанавливает целочисленное значение, связываемое с элементом управления с указанным именем. Метод используется для настройки состояния элемента управления ProgressBar и для установки состояния элемента управления CheckBox
void Putlmage( string имя, string имя файла)
Заменяет изображение, выводимое в элемент управления с атрибутом Image, новым изображением, получаемым из файла с указанным именем
void Putlmage( string имя, Image экземпляр)
То же с той разницей, что используемое изображение является экземпляром класса Image
(объект Views. Form) string имя]
Это индексная операция, возвращающая элемент управления с указанным именем. В реализации Views для системы Windows элемент управления является экземпляром класса в пространстве имен System.Windows. Forms
Е.7 Рекомендуемый стиль программирования Для того чтобы ваши программы были похожи на те, которые создаются при использовании форм Windows в среде Microsoft Visual Studio» мы рекомендуем при оформлении текста программы использовсять следующий базовый шаблон: string f = @" . . . "; try { new Views.Form(f); Views.Form form for (;;) { string name = form.GetControl(); if (name == null) break; ActionPerformed(name);
Е.8 Использование индексных операций
}
form.CloseGUI(); catch (Exception e) { Console.WriteLine("Error e.Message);
while
445
using
Views.Form:\n\n{0}",
Предложение try будет перехватывать все ошибки фрагмента программы от предложения, в котором создается форма, до предложения, где эта форма закрывается. Метод ActionPerformed может иметь структуру, схожую с приведенным ниже фрагментом, хотя для относительно простых форм и некоторых специальных случаев приведенный шаблон можно упростить: void ActionPerformed(string switch (name) { case "narnel" :
name)
{
break; case "name2": break; case ...//Столько конструкций case,
сколько требуется
Е.8 Использование индексных операций Если создается экземпляр класса Views.Form, например, таким образом: Views.Form f = new Views.Form (@"
";
тогда выводимая на экран форма содержит экземпляры элементов управления. Доступ к конкретным элементам управления может быть получен с использованием индексных операций, и такой доступ можно использовать для достижения некоторых эффектов во время выполнения. Например, если используется приведенная выше форма, то программа, создающая экземпляр формы, может выполнить такие предложения: System.Windows.Forms.TextBox tb = f["Boxl"]; System.Windows.Forms.Label lab = f["Labell"]; lab.BackColor = Color.Red lab.ForeColor = Color.Yellow; f.Invalidate();//Заставляет форму перерисоваться
с новыми цветами
446
Приложение Е. Класс Views.Form
которые изменяют шрифт, используемый в элементе управления TextBox, а также цвета элемента управления Label. Замечания: • Имена цветов также определены в пространстве имен System.Drawing. • Класс Views.Form наследует метод Invalidate от своего родительского класса, System.Windows.Form.
Отладка в Windows
ж
Приложение
В этом приложении дается обзор средств отладки С#-программ в системе Windows.
Ж.1 Введение Компиляция программы Для того чтобы программу можно было отлаживать, ее необходимо компилировать с установленным флагом debug. Если этот флаг сброшен, большую часть режимов, описанных в этом приложении, использовать не удастся. Команда командной строки, выполняющая компиляцию программы и установку флага debug, выглядит таким образом: esc
/debug:full
myprogram.esc
Если предположить, что в программе нет ошибок, которые не позволят довести компиляцию до конца, в результате компиляции будут созданы два файла. Для данного примера они будут названы myprogram.exe и myprogram.pdb. Первый файл является обычным выполнимым файлом. Второй представляет собой базу данных программы, содержащую информацию о программе; эту информацию использует отладчик, а также механизм трассировки стека, активизируемый при возникновении исключения. Если для составления и компиляции файла используется среда Visual Studio, тогда важно, чтобы для проекта была установлена конфигурация Debug. Для этого необходимо выполнить следующие шаги. 1. В подокне Solution Explorer выберите имя проекта. 2. В выпадающем меню с именем Project на панели задач Visual Studio выберите Properties. 3. В меню Property Pages, которое появляется в результате выполнения шага 2, выберите в левой панели Configuration Properties. 4. Удостоверьтесь в том, что в поле, помеченном Configuration, имеются надписи либо Debug, либо Active(Debug). Если этих надписей нет, щелкните по этому полю и выберите Debug.
448
Приложение Ж. Отладка в Windows
Отладчик командной строки и полноэкранные средства отладки Полная установка .NET, включая Visual Studio, в системе Windows позволит использовать три различные средства отладки. К этим средствам относятся: 1. Оперативный (Just-In-Time) отладчик, используемый для анализа выполняемой программы, которая встретилась с ошибкой (т. е. с необрабатываемым исключением). Полное имя этой программы отладчик DbgCLR. Для взаимодействия с пользователем он использует GUI. 2. Отладчик командной строки cordbg, который управляется посредством команд, вводимых в командном окне; с помощью этих команд можно запустить программу и наблюдать за ее выполнением. 3. Отладчик Visual Studio .NET. Этот отладчик можно использовать как для анализа программы, встретившейся с ошибкой, так и для трассировки выполняемой программы. Отладчик взаимодействует с пользователем посредством GUI, весьма похожего на тот, который встроен в отладчик DbgCLR.
Ж.2 Отладчик cordbg Команды cordbg Наиболее употребительные команды отладчика приведены в табл. Ж . 1 . Полный перечень команд можно получить, запустив отладчик и введя команду help. В этой таблице запись вроде b[reak] обозначает, что полное наименование команды есть break, но его допустимо сокращать до одной буквы b (другими словами, буквы, заключенные в квадратные скобки, не являются обязательными); некоторые команды имеют синонимы, например, break и stop действуют одинаково. Синонимы также перечислены в таблице.
Запуск отладчика cordbg Отладчик cordbg запускается с командной строки. Поэтому в качестве первого шага необходимо открыть командное окно. Далее в этом окне можно ввести команду (или несколько команд) cd (change directory, сменить каталог), чтобы перейти к той папке, где находится выполнимый файл программы, предназначенной для отладки. Пример сеанса с использованием команды cd может выглядеть таким образом: Microsoft Windows XP [Version 5.1.2600] (С) Copyright 1985-2001 Microsoft Corp. C:\Documents and Settings\nigelh>d: D:\>cd "Csharp Programs\Debugging
Ж.2 Отладчик cordbg
449
Таблица Ж . 1 . Выборочный список команд cordbg Справочная информация по командам отладчика h [elp] -р
Выводят описание команд отладчика
Запуск и останов отладчика и программы r[un]
Начинает процесс отладки
ex[it]
Завершают текущий процесс и сам отладчик
qluit] k [ill]
Завершает текущий процесс
Control-C
Прерывание текущего процесса
Доступ к переменным и состоянию программы p[rint]
Распечатывает переменные (локальные, аргументы, статические и пр.)
sh[ow]
Выводит строки исходного текста программы
w[here]
Выводит трассировку стека для текущего потока
d [ own ]
Позволяет переместиться вниз от текущего значения указателя кадра стека
и [р]
Позволяет переместиться вверх от текущего значения указателя кадра стека
set
Изменяет значение переменной (локальной, статической и пр.)
Обслуживание точек останова b [reak] stop del [ete]
Устанавливают новую точку останова или выводят список текущих точек останова Удаляет одну или несколько точек останова
rem[ove]
Пошаговое выполнение i [п]
Вход в следующее предложение исходного текста программы
si s[tep] n [ext]
Переход через следующее предложение исходного текста программы
so о [ut]
Выход из текущей функции
cont
Продолжение текущего процесса
д[о]
450
Приложение Ж. Отладка в Windows
Прежде всего, мы вводим команду "d:", чтобы перейти на логический диск, обозначаемый буквой D. Если нужный нам каталог находится на диске С, эта первая команда не потребуется. Далее вводится команда cd для перехода в папку с полным именем "D:\Csharp Programs\Debugging". Из-за того, что в имени пути к папке имеется пробел, аргумент команды cd предваряется символом двойной кавычки. Теперь можно ввести команду запуска отладчика. Казалось бы, эта команда должна совпадать с именем запускаемой программы, именно, cordbg (или cordbg.exe, если вы отличаетесь педантичностью). Однако, весьма вероятно, что переменная окружения PATH не содержит имени папки, в которой находится файл cordbg.exe. Если переменная PATH не установлена должным образом, вы получите сообщение 'cordbg' i s not recognized as an internal or external command, operable program or batch f i l e . ['cordbg' не распознана, как внутренняя или внешняя команда, выполнимая программа или командный файл.] В этом случае сначала необходимо найти папку, в которой установлена программа cordbg. Для этого может понадобиться поиск в файловой системе, например, с помощью средства Search (в меню Start). По умолчанию cordbg устанавливается в папке C:\Program Files\Microsoft Visual Studio NET\FrameworkSDK\Bin Найдя местоположение отладчика, можно поступить одним из трех способов: •
Воспользуйтесь системной Панелью управления для изменения значения переменной PATH, в которую надо включить местоположение cordbg, после чего вызовите его снова.
•
В качестве имени программы в командном окне ведите полное имя файла cordbg (вместе с путем к нему), заключенное в двойные кавычки.
•
С помощью текстового редактора, например программы Блокнот, создайте в текущем каталоге командный файл с именем, например, RunCordbg.bat, содержащий единственную строку текста: "C:\Program Files\Microsoft Visual Studio NET\FrameworkSDK\ Bin\cordbg.exe (строка, очевидно, должна быть изменена, если файл cordbg.exe расположен не в том каталоге, который указан в нашем примере). Затем введите в командном окне команду RunCordbg.
При использовании третьего способа вызова отладчика вы увидите на экране приблизительно следующее: d:\>cd "CSharp Programs\Debugging" D:\CSharp Programs\Debugging>runcordbg
Ж.2 Отладчик cordbg
451
C:\Program Files\Microsoft Visual Studio NET\FrameworkSDK\Bin\ cordbg.exe Microsoft (R) CommonLanguage RunTime Test Debugger Shell Version 1.0.3705.0 Copyright (C) Microsoft Corporation 1998-2001. All rights reserved. (cordbg)
Строка (cordbg) является запросом отладчика на ввод команды. Теперь пользователь может вводить команды cordbg, многие из которых были перечислены в табл. Ж . 1 . Одной из наиболее полезных является команда help. Ввод этой команды (или ее синонима, знака вопроса) без параметра выведет список всех команд с краткими пояснениями. Если требуется получить информацию об определенной команде, следует ввести команду help с именем команды в качестве аргумента. Например, ввод help
run
выведет информацию о команде run.
Запуск отлаживаемой программы Команда run. Если отлаживаемая программа называется myprogram.exe, то после запуска отладчика введите команду run
myprogram.exe
При этом в той же папке, откуда была запущена программа cordbg, должны находиться файлы myprogram.exe и myprogram.pdb. (He забудьте, что при запуске компилятора С# с ключом /debug:full, будет создан файл с расширением ".pdb".) Отлаживаемая программа будет загружена, и ее выполнение остановится на первой строке метода Main. Команда go. Ввод команды go (или ее синонима cont) заставляет программу выполняться до тех пор, пока она не дойдет до точки останова, или пока не будет перехвачена ошибка, или пока программа не завершится. Если программа входит в бесконечный цикл, ее можно остановить вводом с клавиатуры комбинации control-C, что заставляет отладчик взять управление на себя и остановиться в ожидании новых команд.
Использование точек останова Команда break. Новую точку останова можно установить с помощью одной из команд вроде следующих: break break break
47 myfile.cs:47 FooClass:BarMethod
452
Приложение Ж. Отладка в Windows
Первый вариант команды устанавливает точку останова на предложении в строке 47 исходного текста отлаживаемой программы. Второй вариант устанавливает точку останова на строке 47 файла с именем "myfile.cs". Третий вариант устанавливает точку останова на первой строке метода с именем BarMethod в классе с именем FooClass. Перечень действующих в настоящий момент точек останова можно получить с помощью команды break
введенной без аргументов. В полученном списке рядом с каждой точкой останова выводится ее номер. Команда remove. Для удаления точки останова следует ввести команду remove с указанием номера удаляемой точки останова. Например, команда
удаляет точку останова с номером 3.
Пошаговое выполнение Если программа находится в состоянии останова (либо на первом предложении программы после использования команды run, либо потому что выполнение достигло точки останова), можно выполнить одно предложение программы и остановить программу на следующем предложении. Такой режим называется пошаговым выполнением. Команда next. Эта команда (или ее синоним so от step over) заставляет отладчик выполнить одно полное предложение программы, даже если это предложение вызывает другой метод в программе. Команда step. Эта команда (или ее синонимы in или si от step into) заставляет отладчик выполнить одно полное предложение программы. Если, однако, это предложение содержит вызов метода, то программа останавливается перед выполнением первого предложения этого метода. Вслед за командами next и step можно указывать число их повторений. Например, команда next 10 эквивалентна вводу 10 команд next. Команда out. Если произошел вход в метод (возможно в результате выполнения команды step), то команда out продолжит выполнение до выхода из этого метода.
Ж.2 Отладчик cordbg
453
Доступ к переменным и к состоянию программы Команда where. Если программа останавливается на некотором предложении, то с помощью команды where можно получить последовательность активных методов (т. е. методов, которые были вызваны, но из которых еще не произошло выхода). Команда show. При вводе команды show на экран выводится последовательность строк исходного текста, окружающих текущее предложение. По умолчанию выводится пять строк до текущего предложения и пять строк после него. Команда print. Если программа останавливается на некотором предложении, то значение любой переменной, видимой в этой точке программы, может быть выведено с помощью команды print. Например, команда print
a [10],count
выведет значение поля count объекта, хранящегося в элементе массива а с номером 10. Команды up и down. Команда where выводит последовательность активных методов; каждый метод имеет локальные переменные, которые могут быть не видны в той точке, где остановилась программа. Эти методы могут принадлежать другим классам, в состав которых входят данные-члены, также невидимые в текущем предложении. Для получения доступа к таким переменным и полям, можно один или несколько раз использовать команду up. Ввод команды up имеет своим результатом назначение текущим предложения, которые вызвало текущий метод. Другими словами, отладчик переходит по цепочке вызываемых методов вверх по направлению к начальному методу Main. Команда down выполняет действие, противоположное команде up, заставляя отладчик перемещаться вниз по цепочке методов от метода Main по направлению к последнему активному методу в цепочке. Команда set. Иногда в процессе отладки требуется установить для некоторой переменной не то значение, которое в ней содержится в настоящий момент. Возможно, наилучшим примером использования этой процедуры будет такая ситуация, когда переменной, в силу ошибки в программе, было присвоено неправильное значение, и программист хотел бы посмотреть, будет ли программа выполняться далее без ошибок, если заменить текущее значение переменной на правильное.
Приложение Ж. Отладка в Windows
454
Ж.З Оперативный отладчик Если выполнение программы прекращается по причине необрабатываемого исключения, Windows выводит окно, пример которого изображен на рис. Ж . 1 .
An exception 'System.Exception' has occurred in Try.exe,
Possible Debuggers; Hew instance of Microsoft CLR Debuc Mew instance of Microsoft Development Environment
Set the currently selected debugger as the default.
Do you want to debug using the selected debugger? Yes
No
I
Рис. Ж . 1 . Оперативный отладчик
В качестве первого пункта в выводимым перечне альтернатив указывается отладчик Microsoft CLR, программа с именем DbgCLR, которая обычно устанавливается в системе Windows, как часть инструментального пакета .NET Software Development Kit (SDK). Второй пункт, среда разработки Microsoft (Microsoft Development Environment, MDE) обычно устанавливается вместе с пакетом Visual Studio и является отладчиком этого пакета. Выбор одного из указанных пунктов и щелчок по кнопке Yes запускает выбранный отладчик. Отладчик позволяет найти в программе местоположение необрабатываемого исключения, а также просмотреть значения переменных на момент ошибки. Оба отладчика работают в интерактивном графическом режиме и предоставляют схожие средства исследования программы. Отладчик Visual Studio рассмотрен в следующем разделе.
Ж.4
Отладчик Visual Studio
455
Ж.4 Отладчик Visual Studio Интерактивная отладочная среда Visual Studio включает в себя интерактивный редактор исходного текста программы и интерактивный отладчик. Каждая программа разрабатывается в виде проекта; имя проекта обычно совпадает с именем той папки, в которой находятся все исходные файлы этой программы. Для использования всех возможностей отладчика важно для отлаживаемого проекта установить конфигурацию Debug. Как это делается, описано в начале этого приложения. Предположим, что мы достигли такого этапа в разработке программы, что она компилируется без ошибок. Однако работает она неправильно. В этом случае можно начать выполнение программы под управлением отладчика Visual Studio, останавливая программу в заданных точках и наблюдая при этом значения переменных.
Установка точек останова Заметьте, что в подокне Solution Explorer выводится список имен всех исходных файлов, составляющих вместе программу. Двойной щелчок по любому из этих имен открывает файл с исходным текстом и выводит его содержимое в главное окно Visual Studio. Разумеется, выводить следует прежде всего файлы с «сомнительными» предложениями, на которых мы хотим устанавливать точки останова. Точки останова. Для установки точки останова на некотором предложении программы откройте файл, в который входит это предложение и щелкните по самому началу строки с этим предложением. После этого выберите New Breakpoint в ниспадающем меню Debug на панели инструментов. Откроется окно, внешний вид которого показан на рис. Ж.2. В окне New Breakpoint мы можете выбрать место установки точки останова. Окно позволяет установить точку останова как в некотором месте функции (т. е. метода), так и на определенной строке файла. Последнее, пожалуй, более удобно. Если щелкнуть по ярлыку File в верхней части окна, откроется вкладка, вид которой приведен на рис. Ж.З. Имя файла, номер строки и номер столбца в этой строке заполняются автоматически по местоположению курсора в окне исходного текста. После установки точки останова в левой части строки исходного текста появится большой оранжевый кружок. Точку останова можно установить и по-другому. Выберите нужное предложение исходного текста, щелкните по нему правой кнопкой мыши, а затем выберите пункт Insert Breakpoint в появившемся меню. Точки останова на данных. К наиболее неприятным относятся такие ошибки, когда какие-то переменные необъяснимо принимают неправильные значения. Некоторые отладчики позволяют прикреплять точки останова к переменным; когда программа выполняет присваивание этой переменной некоторого значения, она останавливается. К сожалению, эта возможность не реализована в текущей версии компилятора С#.
Приложение Ж. Отладка в Windows
456
Functo i n j File | Address J Data | Break executo i n when the program ruches this location in a function Function; [ I Line: j1 Character, i 1
Language:
Condition...
Hit Count..,
breakaways
OK
Cancel
rfeip^
Рис. Ж.2. Окно точки останова в Visual Studio
Function F'ie
j Address j Data |
Break execution when the program reaches this location in a file. File: Line:
)cuments\Visuat Studio Proiects\ConsoteADDlicationl\dassl .cs |29
Character: |1
Condition... 1 (no condition) Hit Count...
break always
OK
Cancel
Help
Рис. Ж.З. Установка точки останова на строке исходного текста
Ж.4 Отладчик Visual Studio
457
Удаление точек останова. Все точки останова могут быть удалены с помощью пункта Clear All Breakpoints в ниспадающем меню Debug. Для удаления одной точки останова самое простое — это открыть окно с исходным текстом и выбрать предложение, которому соответствует эта точка останова. Щелчок правой кнопкой мыши активизирует всплывающее меню, содержащее пункт удаления точки останова Remove breakpoint. Блокировка точек останова. Иногда бывает удобно удалить на время (блокировать) одну или несколько точек останова. Эту операцию позволяет выполнить пункт Disable all breakpoints в ниспадающем меню Debug. Щелчок правой кнопкой мыши по предложению исходного текста активизирует всплывающее меню, содержащее пункт блокирования точки останова Disable breakpoint. Соответственно, выбор пункта разблокирования точек останова Enable all breakpoints в ниспадающем меню Debug позволяет заново активизировать все заблокированные точки останова.
Выполнение программы В случае необходимости точки останова можно установить в программе еще перед ее выполнением. После этого следует выбрать пункт пуска программы Start в ниспадающем меню Debug на панели инструментов. Программа будет выполняться до тех пор, пока ей не понадобится ввод данных с клавиатуры (или из другой программы или устройства), или программа полностью и успешно выполнится, или возникнет ошибка, или будет достигнута точка останова. Во всех случаях, кроме варианта полного успешного выполнения, имеется возможность анализировать значения переменных, а также добавлять, удалять или блокировать точки останова. Если программа остановилась на точке останова, выполнение можно продолжить, выбрав пункт Continue в ниспадающем меню Debug (этот пункт появляется в меню на том месте, где сначала был пункт Start). Можно поступить по-другому, именно, продолжить выполнение программы команда за командой с помощью соответствующих команд отладчика. Ниспадающее меню Debug содержит три разных команды пошагового выполнения - Step Over, Step Into и Step Out, описания которых приведены ниже. С помощью меню Dedug можно также полностью завершить сеанс отладки (если, например, мы решаем повторить процедуру отладки с начала программы, или просто завершили отладку). Step Over. Выбор пункта Dedug/Step Over приводит к выполнению одного полного предложения. Если в этом предложении вызываются один или несколько методов, эти методы будут полностью выполнены (если, конечно, в них нет точек останова и не возникла ошибка). Если возникает необходимость пройти в пошаговом режиме большое количество команд, можно нажимать клавишу F10 на клавиатуре или щелкать мышью по кнопке Step Over на панели инструментов. Часть панели инструментов с указанием, где в ней найти кнопку Step Over, показана на рис. Ж.4.
Приложение Ж. Отладка в Windows
458 File
£dit
View
Project
• о
Build
Qebug Toots W
1 j Hex ! Ц§] *•' , | Progr
&
130 >= 130
API 45, 67 Application Programming Interface Application.Run, метод 377 Array, класс 302 определение 302 Array.Sort, метод 336, 340 ArrayList, класс 302, 304 определение 305" as, оператор 345 ASCII 15 В BinarySearch, метод в классе Array 302 в классе ArrayList 305 в коллекциях 306 Binary Writer, класс 401 Bit Array, класс 321 bool, тип 130
67 DateTime, структура 41 определение 67 свойство Now 43 сравнение 69 форматтеры 418 форматы 61 Debug, класс 247 метод Assert 243, 249 метод Indent 249 метод Listeners.Add 247 метод Listeners.Clear 247 метод Unident 249 метод WriteLine 247 decimal 8 7 DirectoryNotFoundException, исключение 261 DivideByZeroException, исключение 237 double 87
Предметный указатель сравнение 129 Drawlmage, метод 56, 382 DrawLine, метод 381 DrawString, метод 381
ЕСМА, стандарты 359 else, в предложении if 133 Equals, метод 117 со ссылками 295 в коллекциях 306 Escape-последовательности 157 Exam, структура 77 конструктор 79 свойство 82 Exception, класс 236
false 38, 95 представление, как 0 130 File, класс 270 FileLoadException, исключение 261 FileNotFoundException, исключение 261 float 87 Font, класс 382 foreach, цикл 152 в SortedList 315 Form, класс 376 Format, метод 111 FormatException, исключение 237 FromFile, метод 382
GDI+ 376 GetNextldentifier, метод 159 GetText, метод для файлового диалога 272 Graphics, класс 56, 377 GroupBox, элемент управления 209 GUI 9, 26, 183, 376 И HashTable, класс
321
IComparable, интерфейс 321, 339 IComparator, интерфейс 321 IComparer, интерфейс 341 IDE 25 IEnumerable, интерфейс 321 IEnumerator, интерфейс 321 if, предложение 133 Image, класс 41, 382
461 инициализация 41 IndexOf, метод в классе Array 302 в классе ArrayList 305 IndexOutOfRangeException, исключение 237, 302 int 69, 87 инициализация 41 определение 70 сравнение 129 Int32 69 internal, модификатор 356 Invalidate, атрибут элемента управления 388 IOException, исключение 261 is, оператор 345
JPEG, формат изображения
271
Label, элемент управления 185, 205 LastlndexOf, метод в классе ArrayList 305 Length, свойство 150 ListBox, элемент управления 186 long 87 М Main, метод 46 Math, тип 45 Microsoft Intermediate Language 22 Microsoft Word графика 375 и Unicode 421 MSIL22 Ы
.NET runtime, программа 23, 46 NetworkSocketStream, класс 401 new, модификатор 359 NextDouble, метод 278 null 41, 154, 158 в связанных структурах 291 ссылочное значение 292 NullReferenceException, исключение NumberE'ormatlnfo, класс 110, 417
OnPaint, метод 377 OpenFileDialog, элемент управления OutOfMemoryException, исключение OverflowException, исключение 98 overide, модификатор 80
236
272 263
Предметный указатель
462
Parse, метод 63, 70 с перечислимыми типами 323 Реп, класс 381 PictureBox, элемент управления 208, 217, 383 private, модификатор 170 ProgressBar, элемент управления 209 protected internal, модификатор 356 protected, модификатор 356 public, модификатор 81, 170, 356 Putlmage, метод 270
Queue, класс
321
default 165 правила использования 165 System, пространство имен 45, 67, 424 System.Collections, пространство имен 304, 425 System.Diagnostics, пространство имен 243 System.Drawing, пространство имен 377 System.Exception, класс 237 System.Globalization, пространство имен 418 System. 10, пространство имен 259, 369 System.Net, пространство имен 399 System.Runtime.Serialization, пространство имен 369 System.Text, пространство имен 265 System.Threading, пространство имен 378
R RadioButton, элемент управления 209 RAM 258 Random, класс 70, 278 определение 70 ReadLine, метод 64 ReadLine, метод для файлов 259 ref, параметр 115, 364 Remove, метод в классе ArrayList 305 Restore, метод 369 Resume, метод 396 return, предложение 113 в предложении switch 165
Save, метод 369 SaveFileDialog, элемент управления 272 sbyte 87 Serializable, атрибут 369 short 87 Simula67 18 Sort, метод 341 в классе Array 302 в коллекциях 306 SortedList пример 313 циклическая обработка 321 Split, метод 278 Stack, класс 321 StreamReader, класс 259, 262, 269, 401 StreamWriter, класс 259, 269 String, класс 111 string, тип 41, 69, 152 сравнение 129 String.Format, метод для имен файлов 271 StringBuilder, класс 266 struct 41 Suspend, метод 396 switch, предложение 164
Tcl/TK 376 TCP/IP, протокол 400 TcpListener, класс 402 TextBox, элемент управления 186, 206 then, в предложении if 134 this, индексатор в упорядоченном списке 313 Thread, класс 386, 396 ThreadStart, делегат 386 throw, предложение 238 Time, структура 85 TimeSpan, структура 67 ТоАггау, метод в класе ArrayList 305 ToString, метод 56, 61 для int 70 для структуры Time 85 TrackBar, элемент управления 209 true 95 представление, как 1 130 try-catch, предложение 236, 238, 260, 262 U uint 87 ulong 87 Unicode 15, 130, 156, 158, 420 Unix 25 позиции табуляции 264 ushort 87 UTC, универсальное скоординированное время 68 UtcNow, свойство 60
Views 184 создание форм
434
Предметный указатель
463 В
Views.Form, класс 191 атрибут Font 202 атрибут Height 204 атрибут Width 204 атрибуты 205 метод CloseGui 212 метод GetControl 193, 212 метод Getlmage 213 метод GetText 194, 212 метод GetValue 213 метод PutText 194, 213 методы 443 спецификации 435 тег 200 тег 196, 200-201 тег <Panel> 216 тег <space> 197, 201 тег 196, 200 теги группирования 437 теги элементов управления 438 Visual Studio 25, 183, 187 Visual Studio .NET 25
Ввод данных 63 окончание 151 Визуализация 375 Виртуальная функция 358 Виртуальные методы 355 Время по Гринвичу 68 Вывод 55 выражений 56 объектов 56 предложение 55 форматирование 61 Выполнимый файл 20, 30 Выравнивание строк и чисел 109 Выражение 40, 57, 90 Выражения 413 в предложениях вывода 57 отношения 95
Генератор случайных чисел 173, 361 Графика в системе Views 395 Графический интерфейс GDI+ 376 пользователя 183 Группирование объектов 286 Группы объектов 287
W Web-запросы 398 Write, метод 55 для файлов 262 WriteLine, метод 43, 55 для файлов 262 с форматом 107, 418
д
X XML
190
Абстракция 335 Алфавитный порядок в SortedList Анализ программы 52 Анимация 375 Ассемблер 16 Ассоциативность 97
Байт 14 Библиотеки 45 Биты 14, 160 для числовых типов 87 Булев тип 38 Булева алгебра 130 Булева переменная 136 Булевы выражения 131-132 Булевы значения 130 Булевы операторы 131 приоритет 132
314
Данные 77 в иерархии классов 357 объекта 35 Дата как объект 35 как структурный тип 38 тип 36 Даты и время, типы 432 Двоичная система счисления 14 Двоичное представление 160 Делегат 386 Деление 90 • по модулю 91 Дерево 287 Десятичная система 16 Динамичная компиляция 22 Директива компиляции #if 246 Доступ к полям через свойства 43 только для чтения через свойства 85 по имени 298 по номеру 296 Доступность private 81 public 81 и наследование 356 модификаторы в наследовании 355 модификаторы, список 356
Предметный указатель
464
Единицы измерения в системе Views
204
Ж Жесткий диск
258
3 Загрузчик 16 Замещение 337, 355 Запуск программы 46 Знак + для сцепления и сложения Знак равенства 58 Знаки препинания 414 Значащие цифры 89 Значение 58 в упорядоченном списке 299
57
И Игра «Камень—ножницы—бумага» 169, 171, 173, 175, 177, 179, 181 Идентификаторы 49 Иерархия 288, 354 объектов 286 Изображение как объект 35 Изображения на элементах управления 383 Имена типов и объектов 39 файла и программы 47 Индексатор 313 Инициализация изображений 41 структур и классов 41 структуры 79 целочисленных полей 41 Инкапсуляция 82 Интерактивная среда разработки 25 Интерактивный справочник 26 Интерпретация 21 Интерфейс 335 для достижения полиморфизма 336 дублирование блоков 362 определение 338 реализация 339 Интерфейс прикладного программирования 45, 67 Исключение PathTooLongException 261 Исключения для файлов 261 К Кавычки 55, 154 в строках 154 Каталог 28, 260 Квантование времени 385 Класс 41, 45, 286 инициализация полей 292
ArrayList 302, 304 ArrayList, определение 305 BinaryWriter 401 Brush 381 Color 380 Console 259 File 270 Font 382 Image 382 NetworkSocketStream 401 NumberFormatlnfo 417 Pen 381 Random 278 StreamReader 259, 262, 269, 401 StreamWriter 259, 269 TcpListener 402 Thread 386, 396 ThreadStart 386 Views.Form 434-435, 437, 439, 441, 443, 445 абстрактный 338 базовый 337, 356 определение 354 структурная единица 46 Классы кистей 378 производные 337 Клиент-сервер 401 Ключ 28, 299 в упорядоченном списке 312 Ключевые слова 49, 413 Коллекции выбор 323 определяемые пользователем 321 перечислимые 321 список 321 сравнимость 321 Коллекция 152, 296-297, 299 Команда esc 28 ключ /debug 246 Комментарии 51 XML 51 Компилятор 19, 26 Компиляция 19 программы 447 Компьютерная графика 375 Консоль, как экран 54 Константы 58 Конструктор 42, 78 по умолчанию 80 пустой по умолчанию 292 Л Латинские числа, преобразование 166 Лексикографический порядок 130 Лексические ошибки 233 Логические операторы 38, 131 Ложь 38
Предметный указатель
Мантисса 88 Массив 145, 147 диапазон 298 индексируемые переменные 298 индексы 147, 150, 298 как коллекция 297, 300-301, 303, 305, 307, 309, 311 лишний элемент 0 149 простое объявление 145 размер 145, 150 свойство Length 150 элементы 297 Массивы 432 многомерные 307 потоков выполнения 393 прямоугольные 306 рваные 306 Математические функции 432 Метка выбора 165 default 165 Метод 80 Application.Run 377 Close 262 Сору, в классе File 270 Delete, в классе File 270 Drawlmage 382 DrawLine 381 DrawString 381 Exists, в классе File 270 FromFile 382 GetText, для файлового диалога 272 Go, обоснование 48 NextDouble 278 OnPaint 377 Putlmage 270 Read, для файлов 263 ReadLine, возврат null 263 ReadLine, для файлов 263 ReadToEnd 277 ReadToEnd, для файлов 263 Resume 396 Split 278 static 115 String.Format, для имен файлов 271 Suspend 396 void 112 Write для файлов 262 WriteLine для файлов 262 вызов 113 объявление 112 разработка 114, 120 скрытый 359 создание объекта 43 типизированный 113 экземпляра 115 Методы 414 абстрактные 338
465 Многопоточность 375 Множественные присваивания 97 Модификатор checked 98 overide 80 Модификаторы доступности, список
356
Н Назначение 58 с помощью оператора new 42 Наследование 286, 335, 354 для достижения полиморфизма использование 359 ради расширяемости 337 Неравенство ссылок 296 Неявное преобразование 98 Нулевая строка 154 Нуль-символ 158
336
Область видимости 113 Обработчики событий 193, 379 Обращение к членам 39 Объект неформальное определение 33 создание 42 Объектно-ориентированное программирование 18 Объекты создание в иерархии 355 создание из структуры и класса 289 сравнение 116 ООП 18 Оперативная компиляция 22 Оперативный отладчик Microsoft 454 Оператор + 55 as 345 is 345 булев 132 действие над объектами 44 декремента 95 И 131 ИЛИ 131 конкатенации 55 логический 131 назначения 17 НЕ 131 присваивания 17, 58 сцепления 55 точка (.) 39, 43 Операторы 44 выражений 414 перегрузка 304 присваивания 96 сравнения 129 числовые 90
466
Предметный указатель
Операционная система Windows 26 Отладка 238-239, 241, 243, 245, 247, 249 Отладчик 26 CLR 250 CLR Debugger 241, 250 cordbg 448 Visual Studio 455 пошаговое выполнение 255 точки останова 255 Отступы в тексте программы 49 Ошибки времени выполнения 234 семантические 233 П Пакеты 45 Память основная 258 Параметр 42, 112 ref 115 Перегрузка операторов 352 Передача по значению 115 Переменная 58 индексируемая 298 инициализация 58 объявление перед использованием окружения Path 28 Перечислимые типы 322 определение 322 Пикселы, единицы измерения 204 Плавающая точка 15 Повторное использование 18, 45 Повышение типа 91 Погрешность чисел decimal 190 чисел с плавающей точкой 89 Показатель степени 88 Поле 78 объявление 40 Полиморфизм 335 в методах 336 в типах 335 посредством наследования 337 Порт 400 Порядок 88 Поток выполнения 385 завершение 392 как средство анимации 389 массивы 393 Поток данных 259 ввода 259 вывода 259 закрытие 262 ошибки 260 Пошаговое выполнение 255 Предложение 17 catch 261 continue 163
58
if 133 return 113 try-catch 236, 260, 262 вывода 55 языка программирования 48 Предложение ввода 63 Представление данных 14 чисел 87 числа с плавающей точкой 88 Преобразование 98 класса вверх 345 класса вниз 345 классов 343 латинских чисел в арабские 167 неявное 98 типа 345 числовых типов 87 явное 98 Приоритет 97 Присваивание значений объектам 59 в предложениях ввода 63 множественное 97 список операторов 414 строкам 59 Программа 47 содержательные действия 47 Программная инженерия 45 Программная поддержка 25 Прописные и строчные буквы в типах 69 Простой тип, определение 37 Простой цикл 99 Пространство имен 45, 423 System 424 System.Collections 304, 425 System.Diagnostics 243, 426 System.Drawing 377, 426 System.Globalization 427 System.IO ^-259, 369, 427 System.Net 399, 428 System.Net.Sockets 429 System.Runtime.Serialization 429 System.Text 265, 429 System.Threading 378, 430 System.Windows.Forms 430 Процессор 13 Пункты, единицы измерения 204 Пустая строка 158
Работа в сети 398-399, 401 Расширяемость 18, 337 с наследованием 354-355, 357, 359 Редактор 19 отступы 46 Родословная 287
Предметный указатель
Свойство 43, 80-81 ключевые слова get и set 82 определение 82 Связанный список, создание 293 Связи в списке 293 Связывание объектов 286 Семантика 19, 39 Семантические ошибки 233 Сериализация 368-369, 371, 373 двоичная и XML 368 Сигнатура 79 Символ перевода строки 158 табуляции 158 Символы 50 акцентированные 422 Символьный тип 38 Синтаксис 39 Синтаксические ошибки 233 Скобки 57 для булевых выражений 132 Случайные числа, классы 432 Событие 379 Соглашения о структуре программы 46 Создание объекта 42 Сокеты 398, 401 Сокрытие 355 Специальные слова 414 Спецификации Views, хранение в файле 277 Сравнение 95 ссылок 296 объектов 116 операторы 129 типов с плавающей точкой 95 Сравнение циклов while и for 139 Среднее значение 309 Ссылки 289 Ссылочный тип массив 295 строка 295 Стандартное отклонение 309 Статический метод 115 Строка 55 escape-символ 157 вывод 55 нулевая 154 пустая 154, 158 тип 38 Строки конкатенация 153 неизменяемость 153 операции 154 сцепление 153 упорядочение 130 Строки и символы, типы 432 Строковые константы 154 Строчные и прописные буквы
467 в идентификторах 50 в именах классов и структур 41 в именах типов и объектов 37 в свойствах 50 Структура 41, 286 инициализация 79 определение 81 программы 48 управляющая 414 Структурная единица 46 Структурное оформление программы 49 Структурный тип ^struct и class 41 определение 39 Структуры данных, пространство имен 433
Текстовый редактор 26 Терминология объектной ориентации Тип 8 даты 36 имя 36 определение 36 повышение 91 Тип char, методы 159 Тип string, методы 155 Типизированный метод 113 Типы ввода-вывода 36 значений 289 значений, ограничения 289 математических операций 36 план объявления 81 простые 413 с плавающей точкой 88 с фиксированной точкой 90 ссылочные 289 структурные 413 функций 36 чисел 36 Точка входа 47 Точка с запятой 49 Точки останова 255
Удаленные объекты 398 Умножение 90 Упорядочение строк 130 Упорядоченный список 299, 312 значение 299, 312 ключ 299, 312 определение 312 Управляющая переменная инициализация 140 как объект 142 продвижение 140 Условная компиляция 246 Условная операция 129
45
468
Предметный указатель
Ф Файл 258 как последовательность байтов 258 запись 261 ошибки ввода 262 ошибки чтения 262 поиск слова 267 создание 260 суффикс .txt 259 текстовый 259 чтение символов 263 чтение строки 263 Файловые диалоги 272 Файлы в системе Views 270-271, 273, 275, 277, 279, 281, 283, 285 пространства имен 432 с изображениями 270 Фигурные скобки 47 Форма 19 Форма для синтаксиса и семантики 39 Формат ошибки 108 С для денежных единиц 120 спецификации 108, 415 Форматирование 61 выбор 111 вывода 106-107, 109 даты 61 денежные единицы 110 другие типы 110 Форматирующая строка 107 Форматирующий параметр 107 Форматтер 415 XML 369 настройка 417 Фотоальбом 271 Функции объекта 35 Функция 42, 78
Хеш-таблица
299
ц Целочисленные типы 87 Цикл do-while 138 for 135 Цикл for 99 структура 140 бесконечный 141 вложенный 141 вперед 140 двойной 141 использование с массивом назад 141
145
объявление переменной 140 проверка на повторение 140 простой 99 пустой 141 с неоднозначным выходом 142 управляющая переменная 140 Цифровая камера 271 Цицеро, единицы измерения 204
Числа спецификаторы форматов 415 типы 36, 431 Числовые операторы 90 Числовые типы 87, 89 преобразования 99 Член типа 39 статический 39 обращение 39 экземпляра 39 Ш Шестнадцатеричная система 16 Шрифт Courier 264 sans serif 202 serif 202 равноширинный 264 размеры 202 семейства 202 стили 202 Шрифты, в системе Views 202
Экземпляр 37 Элемент типа 39 Элемент управления Button 185, 206 CheckBox 207 GroupBox 209 Label 185, 205 ListBox 186 PictureBox 208, 217 ProgressBar 209 RadioButton 209 TextBox 186, 206 TrackBar 209 позиционирование в Views относительное 188 Элементы управления, позиционирование в Views 215
Язык ассемблера 16 Язык программирования 17
Оглавление К читателям
5
Предисловие
6
С# — откуда и куда Обзор книги Наш подход Система Views , Благодарности Глава 1 : Введение 1.1 Преамбула 1.2 Роль языков программирования 1.3 О компиляции 1.4 Интерактивные среды разработки 1.5 Начинаем работать на С# Основные понятия, рассмотренные в гл. 1 Контрольные вопросы Упражнения Глава 2 : Использование объектов 2.1 Введение в объекты 2.2 Члены объектов 2.3 Структура программ 2.4 Строки, вывод и форматирование 2.5 Переменные, присваивание и ввод 2.6 Основы API С# Основные понятия, рассмотренные в гл. 2 Контрольные вопросы Упражнения Глава 3 : Внутри объектов 3.1 3.2 3.3 3.4 3.5 3.6
Структура типа Поля и свойства Числовые типы Выражения Простые циклы Форматирование вывода
6 7 9 10 10 12 .12 13 19 25 26 30 31 32 33 33 39 45 54 57 67 72 73 74 77 77 81 87 90 99 106
470
Оглавление 3.7 Методы и параметры 3.8 Проект 1 — сравнение телефонных счетов Основные понятия, рассмотренные в гл. 3 Контрольные вопросы Упражнения
Глава 4 : Управление и массивы 4.1 Булевы выражения 4.2 Предложения выбора 4.3 Предложения повторения 4.4 Простые массивы 4.5 Строки и символы 4.6 Дополнительные предложения выбора 4.7 Проект 2. Игра «Камень—ножницы—бумага» Основные понятия, рассмотренные в гл. 4 Контрольные вопросы Упражнения Глава 5: Графические интерфейсы пользователя с применением системы Views 5.1 5.2 5.3 5.4
Графические интерфейсы пользователя Элементы GUI Введение в систему Views Планировка элементов с помощью системы Views 5.5 Элементы управления Views 5.6 Методы Views 5.7 Углубленное использование Views 5.8 Проект 3 — касса супермаркета Основные понятия, рассмотренные в гл. 5 Контрольные вопросы Упражнения
Глава 6: Исключения и отладка 6.1 Ошибки 6.2 Обработка ошибок 6.3 Отладка 6.4 Использование программы отладчика Основные понятия, рассмотренные в гл. 6 Контрольные вопросы
111 117 124 125 .127 129 129 133 135 145 152 162 169 173 174 178 182 182 184 189 200 203 210 215 222 227 228 230 232 232 236 238 250 256 256
Оглавление
Глава 7 : Файлы и потоки 7.1 Файлы и потоки 7.2 Потоки вывода 7.3 Потоки ввода 7.4 Обработка файла по заданным алгоритмам 7.5 Файлы в системе Views Основные понятия, рассмотренные в гл. 7 Контрольные вопросы Упражнения Глава 8 : Коллекции 8.1 8.2 8.3 8.4 8.5
Еще о классах Коллекции Массивы как коллекции Упорядоченные списки Проект 4 — Расписание курсов повышения квалификации Основные понятия, рассмотренные в гл. 8 Контрольные вопросы Упражнения Глава 9 : Полиморфизм и наследование 9.1 Объекты в изменчивом мире 9.2 Интерфейсы ради полиморфизма 9.3 Расширяемость с наследованием 9.4 Проект 5 — Усовершенствование игры StarLord 9.5 Сериализация Основные понятия, рассмотренные в гл. 9 Контрольные вопросы Упражнения Глава 1 0 : Графика и сети 10.1 Графические интерфейсы 10.2 Простые графические средства 10.3 Изображения 10.4 Анимация с помощью потоков 10.5 Работа в сети 10.6 Проект 6 — система ATM клиент-сервер Основные понятия, рассмотренные в гл. 10 Контрольные вопросы Упражнения
471
258 258 260 262 . . . . 267 270 281 282 283 286 286 296 300 312 322 329 330 333 335 335 339 354 . . . 360 368 370 371 374 375 375 376 382 385 398 402 408 409 410
472
Оглавление
Приложение А: Список форм
411
Приложение Б: Ключевые слова и операторы
413
Б.1
Ключевые слова
413
Б.2
Операторы и знаки препинания
414
Приложение В: Ф о р м а т т е р ы
415
8.1 Спецификация формата 8.2 Числовые спецификаторы форматов 8.3 Настройка числовых форматтеров 8.4 Форматтеры DateTime П р и л о ж е н и е Г: Unicode . . . .
415 415 417 418 420
Г.1 Г.2
Ввод символов Unicode Некоторые символы
Приложение Д: Употребительные пространства имен
420 421
423
Д.1
Библиотека классов интегрированной системы .NET
423
Д.2
Использование пространств имен
431
Приложение Е: Класс Views.Form
434
Е.1 Создание форм с помощью Views Е.2 Синтаксис спецификаций Views.Form Е.З Теги группирования Е.4 Теги элементов управления Е.5 Значения атрибутов Е.6 Методы Views.Form Е.7 Рекомендуемый стиль программирования Е.8 Использование индексных операций Приложение Ж : Отладка в Windows
434 435 437 438 441 443 444 445 447
Ж.1 Ж.2 Ж.З Ж.4
Введение Отладчик cordbg Оперативный отладчик Отладчик Visual Studio .
Предметный указатель
447 448 454 455
460