УДК ББК 681.3 Ф91
Фролов А. В., Фролов Г. В. Язык С#. Самоучитель. -
ДИАЛОГ-МИФИ,
- 560 с.
ISBN 5-86404-176-9 Книга представляет собой методическое руководство по изучению современного языка программирования С#. Рассмотрена платформа Microsoft Framework, в среде которой ра ботают программы С#, а также все основные возможности языка С#. Это классы, типы данных, поля, методы, интерфейсы, свойства, делегаты работа с контейнерами, файлами, потоками и др. Одновременно с изучением языка, читатель получит навыки объектно-ориентированного и компонентно-ориентированного программирования, познакомится с обширной библиотекой классов Microsoft Framework. Приводятся многочисленные примеры программ, де различные возможности языка С#. Книга рассчитана на всех, кто желает самостоятельно изучить новый язык программиро вания С#. Она может использоваться в качестве учебного пособия для студентов и школьников.
ББК Учебно-справочное
издание
Фролов Александр Вячеславович Фролов Григорий Вячеславович Язык С#. Самоучитель
Редактор О. А. Голубев Корректор В. С. Кустов Макет И. Чумаковой Лицензия ЛР N 071568 от 25.12.97. в печать Формат 70x100/16. Бум. газетная. Печать Гарнитура Усл. л. л. Тираж 3 ООО экз. Заказ 4590. Акционерное общество "ДИАЛОГ-МИФИ" Москва, ул. Москворечье, 31, корп. 2. Т.: 320-43-55, 320-43-77 E-mail: dialog@bitex.ru Отпечатано на Ордена Трудового Красного Знамени ГУП Чеховский полиграфический комбинат Министерства РФ по делам печати, телевещания и средств массовых коммуникаций Чехов, Московская обл., (272) факс: (272) 62-5-36 ISBN 5-86404-176-9
© Фролов А. В.. Фролов Г. В., 2003 © оформление обложки. ЗАО "ДИАЛОГ-МИФИ", 2003
Введение За всю историю развития компьютеров были созданы десятки (если не сотни) различ ных языков программирования. из них канули в Лету, другие здравствуют и поныне. Языки программирования развиваются и трансформируются, на базе одних возникают другие — словом, все идет своим чередом. В этой книге мы расскажем вам о новом языке — С# (произносится «си-шарп»), представляющим собой одну из самых последних разработок компании Microsoft. Следует сразу отметить, что язык С# — это не просто один язык программиро вания. Он является одним из важнейших компонентов новой стратегической платфор мы Microsoft ориентированной на современные технологии Интернета. Изучая язык С#, вы закладываете фундамент своей успешной деятельности в области созда ния современных приложений с применением технологий компании Microsoft. Уже се годня вы сможете накапливать багаж знаний, необходимый для разработки программ, рассчитанных на операционную систему Microsoft Windows которая еще только готовится к выпуску. Для того чтобы точнее определить роль и место языка С# среди других языков и систем программирования, мы приведем краткий исторический обзор, отразив в нем свой опыт применения различных языков программирования для решения тех или иных задач. Во Введении мы будем употреблять много терминов, возможно, незнакомых читателю. Некоторые из них разъяснены по ходу изложения для изучения других потре буется обратиться к дополнительной литературе. Если Вам непонятен какой-то термин, пропустите его и читайте дальше. После изучения материала можно будет вернуться и прочитать заново непонятные разделы, вооружившись дополнительными знаниями. В частности, если вы в ближайшее время не планируете создавать приложения для Интернета, можете игнорировать непонятную вам терминологию, имеющую отноше ние к созданию Web-приложений. В противном случае для получения дополнительной информации обратитесь к [2] и [3].
От ассемблера к С# Имея более чем 15-летний стаж создания программ, программных комплексов и сис тем, мы перепробовали самые разные языки программирования. Начиная с составле ния программ в машинных кодах для процессора Intel 8080, мы перешли к языку ас семблера, а затем освоили Basic. Работая над автоматизированной системой исследо вания электромагнитных полей в резонаторах ускорителей заряженных частиц, мы обсчитывали результаты измерений и представляли их в графическом виде с по мощью языка Fortran. Нам также довелось поработать с малоизвестным сейчас макро языком PL/M, создав с помощью систему сбора данных результата физических экспериментов, файловую систему для хранения данных на цифровом кассетном маг нитофоне, а также отладчик ассемблерных программ процессора Intel 8080.
3
Следующий этап нашей как программистов был связан с рами серии ЕС и разработкой информационных систем с базами данных. Здесь мы то же начали с ассемблера, а затем перешли на язык PL (не путайте его с упомянутым выше языком Разочаровавшись в языке PL, обладающем, на наш взгляд, мно гочисленными недостатками, мы обратили свой взор на и относительно новый в то время язык С. Имевшаяся в нашем распоряжении реализация языка Pascal для ЕС обладала рядом ограничений и была снабжена недостаточно подробной документацией. Возможно, поэтому мы больше внимания уделили языку С. С тех пор возможности С и Pascal выравнялись, но наше пристрастие к языкам, подобным С, перешло в привычку и сохра нилось до сих пор. Раздобыв транслятор С для компьютеров серии ЕС, мы несколько лет с успехом использовали для создания как прикладных, так и системных программ. Например, для операционной системы (далее — ОС) TKS-432 мы разработали на языке С вирту альную файловую систему, напоминающую по своему устройству файловую систему FAT. В системном программировании мы комбинировали мощь языка С и низкоуров невые возможности языка ассемблера. С появлением персональных компьютеров мы обратили свое внимание на объект но-ориентированный язык С++. Так как мы привыкли к процедурному и структурному программированию, то нам пришлось затратить определенные усилия для того, чтобы разобраться в принципах объектно-ориентированного программирования. Тем не ме нее впоследствии мы не пожалели о затраченных усилиях, оценив преимущества ново го подхода. Такие возможности С++, как инкапсуляция, наследование и полиморфизм, оказа лись очень полезными при разработке автономных и сетевых приложений для опера ционной системы Microsoft Windows. Готовые библиотеки классов, например уже ус таревшая сейчас Borland OWL и постоянно развивающаяся Microsoft MFC, позволяют разрабатывать приложения Windows на порядок быстрее, чем это было при использо вании языка С. Для разработки бухгалтерских программ и автономных мационных систем мы применяли систему FoxPro (впоследствии — Microsoft FoxPro), в которой использовался собственный язык программирования. В сочетании с визу альными средствами быстрого проектирования приложений этот язык позволял созда вать программы с базами данных и довольно сложным пользовательским интерфейсом буквально за считанные дни и недели. После того как в нашей стране, наконец, появился Интернет, мы с большим энтузи азмом приступили к освоению новой для нас области программирования. И конечно, мы попытались применить язык С++ для разработки Web-приложений. В частности, мы создавали расширения серверов Web в виде программ ISAPI и Однако оказалось, что, хотя активные компоненты Web-приложений можно полно стью программировать на С++, гораздо удобнее применять для этого другие языки программирования, специально ориентированные на Интернет. Активные приложения Интернета представляют собой сложный конгломерат техноло для реализации которых применяются самые разнообразные средства и языки про граммирования. Некоторые из этих языков и технологий мы использовали в среде ОС Mi crosoft Windows, а в среде ОС Linux. 4 А. В. Фролов. Г. В. Фролов. Язык С#. Самоучитель
Мы создавали страницы на языке разметки гипертекста и Dynamic HTML, программы на языке Perl, серверные и сце нарии JavaScript и VB Script, серверные сценарии Java, а также серверные элементы управления ActiveX с использованием языка С++. И вот теперь мы получили настоящее удовольствие от работы с языком програм мирования С#, создавая на нем обычные приложения Microsoft Windows и Webприложения для Интернета. В частности, с использованием для службы восстанов ления данных DataRecovery.Ru нами создается программ ный комплекс удаленного восстановления данных, пропавших в результате аппарат ных сбоев, программных ошибок и ошибок пользователей, а также вредоносного воз действия компьютерных вирусов.
Классические языки программирования В начале своей программистской деятельности мы были сильно ограничены в инстру ментах создания программного обеспечения. Сегодня же, наоборот, «ассортимент» языков программирования и технологий очень велик, поэтому всегда легко сделать однозначный выбор. Помимо языков программирования, перечисленных выше, сего дня существуют такие языки, как Object Pascal, Visual Basic, LISP, Piton, RPG, Ada, Oak, SmallTalk и др. И вот сейчас на арену выходит еще один язык программирования — С#, разрабо танный компанией Microsoft в рамках современной технологии Microsoft Этот язык предназначен для создания как обычных автономных приложений Win dows, так и Web-приложений. Как же выбрать язык программирования для реализации своего проекта? Этот вопрос, который задают себе многие, правильнее было бы сформулировать по-другому: «Как выбрать технологию для реализации своего проекта?» Та или иная технология может предполагать применение одного или нескольких язы ков программирования. Совершив ошибку при выборе технологии на начальном этапе проектирования, можно пойти по ложному пути, потеряв много времени и сил. В то же время неправильный выбор языка программирования даже в рамках нужной технологии также может привести к непроизводительным затратам усилий и потере времени. Рассмотрим области применения нескольких наиболее распространенных языков программирования. На наш взгляд, это поможет вам правильно оценить место и роль языка С#.
Ассемблер Язык ассемблера позволяет составлять программы с применением мнемонических обозначений машинных команд и символических меток. Получив на входе файл с тек стом программы, ассемблер переводит текст в машинный код, пригодный для не посредственного исполнения. Таким образом, используя ассемблер, можно оптималь но задействовать все возможности процессора. Заметим, что сегодня применение языка ассемблера оправдано разве лишь при со ставлении системных (драйверов) ОС пли отдельных фрагментов программ, требующих рекордной производительности.
5
Хотя теоретически на ассемблере можно писать любые программы, реально этот язык используют только системные программисты, и то далеко не всегда. Например, те же самые драйверы периферийных устройств для ОС Microsoft Windows и Linux в большинстве случаев могут быть с успехом написаны на языках С или С++. Тем мы не считаем изучение ассемблера бесполезным занятием для начи нающего программиста. Так как язык ассемблера в наилучшей степени отражает архи тектурные особенности центрального процессора, знакомство с ассемблером позволит разобраться в принципах работы компьютерных программ.
С Несмотря на появление своего успешного наследника — объектно-ориентированного языка программирования С++, классический язык С до сих пор широко используется, например теми, кто создает программы для ОС Linux. В результате работы транслятора С, а затем редактора связей исходный текст про граммы С переводится в машинный код, пригодный для исполнения процессором. Благодаря операторам структурного программирования язык С намного облегчает со ставление программ по сравнению с языком ассемблера. Библиотека стандартных функций, поставляющаяся вместе с транслятором и фактически расширяющая язык С, облегчает решение типовых задач программирования, таких, например, как выполне ние математических вычислений, работа с текстовыми строками и т. п. объектно-ориентированный подход, предлагаемый языком С++, значитель но создание программ, в результате чего обоснованность использования классического языка С при создании новых программ представляется нам весьма со мнительной. Более того, начиная изучение программирования с процедурного, а не объектноориентированного языка, можно приобрести вредные привычки процедурного прог раммирования. Эти привычки в дальнейшем затруднят изучение современных объект но-ориентированных и компонентно-ориентированных технологий.
С++ На сегодняшний день язык С++ представляет собой один из наиболее совершенных инструментов создания прикладных и особенно системных программ. Применяя объ ектно-ориентированные возможности этого языка программирования, а также обшир ные библиотеки классов и шаблонов, программист может создавать весьма и весьма сложные приложения. Современные оптимизирующие компиляторы позволяют добиться высокой ско рости исполнения программ, благодаря чему во многих случаях даже критичные ко времени исполнения фрагменты программы можно составлять без использова ния ассемблера. Вместе с тем необходимо отметить, что для того, чтобы освоить в полной мере все возможности языка С++, требуется немало времени. Читая и перечитывая основопола гающий труд, посвященный С++, — книгу Бьерна Страуструпа [1], мы постоянно от крываем для в этом языке что-то новое. И это несмотря на многолетний опыт ис пользования С++.
6
А. В. Фролов, Г. В. Фролов. Язык С# Самоучитель
Составляя программы на С и С++, начинающий программист может много раз насту пать на различные «грабли», прежде чем научится составлять программы с минимальным количеством ошибок. Причина в том, что язык С++ программисту делать в сво ей программе практически все, что угодно. Программа может обратиться к любому дос тупному участку памяти с применением указателей, прочитать содержимое неинициали зированных переменных, выйти за границы обрабатываемого массива, передать функции неправильные параметры, вызвать функцию по ошибочному адресу и т. п. Используя гибкость языка С++, опытный программист сумеет реализовать свои идеи наилучшим образом, а начинающий рискует допустить трудно обнаруживаемые ошибки. Мы применять язык С++ в тех случаях, когда к быстродействию или к размеру загрузочного модуля создаваемой программы предъявляются особые требования. Например, выбор С++ будет правильным для системного программи рования, для решения задач моделирования, для создания компактных утилит или программных модулей, при разработке таких программ, как текстовые процессоры, графические редакторы и компиляторы, модули расширения серверов W e b с высо кой посещаемостью. С другой стороны, применение С++ в сочетании с классической технологией соз дания исходного текста программы и ее последующего компилирования в загрузоч ный модуль во многих случаях будет не оправдано. Например, разработка по этой технологии будет слишком трудоемкой при созда нии бухгалтерских программ, систем складского учета и систем управления докумен тооборотом предприятия, справочно-информационных систем с базами данных, авто номных приложений для Microsoft Windows со сложным пользовательским интерфей сом, который к тому же время от времени изменяется. Здесь больше подойдет одна из систем быстрой разработки приложений (Rapid Application Development, RAD), позволяющих абстрагироваться от многих несущественных деталей и сосредоточиться на решении прикладной задачи. Что касается RAD для С++, то наиболее популярными системами сейчас являются Microsoft Visual С++ в сочетании с библиотекой классов MFC и Borland С++ Builder. Предоставляя в распоряжение программиста визуальные средства проектирования приложений, эти системы значительно ускоряют создание диалоговых программ. Не обходимо, однако, отметить, что использование MFC требует глубоких знаний С++ и совершенного владения технологиями объектно-ориентированного программиро вания. Указанные системы RAD не решают проблем С++, связанных с предостав лением полного доступа программ к ресурсам процесса и с возможностью совершения трудно обнаруживаемых ошибок.
Pascal Изначально разработанный в учебных целях язык программирования Pascal сегодня превратился в современное средство объектно-ориентированного программирования и пользуется большой популярностью. Не в последнюю очередь этой популярности он добился в результате появления мощного и удобного средства ускоренной разработки приложений Borland Delphi. Фактически именно компания Borland внесла наибольший вклад в развитие современного языка Pascal. Введение
7
Заметим, что, обладая всеми достоинствами языка С++, современный объектный Pascal имеет и большинство его недостатков. Программист, составляющий программу на языке Pascal, может совершать множество различных ошибок, проявятся только во время выполнения программы и приведут к ее аварийному завершению. На наш взгляд, область применения языка Pascal практически полностью совпадает с областью применения языка С++. Язык Pascal в сочетании со средой разработки Bor land Delphi часто используется и для разработки небольших утилит, и для создания программ со сложным пользовательским интерфейсом.
Basic Разработанный компанией Microsoft и доступный вместе с первыми версиями ОС MS DOS язык программирования Basic прост в изучении, преподавался в школах и высших учебных заведениях, и потому с него начинали свой трудовой путь многие программисты. Между Basic и такими языками программирования, как язык ассемблера, С и С++, существует одно принципиальное отличие, на которое нужно обратить внимание. Этот язык не компилируемый, а интерпретируемый. Чтобы понять, о чем идет речь, рассмотрим процесс компиляции и редактирования программы, составленной на языке С или С++. Создавая программу на С или С++, программист вначале готовит ее исходный текст в каком-либо текстовом редакторе. Исходный текст несложных программ раз мещается в одном или нескольких файлах, сложные проекты могут содержать десятки и сотни файлов исходного текста. Далее файлы исходного текста передаются компилятору, формирующему объект ный модуль программы (рис.
Файл
Объектный модуль
программы Рис. В. 1. Компиляция исходного текста При компиляции нескольких исходных файлов может создаваться один или несколько файлов объектных модулей. В процессе создания объектного модуля компилятор преобра зует команды исходного текста в машинные команды, предназначенные для исполнения не менее объектный модуль еще готов для исполнения, так как в нем еще присутствуют символические имена ссылок и переменных. Для того чтобы собрать все объектные модули программы в один исполнимый мо дуль, нужен редактор связей. Эта программа собирает все объектные модули вместе, заменяя символические ссылки относительными или абсолютными адресами перемен ных и функций. Дополнительно в загрузочный модуль включаются необходимые функции из библиотек объектных модулей, в частности из стандартной библиотеки модулей компилятора С или С++ (рис.
8
А.
Фролов. Г. В. Фролов Язык С#. Самоучитель
Рис.
Редактирование связей
В результате этой операции создается исполнимый модуль программы в виде фай (программа MS-DOS), *.ехе (программа MS-DOS или Microsoft Windows) или (библиотека динамической загрузки Microsoft Windows). Модуль называется ис полнимым потому, что он содержит машинные команды, непосредственно исполня емые центральным процессором компьютера. Возвращаясь к языку программирования Basic, заметим, что он предполагает при менение совершенно другой схемы подготовки и исполнения программ. В основе классической системы программирования на языке Basic лежит специ альная программа, называемая интерпретатором языка Basic. Интерпретатор Basic построчно считывает исходный файл программы, и также построчно ее «выполняет», а точнее говоря, интерпретирует. Программа Basic работает не с реальной оперативной памятью компьютера, а с не которой моделью этой памяти, в которой располагаются переменные и константы. Для обращения к периферийным устройствам, таким, как консоль ввода-вывода и дисковая память, программа использует специальные операторы Basic. При этом, если, например, в строке программы содержится команда вывода тексто вой строки на консоль, интерпретатор Basic вызывает свой собственный модуль выво да на консоль, передавая ему текстовую строку. Программа Basic сильно ограничена в своих возможностях по обращению к физи ческим ресурсам компьютера. Фактически она работает в некотором изолированном пространстве, выполняя только такие операции, которые определены в языке Basic. Такой подход исключает возникновение критических ошибок, способных нарушить работу ОС. С целью упростить процесс программирования Basic берет на себя всю заботу о преобразовании типов данных, так что программисту не приходится ломать над этим голову. В результате Basic как нельзя лучше подходит для первых упражнений в про граммировании. Однако ценой увеличения надежности и упрощения программирования будет су уменьшение скорости работы программы по сравнению с компилируемы ми программами. Причину этого легко понять, так как интерпретация одной строки
ла
Введение
9
исходного программы Basic вылиться в десятков и сотен машинных команд. Что же касается операторов языка С или С++, то здесь базовые операторы транслируются в одну или несколько машинных команд. Для того чтобы как-то ускорить работу программ Basic, современные системы про граммирования выполняют предварительное преобразование исходного текста в неко торый промежуточный код. Этот код не может непосредственно исполняться процес сором, однако на его интерпретацию уходит намного меньше времени. Современные системы программирования на языке Basic активно развиваются компанией Microsoft. Пройдя множество трансформаций, язык Basic сегодня превра тился в средство быстрого создания приложений RAD с названием Visual Basic. Но вейшая разработка в этой области — система Microsoft Visual Basic предназна ченная для ускоренной разработки приложений для платформы Microsoft Но разговор об этой платформе еще впереди.
Java Язык программирования с «кофейным» названием Java был разработан компанией Sun Microsystems как объектно-ориентированное средство создания приложений, способ ных работать без перетрансляции на различных компьютерных платформах. Прототи пом для разработки послужил язык программирования Oak. С этой целью исходный текст программы преобразовывался в некоторый промежу точный байт-код, который затем интерпретировался специальной программой — виртуальной машиной Java. Для того чтобы программы Java могли работать на раз личных компьютерных платформах, необходимо реализовать виртуальную машину Java для всех платформ. Фактически виртуальная машина Java создана для всех совре менных ОС, включая Microsoft Windows, MacOS, Linux и другие Unix-подобные ОС. С точки зрения защищенности ОС от «беспредела» программ, Java предоставляет неплохое решение, запуская эти программы в защищенной виртуальной среде, назы ваемой «песочницей» (sandbox). Программа Java не имеет непосредственного доступа к физическим ресурсам компьютера и ОС, используя только те средства, что предос тавлены ей в рамках виртуальной машины. Таким образом, Java является не компилируемым, а интерпретируемым языком. Вероятно, у вас уже возникли некоторые ассоциации с языком программирования Ba sic, однако между Java и Basic имеются важные различия. Прежде всего, Java разраба тывалась для применения на различных компьютерных платформах, a Basic — только на платформе Microsoft Windows. Далее, Java изначально создавался как объектноориентированный язык программирования, в то время как первые версии языка Basic были предназначены для процедурного и структурного программирования. Для Java были созданы многочисленные средства ускоренной разработки про грамм. Что касается платформы Microsoft Windows, то для нее одной из наиболее удобных систем RAD, на наш взгляд, служит интегрированная система программиро вания Borland Java Builder. Компания Microsoft создала свою машины Java, дополнив язык Java и библиотеки классов собственными расширениями, работоспособными только на платформе Microsoft Windows. Это послужило одной из причин многочис ленных судебных между компаниями Sun Microsystems и Microsoft. А
Фролов,
В. Фролов. Язык
Самоучитель
По мнению компании Sun Microsystems, нестандартные расширения Java нарушали основной принцип, ради которого создавался язык Java, — обеспечение возможности работы программ на различных платформах без изменения байт-кода. Одним из недостатков Java, сдерживающих его распространение, служит относи невысокое быстродействие. Этот недостаток возникает из-за того, что Java — интерпретируемый язык. Возникают проблемы и с точной реализацией виртуальной машины на различных компьютерных платформах. Из-за принципиальных различий в архитектуре и принципах работы операционных систем унификация виртуальных машин представляет собой довольно задачу. Кроме того, Microsoft Windows практически полностью заполнила рынок ОС для персональных компьютеров. Поэтому разработчикам настольных приложений нет смысла вкладывать значительные средства в совместимость с ОС других типов, осо бенно учитывая проигрыш в скорости работы приложений. Другое дело — серверы Интернета. Здесь Microsoft Windows пока еще не добилась полного господства, разделяя рынок с такими ОС, как Linux, FreeBSD и другими Unixподобными ОС. В настоящее время Java пользуется популярностью, например, как средство создания активных серверных Web-приложений. Работая на сервере Web, программы Java взаимодействуют с серверами баз данных и почтовыми серверами, формируя динамические документы HTML, отправляемые затем посетителям Webсервера. Языку программирования Java посвящено очень много книг. Тем, кто интересуется этим языком программирования, мы можем порекомендовать нашу работу «Программи рование на Java: подробное руководство», созданное по заказу московского представи тельства компании Sun Microsystems Созданная нами большая библиотека примеров приложений Java с подробными описа ниями исходных текстов находится на авторском компакт-диске, который можно при обрести в ЗАО «Диалог-МИФИ» (dialog@bitex.ra).
Языки для создания Интернет-приложений Как мы уже говорили, создание Web-приложений для Интернета предполагает одно временное использование многих технологий и языков программирования. Этот про цесс мы детально описали в своих книгах [2] и [3]. Здесь мы приведем только краткий список языков программирования, чаще всего применяемых для создания Web-приложений. Знакомство с этими языками будет по лезно (а в некоторых случаях — необходимо) при разработке Web-приложений с при менением С#.
HTML говоря, язык разметки гипертекста (Hyper Text Markup Language, HTML) не является языком программирования в обычном понимании этого термина. С ис пользованием этого языка создаются документы HTML, располагаемые в каталогах Wcb-сервсра и предназначенные для просмотра при помощи программы, называемой Web-браузером или просто браузером. Введение
11
Язык HTML выступает в роли связывающего звена, объединяющего текст, иллю страции и активные компоненты, располагающиеся на страницах Web-сервера. Хотя на первый взгляд язык HTML очень прост, его применение требует высокой квалификации. Проблема в том, что этим языком средства создания страниц очень бедны и для того, чтобы страница выглядела красиво, нужно затратить немало усилий. Кроме того, существует проблема совместимости с браузерами раз личных типов, из-за которой одни и те же документы HTML могут выглядеть у посетителей Web-узла по-разному. Подробнее о языке HTML и его использовании вы сможете прочитать в [2]. Для достижения наилучшего результата разработчики Web-приложений долж ны в совершенстве владеть языком H T M L . И хотя существуют многочисленные системы визуального проектирования документов H T M L (такие, например, как Microsoft FrontPage), в сложных случаях вам не обойтись без прямого редактирова ния кода HTML. В рамках платформы Microsoft предлагается технология ускоренного визу ального проектирования Web-приложений. При этом элементы пользовательского ин терфейса Web-приложения создаются с помощью графического редактора Web-форм). Активные компоненты для обработки событий от элементов управления форм могут быть написаны с использованием С# или других языков платформы Mi crosoft (например, Visual Basic Теоретически этот подход позволяет обойтись без знания HTML, однако на прак тике для создания действительно эффективных Web-приложений владение этим язы ком просто необходимо. Дело в том, что при использовании любой технологии созда ния Web-приложений в качестве интерфейса пользователя применяется браузер, «по нимающий» только HTML. Владея HTML, вы сумеете оптимизировать создаваемые страницы по времени загрузки, разобраться в проблемах при возникновении ошибок и реализовать различные нестандартные решения. И вообще, как мы уже говорили, создание Web-приложений представляет собой комплексную проблему. Тут не обойтись знанием какого-то одного языка программи рования, даже самого лучшего и современного.
JavaScript Интерпретируемый объектно-ориентированный язык сценариев JavaScript не имеет никакого отношения к языку Java, несмотря на свое название. Фактически это совер шенно другой язык, разработанный Netscape Communication Corporation. Первоначальное название этого языка LiveScript было изменено по коммерческим со ображениям, так как в тс времена технология Java считалась очень перспективной. Его основная область применения — создание активных Web-приложений. Часто конструкции JavaScript (называемые сценариями JavaScript) встраивают в исходный текст страниц HTML, после чего они «оживают». Становясь активными, документы HTML со сценариями становятся способными реагировать на действия пользователя, выполняемые в окне браузера, например проверять данные, введенные в форму. Сценарии, предназначенные для работы в составе документов HTML под управлением браузера, называются
12
А В Фролов. Г. В.
Язык С#. Самоучитель
Другое применение сценариям JavaScript — создание HTML на Wcb-сервере. В этом случае сценарии JavaScript называются серверными сценариями. Серверные сценарии JavaScript используются в рамках технологии ак тивных серверных страниц (Active Server Pages, ASP), разработанной компанией Mi crosoft специально для создания активных Web-приложений. Язык JavaScript можно использовать и для создания автономно работающих про грамм. В этом случае программы интерпретируются специальной системой Microsoft Windows Scripting Host (WSH). Автономные сценарии JavaScript удобно использовать для выполнения каких-либо пакетных работ, например для анализа содержимого баз данных, резервного копирования и т. п. Язык JavaScript применяется так широко, что фактически каждый разработчик ак тивных Web-приложений должен им владеть. Описание этого языка и многочислен ные примеры его применения (в том числе в рамках технологии ASP) вы найдете в [2].
JScript Язык JScript представляет собой аналог языка JavaScript, разработанный компаний Mi crosoft. Он содержит расширения, которые могут оказаться несовместимыми с браузе рами, отличными от Microsoft Internet Explorer. Поэтому его применение обычно огра ничивается созданием серверных сценариев для приложений ASP.
VB Script Язык VB Script является функциональным аналогом только что описанного языка сце нариев JavaScript, однако он был создан компанией Microsoft на базе языка Basic. Как средство создания клиентских сценариев для документов HTML этот язык имеет ограниченное применение. Причина этого в том, что сценарии VB Script рабо тают только с браузером Microsoft Internet Explorer. Другие браузеры, например широ ко распространенный Netscape Navigator, его игнорируют. Поэтому основная область применения языка VB создание серверных сценариев для приложений ASP и автономных программ, работающих под управлени ем Microsoft WSH. С точки зрения возможностей VB Script не имеет никаких преимуществ перед JavaScript. Поэтому, если вы уже владеете JavaScript, тратить время на изучение VB Script ни к чему. С другой стороны, если у вас есть большой опыт написания про грамм на языке Microsoft Visual Basic, то вы сможете создавать серверные сценарии ASP без изучения JavaScript.
Perl Это язык интерпретируемого типа, кому-то напоминающий Basic, а кому-то — язык С. Вероятно, истина находится где-то посередине. Основное преимущество языка Perl как средства разработки Web-приложений в том, что для него созданы обширные биб лиотеки модулей. Эти библиотеки решают практически все задачи, встающие перед разработчиками программ CGI — ядра активных Web-серверов. Такие операции, как обработка текстовых строк и форм ввода данных, отправка и получение электронной почты, взаимодействие с файлами и базами данных различных Введение
13
типов, решаются с легко и элегантно. Программы занимают не большой объем и относительно быстро интерпретируются сервером Web, обеспечивая приемлемую производительность при средней посещаемости. Языку Perl посвящено множество книг. Что же касается примеров использования Perl для создания реальных то вы найдете их в написанной нами книге [3].
Технология серверных сценариев РНР сильно напоминает упомянутую выше техноло гию Microsoft Аббревиатура расшифровывается рекурсивно как Hyper text Preprocessor, что означает «препроцессор гипертекста РНР». Интерпретируемые серверные сценарии РНР способны динамически создавать до кументы HTML, обращаясь при этом к базам данных, почтовым серверам и другим программным системам и комплексам. В настоящее время интерпретаторы РНР доступны для различных компьютерных платформ, в том числе для Microsoft Windows, Linux и других Unix-подобных ОС. Что же касается ASP, то эта технология работает только в среде Microsoft Windows. При чина такого ограничения заключается в том, что ASP базируется на модели компо нентного объекта (Component Object Model, и технологии элементов управле ния ActiveX, доступных в полной мерс только в среде Microsoft Windows. Подробно об этом мы рассказали в [2].
Новые технологии Microsoft Как видите, к настоящему моменту разработано огромное множество языков и техно логий программирования, несовместимых между собой или совместимых лишь час тично. В то время как разработку автономных приложений можно выполнять на одном каком-то языке программирования, создание приложений для Интернета требует зна ний множества языков и технологий. Новые технологии Microsoft ориентированные на разработку автономных и распределенных приложений Интернета, призваны облегчить создание сложных со временных приложений, их документирование и внедрение. В рамках Microsoft разработчикам программ предоставляется новый интерфейс программирования (Ap plication Program Interface, API), пригодный для создания обычных настольных про грамм Microsoft Windows, системных сервисов Microsoft Windows, а также Webприложений и Web-сервисов. В рамках Microsoft доступны следующие языки программирования: • Microsoft С#. •
Microsoft Visual Basic
•
Managed С++.
•
Microsoft Visual J#
•
JScript.NET. Кроме того, в рамках Microsoft предоставляется чрезвычайно удобная интег рированная среда разработки приложений Microsoft Visual Studio а также среда выполнения программ Microsoft Framework.
14
А В Фролов, Г. В. Фролов. Язык С#. Самоучитель
В составе Microsoft имеется набор сетевых служб и серверов серии En terprise Server, предназначенных для решения задач аутентификации, для создания систем хранения данных, обработки электронной почты и создания бизнес-систем, а также средства для программирования и встраиваемых вычислительных систем, на пример для мобильных телефонов, игровых приставок и т. п. Планируется выпуск ОС Microsoft Windows в полной мере реализующей пре имущества технологии Microsoft Таким образом, овладев языком С# и другими технологиями Microsoft вы смо жете создавать программы и программные системы самого разного уровня, от простейших утилит и сервисов до сложных распределенных корпоративных информационно-справоч ных систем с базами данных, взаимодействующими через Интернет.
Платформа Microsoft
Framework
Платформа Microsoft Framework, предназначенная для работы приложений Mi crosoft дает большие преимущества разработчикам программ. В частности, она способна преодолеть барьеры языковой несовместимости, допуская создание отдель ных компонентов создаваемой системы на различных языках программирования. Среди других преимуществ Microsoft Framework заслуживает упоминания наличие обширной библиотеки классов, существенно облегчающей решение задач, наиболее часто возникающих при создании автономных программ и Web-приложений. Эта библиотека, насчитывающая десятки тысяч (!) классов, готовых к употреблению, позволит вам использовать в своих разработках готовые и отлаженные модули. Платформа Microsoft Framework обеспечивает возможность использова ния модулей, разработанных вами ранее, а также возможность обращения к новым компонентам из разработанного ранее программного кода. В результате после от носительно небольших переделок старые программы смогут приобрести новую функциональность. Приложения Microsoft работают в среде Microsoft Framework в рамках системы исполнения программ Common Language Runtime (CLR). Примененная в Mi crosoft Framework концепция управляемого кода обеспечит надежное и безопас ное выполнение программ, а также значительно уменьшит вероятность допущения ошибок в процессе программирования. Этому же способствует система обработки ис ключений и система автоматического освобождения неиспользуемой оперативной па мяти, называемой системой сборки мусора (garbage collection). Встроенные в язык С# и рассчитанные на среду Microsoft Framework средства документирования, такие, как атрибуты и операторы комментариев спе циального вида, позволят существенно упростить создание конструкторской доку ментации на программный код. Это особенно ценно при разработке больших п р о ектов, когда из-за сложности и объемности задачи сопровождение разработки пре вращается в непосильную задачу и становится настоящим кошмаром для менед жера проекта. В сочетании с мощным средством ускоренной разработки приложений Microsoft Visual Studio набор языков платформы Microsoft послужит отличным под спорьем при создании программ самого разного типа, как автономных, так и рассчи танных на использование в Интернете. Введение
15
Для ОС Microsoft Windows 9x/NT/2000/XP программу установки Microsoft Framework можно бесплатно загрузить с сервера Microsoft (размер дистрибутивного файла около 20 Мбайт). Новые версии Microsoft Windows должны содержать в своем составе готовую для использования среду Framework.
Совмещение разных языков программирования Разработчик приложений для Framework более не стоит перед мучи тельным выбором языка программирования — на этой платформе доступны трансля торы многих языков программирования. Это, например, Microsoft С#, Microsoft Visual Basic Managed С++, JScript.NET, Visual Perl и др. Подробности относительно языка Perl вы найдете на сайте компании ActiveState по адресу http://www.activestate.com. Мы также знаем о существовании планов компа нии Borland (http://www.borland.com) насчет поддержки платформы Microsoft Framework в новой версии своей системы разработки приложений Borland Delphi 7. Хотя теперь проблема выбора языка программирования стоит не так остро, из-за ограничений, присущих некоторым языкам программирования, наиболее полно воз можности платформы Microsoft Framework можно реализовать только с приме нением С#. Что же касается языка Microsoft Visual Basic то для достижения максимальной совместимости с Microsoft Framework он был значительно перера ботан. Не исключено, что вам будет легче освоить С#, нежели изучать все нововведе ния языка Microsoft Visual Basic Для достижения совместимости между различными языками программирования компиляторы языков платформы Microsoft Framework переводят исходный текст программы в язык, называемый Microsoft Intermediate Language (MSIL). Таким образом, на каком бы вы языке платформы Microsoft Framework не писали свою на С#, Visual Basic или каком-либо другом, эта программа всегда будет транслироваться в MSIL. Здесь у читателя может сразу возникнуть аналогия с байт-кодом Java. Мы должны заметить, что возможности С#, в частности и платформы Microsoft Framework, в целом простираются намного дальше простой интерпретации байт-кода виртуальной машиной Java. Чтобы у всех разработчиков языков программирования была возможность созда вать свои компиляторы совместимыми со средой выполнения Microsoft work, была создана спецификация Common Language Specification (CLS). Придержива ясь этой спецификации, разработчики языков программирования могут быть уверены в том, что создаваемые с применением этих языков программы будут удовлетворять минимальным требованиям платформы Microsoft Framework. В частности, эти программы смогут взаимодействовать с программами, разработанными с использова нием других языков платформы Microsoft
Интегрирование с ранее созданными проектами Разумеется, платформа Microsoft Framework и язык С# вряд ли были бы встрече ны программистами если бы в них не была предусмотрена возмож ность использования ранее разработанных программных модулей: библиотек динами-
16
А В Фролов, Г. В. Фролов Язык
Самоучитель
компоновки (Dynamic Load Libraries, DLL), объектов COM и ActiveX. К сча стью, средства в С# имеются, и пользоваться ими достаточно просто. Кроме того, предусмотрена возможность обращения к объектам С# из проектов, разработанных ранее, например на языке С++. Для этого необходимо использовать расширение Managed Extensions, создав классы-оболочки специального вида.
Библиотека классов Microsoft
Framework
Традиционно вместе с языками программирования, такими, как С++ и Pascal, постав ляются библиотеки функций, предназначенные для решения рутинных задач, вроде обработки текстовых строк, файловый ввод-вывод, сортировка, математические вы числения и Интегрированные системы создания программного обеспечения дополняются биб лиотеками классов типа Microsoft MFC и шаблонов. Например, в состав языка С++ включена стандартная библиотека шаблонов (Stan dard Template Library, STL), упрощающая работу с текстовыми строками и контейне рами данных, такими как массивы переменного размера, словари, стеки и т. д. Понимая, что сложные программные системы невозможно создавать на пустом месте, компания Microsoft включила в состав платформы богатейшую библиотеку классов. Эта библиотека насчитывает, как мы уже говорили, десятки тысяч классов. Она со держит классы для работы со строками и датами, с массивами и коллекциями различ ных типов, с потоками и файлами, с графическими изображениями самых разных фор матов, с сетевыми протоколами и протоколами Интернета, сделанными в форма те XML, классы для создания приложений Windows с графическим интерфейсом и т. д. — всего не перечислить. Наличие подобной библиотеки позволяет разработчику сосредоточить усилия на решении своей прикладной задачи, а не тратить их, например, на организацию ас социативных массивов, алгоритмов сортировки, передачу данных с помощью прото кола TCP/IP или на решение других аналогичных задач. Важно, что средства библиотеки классов Microsoft Framework доступны в программах, написанных на любых языках программирования, ориентированных на платформу Microsoft Framework. Теперь действительно не имеет особого зна чения, на каком языке вы будете писать программу, — ваши возможности в любом случае будут примерно одинаковыми. Заметим, что традиционные системы программирования, основанные на таких язы ках, как С++ и Pascal, обеспечивают очень плохую совместимость. Фактически каждая из таких систем представляет собой свой изолированный мир с собственными библио теками функций, классов и шаблонов, использовать которые вместе в рамках одного проекта либо чрезвычайно сложно, либо вообще невозможно. Платформа Micro soft Framework позволяет покончить с этой проблемой раз и навсегда. Хотя библиотека классов Microsoft Framework очень все же может слу читься так, что ее возможностей окажется недостаточно для решения какой-либо специ фической задачи. Ничего страшного — в рамках платформы Microsoft Framework предусмотрены средства для обращения к низкоуровневым функциям ОС, к функциям библиотек динамической компоновки DLL, методам и интерфейсам объектов и ActiveX и т. п. Это делает платформу Microsoft Framework пригодной для решения задач, связанных, например, с системным программированием, автоматизацией производ ственных процессов или исследовательских физических установок. Введение
17
Виртуальная машина CLR Как мы уже говорили, исходный текст программы, написанной на языке программи рования С# или другом языке платформы Microsoft Framework, перед исполне нием транслируется в промежуточный язык MSIL. С целью обеспечения безопасности исполнения код MSIL интерпретируется специ альной виртуальной машиной в рамках системы исполнения программ Common Lan guage Runtime (CLR). Заметим, что теоретически интерпретатор MSIL может быть создан не только для Microsoft Windows, но и для других ОС, например для Linux. Об одном таком проекте с названием DotGNU мы расскажем чуть позже. Что такое виртуальная машина и как она обеспечивает безопасность выполнения программы MSIL? Концепция виртуальной машины возникла очень давно, еще на заре компьютерной техники. Эта концепция была реализована в ОС IBM VM, созданной для «больших» вычислительных машин (мейнфреймов) серии IBM 360/370. ОС IBM VM разделяла физические ресурсы одного дорогостоящего компьютера между несколькими ОС. Каждая ОС получала в пользование виртуальные ресурсы компьютера — виртуальный диск, виртуальный процессор, виртуальную оперативную память и виртуальные устройства ввода-вывода. Получалось, что каждая ОС работала на своем виртуальном компьютере (виртуальной машине). Некоторые ресурсы (например, дисковое пространство и участки оперативной па мяти) выделялись виртуальным машинам в монопольное пользование, некоторые ис пользовались по очереди. Например, ресурсы центрального процессора выделялись на какое-то время сначала одной виртуальной машине, затем другой и т. д. При этом создавалось впечатление, что все ОС работают одновременно. Современные системы виртуальных машин, такие, как, например, VMWare, позво ляют запускать на одном компьютере сразу несколько ОС — Microsoft Windows раз личных версий, MS-DOS, Linux и др. Лишь бы хватило мощности процессора, объема дисковой и оперативной памяти. Для нас сейчас принципиально важным является то, что программы, работающие в рамках одной виртуальной машины, не имеют никакого доступа к ресурсам другой виртуальной машины. Если такая программа выполнит недопустимую операцию и «повесит» ОС своей виртуальной машины, это никак не отразится на работоспособ ности других виртуальных машин. Так вот, виртуальная машина CLR, обеспечивающая работу программ платформы Microsoft Framework, закрывает доступ этим программам к ресурсам других про цессов, работающих на том же компьютере. В результате программа никоим образом не сможет нарушить работоспособность остальных программ или ОС. Код MSIL, получающийся в результате трансляции программы, составленной на языке С# или другом языке платформы Microsoft Framework, выполняется под полным контролем виртуальной машины CLR. Такой код, в отличие от обычного ис полняемого кода, получающегося после трансляции программ С и Pascal, называется управляемым кодом (managed code). Правила управляемого кода обеспечивают кор ректную работу программ, написанных на любом языке платформы Microsoft Framework.
18
В. Фролов, Г. В. Фролов. Язык С#. Самоучитель
Домены приложений Для безопасности и надежности работы приложений Microsoft Framework в рамках виртуальной машины CLR реализованы так называемые домены приложений (application domains). Каждая программа исполняется в рамках своего домена и не имеет непосредствен ного доступа к ресурсам остальных доменов. В то же время с точки зрения ОС несколько доменов могут работать в рамках одно го процесса, что дает повышение общей производительности работы виртуальной ма шины CLR. Для обеспечения одновременной работы нескольких приложений Micro soft Framework нет необходимости выполнять переключение процессов, отни мающее немало вычислительных ресурсов системы.
Компилятор JIT Каким же образом происходит интерпретация кода передаваемого для исполне ния только что упомянутой виртуальной машине CLR? Центральный процессор компьютера может выполнять только машинные команды, поэтому необходимо обеспечить преобразование кода в коды машинных ко манд. Для того чтобы обеспечить высокую скорость такого преобразования, виртуаль ная машина CLR использует специальный компилятор, называемый компилятором just-in-time (JIT). Преобразование может выполняться однократно во время установки приложения на диск компьютера либо каждый раз при запуске приложения. Первый способ, оче видно, обеспечивает более высокую скорость выполнения приложения по сравнению со вторым, но более требователен к дисковой памяти. Впрочем, сегодня, когда в обыч ный настольный компьютер можно установить один — два недорогих диска объемом Гбайт, этот фактор не играет существенной роли. Заметим, что похожая система исполнения байт-кода не является изобретением компа нии Microsoft и уже использовалась раньше, например виртуальной машиной Java.
Сборки Еще одним преимуществом технологии, предоставляемой в рамках платформы Micro soft Framework, перед традиционными технологиями программного обеспечения является наличие так называемых сборок. Сборка представляет собой один или не сколько файлов, содержащих все необходимое не только для работы приложения, но и для ее самодокументирования. Чтобы оценить преимущества данного подхода, достаточно вспомнить, как проис ходит процесс развертывания обычных программ. Как правило, программы поставляются в виде загрузочного файла типа *.ехе, к ко торому может прилагаться набор файлов библиотек динамической компоновки DLL, а также набор элементов управления и ActiveX. Помимо этого для работы загрузочного модуля программы могут потребоваться файлы библиотек DLL среды исполнения компилятора, а также библиотек DLL, реа лизующих функциональность библиотек классов, таких, как Введение
Чтобы развернуть подобную программу на компьютере, необходимо скопировать на диск этого компьютера все файлы, полученные в результате трансляции и редакти рования файлов исходных текстов программы. Кроме того, необходимо скопировать в системный каталог ОС Microsoft Windows файлы библиотек DLL среды исполнения компилятора и файлы библиотек DLL дополнительных классов, использованных в проекте. Если в состав программного комплекса входят объекты и ActiveX, не обходимо их установить, зарегистрировав в системном реестре Microsoft Windows. Копируя файлы стандартных библиотек DLL компилятора и библиотек классов (например, библиотеки MFC), необходимо учитывать номера версий. При копирова нии библиотеки новых версий должны заменять библиотеки старых версий, но ни в коем случае не наоборот. В результате развертывание сложного программного комплекса может превратить ся в весьма нетривиальную процедуру, для реализации которой потребуется создание специальной инсталляционной программы. Существуют даже специальные системы автоматизированного создания таких инсталляционных программ, например InstallShield. Освоение подобных систем может отнять немало времени. Что же касается приложений, создаваемых для платформы Microsoft work, то благодаря использованию сборок их развертывание сводится к простому ко пированию файлов сборки на диск целевого компьютера. Входящий в сборку перечень содержимого — файл манифеста (manifest) содержит всю информацию, необходимую для правильной загрузки и работы приложения. Если приложение сложное и состоит из нескольких сборок, то в манифесте перечисляются все необходимые дополнительные сборки.
Упрощение отладки программ С# Известно, что на отладку сложной программы можно потратить намного больше вре мени и сил, чем на ее разработку и написание. Объем отладочного кода, создаваемого специально для тестирования модулей программы, может многократно превышать объем кода самих отлаживаемых модулей. Исполнимый код, получающийся в результате трансляции исходного текста про граммы, написанной на таких языках программирования, как С++ и Pascal, имеет практически полный доступ к ресурсам своего процесса. Такие ошибки, как чтение неинициализированных переменных, неправильное преобразование типов указателей или их неправильная инициализация, забывчивость при освобождении динамически блоков памяти, ошибки при выполнении числовых операций, могут при вести к аварийному завершению программы или к другим плачевным результатам. Многие ошибки обычно остаются незамеченными на этапе компиляции и сказыва ются только при работе программы, причем, как это обычно бывает, в самый неподхо дящий момент. Компилятор языка С# исключает возникновение перечисленных выше и многих других ошибок еще на компиляции, что существенно облегчает и ускоряет про цесс отладки сложных программ. Необходимые для этого средства встроены непо средственно в язык С#.
20
А
Фролов, Г. В. Фролов. Язык
Самоучитель
других средств, встроенных в язык С# и упрощающих отладку программ, упомянуть объектно-ориентированную обработку исключений и систему сборки мусора. Применение исключений для обработки ошибок позволяет сократить объем исход ного текста и дополнительно избавляет от необходимости при каждом вызове функ ции проверять ее код возврата. Проверка ошибок может производиться для целого фрагмента программы, содержащего множество обращений к различным методам и интерфейсам, что приводит к лучшей читаемости исходного текста. Это также спа сет, вы случайно забудете проверить код возврата какого-либо метода. Заметим, что объектно-ориентированная обработка исключений не является досто инством одного только языка С#. Сходные возможности имеются, например, в языках программирования С++ и Java. Что касается системы сборки мусора, то она позволяет автоматизировать осво бождение ненужных более блоков оперативной памяти. Как только заказанный про граммой блок памяти становится ненужным, он отмечается как подлежащий уничто жению. Специальный фоновый процесс сборки мусора удалит такой блок при первой же возможности. Существует также возможность освобождения ресурсов явным образом на том или ином этапе работы программы. Такая возможность может пригодиться при освобож дении таких критичных ресурсов, как, например, открытых файлов или соединений с базой данных. Аналогичная система сборки мусора с успехом используется и в других языках программирования, например в языке Java.
Программирование на С# для Microsoft Windows Когда ОС Microsoft Windows только появилась на свет, мы, как и множество других про граммистов, создавали для нее приложения на языках С и С++. При этом нам приходилось напрямую обращаться к программному интерфейсу (API) этой ОС, обрабатывая сообще ния при помощи оператора Пока приложения состояли из нескольких окон и диалоговых панелей, такая техно логия программирования не вызывала особых проблем. Когда же перед нами встала задача создания действительно сложных приложений Windows, имеющих очень боль шое количество окон и диалоговых панелей, а также нетривиальный пользовательский интерфейс, пришлось примейить библиотеку классов Несмотря на то что MFC сильно упрощает создание приложений Microsoft Win dows, даже в комплекте с этой библиотекой инструментарий Microsoft Visual С++ едва ли можно назвать настоящим визуальным средством проектирования приложений. Во-первых, все еще приходится писать слишком много программного кода, имеющего непосредственного отношения к прикладной задаче, для решения кото рой создается приложение. Во-вторых, визуальные средства проектирования пользовательского интерфей са не позволяют выйти дизайну этого интерфейса за рамки стандартных «серых» Введение
21
диалоговых панелей и надоевших уже элементов — кнопок, меню, спи сков п. Сегодня, когда пользователь компьютера привык видеть на экране красоч но оформленные страницы Web-сайтов, едва ли стоит предлагать ему программы с блеклым интерфейсом, разработанным еще на заре создания Microsoft Windows. Намного более удобные средства проектирования диалоговых приложений предо ставляет такое средство, как Microsoft Visual Basic. Однако разные версии этой среды разработки приложений предлагали разные и порой несовместимые между собой ме ханизмы компонентного программирования, что затрудняло миграцию разработанных ранее проектов в новую среду разработки. Кроме того, скорость работы интерпрети руемых приложений, созданных с применением Microsoft Visual Basic, была не слиш ком высока. В языке Microsoft Visual Basic не было полной реализации объектноориентированного и, тем более, компонентно-ориентированного программирования. Инструментальная среда Borland Delphi совмещала в себе преимущества визуаль ных средств проектирования и высокой скорости программ, загрузочный код которых получался в результате компиляции исходных текстов, написанных на языке Pascal. Через некоторое время появилось аналогичное решение и для языка С++ с названием Borland С++ Builder. В этих инструментальных средствах весьма скромные по своим возможностям, об ладающие незамысловатым дизайном стандартные диалоговые панели Microsoft Win dows были заменены формами. Пользуясь визуальными средствами проектирования, программист может разместить на такой форме различные элементы управления, про сто перетащив их из инструментальной панели. Так же легко он может задать цвет фо на формы или графическое изображение подложки. Если форма должна содержать статические или динамические строки текста, инструментарий разработчика позволяет легко выбрать шрифт и цвет этого текста. Но самое главное, появилась возможность разрабатывать собственные компонен ты, реализующие элементы пользовательского интерфейса, и добавлять их в палитру инструментальной панели. С применением этих компонентов становится возможным визуальное проектирование достаточно сложных интерактивных программ. Визуальные средства проектирования приложений Microsoft Visual Studio унаследовали самое лучшее от предыдущих систем визуального проектирования, до бавив преимущества платформы Microsoft Framework. Вероятно, своим успехом система Microsoft Visual Studio и язык программирования С# не в последнюю очередь обязаны тому, что в их разработке принимали участие ведущие сотрудники компании Microsoft Anders Hejlsberg и Scott Wiltamuth. Первый из них известен как создатель популярной системы программирования Borland Turbo Pascal, а также упо минавшейся выше системы визуальной разработки приложений Borland Delphi. Важно отметить, что приемы создания автономных приложений, реализованные в Microsoft Visual Studio можно с успехом применить и для создания Webприложений Интернета. Более того, существует возможность переноса автономных при ложений Microsoft Framework с оконным пользовательским интерфейсом Web-сервера. Таким образом, изучая язык С# и систему Microsoft Visual Studio для создания автономных приложений Microsoft Windows, Вы, возможно, сделаете свой первый шаг на пути к освоению наиболее современных технологий Интернета.
22
А В. Фролов, Г. В, Фролов, Язык С#. Самоучитель
Проект DotGNU, или С# для Linux Ну хорошо, скажете вы. Пусть технологии Microsoft действительно облегчают создание автономных приложений Microsoft Windows, а также приложений Интернета, на использование серверных версий ОС Microsoft Windows. Но дают ли что-нибудь идеи, положенные в основу Microsoft создателям программ для других ОС, в частности широко распространенной ОС Linux? Оказывается, да. Прежде всего, следует упомянуть новейшие разработки ком пании Borland, с которых можно создавать современные W e b - п р и л о жения не только для Microsoft Windows, но и для Linux, а также для других Unixподобных Далее, по адресу находится Web-сайт проекта DotGNU Portable.NET. В рамках этого проекта разрабатывается свободно доступ ный инструментарий, с помощью которого можно создавать и выполнять приложения, созданные по технологии Microsoft в среде ОС В настоящий момент на сайте проекта DotGNU Portable.NET вы можете бесплатно загрузить транслятор языка С#, библиотеку классов среду выполнения про грамм, дизассемблер промежуточного языка и другие утилиты. Сам по себе проект DotGNU Portable является составной частью общего проекта DotGNU. Сайт этого проекта вы найдете по адресу http://www.dotgnu.org. Участники про екта DotGNU поставили перед собой цель создать полную замену проекту Microsoft обеспечив при этом свободный и бесплатный доступ всем желающим к любым исходным текстам, компонентам и технологиям проекта. Заметим, что Microsoft не поставляет ис ходные тексты среды исполнения и библиотеки классов Microsoft Framework. В новой среде Portable планируется обеспечить возможность выполнения байт-кода программ Java, реализовать безопасную среду исполнения приложений, как автономных, так и распределенных. Пока проект DotGNU находится в начальной стадии своего развития, поэтому рано еще делать заключения о результатах его реализации. Тем не менее сам факт его воз никновения подчеркивает особое значение языка программирования С# и платформы Microsoft для которой он был разработан.
Отличия С# от С++ Если вы ранее программировали на языке С++, вам будет интересно, чем отличается С# от этого языка. Мы будем делать замечания по этому поводу на протяжении всей книги по мере изложения материала, но здесь вы найдете краткий перечень наиболее важных отличий.
Классы и наследование Классы С#, подобно классам Java, могут наследоваться только от одного базового класса. Таким образом, в С# множественного наследования, допускаемого в С++. В языке С# объекты любого типа (даже и являются объектами соответ ствующих классов.
23
Изменены правила определения методов производного класса, замещающих (пере гружающих) методы базового класса. Теперь для них необходимо явным образом ука зывать ключевые слова, такие, как n e w или Изменен способ вызова из производного класса перегруженного метода базового класса. Если в классе не определен конструктор, то создается конструктор по умолчанию. Он инициализирует все поля класса значениями по умолчанию, которые зависят от типов инициализируемых полей. В программах, написанных на С++, конструктор по умолчанию никогда не создается. Добавлен альтернативный способ инициализации списком в конструкторе базового класса. Отсутствуют деструкторы классов. Для освобождения ненужных блоков памяти применяется система сборки мусора (garbage collection). Параметры методов класса не могут иметь значения по умолчанию. Для достиже ния аналогичной функциональности в С# нужно применять перегрузку методов. В программах, написанных на С#, невозможно определить глобальные переменные или методы. Определения переменных и методов должны находиться внутри классов Что же касается локальных переменных, определенных в теле методов, то перед использованием они должны быть проинициализированы. Ошибочное к неинициализированным переменным обнаруживается еще на этапе компиляции про граммы, что упрощает процесс отладки.
Интерфейсы Схожая с множественным наследованием функциональность достигается в С#, так же как и в Java, с помощью реализации множественных интерфейсов.
Типы данных Все данные делятся на ссылочные и размерные. Ссылочные данные хранятся в общем пуле памяти, а размерные — в стеке метода. Для ускорения работы с размерными ти пами при представлении их в виде классов применяется механизм упаковки, а для об ратного представления — распаковки. Операции упаковки и распаковки полностью прозрачны для программиста. типа могут принимать только два значения: t r u e и f a l s e . При этом не допускается преобразование этого типа данных в другие, такие, как i n t . Разрядность, т. с. количество битов, отведенных для хранения данных определен ного типа, зависит только от этого типа, но не от разрядности ОС. Таким образом, данные типа i n t всегда будут занимать в памяти 32 разряда независимо от того, уста новлен в компьютере 32-разрядный процессор или 64-разрядный. В языке С++ для хранения данных типа l o n g отводится 32 бита (т. е. 4 байта), а в языке С# — 64 бита. Имеются важные отличия в представлении текстовых строк. Язык С++ допускает хранение строк в виде массивов символов ANSI или UNICODE, причем последним элементом этого массива должен быть двоичный нуль. Соответственно в библиотеке компилятора имеется набор функций для работы с этими строками.
24
А. В. Фролов, Г. В. Фролов. Язык С#. Самоучитель
Что же касается С#, то в нем строки являются объектами класса впрочем, и всех других типов). В качестве внутреннего представления строк и отдель ных символов строки применяется только кодировка UNICODE. В языке С# не используются битовые поля.
Указатели Использование указателей ограничено специально отмеченными областями небезо пасного кода (unsafe code). Таким образом, в обычных программах исключаются ошибки, связанные с неправильным применением указателей. Эти ошибки часто до пускаются в программах, составленных на языках С и С++. Указатели на функции, применяемые в С++, потенциально опасны тем, что прог раммист по ошибке может их неправильно проинициализировать. В языке вместо них применяется аналогичный по функциональности, но менее чувствительный к ошибкам программистов и объектно-ориентированный механизм специальных делега тов (delegates). Делегаты чаще всего применяются для обработки событий (events).
Массивы В языке С# для обозначения массивов применен другой синтаксис, чем в языке С++. При объявлении массива квадратные скобки должны располагаться сразу после обо значения типа данных, а не после имени массива, как это было в С++.
Структуры В языке С# структуры являются размерными типами данных, а классы — ссылочны ми. В языке С++ такого разделения между структурами и классами нет.
Операторы и ключевые слова Применяя оператор s w i t c h в языке программирования С++, вы могли опускать в блоках c a s e оператор b r e a k . Если в блоке c a s e отсутствовал оператор b r e a k , то после обработки этого блока программа автоматически переходила к обработке сле дующего блока, c a s e или Потенциально такое поведение программы могло приводить к ошибкам, если про граммист по забывчивости не завершал блок c a s e оператором b r e a k . Чтобы изба виться от ошибок подобного рода, в языке С# описанная выше ситуация исключается на стадии компилирования. Таким образом, автоматический переход к обработке следующего блока c a s e в языке С# запрещен. Если же вам все же необходимо организовать подобный пере ход, то его можно сделать явным образом при помощи оператора g o t o . В операторе обработки исключений t r y - c a t c h добавлен блок выпол няющийся вне зависимости от того, произошло исключение или нет. По сравнению с языком С++ в язык С# добавлены новые операторы и ключевые слова i s , a s , o u t , f o r e a c h и др. По-другому, чем в С++, трактуются s t a t i c и extern. Для использования понятия пространства имен в язык С# добавлено ключевое сло во n a m e s p a c e . Введение
25
Директивы препроцессора В языке используются заголовочные знакомые программистам С++. Соответственно не и директива ttinclude, предназначенная для включения содержимого таких файлов в исходный текст программы. Благодаря отказу от использования заголовочных файлов описание и определение класса, а также его полей и методов размещается в одном месте — в файле исходного текста программы. Безусловно, это упрощает работу с файлами проектов, так как те перь нет необходимости при редактировании исходного текста класса переключаться от файла исходного текста к заголовочному файлу и обратно. Для ссылок на типы в заданных пространствах имен без указания полного имени применяется команда Хотя в языке С# разрешено использование директив препроцессора ttdefine, и аналогичных, с их помощью можно только определять макрокоманды, но не задавать для них значения. Таким образом, область применения директив пре процессора сужается до условной компиляции. Причина запрета использования директив препроцессора для создания макро команд, заменяющих константы или фрагменты кода, положительно сказывается на читабельности исходных текстов программ и снижает вероятность допущения ошибок.
Чем транслировать программы С# Чтобы транслировать исходные тексты программ, приведенные в книге, можно ис пользовать либо визуальную среду проектирования программ Microsoft Visual Studio либо пакетный транслятор, входящий в комплект Microsoft Framework SDK. В то время как Microsoft Visual Studio нужно покупать, инструментарий Microsoft Framework SDK доступен для бесплатной загрузки из Интернета. Приобретая Microsoft Visual Studio учтите, что это средство разработки по ставляется в разных редакциях: Professional, Enterprise Developer, Enterprise Architect, Academic и Visual C# Standard. Для работы с нашей книгой подойдет любая вер сия, даже самая недорогая. В частности, годится академическая версия Microsoft Vis ual Studio Academic, в которой есть дополнительные примеры программ, а также другие утилиты и средства, помогающие студентам в процессе изучения возможно стей платформы Microsoft.NET. Недорогая редакция Visual С# Standard вполне пригодна для работы с про граммами, приведенными в нашей книге, однако в ней имеются серьезные ограниче ния, например с ее помощью нельзя создавать библиотеки компонентов. Такие биб лиотеки заметно упрощают реализацию сложных программных проектов. Версии Microsoft Visual Studio в редакциях Enterprise Developer и Enterprise Architect предназначены для разработки корпоративных приложений с базами данных и различными Интернет-службами. Что касается бесплатного набора инструментов Microsoft Framework SDK, то его можно загрузить с Web-узла компании Microsoft по адресу http://msdn.microsoft.com.
26
А В Фролов, Г. В. Фролов. Язык С# Самоучитель
Для этого откройте раздел Downloads и в разделе Software Development Kits щелкните строку Microsoft Framework SDK. Вы окажетесь на странице загрузки, откуда можно скачать нужный SDK либо в виде одного файла размером Мбайт, либо частя ми размером примерно Мбайт. Ввиду значительного объема информации для загрузки следует использовать ско ростные линии передачи данных. Через обычный модем загрузка будет выполняться слишком долго. В качестве альтернативы компания Microsoft предлагает приобрести по очень небольшой цене компакт-диск с Microsoft Framework SDK. Вне зависимости от того, будете вы транслировать свои программы при помощи Microsoft Visual Studio или при помощи Microsoft Framework SDK, необхо димо загрузить и установить пакет обновлений Framework Service Pack. Когда мы работали над книгой, была доступна только первая версия пакета исправлений. Объем пакета исправлений невелик (он может поместиться на одну дискету), а бес платно получить его можно, например, на упомянутой выше странице загрузки Micro soft Framework SDK.
Условные обозначения в книге В нашей книге вы найдете много фрагментов исходных текстов программ. Для выде ления программ и их фрагментов мы использовали с п е ц и а л ь н ы й шрифт, например: оператор s w i t c h , тип данных d o u b l e . Элементы пользовательского интерфейса и ссылки, расположенные на Web-стра ницах, выделяются полужирным шрифтом, например меню File, ссылка Download. Курсивом выделяются новые понятия и термины. Угловыми скобками мы выделяем символы или строки, вместо которых в программе должен быть подставлен конкретный идентификатор. Например, вместо строк < В ы р а ж е н и е > и < О п е р а т о р > в условном операторе, задающих выражение и оператор в общем виде, необходимо подставить конкретное и конкрет ный оператор: if При помощи квадратных скобок выделяются необязательные фрагменты опе раторов, выражений или других элементов программ. Например, в операторе кон струкция может отсутствовать: if 1> [else 2>] Выражения вида означают интервал значений. Если квадратная скобка направлена внутрь, то расположенное рядом с ней значение принадлежит данному ин а если — то не принадлежит. Приведенное выше выражение описывает значения, большие но меньшие или равные 100. Введение
27
Многоточие используется в листингах программ для обозначения пропущен ных строк повторяющихся синтаксических конструкций: namespace H e l l o
Благодарности Мы благодарим Сергея Ноженко, который натолкнул нас на идею использования язы ка С# в системе удаленного восстановления данных через для службы Daа также на идею создания этой Мы также благодарим всех сотрудников издательства «Диалог-МИФИ», благодаря труду которых стало возможно появление этой книги.
Как связаться с авторами книги Свои замечания по этой книге и предложения присылайте авторам по адресу alexandre@frolov.pp.ru. Информацию о других наших книгах и проектах можно найти по адресам: http://www.frolov.pp.ru, http://www.datarecovery.ru, http://www.zerohops.ru.
28
А.
Фролов, Г. В. Фролов Язык С# Самоучитель
Глава 1. Базовые понятия и определения Как мы отметили во Введении, язык С# унаследовал многое от других языков про граммирования, таких, как С, С++ и Java. Поэтому, если вы уже владеете одним из этих языков, освоить С# будет намного легче. Наша книга предназначена не только для опытных, но и для начинающих програм мистов, поэтому для работы с ней вам не потребуется предварительного изучения ка ких-либо еще языков программирования. В гл. 1 мы рассмотрим основы — базовые понятия и определения, элементарные типы данных, основные операторы и выражения языка С#. Мы приведем несколько примеров простейших программ, демонстрирующих приемы работы с элементарными типами данных и операторами С#. Многие из важнейших понятий языка С# будут только упомянуты в этой главе, а их детальное изучение мы отложим до следующих глав нашей книги.
Первая программа на языке С# Когда мы только обдумывали содержание этой книги, то перед нами встала проблема, с чего начать изложение материала — с теории или практики. Теоретический материал нужно иллюстрировать примерами программ. В то же время для написания даже простейшей программы нужны элементарные теоретиче ские знания. Практически все учебники по языкам программирования начинаются с изучения исходного текста программы, отображающей на консоли текстовую строку Эта традиция пошла еще с самых первых учебников по языку программи рования С. Мы считаем, что при изучении С# такой подход вполне оправдан, так как для луч шего понимания базовых понятий необходимо приводить исходные тексты реальных работающих программ. Даже в самой простейшей программе С# используется слиш ком много особенностей языка, детальное изучение которых при первом знакомстве нецелесообразно. Однако мы не будем отступать от испытанной временем практики и постараемся сделать все необходимые пояснения.
Исходный текст простейшей программы Наша первая программа С# сразу после запуска выводит на консоль строку С# а затем ждет, когда вы нажмете клавишу Enter на клавиатуре компью Исходный программы представлен в листинге
29
Листинг
Файл
u s i n g System; namespace H e l l o class static
void C#
Как видите, исходный программы состоит всего из нескольких строк, причем некоторые из них используются только для описаний программы и ее объектов, а не непосредственно исполняются и приводят к появлению видимых результатов. На данном этапе вам нужно представлять себе только общую структуру этой програм мы, вникая в подробности. Мы пока отложим детальный разговор о многих поняти ях, упомянутых здесь.
Пространство имен System Первая строка нашей программы содержит ключевое слово u s i n g и предписывает компилятору просматривать в процессе своей работы так называемое пространство имен using
System;
В состав среды выполнения программ Microsoft Framework входит обширная библиотека, насчитывающая десятки тысяч классов. Сильно упрощая, скажем, что классы представляют собой описания некоторых данных и методов работы с этими данными. Пользуясь классами как кирпичиками (или как прототипами), можно созда вать весьма и весьма сложные программы, не затрачивая на это колоссальных усилий. Чтобы компилятор ориентироваться в названиях классов, а также определен ных в рамках этих классов символических именах, в языке С# используются простран ства имен. Указывая при помощи ключевого слова u s i n g пространство имен S y s t e m , мы открываем компилятору доступ к классам, необходимым, в частности, для текстовых строк с клавиатуры и вывода их на консоль. В своих примерах программ мы постоянно будем использовать пространство имен S y s t e m и другие пространства имен.
Определение собственного пространства имен Любая, простейшая программа С# создает свои классы. Она также может опре делять для этих классов собственные пространства имен. Такое определение делается при помощи ключевого слова n a m e s p a c e :
30
А В. Фролов, Г.
Фролов. Язык С# Самоучитель
namespace
Hello
ключевого слова n a m e s p a c e указывается имя определяемого пространства имен. В данном случае наше пространство имен будет называться С помощью фигурных скобок мы ограничиваем строки программы, имеющие отношение к определяемому пространству имен.
Класс Как вы скоро узнаете, все данные в языке С# представляются в виде объектов некото рых классов. Наша программа тоже создает класс H e l l o A p p , в котором определен единственный метод Main: class
HelloApp
s t a t i c void C#
Название класса задается параметром полагается внутри фигурных скобок: class
c l a s s , а содержимое класса рас
HelloApp
Для своей первой программы мы выбрали произвольное название содержащего ее H e l l o A p p . Это сокращение от Hello, Application. В названии класса мы отразили назначение класса и всего приложения — отображение приветствен ного сообщения. Но, строго говоря, здесь мы могли бы использовать любое д о п у с тимое название.
Метод Main Как мы уже говорили, классы представляют собой некоторые данные и методы для работы с ними. В нашем приложении определен класс H e l l o A p p , а в этом — метод Main: s t a t i c void C#
Глава 1 Базовые понятия и определения
31
Отвлечемся пока от ключевых слов и v o i d , а также от круглых расположенных после имени метода Main. Чтобы система Microsoft запустить приложение, в одном из классов приложения необходимо определить метод с именем Main. Этот метод нужно сделать статическим, снабдив ключевым словом s t a t i c , ничего не получится. Запомните пока просто как аксиому, что в программе обязательно должен быть статический метод M a i n , определенный подобным образом. Именно этот метод полу чает управление при запуске приложения. Позже мы проясним ситуацию с ключевыми словами s t a t i c и v o i d . Тело метода M a i n ограничено фигурными скобками, внутри которых находятся два оператора: C# Первый из них выводит строку С# на консоль, а второй ожи дает, пока кто-нибудь не введет с клавиатуры произвольную строку и не нажмет кла вишу Enter. В первой строке нашего метода мы обращаемся к методу W r i t e L i n e , предназна ченному для вывода данных на консоль. Этот метод определен в классе C o n s o l e , ко торый принадлежит упоминавшемуся ранее пространству имен В круглых скобках методу передаются параметры, определяющие, что собственно нужно выводить на консоль. В данном случае мы выводим текстовую строку « H e l l o , С# ограничив ее двойными кавычками. Метод R e a d L i n e тоже определен в классе C o n s o l e из пространства имен S y s t e m . Он предназначен для получения текстовой строки, введенной с консоли. Мы не передаем методу R e a d L i n e никаких параметров. Единственное назначение метода R e a d L i n e в нашей программе — приостановить ее работу после вывода на консоль строки сообщения С# Если этого не сделать, то при запуске программы в среде ОС Microsoft Windows консольное окно с сообщением появится на очень короткое время, а затем будет автоматически уничтожено. В результате вы не успеете ничего рассмотреть. Поэтому многие приме ры консольных программ, приведенные в нашей книге, будут завершаться вызовом метода R e a d L i n e .
Трансляция программы при помощи Framework SDK Если вы решили транслировать примеры программ, приведенных в нашей книге, при помощи Microsoft Framework SDK, то этот раздел для вас. Процедура созда ния простейших консольных приложений и их трансляции с применением Microsoft Visual Studio описана в следующем разделе. Прежде всего вам нужно создать файл исходного текста программы, воспользо вавшись для этого любым текстовым например программой Notepad или
32
А.
Фролов, Г. В. Фролов. Язык С#. Самоучитель
встроенным файлового менеджера FAR. Вы должны получить простой файл без элементов стилевого и шрифтового оформления. Поэтому для соз дания и редактирования исходных текстов программ не стоит использовать текстовые процессоры вроде Microsoft Word. Трансляция исходного текста программы запускается командой следующего вида: HelloApp.cs Эту команду нужно ввести в консольном приглашении или в приглашении про граммы FAR. В приведенной выше строке команды вам, вероятно, придется изменить путь к файлу программы csc.exe транслятора С#, указав тот каталог, куда вы установили утилиты Microsoft Framework SDK. Чтобы найти каталог с файлом csc.exe, можно использовать, например, стандартные средства поиска файлов ОС Microsoft Windows. Прежде чем запускать команду трансляции, сделайте текущим каталог, в котором расположен файл исходного текста программы. После завершения работы транслятора в текущем каталоге будет создан исполни мый файл HelloApp.exe. Запустив его, вы увидите на консоли сообщение H e l l o , С# w o r l d ! Посмотрев на сообщение, нажмите клавишу Enter. Программа завершит свою рабо ту, после чего консольное окно исчезнет с экрана. Если сообщение появилось, все в порядке. В том случае, когда вместо него вы получили сообщение о невозможности запуска программы csc.exe, убедитесь, что вы правильно установили Microsoft Framework SDK и правильно указали в команде запуска транслятора путь к файлу csc.exe. При появлении других ошибок проверьте, правильно ли был набран исходный текст программы.
Использование Microsoft Visual Studio Интегрированная система разработки Microsoft Visual Studio значительно облег чает создание программ С#. В ее составе имеются мощные отладочные средства, спра вочная библиотека MSDN Library, содержащая невероятно огромное количество ин формации (на английском языке), визуальные средства проектирования приложений Microsoft Windows и многое другое. Подробный рассказ об использовании Microsoft Visual Studio может соста вить предмет отдельной книги. В нашей книге мы опишем только некоторые приемы использования этого замечательного инструмента.
Создание нового проекта Чтобы создать простейшую консольную программу с использованием Microsoft Visual запустите эту среду разработки приложений и выберите из меню File строку New. Затем из меню второго уровня выберите строку Project. Глава 2 Язык
Базовые понятия и определения Самоучитель
33
диалоговое окно New Project,
Рис.
на рис.
Создание нового проекта
В списке Project Types укажите тип проекта, щелкнув мышью строку Visual С# Projects. Далее в списке шаблонов Templates выберите шаблон проекта консольного приложения Console Application, щелкнув левой клавишей мыши соответствующий значок в правой части окна New Project. В поле Name введите имя проекта, а в поле Location — путь к каталогу проектов, в котором будет создан каталог создаваемого проекта. Для выбора пути к каталогу проектов вы можете воспользоваться кнопкой Browse. Проделав все описанные выше действия, щелкните кнопку ОК. В результате будет запущен мастер проектов, который автоматически создаст и загрузит в окно редакти рования исходный текст программы: u s i n g System; namespace H e l l o Summary d e s c r i p t i o n f o r C l a s s l . class
Classl The main e n t r y p o i n t
s t a t i c void С
for the a p p l i c a t i o n . args)
TODO: Add c o d e to s t a r t a p p l i c a t i o n h e r e
34
А. В. Фролов. Г. В. Фролов. Язык С#. Самоучитель
Сравнив этот текст с текстом, приведенным в листинге вы сможете обнаружить некоторые отличия. Во-первых, мастер проектов вставил в текст программы строки комментариев, на чинающиеся с двух и трех слешей Комментарии вида предназначены для ав томатического создания документации для программного проекта. Во время своей ра боты транслятор С# игнорирует строки комментария, и вы пока можете поступать та ким же образом. Позже мы расскажем о комментариях подробнее. Во-вторых, мастер проектов выбрал для класса имя в то время как мы на звали этот класс Название данного класса не имеет никакого значения для работоспособности программы, однако лучше использовать семантически значимые имена. Поэтому мы скоро изменим это имя. В-третьих, перед объявлением метода Main появилась конструкция Это так называемый атрибут. Атрибуты С# определяют различные параметры объектов, перед которыми они находятся. В данном случае атрибут означает, что по умолчанию будет использо вана так называемая однозадачная разделенных потоков single-threaded apartment (STA). Разговор о моделях потоков мы отложим до гл. посвященной многопоточным программам и компонентному программированию. Почти во всех примерах программ, приведенных в нашей книге, вы можете не указывать данный атрибут. И наконец, в-четвертых, в программе, созданной мастером проектов, метод M a i n имеет параметр a r g s : s t a t i c void
args)
Через этот параметр передаются аргументы запуска программы, указываемые в ко мандной строке. В своих примерах программ мы обычно не используем параметр метода Main, но позже вы узнаете, что с ним можно делать. Пока же его существование можно про сто игнорировать.
Проекты и решения При создании программ и комплексов программ в интегрированной системе разработки Microsoft Visual Studio используются понятия решение (solution) и проект (project). Проект представляет собой набор файлов исходных текстов, файлов графических изображений и других файлов, необходимых для создания программы. Например, для нашего проекта создается файл исходного текста HelloApp.cs и ряд других файлов, например файл значка (пиктограммы). Обычно сложные программные комплексы содержат в себе много программ и компо нентов, причем, возможно, созданных с использованием разных языков программирова ния и даже рассчитанных для использования на различных компьютерных платформах. Для облегчения разработки Microsoft Visual Studio позволяет представить все создаваемые компоненты такого комплекса в виде набора проектов, объединенных общим решением. В нашем случае при создании проекта было также автоматически создано решение Hello. Информация об этом решении была сохранена в файле с именем Hello.sln. По сле того как вы закончите работу с Microsoft Visual Studio для того, чтобы снова Глава
Базовые понятия и определения
35
вернуться к работе над проектом, достаточно просто щелкнуть дважды пиктограмму этого файла в папке проекта. образующие можно посмотреть в виде иерархического де рева на вкладке Solution Explorer (рис. -
1.2. Вкладка Solution Explorer
...
Окно вкладки Solution Explorer находится в правом верхнем углу главного окна Microsoft Visual Studio Как видно на рис. в решение Hello входит один проект, который тоже называ ется Hello. Проект Hello, в свою очередь, содержит определение ссылок Reference, ко торые нам пока не интересны, файл значка App.ico, файл сборки (assembly) с именем а также файл с исходным текстом нашей программы Classl.cs.
Изменение проекта Теперь, чтобы заставить программу делать то, что нам нужно, внесем некоторые изме нения в исходный текст, созданный мастером проекта. Прежде всего, щелкните мышью имя файла Classl.cs на вкладке Solution Explorer (рис. После этого в нижнем правом углу главного окна Microsoft Visual Studio откроется окно с вкладкой Properties, предназначенной для редактирования свойств файла (рис.
1.3. Вкладка Properties
36
А
Фролов, Г. В. Фролов Язык
Самоучитель
клавишей мыши название файла Classl.cs в поле File Name и подождите некоторое время, не двигая курсор мыши. Вы сможете отредактировать имя файла. Замените именем HelloApp.cs (рис. 1.3). Далее внесите изменения в исходный текст программы. Замените название класса C l a s s l на H e l l o A p p и добавьте в исходный текст метода M a i n строки, одна из которых предназначена для вывода на консоль сообщения « H e l l o , С# а вторая ожидает нажатия на клавишу Enter: C# Ниже мы привели новый вариант исходного текста, который должен получиться у вас после выполнения всех перечисленных действий: u s i n g System; namespace H e l l o Summary d e s c r i p t i o n f o r H e l l o A p p . class
HelloApp The main e n t r y p o i n t f o r t h e
s t a t i c void TODO:
args) Add c o d e t o s t a r t a p p l i c a t i o n h e r e C#
Строки комментариев можно удалить, точно так же как и строку с атрибутом Теперь мы готовы оттранслировать программу и запустить ее на выполнение. Что бы это сделать, просто нажмите клавишу F5. Если все было сделано правильно, через некоторое время на экране компьютера появится консольное окно с сообщением « H e l l o , С# Нажмите в этом клавишу Enter — и окно исчезнет. В том случае, если вы ошиблись при наборе кода, в нижней части главного окна Microsoft Visual Studio появится сообщение об ошибке. Кроме того, ошибочные строки выделяются в редактирования волнистой линией синего цвета. Если под вести курсор мыши к такой линии, около курсора появится текст соответствующего сообщения об ошибке. Глава
Базовые понятия и определения
37
Элементарные типы данных Известно, что компьютеры обрабатывают данные. Устройство компьютера и принцип его работы очень хорошо описаны в книге Чарльза Пстцольда [4]. Мы настоятельно рекомендуем ознакомиться с этой книгой всех новичков в области программирования. Что же касается данных и обрабатывающих эти данные команд, то ими мы займемся в оставшейся части этой главы. За обработку данных отвечает центральный процессор компьютера. Внутри про цессора имеется управляющее устройство, арифметико-логическое устройство, набор регистров для временного хранения данных, схемы адресации внешнего устройства оперативной памяти и устройств ввода-вывода, а также другие схемы и устройства. В оперативную память тем или иным способом записывается программа набор инструкций (машинных команд), предписывающих центральному процессору выпол нить заданные действия в определенной последовательности. Процессор извлекает машинные команды из оперативной памяти и выполняет их. Что могут делать машинные команды? Эти команды могут писать данные в ячейки оперативной памяти компьютера или во внутренние регистры процессора, а также читать их оттуда, читать регистры ввода и писать в регистры вывода, обмениваясь данными с периферийными устройствами (клавиатурой, принтером и т. д.). Помимо команд чтения и записи данных, существуют команды, предназначенные для изменения нормального линейного хода чтения и выполнения машинных команд. Это так называемые команды условного и безусловного перехода. Команды безусловного перехода предписывают процессору выбирать вместо следую щей команды другую команду, адрес которой задается как параметр команды. Что же ка сается команд условного перехода, то помимо адреса перехода им передается условие, при выполнении которого нужно изменить линейный порядок исполнения программы. Программирование в машинных командах предполагает ввод числовых кодов ко манд и параметров в оперативную память компьютера. В первом компьютере, который мы собрали самостоятельно на базе процессора Intel 8080, для этого использовалась небольшая кнопочная клавиатура. Мы убедились на собственном опыте, что программирование в машинных кодах и ручной ввод команд в оперативную память компьютера — ужасно утомительное за нятие. Чтобы избавить программистов от рутинной работы с числами, на заре компь ютерной техники использовали язык ассемблера. В языке ассемблера числовые коды машинных команд были заменены легко запо минающимися символическими обозначениями. Например, команда сложения чисел обозначается как a d d (от слова addition, означающего «сложение»), а команда вычи тания — как s u b (subtraction — Кроме команд, символические обозначения можно использовать для ссылок на ре гистры процессора, области оперативной памяти, выделяемые для хранения данных, а также для ссылок на адреса расположения машинных команд. Любые программы, составленные в машинных кодах или написанные на таких языках, как С, С++ или С#, имеют дело с элементарными типами данных, такими, как числа и текстовые строки, а также с командами и выражениями. Прежде чем мы
38
А.
Фролов, Г. В.
Язык С#. Самоучитель
продвинуться дальше в изучении технологий программирования, нам необхо димо со всем этим познакомиться. Далее мы рассмотрим элементарные типы данных. Элементарными типами данных мы будем считать биты, байты, типы данных, предназначенные для хранения чисел и текстовых строк. Они будут вам встречаться при использовании практически любого языка программирования, в том числе и языка С#.
Бит Известно, что наименьшей хранения информации является бит. Бит может принимать только два состояния — 1 или 0. Про состояние 1 часто говорят, что бит установлен, а про состояние 0 — что бит сброшен.
Байт Так как в 1 бите данных может храниться очень мало информации, биты часто диняют в байты (рис. 1.4), комбинируя их по 8 штук. Самый левый бит с номером 7 называют старшим битом, а самый правый с номером битом. 7
Рис.
6
5
4
3
2
1
0
Внутреннее устройство байта
Биты в байте часто называют разрядами. Старший разряд байта — это его старший бит, а младший разряд — младший бит. Всего в байте разрядов, поэтому говорят, что байт представляет собой 8-разрядный тип данных. Очевидно, бит является одно разрядным типом данных. Каждый из восьми разрядов 1 байта (т. е. каждый бит байта) может принимать состоя ние 0 или Всего же с помощью 1 байта можно закодировать 256 состояний. Состояние байта, в котором все разряды сброшены, соответствует нулевому значе нию байта, а состояние, в котором все разряды установлены, — значению 255. Таким образом, с помощью байта можно представить целые числа в диапазоне от 0 до 255. Для примера в табл. мы показали несколько состояний разрядов байта и соответст вующие им числовые значения. Таблица
Числовые Биты
Числовое
00000000
0
00000001
1 2 3 4
11111101
253
11111110
254
11111111
255
Глава
Базовые понятия и
39
Хотя с помощью I байта можно больше информации, чем при помощи бита (например, можно закодировать все символы русского или латин ского алфавита), реально в программах работают и с более емкими структурами дан ных. Сейчас нас интересуют данные, хранящиеся в оперативной памяти. Оперативную память можно представить как набор (массив) ячеек, каждая из которых хранит 1 байт данных (рис. 1.5). 0000 0001 0002 0003 0004
1А 08 В7 25 30
4FFF
56
Рис.
1.5. Адресация байтов оперативной памяти
Размер оперативной памяти в современных компьютерах может быть очень боль шим. Для измерения больших объемов памяти используют такие единицы, как кило (Кбайт), мегабайт (Мбайт), гигабайт (Гбайт) и терабайт (Тбайт). В одном ки лобайте содержится 1024 байт, в одном мегабайте — Кбайт и т. д. В нашем первом самодельном компьютере была установлена оперативная память объемом всего 8 Кбайт, что позволяло разместить в ней только простейшие програм мы или всего несколько страниц текста. Сегодня мы пользуемся компьютерами с объ емом оперативной памяти, равным Мбайт и даже больше. В память такого объема можно целую библиотеку книг. Для того чтобы записать или прочитать содержимое байта оперативной памяти, в соответствующей машинной команде тем или иным способом нужно указать номер соответствующей ячейки. Например, на рис. 1.1 видно, что в ячейке 0000 хранится значение 0x1 А, а в ячейке 0004 — 0x30. Здесь мы использовали представление чисел, снабдив их префик сом Ох, как это принято в языках программирования С, и С#. Подробнее о различных представлениях чисел (двоичном, десятичном и читайте в [4].
Числовые типы данных Первые компьютеры создавались главным образом для ускорения математических вы числений и потому работали с числами. Это обстоятельство подчеркивалось и в отече ственном переводе зарубежного термина компьютер (computer) — «вычислительная машина». Буквальный перевод этого «вычислитель». Данные элементарных типов (бит и байт) позволяют хранить числа в весьма огра ниченном диапазоне значений. С помощью 1 бита можно представить всего два чис ла — 0 и а с помощью 1 байта — 256 целых чисел в диапазоне от 0 до 255. Комбинируя несколько байтов, можно создавать типы данных, предназначенные для хранения намного большего количества значений. Например, вместе 2 байта, можно определить тип данных, способный хранить целые зна чения от 0 до - 1 65 535. При можно комбинировать вместе 4, 8 пли больше байт, создавая типы данных, пригодные для реальных вычислений. 40
А.
Фролов, Г. В.
Язык
Самоучитель
Для того чтобы в не только положительные, но и отрицатель ные числа, старший разряд байта (или комбинации нескольких расположенных рядом байтов) используют для хранения знака числа. Когда этот разряд сброшен, то число имеет положительный знак, а когда установлен — отрицательный. При этом в осталь ных разрядах хранится абсолютное значение числа. Отдельные байты или объединения смежных байтов оперативной памяти можно использовать для создания так называемых переменных — объектов, предназначенных для хранения числовых данных. Переменная имеет свой адрес и разрядность. Чем больше разрядность, тем большее число можно записать в переменную. Кстати, само слово «переменная» подчеркивает тот факт, что программа может изменять содержимое отведенной для нее области памяти. На рис. 1.6 мы разместили в оперативной памяти 3 переменные, одна из которых занимает 2 байта, вторая — 4 и третья — 1 байт. Области памяти, отведенные для пе ременных, выделены полужирной рамкой. Мы также снабдили переменные именами (идентификаторами), чтобы их было легче различать. 0000 0001 0002 0003 0004 0005 0006
1А 08 В7 25 В7 25 30
4FFE 4FFF
34 56
—
Count
Рис.
1.6.
Именованные переменные
Переменная с именем занимает в памяти 2 байта, первый из которых имеет адрес 0x0000, а Для переменной M a x H e i g h t выделены 4 байта с адре сами от 0x0003 до 0x0006, а для переменной C o u n t — 1 байт с адресом 0x4FFF. Программы, составляемые непосредственно в машинных кодах, вынуждены опе рировать числовыми значениями адресов. Что же касается программ, составленных на языке ассемблера, С или на других языках более высокого уровня, то в них вы мо жете обращаться к переменным по именам. Компьютеру проще обрабатывать числа, человеку же понятнее символические имена. Обычно имена переменных выбираются так, чтобы они указывали назначение переменных. Например, нетрудно догадаться, что в переменной MaxWidth хранится максимальное значение ширины, в переменной максимальное значе ние высоты, а переменная C o u n t используется для хранения значения счетчика. Пока для нас не важно, высота и ширина какого объекта хранится в переменных H e i g h t и MaxWidth, а также что именно подсчитывает счетчик C o u n t . Имя переменной может состоять из произвольной последовательности букв и цифр, а также из символа подчеркивания считающегося буквой. Имя не должно начинаться с цифры, содержать пробелы или совпадать с зарезервированными ключе выми словами С#. Список зарезервированных ключевых слов С# вы найдете в прило жении к этой книге. Глава
Базовые понятия и определения
41
Вот примеры правильных имен:
sMyString pay_date Расположенные ниже последовательности символов недопустимо использовать при определении имен переменных: 88 Time Machine int $total Первые две последовательности начинаются с цифры, третья содержит пробел, четвертая совпадает с зарезервированным именем i n t , а пятая и шестая содержат не допустимые Учтите, что транслятор С# (так же как и трансляторы языков С и С++) делает раз личие между строчными и прописными буквами. Таким образом, ниже представлены разные имена: TimeMachineLoader
В языке Pascal, напротив, все эти имена будут означать одно и то же, так как транс лятор Pascal игнорирует различия между строчными и прописными буквами. На наш взгляд, допустимость различного обозначения одних и тех же вещей является недос татком и в конечном счете может привести к возникновению трудно обнаруживаемых ошибок.
Текстовые символы и строки Помимо «перемалывания» чисел в процессе математических вычислений, компьютер можно использовать и для решения многих других задач, например для обработки текста. Выше мы уже говорили, что 1 байта достаточно для представления всех символов русского или латинского алфавита. На самом деле задача представления символов раз личных алфавитов в компьютере не так проста, как может показаться на первый Вот одна из проблем: в одном документе иногда используются символы сразу не скольких алфавитов, кириллица, латиница и символы греческого алфавита. С того, что символы текста могут быть строчными и прописными, 1 байта бу дет недостаточно для представления кодов символов нескольких алфавитов.
42
А
Фролов, Г. В. Фролов, Язык
Самоучитель
Другая проблема связана с тем, что существует великое множество способов коди рования символов. Придумана, например, почти дюжина кодировок символов кирил лицы для компьютеров и ОС разных типов. Кодировку символов в ОС Microsoft Windows удобно изучать при помощи про граммы Character Map, которую можно найти в группе программ Accessories. На рис. мы показали один из возможных вариантов кодировки кириллицы — так называемую кодировку ANSI.
Capital
Рис.
1.7.
ANSI-кодировка символов кириллицы
Чтобы узнать код символа, щелкните его мышью в окне программы. Код будет по казан в нижней части окна (в круглых скобках). Например, если щелкнуть прописную латинскую букву А, внизу отобразится ее код Как видите, верхнюю таблицы кодов занимают символы пунктуации, цифры и латинские символы. Дальше идут различные специальные символы и симво лы кириллицы. На рис. 1.8 мы показали одну из кодировок символов кириллицы, используемых в ОС MS-DOS. Здесь символы пунктуации, цифры и латинские символы кодируются таким же образом, как и в предыдущем случае, но символы кириллицы расположены в другом месте кодовой таблицы. Глава
Базовые понятия и определения
43
Map Aral
to
|A
Advanced
Capital
7.8.
A
DOS-кодировка символов кириллицы
Чтобы закодировать символы сразу всех алфавитов, была разработана так называе мая кодировка UNICODE. В ней каждый символ представляется не одним, а двумя байтами. С помощью 2 байтов можно представить максимально 65 535 символов, что, по всей видимости, достаточно во всех случаях. С использованием UNICODE можно составлять текстовые документы, в которых одновременно применяются символы любых алфавитов. В компьютерах и ОС каждый символ текста кодировался 1 байтом, т. с. применялась кодировка символов. Наряду с другими кодировками такая кодировка применяется и до сих пор. При записи текстовой строки с вой кодировкой в оперативную память компьютера коды символов располагаются в смежных ячейках памяти, как это показано на рис. 1.9. 1001 1004 1005
48 65 6С 6С 6F 00 30
HelloMessage
1.9. Хранение в памяти строки 4FFF
44
56
А В Фролов. Г.
Фролов Язык С# Самоучитель
Здесь символы « Н е 1 1 о » с именем H e l l o M e s s a g e занимают ячейки памя ти с от 1000 до 1004 При этом адресом строки является адрес 1 байта. В нашем случае это адрес Текстовые строки могут иметь любую длину, поэтому нужно как-то обозначить конец строки в памяти. В языках программирования С и текстовая строка всегда завершается нулевым байтом. На рис. 1.6 это байт с адресом 1005. В Pascal перед на чалом строки в памяти располагается хранящий длину строки, а завершающий, нулевой символ не используется. Из-за того что в 1 байте можно хранить числа от 0 до 255, длина закодированной этим способом текстовой строки в языке Pascal не мо жет превышать 255 символов. Если для хранения символов использовать кодировку UNICODE, то строка будет занимать в оперативной памяти вдвое больше места, так как каждый символ будет кодироваться не одним, а 2 байтами. Такова цена универсальности. В языке С# для хранения текстовых символов и строк применяется кодировка UNICODE, допускающая представление символов всех существующих алфавитов.
Обозначение типов данных в С# Итак, теперь мы знаем, что программа может обращаться к переменным, расположен ным в оперативной памяти компьютера, не только по адресам, но и по символическим именам. Однако одного только имени недостаточно для того, чтобы правильно рабо тать с переменной. Необходимо еще знать, какой тип данных хранит переменная, и сколько байтов памяти она занимает. Во языках программирования для обозначения типа данных используются специальные ключевые слова, и язык С# не является исключением. Заметим, что в отличие от других языков программирования все переменные в С# рассматриваются как объекты тех или иных классов. Пока мы еще не приступили к объектно-ориентированного программирования, не будем принимать нос обстоятельство во внимание. Позже мы вернемся к этому вопросу и рассмотрим его во всех подробностях. В языке С# можно использовать числовые типы данных со знаком, числовые пере менные без знака, символьные переменные, логические (булевы) переменные и стро ки, а также перечисления. Рассмотрим элементарные типы данных С# и их обозначе ние подробнее.
Числа без знака Вы уже знаете, что если все разряды байта используются для представления абсолют ного значения числа, то байт хранит только положительные числа и нуль. В табл. 1.2 мы перечислили имена числовых типов данных без знака, используемые в языке про С#.
Глава
Базовые понятия и определения
45
Таблица
1.2. Числовые типы данных без знака
Тип
Возможные
значения
Описание
От 0 до 255
8-разрядное значение без знака, занимает 1 байт памяти
ushort
От 0 до 65 535
значение без знака, занимает 2 байта памяти
uint
От 0 до 4 294 967 295
32-разрядное значение без знака, занимает 4 байта памяти
до 18 446 744 073 709 551 615
64-разрядное значение без знака, занимает 8 байт памяти
Чтобы использовать переменные в программе С#, их необходимо объявить, указав имя и тип переменной, например: byte ushort uint ulong
myByte; myShort;
Здесь объявлены 4 переменные с именами m y B y t e , m y S h o r t , и Обратите внимание, что мы записали объявление каждой переменной на отдельной строке, завершив его символом «точка с запятой». Можно было бы расположить все объявления на двух или даже на одной строке, однако такая запись выглядит менее на глядно: b y t e myByte;
u s h o r t myShort; u i n t
u l o n g myLong;
Если объявляются несколько переменных одного типа, можно использовать список имен переменных, разделенных запятыми, например: ushort uint height,
yCoord, width;
zCoord;
Здесь мы объявили 3 переменные x C o o r d , y C o o r d и z C o o r d типа u s h o r t , а также две переменные h e i g h t и w i d t h типа u i n t . Транслятор С#, преобразуя исходный текст программы в промежуточный код MSIL, о котором мы упоминали во Введении, игнорирует незначащие пробелы, сим волы табуляции, символы возврата каретки и перехода на другую строку. Однако если не ставить пробел между обозначением типа переменной и ее именем, компилятор бу дет считать такую строку ошибочной:
Обратите внимание, что в этой строке после символов мы записали так назы ваемую строку комментария. Комментарии позволяют вставлять в исходный текст программы произвольные поясняющие текстовые строки. В языке С# существуют трех типов. На данный вам следует знать, что компилятор игно рирует символы все символы, за до конца текущей строки: b y t e myByte;
46
Это правильное определение переменной типа b y t e А В Фролов, Г. В. Фролов. Язык С#. Самоучитель
Если нужно вставить комментарий в середину строки, используется конструкция Текст комментария помещается между парой символов открывающей комментарий, и парой символов закрывающей комментарий. Например: byte
это
myByte;
С помощью конструкции можно создавать многострочные комментарии, включающие в себя при необходимости комментарии. В частности, данный фрагмент исходного текста программы будет рассматриваться компилятором как многострочный комментарий:
Это переменные для хранения чисел б е з знака byte ushort uint
myByte;
э т о байт
Использование комментариев делает программу более понятной. Не жалейте вре на комментирование программ, особенно сложных. Впоследствии, если вам при дется дорабатывать программу, комментарии помогут вспомнить назначение фрагмен тов ее исходного текста.
Числа со знаком Числовые типы данных со знаком, доступные программисту С#, представлены в табл. 1.3. Как видите, при помощи этих типов данных можно представить числа в довольно широком диапазоне значений. Таблица 1.3. Числовые типы данных со знаком Тип
Возможные
sbyte short
значения
Описание
до 127
8-разрядное значение без знака, занимает 1 байт памяти
768 до 32 767
значение без знака, занимает 2 байта памяти
147 483 648 2 147 483 647 long
От - 9 223 372 036 854 775 808 до 9 223 372 036 854 775 807
Вот примеры объявления данных со знаком: sbyte short int long Глава
32-разрядное значение без знака, занимает 4 байта памяти 64-разрядное значение без знака, занимает 8 байт памяти
для перечисленных в этой таблице типов
distanceX; distanceY; height; Базовые понятия и определения
47
небольшое замечание относительно выбора имен Прежде всего, имена нужно задавать таким образом, чтобы в контексте программы было понятно, для чего эти переменные используются и что они хранят. Компилятор С# не запрещает указывать такие имена переменных, как х, у, a b , d и т. п. Однако смысл имени переменной будет намного прозрачнее, если составлять его из слов английского языка, обозначающих связанное с этой переменной понятие. Например, по имени сразу можно догадаться, что в соответст вующей переменной хранится расстояние от дома. Кроме того, рекомендуется (хотя это и не обязательно) начинать имя переменной со строчной буквы и применять прописные буквы для выделения слов, входящих в со ставное имя переменной.
Числа с плавающей точкой При проведении научных вычислений часто используются числа в формате с плаваю щей точкой, такие, например, как Для представления данных в формате с плавающей точкой в языке С# используется два типа и d o u b l e (табл. 1.4). Таблица Тип
1.4.
типы данных с Возможные От
double
точкой
От
32-разрядное число с плавающей точкой, максимальная точность представления чисел — 7 десятичных цифр до
64-разрядное число с плавающей точкой, максимальная точность представления чисел десятичных цифр
Вот примеры объявления переменных, предназначенных для хранения чисел с пла вающей точкой, в программе С#: float double Как видите, диапазон значений, представляемых с помощью типов данных и d o u b l e , чрезвычайно широк и вполне подходит для научных расчетов.
Числа для финансистов Деньги, как известно, любят счет. Если речь идет о компьютерных системах банка, оперирующих с астрономическими денежными суммами, необходимо обеспечить мак симально возможную точность вычислений. Многократное возникновение ошибки округления может привести к бесследному исчезновению (или, наоборот, к появлению из ниоткуда) довольно заметных денеж ных сумм. 48
Фролов, Г. В
Язык
Самоучитель
Из нам случай, недобросовестный программист пользо вался данным обстоятельством с выгодой для себя. Мизерные денежные суммы, отбрасываемые при округлении результатов вычислений, он переводил на свой проводя в жизнь известное утверждение о том, что копейка рубль бережет. Когда махинации вскрылись, оказалось, что на счету злоумышленника скопилась изрядная сумма денег. В языке С# предусмотрен один тип данных специально для работы с деньгами — d e c i m a l (табл. 1.5). Он обеспечивает колоссальную точность представления денеж ных сумм 29 десятичных цифр! Таблица Тип
1.5.
Числовые типы данных для работы с деньгами Возможные От
значения до
Описание число с плавающей точкой, максимальная точность представления чисел — 29 десятичных цифр
Вот пример объявления переменной типа
Текстовые символы Для хранения отдельных символов текста (таких, как буквы, знаки пунктуации и управляющие символы) в языке С# предусмотрен специальный тип данных char. Как мы уже говорили, для этого типа данных используется кодировка UNICODE, поэтому символы c h a r занимают в памяти 2 байта. Заметим, что переменные типа c h a r в языках программирования С и С++ занимают в памяти только 1 байт. Вот как можно объявить переменную типа c h a r в программе, написанной на С#: char
Текстовые строки Многие программы работают с текстовыми строками. Язык С# содержит мощные и удобные объектно-ориентированные средства, позволяющие выполнять со строками все необходимые операции. Про строки мы пока скажем лишь то, что они определяются с помощью ключевого слова и хранятся в кодировке UNICODE. Детальное описание приемов рабо ты со строками мы приведем после как рассмотрим технологию объектноориентированного программирования. Вот так можно объявить в программе С# переменную типа string Глава 1 Базовые понятия и определения
49
Логический тип данных Логические переменные в С# объявляются с помощью ключевого слова и могут принимать одно из двух значений — t r u e (истина) или (ложь). В этом логические переменные напоминают отдельные разряды байта. Дейст вительно, каждый бит может хранить только одно из двух значений — 1 или 0. Во многих языках программирования (в частности, в языках С и С++) значение 1 сопоставляется с истиной, а значение 0 — с ложью. В языке С# такое сопоставле ние не используется. Подробнее об этом мы поговорим позже при описании услов ных операторов.
Перечисления Для определения некоторых типов данных вместо чисел, текстовых символов и строк больше подходят перечисления. Например, мы можем пронумеровать дни недели и ссылаться в программах на номера, но проще использовать привычные для нас на звания — понедельник, вторник, среда и т. д. В языке С# перечислимый тип данных задается при помощи ключевого слова и фигурных скобок, например: enum Days
Sunday
понедельник вторник среда четверг пятница суббота воскресенье
Здесь с помощью ключевого слова e n u m мы создали собственный перечислимый тип данных D a y s и определили составляющие его элементы перечисления, располо жив их внутри фигурных скобок. Так как компилятор С# игнорирует незначащие пробелы и символы новой строки, в тексте программы все элементы перечисления могут находиться и на одной строке. Например, ниже определен перечислимый тип для представления основных компо нентов цвета — красного, зеленого и голубого: enum B a s e C o l o r s
Red, Green, B l u e
Компилятор C# автоматически ставит в соответствие элементам перечисления це лочисленные значения, однако программист может оперировать именами переменных, что намного удобнее. Обратите внимание, что оператор объявления переменной нужно точкой с запятой. При объявлении перечисления этот символ используется. По умолчанию перечисления создаются на базе типа данных i n t . Однако вы мо жете создавать перечисления и других типов. Вот полный список типов, которые
50
А В. Фролов, Г. В. Фролов. Язык С#. Самоучитель
могут служить базовыми для перечислений: b y t e , u s h o r t , short, int, long. Вот пример перечисления, созданного на базе типа Days
sbyte,
byte
Friday, Sunday
понедельник вторник среда четверг пятница суббота воскресенье
Так как переменные типа занимают в памяти вчетверо меньше места, чем пе ременные типа i n t , новый вариант перечисления D a y s будет компактнее. Такие типы данных, как l o n g и u l o n g , используют в качестве базовых для созда ния перечислений с очень большим количеством элементов.
Литералы Литералы применяются в исходном тексте программы для обозначения числовых или логических значений, текстовых символов и строк. С помощью литералов програм мист может присвоить начальные значения переменным.
Целочисленные литералы Для обозначения целых десятичных чисел в языке С# используются цифры от 0 до 9, а также (при необходимости) дополнительные суффиксы. Вот примеры простейших целочисленных литералов: 25 0 767 6786867867 1233 Целочисленные литералы без дополнительных суффиксов могут представлять зна типов с о знаком или без знака: b y t e , s b y t e , i n t , u i n t , l o n g , u l o n g . Чтобы указать, что литерал представляет собой число без знака, он снабжается суффиксом и или U, например: 256u 1U 0U В этом случае литерал соответствует типам b y t e , u i n t , u l o n g . Суффиксы 1 или L применяются для создания литералов типа l o n g и u l o n g : 2 5 6 4 1 2 4 7 3 9 3 6 2 7 4 1 11 Комбинируя в произвольном порядке суффиксы u, U, 1 и ралы типа u l o n g :
можно создавать лите
1 4 5 6 3 7 3 6 5 U L 7 6 7 8 7 2 3 4 7 1 1 u 7678723471Lu Глава
Базовые понятия и определения
51
На экране букву 1 очень легко перепутать с цифрой 1, поэтому в суффиксах лите ралов рекомендуется применять вместо нее прописную букву L. литералы могут обозначать и шестнадцатеричные числа. В этом случае они составляются из цифр от 0 до 9 и букв А, В, С, D, Е, F. Перед числом помещается префикс Ох, например: 0x1 0x23BF
0xFE67BCA0001
Для обозначения знака числа допускается использование символов
и
Литералы с плавающей точкой Литералы с плавающей точкой бывают типа d o u b l e и d e c i m a l . Если в ли терале используется суффикс f или F, то это литерал типа а если суффикс d или D — типа В том случае, когда суффикс не указан, литерал принимает тип double. Для обозначения литерала типа используется суффикс m или М. Экспоненциальная часть литерала отделяется символом е или Е. Для обозначения знака числа используются символы и Вот примеры литералов с плавающей точкой типа 3F
-3.7E-10F Ниже мы привели примеры литералов типа d o u b l e :
.25 3 . 3 Вот несколько примеров литералов типа значения денежных сумм:
применяемых обычно для обо
З.ЗМ 6 . 5 2 т
Символьные литералы Обычно с помощью символьных литералов представляют одиночные символы, заклю чая их в одинарные кавычки, например:
С помощью символа обратного слеша можно указать в качестве символьного литерала некоторые специальные и управляющие символы. Например, литерал \п символ новой строки. Последовательность символа \ и идущего вслед за ним символа называют В табл. мы привели escape-последовательности, допусти мые в языке С#, а также соответствующие им 16-разрядные значения в кодировке UNICODE.
52
А. В Фролов, Г.
Язык С#. Самоучитель
Таблица
1.6.
Escape-последовательности
Escapeпоследовательность
Символ Одинарная
\
Код, UNICODE 0x0027
Двойная
0x0022
Обратный слеш
0хО05С
\0
Нулевое значение
0x0000
\а
Звуковой сигнал
0x0007
Возврат каретки
0x0008
\f
Новая страница
ОхОООС
\п
Новая строка
ОхОООА
Возврат каретки
OxOOOD
\t
Горизонтальная табуляция
0x0009
\v
Вертикальная табуляция
\х
Произвольное значение в шестнадцатеричной кодировке
От 0x0000 до OxFFFF
В наших программах мы часто будем пользоваться литералом для перевода строки при выдаче данных на консоль. С помощью конструкции вида 4 или \ U 1 2 3 4 можно задать так назы символьный литерал UNICODE. Цифры, следующие за escape-последо вательностью \ и , задают код UNICODE определяемого символа. Так как символы UNICODE занимают в памяти 2 байта, этот код должен находиться в интервале от 0x0000 до OxFFFF.
Строковые литералы Для представления текстовых строк в языке программирования С# используют стро ковые литералы. Строковый литерал представляет собой простую строку символов, в двойные кавычки, например: "Hello,
С#
Строковые литералы могут включать в себя escape-последовательности, описанные в предыдущем разделе. Например, для разделения слов в строковом литерале может применяться символ табуляции \ t : "Случайные Для того чтобы включить в литерал символ двойной кавычки, его нужно предста вить в виде escape-последовательности \ например: "Гостиница
Глава 1 Базовые понятия и определения
53
Если же нужно включить в литерал символ обратного слеша ( то его нужно пов торить дважды. Вот, например, как с помощью текстового литерала можно задать путь к каталогу
В языке С# строковый литерал может иметь префикс Он задает так называемый буквальный или дословный (verbatim) литерал. В таком литерале все символы интер претируются буквально, поэтому, например, нет необходимости повторять дважды символ обратного слеша:
Если же нужно включить в буквальный строковый литерал символ двойной кавыч ки, его все же придется повторить дважды:
Обычные строковые литералы С++ должны полностью располагаться на одной строке. Что же касается буквальных строковых литералов, то они могут быть не толь ко однострочными, но и многострочными, например: многострочный литерал" Такой литерал включает в себя все пробелы, символы новой строки и любые другие символы, располагающиеся между двойными кавычками, ограничивающими литерал.
Логические литералы Логические литералы предназначены для задания значений логическим перемен ным. В языке С# используются два логических литерала — t r u e (истина) и (ложь). Напомним, что в языке С# логические литералы не сопоставляются с целочислен ными значениями, такими, например, как 1 и 0.
Литерал null В языках программирования С и С++ переменные всегда содержат какие-либо значе ния. Программист может задать эти значения явно с помощью оператора присваива ния (который мы скоро рассмотрим), а может и не задавать. В последнем случае в пе ременной будет храниться либо нуль, либо случайное значение. Что же касается языка С#, то в нем с помощью специального литерала про граммист может указать, что переменная вообще не содержит никакого значения. Заметим, что литерал 1 не эквивалентен нулевому значению. Он предназначен для использования в тех ситуациях, когда в переменной не хранится ни нулевое, ни какое-либо другое значение.
54
А. В. Фролов, Г. В. Фролов. Язык
Самоучитель
Базовые выражения и операторы С# Итак, мы познакомились с типами данных, переменными и литералами. Теперь нам нужно научиться инициализировать переменные при литералов, а также изу чить основные операторы языка С#. Операторы представляют собой символы, с помощью которых программа может выполнять те или иные действия над переменными, литералами и другими объектами. В результате выполнения оператора всегда получается какое-то значение, представ ляющее собой результат выполнения оператора. Объекты программы, над которыми выполняются действия, называются операнда ми. Например, оператор сложения чисел принимает два операнда. Результатом сложе ния будет число, равное сумме значений операндов, т. е. сумме складываемых чисел. Операторы и операнды формируют выражения. Изучение операторов и выражений языка С# мы начнем с фундаментального опе ратора присваивания. Далее мы рассмотрим арифметические, логические, условные и другие операторы, а также выражения, составляемые с их применением.
Инициализация переменных и оператор присваивания Прежде чем использовать переменную, программа должна задать для нее начальное значение. В языке С# это обязательное условие. Начальное значение может быть зада но с помощью оператора присваивания при объявлении переменной, а также в процес се выполнения программы. Вот несколько выражений, в которых присваиваются начальные значения пере различных типов при объявлении этих переменных: Числовые переменные б е з знака byte myByte ushort uint 0x1233; ulong 6786867867; Числовые переменные со знаком sbyte -25; short distanceY int height -0x1234; long width 567743; Переменные с плавающей точкой float 3.1415926F; double decimal totalAccountValue 9000000000m; Текстовый символ char Текстовая строка string s t r i n g message Глава
"Ivan" "Гостиница
Базовые понятия и определения
55
Перечисление enum B a s e C o l o r s Red,
Green,
Blue
Инициализаторы всех типов данных, кроме перечисления, выглядят одинаково. Составляя выражение для инициализации, вначале вы указываете тип дан ных, затем имя переменной, потом оператор присваивания в виде знака равенства и, наконец, литерал типа. После инициализации программа может присвоить переменной новое значение с помощью все того же оператора присваивания: int х х 1; Здесь мы вначале присваиваем переменной х значение 0, а затем — значение Для инициализации одной переменной может использоваться другая, уже инициа лизированная ранее переменная: int х int у int Z Z
Х;
у;
Если нескольким переменным нужно присвоить одно и то же значение, это можно сде лать за один прием при помощи многократного использования оператора присваивания: int х, х у
у,
z
z;
1
Такое выражение вычисляется справа налево. Вначале значение 1 присваивается переменной z, затем значение переменной z присваивается переменной у, и, наконец, значение переменной у присваивается переменной х. В результате все 3 переменные будут значением
Инициализация перечислений По умолчанию компилятор автоматически присваивает значения элементам перечис ления начиная со значения 0. Если вам нужно определить конкретные значения для элементов перечисления, это можно сделать с помощью оператора присваивания Вот пример инициализированного перечисления: enum Days Monday Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
56
понедельник вторник среда четверг пятница суббота воскресенье А В. Фролов, Г.
Фролов. Язык
Самоучитель
Здесь нумерация элементов начинается не с нуля, как это принято по умолчанию, ас Таким образом, понедельнику будет соответствовать значение вторни ку — 2 и т. д. Ниже мы применили инициализацию всех элементов перечисления шестнадцатеричными числами (для обозначения чисел используется префикс Ох), а также изменили базовый тип enum B a s e C o l o r s
uint
Red Green Blue При инициализации перечислений можно использовать выражения, зависящие от других элементов перечисления, например: BaseColors Red Green Blue White
uint
OxFFOOOO, OxOOFFOO, OxOOOOFF, Red | Green | B l u e
Здесь значение элемента означающего белый цвет, получается путем по разрядного сложения красного, зеленого и голубого компонентов цвета.
Проверка результата инициализации Теперь, когда мы познакомились с операторами инициализации, давайте напишем простую программу, инициализирующую переменные нескольких типов и отобра жающую результаты инициализации в консольном окне. Исходный текст такой программы представлен в листинге Листинг
1.2.
Файл
u s i n g System; namespace DataTypes class
DataTypesApp
s t a t i c void
args)
string helloMessage
"Hello,
C#
3.1415926F;
Глава
понятия и определения
57
true; false; ложь char
Прежде всего программа инициализирует текстовую при помощи текстового литерала С# string helloMessage
строку
" H e l l o , C#
при помощи метода W r i t e L i n e наша программа отображает содержимое переменной на консоли:
Обратите внимание: теперь мы передаем методу W r i t e L i n e не литерал, как это было в самой первой программе (листинг а заранее проинициализированную пе ременную Но результат будет тот же текст сообщения с приветствием появится в окне. В следующих двух строках мы инициализируем числовую переменную в формате с плавающей точкой приблизительным значением числа л, а затем выводим содержи мое этой переменной на консоль: 3.1415926F; Здесь мы применили новый формат вызова метода W r i t e L i n e . Теперь этому ме тоду передаются два параметра, разделенные запятой. Первый из них представляет со бой буквальный строковый литерал а второй — число вую переменную p i N u m b e r . Метод W r i t e L i n e обладает большими возможностями, но мы будем изучать их постепенно по мере изложения материала. В данном случае мы передали в качестве первого параметра так называемую строку формата, определяющую, что и как нужно выводить на консоль. Второй параметр представляет собой переменную, содержимое которой нужно вывести. Во время работы программы метод вначале выведет на консоль стро ку « Ч и с л о "ПИ" а затем вместо конструкции подставит значение второ го параметра метода, т. е. содержимое В следующем фрагменте программы, отображающем содержимое двух логических переменных, мы тоже указали методу строку формата:
58
А
Фролов, Г. В.
Язык С#. Самоучитель
true; false; ложь При обработке строки формата конструкции ставится в соответствие со держимое переменной itlsTrueValue, передаваемое методу W r i t e L i n e в ка честве второго параметра. Конструкция заменяется содержимым перемен ной itlsFalseValue, передаваемой методу W r i t e L i n e через третий параметр. С помощью одного обращения к методу WriteLine можно вывести на консоль несколько значений, указывая их в параметрах метода после строки формата. Этим удобным приемом мы будем часто пользоваться в наших примерах программ. В завершающем фрагменте кода программы мы выводим содержимое переменной, символьным литералом: char
Вот что появится на консоли в результате работы программы: H e l l o , С# w o r l d ! Число 3,141593 Правда True, ложь False Символ-разделитель с Обратите внимание, что в качестве значений логических переменных отобразились строки True и а не 1 и 0. Как мы уже говорили, в языке С# логическим пере менным не ставятся в соответствие никакие числа или символы. А что будет, если попытаться вывести на консоль содержимое переменной, которая еще не была проинициализирована? Это у вас не получится. Попробуйте, например, добавить к программе следующие две строки: int
notlnitialized; Ошибка!
Здесь мы объявили числовую переменную с именем типа int и, не присвоив ей никакого значения, пытаемся отобразить содержимое этой перемен ной на консоли методом В процессе компиляции вы получите следующее сообщение об ошибке: Use o f u n a s s i g n e d l o c a l v a r i a b l e Как мы уже говорили во Введении, язык С# не допускает использования неинициа лизированных переменных. Как правило, это происходит по ошибке. Язык С#, в отли чие от языков С и С++, позволяет обнаруживать ошибки обращения к неинициа лизированным переменным еще на этапе компиляции, что значительно упрощает от ладку программ. Глава
Базовые понятия и определения
59
Значение в левой части Как вы теперь знаете, с помощью оператора присваивания можно задать начальное переменной при объявлении или изменить это значение в процессе вы полнения программы. При этом изменяемое значение должно находиться слева от опе ратора присваивания, а литерал или другое выражение, применяемые для инициализа ции или изменения значения, — справа: int х
х; 1;
Программа, однако, не может изменить значение литерала, поэтому следующие строки будут ошибочными: 1 = 2; "Text" int 2 x
"New
Для объекта, который может быть использован для присваивания (например, пере менная), придуман специальный термин — Этот термин образован как сокра щение от left value (значение в левой части). Таким образом, в левой части оператора присваивания всегда должен располагать ся объект типа l v a l u e .
Математические операторы В языке С# имеются математические операторы, предназначенные для сложения, вы читания, умножения, деления и вычисления остатка от деления. Эти операторы, а так же используемые для их обозначения символы приведены в табл. Таблица
Математические
Символ
Оператор Сложение
-
Вычитание
*
Умножение Деление
%
Вычисление остатка при целочисленном делении Рассмотрим использование этих операторов в программах С#.
Сложение При помощи оператора сложения можно складывать содержимое числовых перемен ных и числовых литералов. Результат операции должен быть записан в переменную, т. е. в объект типа l v a l u e . Результат операции сложения получается путем вычисления суммы двух склады ваемых величин. Так как в операции принимают участие два операнда, операция сло жения называется Помимо бинарных операторов имеются и так 60
В Фролов, Г. В. Фролов Язык С# Самоучитель
унарные (unary) операторы, имеющие дело только с одним операндом. Унарными операторами мы займемся позже в этой главе. Для применений оператора сложения мы подготовили программу, исходный текст которой представлен в листинге 1.3. 1.3.
Файл
u s i n g System; namespace class
MathOperatorsApp
s t a t i c void i n t x, y, x 1; У
args) z;
у string hello s t r i n g cSharp s t r i n g message
z
"Hello, "C#"; hello cSharp
В первой строке метода M a i n , получающего управление при запуске программы, мы объявили 3 переменные типа i n t с именами х, у и z: i n t х,
у,
Для инициализации переменной х мы использовали числовой литерал х Здесь пока нет для нас ничего нового. Но вот в следующей строке для инициализа ции переменной у мы использовали сумму двух числовых литералов: У
1
2;
В результате переменная у будет содержать значение 3. Далее мы инициализируем переменную z
х
у
10
В нее записывается сумма значений, хранящихся в переменных х и у, к которой прибавляется число 10. Глава
Базовые понятия и определения
61
После выполнения всех этих действий содержимое наших числовых переменных выводится на консоль при помоши известного вам метода WriteLine: у
z
x, y,
Забегая вперед, скажем, что оператор можно применять не только к чи словым, но и к строковым данным. При этом сложение заменяется операцией конка тенации, т. е. слиянием строк. В следующем фрагменте кода мы объявляем две текстовые переменные, инициали зируя их строковыми литералами. Затем мы выводим на консоль результат «сложе ния» этих текстовых переменных, добавляя к ним еще один текстовый литерал: string hello s t r i n g cSharp string
"Hello, "C#"; hello cSharp
Вот что появится в консольном окне после запуска программы: х у = 3 , H e l l o , С# w o r l d ! Как видите, получился ожидаемый результат. Числа сложились, а 3 текстовые строки слились в одну. Опять же, забегая вперед, скажем, что в языке С#, так же как и в языке С++, допус кается переопределение большинства операций. Что касается текстовых строк, то по умолчанию операция сложения для них заменена конкатенацией. Как вы узнаете, в языке С# можно конкатенировать текстовые строки с любыми объектами. Позже вы научитесь создавать свои собственные типы данных и определять для них смысл опе раторов.
Вычитание В операции вычитания, так же как и в только что рассмотренной операции сложения, принимают участие два операнда, int х int у int z
4; 2; х-у;
Здесь в переменную z записывается результат вычитания значения переменной у из значения переменной х. В операции вычитания могут принимать участие и числовые литералы, например: int х int z
4;
выполнения этой операции в переменную z будет записано отрицательное значение Чтобы продемонстрировать применение оператора вычитания, мы приготовили от дельную программу (листинг
62
А.
Фролов, Г. В. Фролов. Язык
Самоучитель
Листинг
1.4.
Файл
u s i n g System; namespace class s t a t i c void i n t x, y, z; X 1; У -1-5; z x - у у x, y,
z
z)
u i n t uX - 3
uX -
В первых строках метода Main мы в результате вычислений получаем, как и ожи дается, значение Здесь мы также применили знак вычитания для изменения знака числового литерала, что вполне допустимо: У Теперь обратите внимание на следующий фрагмент кода: u i n t uX - 3
uX -
Мы объявили переменную без знака типа u i n t с именем записав в нее число 2. Затем мы вывели на консоль результат вычитания из содержимого этой переменной значения 3. Вот что появилось на консоли после запуска программы: х 1, у -б, z 17 2 - 3 4294967295 В первой строке все правильно — здесь показан результат выполнения арифмети ческих действий. Что же касается второй строки, то в результате вычитания из числа 2 числа 3 мы получили ни много ни 4 294 967 В чем причина ошибки? Все дело в том, что мы попытались использовать в операции вычитания перемен ную без знака Вспомните, что для представления знака числа используется его старший разряд. Если число отрицательное, этот разряд установлен. Глава
Базовые понятия и определения
63
При вычитании из 2 числа 3 должно получиться число В представлении число -1 записывается как OxFFFFFFFF. Выполнив десятичного числа 4 294 295 в например при помощи стандартного калькулятора Microsoft Windows, вы сможете что это число как раз равно OxFFFFFFFF. Но так как тип первого операнда (переменная — целое число без знака, то отрица тельный результат вычитания трактуется как большое положительное число. Составляя выражения для выполнения математических вычислений, очень важно правильно выбирать типы переменных, иначе появятся ошибки. Компилятор С# не сможет обнаружить подобного рода, потому что ему не известно, с какими числами (со знаком или без знака) вы собираетесь работать.
Умножение Для обозначения операции умножения используется символ Эта операция выполняется очевидным образом — результат вычисляется путем перемножения обоих операндов. Вот пример использования операции умножения: int first i n t second int result
f i r s t * second *
Как видите, в операции умножения могут быть задействованы числовые перемен ные и числовые литералы. Хотя смысл операции умножения и способ применения соответствующего опера тора достаточно понятен, здесь есть свои подводные камни. Давайте рассмотрим следующую программу (листинг 1.5). Листинг
1.5.
Файл
u s i n g System; namespace class s t a t i c void i n t x, x 2 X У X z
args)
2; У у
z
0 u i n t uX
127
400000000; 3
64
А В Фролов,
uX *
В. Фролов. Язык С# Самоучитель
uint
4000000000; uXl * 3 ) ;
3
Получив управление, метод M a i n выполняет операции умножения, в которых за действованы переменные х, у и z, а также числовые литералы. Смысл выполняемых действий очевиден, а результат соответствует ожиданиям. Далее мы выводим на консоль результат умножения на 0: 0 127 Здесь тоже все понятно — на консоль будет выведено нулевое значение, так как ре зультат умножения любого числа на 0 будет равен нулю. А вот теперь мы попробуем умножить на 3 два довольно больших целых числа: u i n t uX
400000000; * 3
u i n t uXl
uX * 3 ) ;
4000000000;
Результат всех вычислений показан ниже: х 2, у 4, z 80 127 0 0 400000000 * 3 1200000000 4000000000 3 3410065408
* 3
uXl *
В результате умножения числа 400 000 000 на 3 получилось, как мы и ожидали, значе ние 1 200 000 000. Однако та же самая над числом 4 000 000 000 привела к не правильному результату — вместо 000 000 000 у нас получилось 3 065 Где же ошибка? Ошибка была допущена при выборе типа данных для переменной u X l . Если вы по смотрите на табл. 1.2, то увидите, что в переменной типа u i n t можно хранить значения, не превосходящие 4 294 967 295. Мы же записать туда число 000 000 000, почти втрое превосходящее максимально допустимое. В результате произошло перепол нение и старшие разряды результата умножения оказались потеряны. Чтоб исправить ошибку, нужно заменить тип u i n t на u l o n g , специально предна значенный для хранения больших чисел. После этого операция умножения будет вы полнена правильно.
Деление Для обозначения операции деления используется слеш — делимое и делитель: int
X
у int z Глава 3 Язык
В операции участвуют два
4 х/у; Базовые понятия и определения
65
Если делятся целые числа, то остаток от деления теряется. В результате деления чисел с плавающей точкой получается число с плавающей точкой. Если один из опе рандов является целым числом, а другой — числом с плавающей точкой, то в резуль тате получится число с плавающей точкой. Как и во многих других языках программирования, деление на нуль не допускается. Все вышеперечисленные особенности операции деления демонстрирует програм ма, исходный текст которой приведен в листинге 1.6. Листинг
1.6.
ch01\MathOperators3\MathOperatorsApp3.cs
u s i n g System; namespace class static void
args) 3 4
6 6 4.0 4
int x int у int z
3); 6.0 6.0
4.0);
5 0; x/y;
Если запустить эту программу на выполнение, она выведет на консоль следующие строки:
6.0
= 2 = 1 4.0 4
1,5
Обратите внимание, что при целочисленном делении остаток действительно теря ется. Об этом нам говорит вторая строка с результатами деления числа 6 на число 4. Третья строка представляет результат деления таких же чисел, но в формате с пла вающей запятой. Как видите, теперь в результате вычислений мы получили ожидае мый результат — значение 1,5. Последняя строка отражает результат деления числа с плавающей точкой на целое число. Из нее видно, что результат такого деления в виде числа с пла вающей запятой. 66
А. В. Фролов, Г. В. Фролов. Язык С#. Самоучитель
Теперь займемся фрагментом программы, расположенным между операторами комментариев * и * int х int у int z
5; 0; х/у;
Напомним, что компилятор игнорирует все, что находится между этими операторами. Если убрать операторы комментариев, оттранслировать, а затем запустить про грамму на выполнение, то на экране появится сообщение об ошибке следующего вида: has occurred in В сообщении говорится, что при выполнении программы произошло так называемое исключение класса DivideByZeroException. Механизм исключений повсеместно применяется в языке С# для обработки оши бок, и мы рассмотрим его позже в гл. 9. Сейчас же мы только скажем, что состояние исключения, которое привело к аварийному завершению программы, произошло в ре зультате деления на нуль. Механизм обработки исключений, предусмотренный в С#, позволяет перехватить из программы ошибочную ситуацию и обработать ее соответствующим образом. Об работчик исключения может, например, выдать осмысленное сообщение на экран, проигнорировать ошибку или скорректировать программы.
Вычисление остатка при целочисленном делении Как мы говорили в предыдущем разделе, при целочисленном делении остаток отбра сывается. В некоторых случаях, однако, программисту нужен именно остаток от деле ния двух целых чисел, например чтобы узнать, делятся эти числа нацело или нет. Для вычисления остатка при целочисленном делении в языке С# используется опе ратор деления по модулю Он применяется таким же образом, что и оператор деле ния, но в результате вычисляется не частное, а остаток. В листинге мы представили исходный текст программы, демонстрирующей вы полнение операций целочисленного деления и деления по модулю. Листинг
1.7.
Файл
u s i n g System; namespace class s t a t i c void 5%2);
Глава
Базовые понятия и определения
args) 2
5 % 2
67
Вот что будет введено на консоль после запуска этой программы: 5 / 2
2, 5
2
1
Если разделить 5 на 2, то частное будет равно двум, а остаток — единице. Деление по модулю на нуль, так же как и обычное целочисленное деление на нуль, в языке С# Попытка выполнить такое деление приведет к исключению класса
Унарные операторы До сих пор мы изучали бинарные (binary) операторы, работающие с двумя операнда ми. Однако в языке С# имеется также набор унарных (unary) операторов, имеющих де ло только с одним операндом. Набор унарных операторов языка С# аналогичен набору унарных операторов языков С и С++ (табл. Таблица
1.8.
Унарные операторы Оператор
Символ Унарный плюс Унарный минус
-
Инкремент
—
Декремент Унарное логическое отрицание Унарная поразрядная операция дополнения
~
Преобразование типа выражения Рассмотрим эти операторы.
Унарный плюс Результатом выполнения унарного оператора плюс над числовым операндом является значение операнда, т. е. фактически унарный плюс не выполняет никаких действий над числовыми значениями.
Унарный минус Унарный минус изменяет знак операнда на противоположный: int х int у
-х;
Здесь в первой строке унарный минус добавлен в качестве префикса к числово му литералу 5, в результате чего в переменную х будет записано отрицательное значение - 5 . Во второй строке оператор унарного минуса используется для того, чтобы записать в переменную у значение, равное по модулю значению переменной х, но противопо ложное по знаку.
68
В. Фролов. Г. В. Фролов. Язык С#. Самоучитель
Инкремент и декремент Операторы инкремента и декремента могут использоваться либо в пре фиксной либо в постфиксной форме. Префиксная форма предполагает разме щение оператора перед операндом, а постфиксная — после операнда. Вот примеры использования постфиксного оператора int х х+ + В результате выполнения этого фрагмента кода переменная х будет содержать зна чение 6. По выполняемым результатам унарный оператор использованный во второй строке, эквивалентен следующему бинарному оператору: X
X
Аналогично вместо унарного оператора х
можно использовать бинарный оператор:
х -
Если используется префиксная форма унарного оператора инкремента или декре мента, то содержимое переменной изменяется до ее чтения, а если постфиксная — по сле. Поясним это на примере. Ниже мы использовали префиксный оператор инкремента, поэтому вначале содер жимое переменной х увеличивается на единицу, а затем ее новое значение, равное шести, используется для инициализации переменной у: int х int у
++х;
При использовании постфиксного оператора инкремента все будет по-другому: int х int у
х++;
Вначале переменная у будет проинициализирована исходным значением, храня щимся в переменной х, равным пяти. После этого содержимое переменной х увели чится на единицу. Таким образом, выполнение любого из приведенных выше фрагментов кода приве дет к тому, что в переменную х будет записано значение 6. Однако содержимое пере менной у будет различным. В первом случае оно будет равно шести, а во втором —
Унарное логическое отрицание Оператор унарного логического отрицания вычисляет значение своего логического операнда, а затем изменяет его на противоположное значение. Вот пример использо вания этого оператора: bool bool check Глава
true;
Базовые понятия и определения
69
Здесь мы вначале записали в логическую переменную значение t r u e , а затем использовали это значение и оператор отрицания для инициализации логической переменной c h e c k . В результате переменная c h e c k будет содержать зна чение f a l s e . Оператор унарного логического отрицания может использоваться с любым логиче ским выражением, а также с логическими литералами: bool isBlackColor Подробнее о логических выражениях мы поговорим позже, в разделе «Логические операторы» этой главы.
Унарная поразрядная операция дополнения Этот оператор вычисляет поразрядное дополнение своего операнда. В результате вы полнения этой операции состояние всех разрядов операнда будет изменено на проти воположное. Вот пример использования унарной поразрядной операции дополнения: u i n t op uint Операции с отдельными разрядами мы рассмотрим подробнее позже, в разделе «Поразрядные операторы».
Преобразование типа выражения В процессе обработки программы транслятор выполняет явные и неявные преобразо вания типов данных. Например, в следующей строке перед сложением целочисленное значение 5 неявным образом преобразуется в тип с плавающей точкой: long х В языке С# имеется унарный оператор, позволяющий выполнять явное преобразо вание типов данных. Он используется следующим образом: long х Здесь при помощи конструкции мы указали, что перед сложением необхо димо преобразовать числовой литерал 5 в литерал с плавающей точкой типа l o n g . В гл. 5 мы расскажем о преобразованиях типов в С# более подробно.
Пример использования унарных операторов Исходный текст программы, демонстрирующей использование некоторых унарных операторов, представлен в листинге 1.8. Листинг
1.8.
Файл
u s i n g System; namespace UnaryOp class s t a t i c void
70
А. В.
Г. В. Фролов. Язык С#. Самоучитель
int x int у
++X;
x, y) int int
5; xl,
u i n t op uint
yl);
0x5a5a; ~op; со
op,
Первые два фрагмента показывают отличия между префиксной и постфиксной унарной операцией инкремента. Третий фрагмент демонстрирует выполнение унарной поразрядной операции дополнения. Обратите внимание, что значение операнда (пере менной задано в шестнадцатеричном виде и равно 0 х 5 а 5 а . После выполнения преобразования состояние всех разрядов операнда изменяется на противоположное. Вот результат работы программы: х xl
6, у 6, y l 5 5А5А, СО
FFFFA5A5
Как видите, префиксная и постфиксная операции инкремента не эквивалентны. Что же касается унарной поразрядной операции дополнения, то она выполнилась так, как мы ожидали — состояние всех разрядов изменилось на противоположное (или, как говорят, оказалось инвертированным). Чтобы отобразить результат операции дополнения в шестнадцатерич ном виде, мы указали в строке формата методу W r i t e L i n e спецификатор формата X, отделив его от номера параметра двоеточием: со В табл. 1.9 мы перечислили стандартные символы, которые можно использовать для определения формата вывода при помощи метода W r i t e L i n e . Таблица
1.9.
Символы с, С
Стандартные символы формата Тип
данных,
соответствующий
D
Десятичный
Е
Научный (экспоненциальный)
f,
F
Формат с фиксированной точкой
g,
G
Общий формат
е,
Глава
формату
Деньги
Базовые понятия и определения
71
Тип
Символы n, N
данных,
соответствующий
формату
Числовой формат Формат для представления чисел с округлением. Число, преобразованное в строку с использованием этого формата, сохранит свое ние при обратном преобразовании из текстовой строки в число
R
X
формат
Постепенно мы расскажем вам и о других возможностях форматирования тексто вых строк, отображаемых на консоли методом
Составные операторы В разделе «Инкремент и декремент» мы рассказывали вам об унарных операторах ин кремента и декремента, позволяющих соответственно увеличивать или уменьшать на единицу значение операнда. Использование этих операторов позволяет сделать исходный текст программы бо лее компактным и легко читаемым. Кроме того, такой код будет выполняться быстрее из-за оптимизации, выполняемой компилятором. В языке С# имеется еще целый ряд так называемых составных операторов, при менение которых позволяет улучшить читаемость исходного текста программы и уве личить скорость ее работы. Составные операторы получаются комбинированием бинарного оператора и опера тора присваивания (табл. Таблица
1.10.
Составные операторы
Символ Сложение и присваивание Вычитание и присваивание Умножение и присваивание
*—
Деление и присваивание Вычисление остатка от деления и присваивание Логическое И и присваивание
|=
Логическое ИЛИ и присваивание Логическое И С К Л Ю Ч А Ю Щ Е Е ИЛИ и присваивание Сдвиг влево и присваивание Сдвиг вправо и присваивание
Как пользоваться составными операторами? Предположим, нам нужно увеличить значение, хранящееся в переменной х, на 2. Это можно сделать при помощи обычного бинарного оператора сложения, например, так: int
X
X
х
72
2 А В Фролов, Г. В. Фролов, Язык С#. Самоучитель
Если же использовать составной оператор сложения и присваивания, то соответст вующий код будет выглядеть проще: х Как видите, здесь переменная х упоминается только один раз. Аналогичным образом можно использовать и другие числовые составные операторы: х 3; х 5; х \= х 2; Пока речь идет об изменении значений обычных числовых переменных, преиму щество использования составных операторов, возможно, не столь очевидно. Однако если необходимо увеличить или уменьшить поле структуры или элемент массива, то оно станет намного заметнее. О структурах и массивах мы поговорим позже. Тогда же мы и вернемся к рассказу о преимуществах составных операторов. Логические составные операторы и операторы сдвига используются аналогичным образом. Соответствующие им бинарные поразрядные операторы описаны в следую щем разделе. Если вы ранее программировали на языке Pascal или другом языке, не допускаю щем использования рассмотренных выше операторов инкремента, декремента, а также составных операторов, мы рекомендуем изучить новые возможности. Хотя на первый взгляд обозначение этих операторов могут выглядеть для вас непонят ными и нелогичными, время, потраченное на их изучение, не пропадет даром. Эти опера торы действительно помогают создавать более эффективный программный код. Что же касается непонятного и нелогичного, то взгляните на внешний вид операто ра присваивания языка Pascal, представляющего комбинацию знака равенства и двое точия С точки зрения программиста, работающего с языками С и С++, назначе ние комбинации символов тоже может показаться непонятным и нелогичным — при чем тут двоеточие?
Поразрядные операторы Ранее в разделе «Элементарные типы данных» этой главы мы рассказывали вам о бай тах и разрядах. В языке С# имеются так называемые поразрядные операторы, с помо щью которых можно изменять состояние отдельных или всех разрядов значений, хра нящихся в переменных. Поразрядные операторы предназначены для выполнения операций логическое И (AND), логическое ИЛИ (OR), логическое ИСКЛЮЧАЮЩЕЕ ИЛИ и логи ческое отрицание (NOT), а также операций поразрядного сдвига влево и вправо. Соответствующие им символы перечислены в табл. Таблица
Поразрядные операторы Оператор
Символ Логическое И
|
Глава
Логическое ИЛИ Логическое ИСКЛЮЧАЮЩЕЕ ИЛИ Базовые понятия и определения
73
Оператор
Символ Унарная поразрядная
дополнения
Сдвиг влево Сдвиг вправо Расскажем подробнее об этих операциях и соответствующих поразрядных операторах.
Поразрядное логическое И Операция поразрядное логическое И обозначается символом и выполняется над двумя операндами. При этом состояние каждого разряда результата операции зависит от состояний исходных разрядов обоих операндов. Если разряды с одинаковыми номерами установлены, то разряд результата тем же номером тоже будет установлен. В противном случае разряд результата будет сброшен. Для наглядности мы показали результат выполнения операции логическое И над двумя одноразрядными операндами в табл. Таблица
1.12.
Первый
Выполнение операции логическое И Второй
операнд
Результат
0
0
0
0
1
0
1
0
0
1
1
1
Как видите, чтобы разряд результата был установлен, разряды первого и второго операнда должны быть установлены. На рис. мы показали результат выполнения поразрядной операции логическое И для 2 байтов данных с произвольно установленными разрядами. 7
6
5
4
3
1
1
1
0
0
I
I |
1
1
I
I
I
I Первый операнд
Второй операнд
1
°
I° I
2
1
1.10.
I
I
Результат операции логическое И
поразрядной операции И
В байте результата установлены только те разряды, которые установлены и в пер вом и во втором операнде.
Поразрядное логическое ИЛИ Операция логическое ИЛИ, как и только что рассмотренная операция И, выполняется над двумя операндами. Она обозначается символом Состояние каждо го разряда результата операции зависит от состояний исходных разрядов обоих опе рандов, но эта зависимость другая.
74
А
Фролов, Г. В. Фролов. Язык С#. Самоучитель
разряды с одинаковыми номерами сброшены, то разряд результата с тем же номером тоже будет сброшен. В противном случае разряд результата будет установлен (табл. Таблица
1.13.
Выполнение операции логическое ИЛИ Второй
Результат
0 0
0 1
1
0
1 1
1
1
1
0
Как видите, чтобы разряд результата был установлен, разряды первого или второго операнда должны быть установлены. Это же иллюстрирует и рис. 7
6
S
I
1
°
I
4
3
I
2
1
°
0
I
0
I Первый операнд Второй операнд
1
операции логическое ИЛИ Выполнение поразрядной операции логическое ИЛИ
Поразрядное логическое ИСКЛЮЧАЮЩЕЕ ИЛИ Операция поразрядное логическое ИСКЛЮЧАЮЩЕЕ ИЛИ работает следующим об разом. Если разряды имеют одинаковое состояние (оба сброшены или оба установле ны), то разряд результата с тем же номером будет установлен. В противном случае разряд результата будет сброшен (табл. Таблица
ИСКЛЮЧАЮЩЕЕ ИЛИ Второй
операнд
Результат
0
0
1
0 1
1 0 1
0 0
1
1
Чтобы разряд результата был установлен, разряды первого или второго операнда должны находиться в одинаковом состоянии (рис. 7
6
5
4
3
2
1
0
1
1
1
0
0
1
1
0
Первый операнд Второй операнд
1
0
0
0
0
1
0
1
Выполнение поразрядной операции Глава
Базовые понятия и определения
Результат логическое ИСКЛЮЧАЮЩЕЕ ИЛИ ИСКЛЮЧАЮЩЕЕ ИЛИ
75
ИСКЛЮЧАЮЩЕЕ ИЛИ удобно применять в тех случаях, когда нужно определить, находятся ли разряды двух числовых значений в одинаковых состояниях, причем не имеет значения, какое это состояние или 0).
Унарная поразрядная операция дополнения Унарная поразрядная операция дополнения обозначается символом ~. Она заменя ет состояние каждого разряда противоположным. Эта операция только над одним операндом (табл. т. е. она унарная. Таблица
Выполнение унарной поразрядной
операции
дополнения
Результат
Операнд 0
1
1
0 Сказанное иллюстрирует рис. 1.13. 7
6
1
1
0
0
5
4
3
2
1
0 0
1
1
0
0
0 0
Операнд
1
Результат логического отрицания НЕ
Выполнение поразрядной операции логическое отрицание НЕ Как видно на этом рисунке, после выполнения операции логическое отрицание НЕ все разряды байта изменили свое состояние.
Поразрядный сдвиг В языке С# имеются два оператора поразрядного сдвига: и Первый из них вы полняет поразрядный сдвиг влево, а второй — вправо. При сдвиге влево старшие разряды теряются (отбрасываются), а на место младших разрядов записываются нули. Способ выполнения сдвига вправо зависит от того, над данными какого типа вы полняется операция. Если операция сдвига вправо выполняется над целыми числами без знака ( b y t e , u i n t , u l o n g ) , младшие разряды теряются, а на место старших разрядов запи сываются нули. Когда операция сдвига вправо выполняется над целыми числами со знаком ( s b y t e , i n t , l o n g ) , т о для чисел старшие разряды заполняются нулями, а для отрицательных — единицами. Младшие разряды теряются в любом случае. Пример выполнения операции поразрядного сдвига для чисел со знаком и без знака показан на рис.
76
А
Фролов, Г. В. Фролов. Язык С#. Самоучитель
7
6
5
4
3
2
1
0 Операнд Сдвиг вправо на 3 разряда (число без знака)
Операнд Сдвиг вправо на 3 разряда (число со знаком)
Сдвиг влево на 3 разряда (число без знака) Рис.
Выполнение поразрядной операции сдвига
Пример использования поразрядных операторов Выполнение поразрядных операторов демонстрируется в программе, исходный текст которой приведен в листинге 1.9. Здесь мы последовательно применяем несколько различных поразрядных операторов к числу 0х5А5А. В двоичном представлении это число записывается как 1.9.
Файл
u s i n g System; namespace B i t w i s e class
BitWiseApp
s t a t i c void
args)
u i n t op 0x5A5A; uint result op & OxA; op & OxA op, result
op | OxF; op | OxF
result
op op
OxF
op, Глава
Базовые понятия и определения
77
result
-op;
op, result
op
3 op
3
op
3
op, result
op
Результат работы нашей программы представлен op op op
5А5А, 5А5А,
op
А | OxF 5A5F op OxF FFFFA5A5 5A5A, op 3 2D2D0 5A5A, 3 B4B
Мы оставляем вам эту программу для самостоятельного изучения. Представьте ре зультаты работы поразрядных операторов в двоичном виде и убедитесь, что все дейст вия над разрядами исходного числа выполнены правильно.
Логические операторы Логические операторы (табл. 1.16) предназначены для выполнения логических опера ций над логическими данными, объявленными в программе при помощи ключевого слова b o o l . Напомним, что логические переменные могут принимать одно из двух значений — t r u e (истина) или f a l s e (ложь). В языке С# эти значения никак не соотносятся с числами 1 и 0. Результатом выполнения логического оператора всегда является ло гическое значение t r u e или f a l s e . Таблица
1.16.
Логические операторы
Символ
Оператор Логический оператор И
II
Логический оператор ИЛИ Унарное логическое отрицание
Если оба ния этой операции
78
оператора И равны t r u e , то результатом выполне t r u e . В противном случае результат будет
А В Фролов, Г.
Фролов. Язык С#. Самоучитель
Если один из операндов логического оператора ИЛИ равен t r u e , то результатом выполнения этой операции будет t r u e . Если же оба операнда равны то и ре зультат будет тоже равен Вот пример использования логических операторов: b o o l op true; bool r e s u l t l op bool result2 op bool result3
false; false;
В результате выполнения этих операторов в переменных r e s u l t l и r e s u l t 3 бу дет храниться значение а в переменной r e s u l t 2 — значение t r u e . Логические операторы широко используются в так называемых условных операто рах, о которых мы расскажем в следующей главе нашей книги.
Операторы отношения Результат выполнения бинарных операторов отношения (табл. 1.17) представляется в виде логических значений t r u e или f a l s e . 1.17.
Операторы отношения
Символ
Оператор Равно Не равно Меньше Меньше или равно Больше Больше или равно
Операторы отношения позволяют сравнивать значения переменных и выражений между собой. Например, результат вычисления выражения 2 4 будет t r u e , а ре зультат вычисления выражения 1 2 будет равен Операторы отношения, так же как и рассмотренные в предыдущем разделе логиче ские операторы, используются в условных операторах, рассказ о которых мы отложим до следующей главы.
Приоритеты операторов В своих программах вы часто будете составлять выражения из нескольких операторов. Не исключено, что в этих выражениях будут встречаться унарные и бинарные операторы. Вот пример составного выражения: int х int у int
Глава
1; 2; 2 + х * 3 / 5
+
у++
Базовые понятия и определения
79
Рассматривая такое выражение, трудно сразу сказать, какое значение получит пере менная после его вычисления. Проблема состоит в том, что далеко не всегда оче виден порядок выполнения отдельных операторов в составном выражении. Однако в зави симости от порядка выполнения может получиться разный результат. Приведем более простой пример. Пусть нам нужно вычислить значение переменной х после выполнения следующей строки х
2
3 * 4;
Если вычислять это выражение слева направо, то сначала нужно сложить числа 2 и 3, а затем результат умножить на 4. После подобных вычислений значение перемен ной х будет равно 20. Если же вычислять это выражение справа налево, то сначала нужно умножить 3 на 4, а затем к результату добавить 2. При этом в переменную х будет записано значение 14. Чтобы избежать неоднозначности, все операторы в языке С# (как, впрочем, и в других языках программирования) имеют свой Более приоритетные операторы выполняются в первую очередь. В приведенном выше примере оператор умножения имеет высший приоритет по сравнению с сложения, поэтому в результате вычисления получится значение При необходимости мы можем задать порядок вычислений в составных выражени ях подобного рода при помощи скобок, например: int х
(2
* 4;
Здесь мы сообщаем компилятору, что вначале нужно сложить числа 2 и 3, а затем умножить полученный результат на 4. Для наглядности мы рекомендуем в сложных выражениях всегда задавать порядок выполнения операций явным образом. Это поможет избежать ошибок, связанных с неправильной оценкой приоритетов операторов. В табл. мы привели полную таблицу приоритетов операторов С#. С некоторы ми операторами, представленными в ней, вы уже знакомы, а с некоторыми познакоми тесь позже. Таблица
1.18.
Приоритеты операторов Операторы
f(x) new t y p e o f checked -
Категория
а[х] х++ checked un ++x
(T)x
Простые операторы
Унарные операторы Операторы умножения и деления
%
Аддитивные операторы Операторы сдвига i s
80
a s
Операторы отношения и проверки типа В. Фролов, Г. В. Фролов. Язык С#. Самоучитель
Операторы
Категория Операторы
и неравенства
Поразрядный оператор И Поразрядный оператор ИСКЛЮЧАЮЩЕЕ Поразрядный оператор ИЛИ Логический оператор И
II
Логический оператор ИЛИ Оператор условия Операторы присваивания
Как видите, по приоритету вначале выполняются простые операторы, затем унар ные, после них — операторы умножения и деления, затем — сложения и сдвига. Опе раторы отношения, равенства и неравенства, а также логические операторы имеют низкий приоритет. Но самый низкий приоритет отдается операторам присваивания (простым и составным).
Глава
Базовые понятия и определения
81
Глава 2. Управляющие операторы В языке С# имеется специальный набор операторов, управляющих ходом выполнения программы. С некоторыми из этих операторов мы уже сталкивались в предыдущей главе, а с некоторыми нам еще только предстоит познакомиться. От программ было бы мало проку, если бы они представляли собой набор команд, выполняющихся только в строго линейной последовательности. Именно так ведут се бя все программы, рассмотренные нами в предыдущей главе. Чтобы программы могли проверять какие-либо условия и на основании результатов этих проверок выполнять те или иные действия, в любом языке программирования имеются условные операто ры и операторы выбора. Для выполнения каких-либо повторяющихся действий применяются специальные итерационные (циклические) операторы. И наконец, операторы безусловного перехода позволяют программе прервать ис полнение текущей последовательности команд, переключившись на дру гой последовательности команд. Все эти команды позволяют управлять ходом строк программы, поэто му они и называются управляющими. В табл. мы перечислили некоторые управляющие операторы С# и кратко опи сали их применение. Таблица
2.1.
if
goto for
do continue break return
Управляющие операторы С# Выполнение строк программы в зависимости от значения логиче ского выражения Оператор выбора. Используется для того или фрагмента программы в зависимости от значения переменной или выражения Оператор безусловного перехода к выполнению новой последова тельности команд Оператор цикла. Проверка выполнения условия завершения, а также итерация выполняются в начале цикла Оператор цикла с проверкой условия завершения, выполняемой в начале цикла Оператор цикла для просмотра всех элементов массива или коллекции Оператор цикла с проверкой условия завершения, выполняемой в конце цикла Выполнение цикла с начала Прерывание выполнения цикла Возврат управления из метода
После изучения условных и операторов, а также операторов передачи управления мы расскажем вам о пустом и составном операторе (блоке).
82
Условный оператор С помощью условного оператора i f программа может проверить выполнение некото рого условия и на основании результатов проверки принять решение о выполнении то го или иного фрагмента кода. Мы будем делить условные операторы на простые и вложенные. Кроме того, мы рассмотрим сокращенную запись условного оператора.
Простой условный оператор Исходный текст практически любой реальной программы содержит простые и вложенные условные операторы. Вот внешний вид простого условного оператора в наиболее общем виде: 1> [else 2>]
Оператор может дополнительно содержать необязательную конструкцию e l s e . Так как эта конструкция необязательная, мы заключили ее в квадратные скобки. Действует этот оператор следующим образом. Если логическое равно t r u e , выполняется 1>. В противном случае управление передается оператору 2>. Если же кон струкция e l s e не определена, программа продолжает свою работу со строки, распо ложенной после оператора i f . Приведем простой пример: int i int j if(i
2
j)
i,
j);
else
i,
В этом примере для выражения мы использовали оператор отно шения Операторы отношения были описаны в гл. 1 в разделе «Операторы отноше ния». Если содержимое переменной i больше содержимого переменной то в действие вступит строка, расположенная сразу после оператора i f , а если меньше, то строка, расположенная сразу после оператора В результате работы этого фрагмента программы на консоли будет отображено правильное неравенство 2 В предыдущем примере в зависимости от результата проверки условия мы выполняли одно из двух выражений, ограниченных символом точка с запятой. Если нужно выполнять не одно, а несколько таких выражений, следует использовать фигурные Глава 2. Управляющие операторы
83
int i int j
3;
if(i
0) x j,
i, x)
Здесь мы заключили в фигурные скобки две строки программы с ко торые должны выполняться, если переменная i не равна нулю. В первой строке фрагмента программа делит переменную j на перемен ную i, записывая результат в переменную х. Обратите внимание, что перед делением мы явным образом преобразуем тип переменной j из целого числа в число с плаваю щей запятой. Если этого не сделать, в результате целочисленного деления пропадет остаток от деления.
Вложенный условный оператор Условные операторы допускается вкладывать друг в друга без ограничений. В резуль тате можно проверять довольно сложные условия. Рассмотрим, например, следующий фрагмент программы: int i int j 2
- j)
0)
else b o o l f; f (i if
-
* 2
-
* 2
0",
i,
i)
Здесь мы вначале проверяем значение выражения *2 0. Учитывая ини циализацию переменных i и это выражение должно быть ложно, т. е. равно f a l s e . Далее в дело включается конструкция В зависимости от знака выражения на консоли отображается одна из двух строк. В нашем случае выражение имеет отрицательный знак (оно равно - 2 ) , поэтому на кон соль будет выведено. (2 - 3)
2
0
Второй оператор if выполняется только в том случае, если выражение меньше или равно нулю. В этом операторе анализируется содержимое логической пе ременной
84
А.
Фролов, Г. В. Фролов. Язык
Самоучитель
Если эта переменная равна t r u e , то на консоль выводится текстовая строка, пока занная i
i Переменная f вычисляется следующим образом:
f
(i
i)
Здесь в переменную f записывается результат операции сравнения переменной i с самой собой. Он всегда равен t r u e . Вы можете испытать описанные выше приемы использования простых и вложенных операторов на программе, исходный текст которой приведен в листинге Листинг
Файл
ch02\lfElse\lfElseApp.cs
u s i n g System; namespace I f E l s e class
IfElseApp
static void
args)
int i int j if(i
j)
i,
else if(i
i,
j);
0)
float x
i; i,
- j)
2
j,
0) -
* 2
-
* 2
0",
i,
j);
else
f
0",
i,
j);
b o o l f; (i i) i f (f)
Глава 2. Управляющие операторы
85
Проведите над ней ряд экспериментов. Попытайтесь, например, изменить началь ные значения переменных, а также логические заданные в качестве выра жений для оператора i
Оператор проверки В языке С# существует упрощенный вариант условного оператора, называемый опе ратором проверки или тернарным оператором. В общем виде этот оператор выглядит так: 1>
< О п е р а т о р 2>
Если
значение выражения истинно, 1 > , а если ложно — оператор < О п е р а т о р 2>. Вот простой пример использования оператора проверки: int i
(х
8
вычисляется
оператор
9;
Здесь если значение переменной х больше 100, переменной i присваивается значе ние 8, а если меньше, то 9.
Множественный выбор Очень часто перед программистом встает задача последовательной проверки несколь ких условий, причем при выполнении каждого условия нужно какието действия. Кроме того, отдельно нужно обрабатывать ситуацию, когда ни одно из проверяемых условий не выполнено. Для решения этой задачи можно использовать вложенные условные операторы i f следующего вида: 1>
else < О п е р а т о р 2> else < О п е р а т о р 3>
else < О п е р а т о р N>
Здесь вначале вычисляется Если оно истинно (т. е. равно t r u e ) , то выполняется « Э п е р а т о р 1>. В противном случае вычисляется <Выражение2>. Если оно истинно, выполняется « Э п е р а т о р 2>, и т . д . В том случае, когда все про веряемые выражения ложны, выполняется < О п е р а т о р N>. Использование вложенных операторов i f для множественного выбора демонстри руется в программе, исходный текст которой приведен в листинге 2.2.
86
А. В. Фролов, Г
Фролов, Язык С#. Самоучитель
Листинг
2.2.
Файл
u s i n g System; namespace S e l e c t N u m b e r s class s t a t i c void
args) произвольное
string inputString; inputString int inputNumber catch пустая inputNumber catch неправильный inputNumber
0;
catch inputNumber ввели число 10
inputNumber
100)
"Это число больше 1 0 , но меньше или равно e l s e i f (inputNumber 100) число больше e l s e if (inputNumber 0) число равно else число меньше
Глава 2. Управляющие операторы
87
В эту программу мы внесли много нововведений, из-за чего ее исходный текст может показаться вам довольно сложным. Однако мы рассмотрим все новшества достаточно подробно, чтобы у вас не оставалось относительно назначения всех ее строк. Главное отличие этой программы от предыдущих, описанных в нашей книге, в том, что она не только выводит данные на консоль, но и вводит числа с клавиатуры. Прежде всего с помощью метода W r i t e наша программа сообщает пользователю о том, что он должен ввести число: произвольное число: Здесь вместо метода мы использовали метод Отличие между ними заключается в том, что метод WriteLine после вывод строки на консоль авто матически переходит на новую строку, а метод этого не делает. Для ввода мы использовали известный вам метод ReadLine, однако теперь мы сохраняем полученное от него значение в строковой переменной string
inputString;
inputString Как это работает? Получив управление, метод ReadLine приостанавливает работу программы до тех пор, пока пользователь не введет с клавиатуры текстовую строку, завершив ввода при помощи клавиши Enter. Далее символы введенной строки сохраняются в переменной inputString. Нам, однако, нужно вводить не любые текстовые строки, а только такие, которые пред ставляют целые числа. Чтобы преобразовать текстовую строку в число, нужно вызвать один из методов класса Convert, определенного в пространстве имен System. Для преобразования текстовой строки в 32-разрядное целое число со знаком ис пользуется метод i n t inputNumber; В качестве параметра мы передаем преобразуемую текстовую строку этому методу, а назад получаем искомое число. Преобразование текстовой строки в числа и данные других типов можно выпол нить при помощи методов класса перечисленных в табл. 2.2. Таблица
Методы класса Convert для преобразования строк в числа Метод
Тип числовых данных
Метод
Тип числовых данных
ToBoolean
bool
ToUintl6
ushort
ToChar
char
ToUInt32
uint
ToSbyte
sbyte
ToUInt64
ulong
ToByte
byte
ToSingle
short
ToDouble
ToInt32 88
int
double decimal
А. В. Фролов, Г. В. Фролов. Язык С#. Самоучитель
Применяя эти методы, вы сможете вводить с клавиатуры числовые, логические и символьные значения. В частности, для преобразования строки в число типа i n t нужно использовать метод T o I n t 3 2, что мы и сделали в своей программе. Однако зададим себе вопрос, что будет, если ввести такую строку, которая не может быть преобразована в число, а затем передать ее методу ToInt32. При этом возникнет ошибочная ситуация. Метод T o I n t 3 2 не сможет выполнить преобразование и передаст (или, как еще говорят, возбудит) исключение. Исключениям мы посвятим отдельную главу нашей книги. Сейчас мы только заме тим, что при возникновении исключения нормальный ход выполнения программы на рушается. Если в программе не предусмотрен обработчик исключения, она завершит свою работу с сообщением об ошибке. В процессе преобразования текстовой строки в целое число могут возникать ошиб ки разных типов. Например, пользователь может ввести вместе с цифрами (или вместо цифр) буквы, ввести слишком большое число или число с ошибочным форматом (на пример, вместо целого числа ввести число с плавающей запятой). Наконец, пользова тель может вообще не ввести ничего, просто нажав в ответ на приглашение клавишу Enter. Для того чтобы различить ошибочные ситуации, метод в каждом случае возбуждает исключения разных типов (классов). В своей программе мы предусмотре ли обработку таких исключений: try
catch пустая inputNumber catch неправильный inputNumber catch
Вызов метода во время работы которого может произойти исключение, мы заключили в блок Вслед за блоком t r y мы расположили несколько блоков c a t c h , каждый из кото рых обрабатывает ошибку своего класса и выводит сообщение об ошибке на консоль. того, все обработчики исключений записывают в переменную i n p u t N u m b e r нулевое значение. Глава
Управляющие операторы
89
Конечно, представленный здесь способ обработки ошибок далек от однако мы только продемонстрировали общий принцип. Детальный разговор о том, как обрабатывать ошибки в С#, мы отложим до гл. 9, посвященной исключениям. Но вот, наконец, мы можем вернуться к нашему вложенному оператору i f , ис пользованному для множественного выбора: 10
inputNumber
100)
"Это число больше 1 0 , но меньше или равно e l s e i f (inputNumber 100) число больше 10 e l s e if (inputNumber 0) число равно else число меньше Ниже мы привели выражение, которое вычисляется в самом начале работы вло женного оператора inputNumber
10
inputNumber
100
Как видите, в этом выражении есть два оператора отношения: и а также один логический оператор И, обозначенный символами Выражение проверяет, лежит ли введенное значение в интервале 10, При обработке оператора в соответствии с приоритетами операторов вначале вычисляется значение выражения i n p u t N u m b e r 10. Если оно ложно, то все выражение будет ложно. В этом случае выражение inputNumber 1 0 0 не вычисляется, так как в этом нет никакого смысла. Если же выражение, расположенное слева от оператора истинно, то вычисляет ся выражение i n p u t N u m b e r 1 0 0 . В том случае, когда оба эти выражения ис тинны, наша программа выводит на консоль сообщение о том, что введенное число больше но меньше или равно В том случае, когда введенное число не принадлежит интервалу срабаты вает первый блок e l s e . Внутри ее находится еще один, вложенный оператор про веряющий условие i n p u t N u m b e r 1 0 0 . Далее аналогичным образом с помощью оператора мы сравниваем число с нулем. В том случае, если ни одно из наших условий не выполнено, в дело вступает по следний блок Внутри его находится строка программы, отображающая на кон соли сообщение о том, что введенное число меньше нуля.
Применение логических операций В предыдущей программе для определения принадлежности значения переменной i n p u t N u m b e r интервалу 100] мы использовали следующую конструкцию с ло гическим оператором И: 10
100)
inputNumber
"Это число больше 10, но меньше 90
равно
Фролов,
В Фролов. Язык
Самоучитель
Аналогичного результата можно было бы добиться при помощи вложенного опера тора 10) 100)
"Это ч и с л о б о л ь ш е
10,
н о меньше и л и р а в н о
Если проверяется сложное условие, логические операторы позволят сократить ис ходный текст программы и сделать его нагляднее. Список логических операторов мы привели в гл. 1 (табл. Интерпретация выражений, содержащих большое количество операторов отноше ний и логических операторов, может вызывать определенные трудности. Чтобы не за путаться с приоритетами операций, используйте круглые скобки, например: 20)
10) (х
(inputNumber
Здесь тело оператора будет выполнено в том случае, если число i n p u t N u m b e r принадлежит интервалу ]10, 100] или число х не принадлежит интервалу ]20, 200]. Наличие скобок никак не скажется скорости компиляции или исполнения про граммы, но позволит улучшить ее читаемость и в конечном счете облегчит отладку.
Оператор выбора Выше в разделе «Множественный выбор» мы приводили пример программы, которая вы полняла те или иные действия в зависимости от значения числа, введенного с клавиатуры. Задача выбора одного действия из нескольких по результатам вычисления какоголибо выражения возникает очень часто, и в языке С# предусмотрен специальный опе ратор выбора предназначенный для ее решения. Этот оператор используется для исполнения того или иного фрагмента программы в зависимости от значения пе ременной или выражения. Ниже мы показали оператор в общем виде (квадратными скобками отме чены необязательные конструкции оператора):
case < О п е р а т о р 1> [<Оператор п е р е х о д а > ] [case < О п е р а т о р 2> [<Оператор
Глава 2. Управляющие операторы
91
<Оператор Оператор выполняется следующим образом. Вначале вычисляется значение выражения сле дующего за словом Это выражение должно быть целочисленным (со знаком или без знака), символьным c h a r либо строковым s t r i n g . Оно также может быть перечислением enum, созданным на базе значений указанных типов. Результат вычислений сравнивается с выражениями-константами блоков c a s e (на зываемых также метками В случае совпадения значения управление передает ся соответствующего блока c a s e . Если совпадений не обнаружено, управ ление передается оператору N> блока Операторы, расположенные в блоках c a s e и можно заключать в фигур ные скобки, хотя это и не обязательно. По сравнению с вложенным оператором оператор выбора позволяет создавать более эффективные программы, так как компилятор соответствующим образом опти мизирует создаваемый код. В частности, для ускорения работы оператора создается специальная таблица пере ходов, каждый из которых соответствует своему блоку c a s e . Вычислив условие, про грамма сразу получает из таблицы информацию, необходимую для исполнения соот ветствующего блока c a s e или блока Что же касается вложенного операто ра i f , то он последовательно сравнивает полученный результат со всеми констант ными выражениями, а на это требуется больше времени.
Примеры применения оператора выбора Вот простейший пример использования оператора s w i t c h для выбора одной из не скольких строк, отображаемых на консоли. Выбор делается в зависимости от значения переменной i : int i
2;
case
case
case f
92
А В Фролов, Г В.
Язык
Самоучитель
Так как содержимое переменной i равно двум, на консоли всегда будет отобра жаться строка 2». Обратите внимание на оператор перехода b r e a k , расположенный в конце каждого блока c a s e , а также в конце блока Этот оператор завершает выполнение строк программы текущего блока и передает управление вниз, за пределы оператора В листинге 2.3 мы привели исходный текст программы, которая вводит текстовую строку с клавиатуры и сравнивает ее с одной из трех строк. При совпадении програм ма выводит на консоль соответствующее сообщение. Если совпадений нет, выводится сообщение об ошибке. Листинг
2.3.
Файл
ch02\SwitchOp\SwitchOpApp.cs
u s i n g System; namespace class s t a t i c void
args) одну из строк
string inputString; inputString
case
ввели строку
case (@"Вы ввели строку case ввели строку
break;
Глава 2. Управляющие операторы
при
93
В отличие от предыдущего примера здесь в качестве переключающего выражения не число, а текстовая строка.
Объединение меток case В некоторых случаях требуется выполнять одну и ту же обработку для разных значе ний выражения, расположенного после ключевого слова В языке С# это можно сделать следующим образом: int i case 1: case 1 или case 3
Здесь мы объединили вместе метки c a s e 1 и c a s e 2, определив одинаковую об работку для значений переменной i, равных единице или двум. В любом из этих слу чаев на консоли будет отображаться строка « c a s e 1 и л и 2».
Пропущенный break Тем из вас, кто раньше составлял программы на языках С и С++, может показаться, что единственное отличие оператора s w i t c h в языке С# заключается в возможности использовать в качестве переключающего выражения текстовые строки. Однако есть и еще одно важное отличие, на которое нам хотелось бы обратить внимание. Рассмотрим следующий программы: Эта программа транслироваться не int i 1;
case case
94
А. В.
В. Фролов. Язык
Самоучитель
break;
Как видите, здесь в два первых блока c a s e мы намеренно «забыли» вставить опе раторы b r e a k . К чему это приведет? Если бы программа была написана на языке С или С++, на консоль были бы после довательно выведены 3 строки: case 1 case 2 case 3 При отсутствии оператора b r e a k в программе С или С++ управление передается на следующий блок c a s e или а не за пределы оператора s w i t c h . Эта осо бенность применялась многими программистами в тех случаях, когда нужно было со вместить обработку, выполняемую в разных блоках c a s e . В языке С# такое совмещение не допускается. Вы можете объединять метки, но конструкция, приведенная выше, компилироваться не будет. Причина этого заключается в том, что разработчики программ С или С++ часто пропускали оператор b r e a k по ошибке, а вовсе не потому, что это было необходимо для реализации логики работы программы. Компилятор С# пресекает такие ошибки еще до того, как они смогут себя проявить во время работы программы.
Итерационные операторы Итерационные операторы применяются в программах С# для выполнения каких-либо повторяющихся действий, т. е. для организации циклов. Иногда эти операторы назы ваются циклическими. В этом разделе мы расскажем об использовании итерационных операторов w h i l e , do и f o r e a c h .
Оператор for Оператор предназначен для повторного выполнения оператора или группы опера торов заданное количество раз. Вот как выглядит этот оператор в общем виде: <Оператор> Оператор Глава 2. Управляющие операторы
выполняется один раз перед началом цикла.
95
каждой итерацией перед каждым выполнением тела цикла т о р > ) проверяется И наконец, после каждой итерации выполняется опе ратор Как правило, в цикле имеется переменная, играющая роль так называемой пере менной При каждой итерации переменная цикла изменяет свое значение в за данных пределах. Начальное значение переменной цикла задается в программе до оператора или в операторе Предельное значение переменной цикла определяется оператором приращения, а проверка ее текущего значения — в блоке У с л о в и е Поясним сказанное на простом int i 0; i
10; i++) ",i
Здесь переменная i используется в качестве переменной цикла. Перед началом цикла присваивается нулевое значение. Перед каждой итерацией содержимое пере менной i сравнивается с числом 10. Если i меньше тело цикла выполняется один раз. В тело цикла мы поместили вызов метода отображающий текущее значе ние переменной цикла на консоли. После выполнения тела цикла значение i увеличивается на единицу в блоке при ращения. Далее переменная цикла вновь сравнивается с числом 10. Когда значение i превысит цикл завершится. Таким образом, параметр цикла анализируется перед выполнением тела цикла, а модифицируется после его выполнения. Вот что выведет на консоль приведенный выше фрагмент программы: 0 1 2 3 4 5 6 7 8 9
Прерывание цикла С помощью оператора b r e a k можно в любой момент прервать выполнение цикла. Например, в следующем фрагменте программы мы прерываем работу цикла, когда значение переменной i становится больше пяти: 0; i
1 0 ; i++)
if(i 5) break; i
В результате на консоль будут выведены цифры от 0 до 5: 0
2 3 4 5
96
А В. Фролов, Г В.
Язык
Самоучитель
Оператор b r e a k в паре с условным оператором может заменить блок проверки ус ловия в операторе i + if(i 10) break;
Как видите, здесь пропущена проверка условия. Далее можно опустить все блоки оператора верку условия, а также итерацию int i
выполняя инициализацию, про
0;
if(i
10) i
i++
Это позволяет любую необходимую логику циклической обработки. Например, можно изменять значение переменной цикла перед итерацией, а не после нее: 0;
i
i++ ",i При создании цикла вам обязательно нужно предусмотреть условие его заверше ния. Если же этого не сделать, цикл будет выполняться бесконечно. Программа при этом будет работать вхолостую на одном месте, или, как еще говорят, «зациклится». Вот пример цикла, из которого нет выхода:
",i Здесь мы предусмотрели проверку значения переменной цикла, поэтому про грамма будет постоянно выводить на консоль возрастающие значения, пока вы не пре рвете ее работу. Кстати, это можно сделать, нажав комбинацию клавиш или закрыв консольное окно.
Возобновление цикла В отличие от оператора b r e a k , прерывающего цикл, оператор c o n t i n u e позволяет возобновить выполнение цикла с самого начала. Глава 2. Управляющие операторы Самоучитель
97
Вот как он i++) if(i
9)
else
Если в ходе выполнения цикла значение переменной i не достигло девяти, цикл возобновляет свою работу с начала (т. е. с вывода значения переменной цикла на консоль). Когда указанное значение будет достигнуто, выполнение цикла прервется оператором b r e a k .
Оператор while Оператор
проверяет условие завершения цикла перед выполнением тела цикла:
= 0; 10)
В отличие от оператора f o r оператор w h i l e никак не изменяет значения пере менной цикла, поэтому мы должны позаботиться об этом сами. Перед тем как приступить к выполнению цикла, мы устанавливаем начальное зна чение параметра цикла i, равное нулю. После выполнения тела цикла мы сами изме няем значение параметра цикла, увеличивая его на единицу. Цикл будет прерван, как только значение переменной i превысит В цикле w h i l e можно использовать описанные ранее операторы прерывания цик л а b r e a k и возобновления цикла c o n t i n u e . Следующий цикл будет выполняться бесконечно:
i++ Еще раз из цикла.
вам, что нужно всегда предусматривать возможность выхода
Оператор do Оператор do используется вместе с ключевым словом w h i l e . При этом условие за вершения цикла проверяется после выполнения его тела:
98
В. Фролов. Г. В. Фролов. Язык
Самоучитель
0;
i do
",i 10) В этом цикле мы сами устанавливаем начальное значение параметра цикла i и са ми его изменяем, увеличивая на единицу. Как только это значение достигнет цикл будет прерван. Аналогично циклу w h i l e цикл d o допускает прерывание оператором b r e a k и возобновление оператором
Оператор foreach Для обработки таких типов данных, как массивы и контейнеры, язык С# предлагает очень удобный оператор для которого нет аналога в языках программиро вания С и С++. Так как изучение этих типов данных еще впереди, в этой главе мы дадим упрощен ное определение массива и приведем простейший пример программы, работающей с массивами при помощи операторов и Итак, мы будем считать массив набором упорядоченных объектов, каждый из ко торых имеет свой номер, или индекс. Первый элемент массива имеет индекс 0, вто рой — 1 и т. д. Массив целых чисел со знаком объявляется следующим образом: Пара квадратных скобок указывает на то, что переменная является массивом. Прежде чем пользоваться массивом, его необходимо создать, указав максимальное количество объектов, которые могут в нем храниться. Вот как объявляется и создается массив переменных типа i n t : nums
nums new
Созданный массив инициализируется значениями по умолчанию. Для числовых массивов в качестве такого значения используется 0. Чтобы записать в элемент массива с заданным номером какое-либо значение, необ ходимо указать индекс этого элемента в квадратных скобках. Ниже мы в инициализируем массив, записывая в его элементы числа от 0 до 9, причем в нулевой элемент массива записывается значение 0, в первый — значе ние 1 и т. д.: for(i
0;
i
10;
i++)
Глава 2. Управляющие операторы
99
Чтобы отобразить содержимое всех ячеек массива, можно использовать обычный цикл f o r : 0; i
1 0 ; i++)
мы последовательно выводим на консоль все значения, хранящиеся в массиве. Хотя на первый взгляд этот способ обработки всех элементов массива достаточно прост, ему присущи некоторые недостатки. Например, нам нужно объявлять и ини циализировать переменную цикла (применяемую в роли индекса массива), а затем увеличивать ее значение при каждой итерации. При этом нужно следить, чтобы значе ние переменной цикла не превысило размер массива, иначе возникнет исключение. Оператором o r e a c h пользоваться намного проще: current in В скобках после ключевого слова f o r e a c h мы объявляем переменную c u r r e n t типа i n t , которой при каждой итерации будут последовательно присваиваться все значения массива Имя этого массива указывается после ключевого слова i n . Таким образом, нам не нужна переменная цикла и, следовательно, не нужно ее инициализировать, инкрементировать и проверять, не вышло ли значение индекса массива за допустимые пределы. Оператор f o r e a c h сделает все за нас. Он последовательно присвоит значение всех элементов массива переменной c u r r e n t , а нам останется только выводить при каждой итерации значение этой переменной на консоль.
Пример использования итерационных операторов Описанные выше приемы использования итерационных операторов демонстрирует программа, исходный текст которой представлен в листинге 2.4. Листинг 2.4.
Файл
using namespace
Iteration
class s t a t i c void
args)
int i 0; i
10;
i++) ",i
100
i
10;
for
(вариант
for
(вариант
i++)
if(i 5) break; ",i
if(i
9)
else
i
0; 10) ",i
i do
0;
",i i++
nums
new 0;
i
10; current
Глава
Управляющие операторы
i++) i n nums)
101
Так как в ней используются описанные ранее фрагменты кода, то мы оставляем вам эту программу для самостоятельного изучения. Экспериментируя с программой, по пытайтесь изменять параметры цикла, условия выхода и т. д. При исследовании приемов работы с массивом попытайтесь заставить программу выйти за его пределы, указав недопустимое значение индекса. Посмотрите, какого ти па исключение при этом произойдет. На протяжении нашей книги мы будем приводить и другие примеры использования итерационных операторов. А сейчас займемся операторами безусловного перехода.
Операторы безусловного перехода Как мы уже говорили, операторы безусловного перехода предписывают программе прервать нормальный ход своей работы и перейти к исполнению другой последова тельности команд.
Операторы break и continue Рассказывая об операторе выбора мы описали один такой оператор безус ловного перехода, а именно оператор b r e a k . Напомним, что этот оператор завершает выполнение команд текущего блока c a s e или после чего передает управ ление вниз за пределы оператора В итерационных операторах f o r , w h i l e и do оператор b r e a k применяется вме сте с другим оператором безусловного перехода c o n t i n u e . В то время как оператор b r e a k прерывает цикл и передает управление вниз за пределы цикла, оператор вызывает выполнение новой итерации цикла.
Оператор return Еще одна команда безусловного перехода r e t u r n будет описана позже. Она прерыва ет выполнение текущего метода и может дополнительно вернуть в вызывающий метод определенное значение.
Оператор goto Среди разработчиков языков программирования и программистов, наверное, наи большее количество споров вызывает команда безусловной передачи на метку g o t o . Эта команда присутствует практически во всех языках программиро вания, кроме тех, которые пропагандируют так называемый «чистый» структурный подход к программированию. О здесь идет речь и почему команда g o t o вызывает споры?
Организация цикла с помощью goto Чтобы понять, для чего в первых языках программирования был создан оператор g o t o , представим себе, что нам нужно создать цикл, но в нашем распоряжении нет ни одного итерационного оператора, такого, как или d o , а есть только усоператор Фролов, Г. В. Фролов. Язык С#. Самоучитель
Данная задача решена в программе, исходный текст которой представлен в лис тинге 2.5. Листинг
2.5.
Файл
u s i n g System; namespace GoToOp c l a s s GoToOpApp s t a t i c void int i
args)
i; 0; ",i
if(i 10) goto
Обратите внимание на строки, выделенные в этом тексте полужирным шрифтом. Первая из них представляет собой так называемую
Метка — это идентификатор, с помощью которого можно отметить какое-то место в программе. Метка не является исполняемым оператором, т. е. сама по себе она не выполняет никаких действий. Другая пара строк представляет собой условный оператор, в теле которого распо ложен оператор g o t o : if(i 10) goto Если условие выполняется (т. е. если в переменной i находится значение, мень шее 10), то управление передается в строку программы, расположенную сразу после метки Переменная i применяется здесь в качестве переменной цикла. Вначале мы записыва ем в нее нулевое значение. Это действие не что иное, как инициализация цикла. Далее при помощи метода мы выводим на консоль текущее значение пере менной а затем увеличиваем это значение на единицу. Таким образом, переменная цикла получает приращение. И наконец, наша программа проверяет условие выхода из цикла. Если значение пе ременной цикла не достигло 10, управление передается снова методу W r i t e . В про тивном случае цикл завершает свою работу. Глава 2.
Очевидно, при использовании любого из итерационных операторов, описанных ра нее, исходный текст выглядел бы намного понятнее. Итерационные и ус операторы, а также оператор выбора позволяют подчеркнуть в исходном сте программы структуру. Одного взгляда на исходный текст достаточно, чтобы найти циклы, операторы обработки условий, а также проследить вложенность циклов и условных операторов. Что же касается оператора g o t o , то при таком использовании, как в только что рассмотренной программе (листинг 2.5), он только скрывает структуру программы. Пока у нас только одна метка и только один оператор перехода на эту метку, но пред ставьте, что получится, если их будет много — десяток или больше. Изучая исходный текст программы, будет очень трудно проследить, при каких условиях и куда переда ется управление, а это приведет к тому, что программу будет трудно отлаживать. Тем не менее разработчики языка С# решили оставить оператор g o t o в этом су персовременном языке программирования, несмотря на его недостатки. Более того, они расширили возможность его применения. Почему? Дело в том, что при грамотном использовании оператор g o t o все же позволяет пи сать эффективные программы с понятным исходным текстом.
Применение goto в операторе выбора switch Рассказывая об операторе выбора s w i t c h , мы обращали ваше внимание на то, что внутри этого оператора не может быть пропущенных операторов b r e a k — ком пилятор С# рассматривает данную ситуацию как ошибочную. Тем не менее если вам нужно выполнить последовательно несколько блоков c a s e , то в языке С# это можно сделать при помощи оператора g o t o . Рассмотрим следующую учебную задачу. Мы будем выводить на консоль фрагменты фразы «мама мыла раму». Программа должна вводить слова этой фразы с клавиатуры. Если введено любое из слов «мама», «мыла» или «раму», нужно показать на консоли это слово, а также все слова фразы, расположенные после введенного. Например, если пользователь ввел слово «мама», программа должна вывести всю фра зу целиком. Если введено слово программа должна вывести слово «мыла раму». И наконец, если введено слово «раму», программа должна также вывести слово «раму». В листинге 2.6 мы привели исходный текст программы, выполняющей эти незамы словатые действия. Листинг
2.6.
Файл
u s i n g System; namespace G o t o I n S w i t c h class
GotoInSwitchApp
static void string
args)
inputString; В. Фролов, Г. В. Фролов, Язык С#. Самоучитель
одну
строк
или inputString "exit")
case goto case case goto case case
при
Прежде всего обратите внимание, что наша программа вводит и обрабатывает строки в
одну из строк или inputString "exit") break;
Условие завершения цикла — ввод пользователем слова Теперь, когда у нас есть цикл ввода строк, мы можем вводить разные слова, не перезапуская программу после ввода каждого слова. В теле первых двух операторах после вызова метода отображаю щего соответствующее слово на консоли, мы расположили оператор g o t o c a s e Глава 2. Управляющие операторы
105
case goto case case g o t o c a s e "раму"; Таким образом, после вывода на консоль слова «мама» программа переходит с помо щью оператора g o t o к обработке следующего блока c a s e . В результате достигается нужный нам эффект — программа может начинать вывод известной фразы школьного бу кваря начиная с любого слова и затем продолжит вывод оставшейся части фразы. После обработки тела любого блока программа может перейти к исполнению строк блока Для этого она должна использовать оператор g o t o следующе го вида: goto
default;
Заметим, что описанным выше образом нельзя передать управление за пределы оператора Такая ситуация будет рассматриваться компилятором как оши бочная.
Другие применения оператора goto Оператор g o t o можно использовать вместо оператора b r e a k для передачи управле ния за пределы цикла (однако нельзя передавать таким способом управление внутрь цикла). Заметим, однако, что единственное преимущество, получаемое при этом по срав нению с использованием оператора b r e a k , — возможность передать управление поч ти на любую строку, расположенную выше или ниже цикла. Мы считаем, что по возможности следует избегать использования так как он запутывает исходный текст программы. В большинстве случаев без вполне можно Единственным исключением здесь является, пожалуй, применение g o t o в операторе выбора
Пустой оператор Наверное, самый простой оператор в языке С# — это пустой оператор. Он состоит из точки с запятой и может располагаться в любом месте исходного текста программы, где по правилам языка требуется наличие оператора. В частности, итерационные операторы требуют обязательного присутствия в своем теле какого-либо оператора, хотя бы и пустого. Ниже мы привели пример использова ния пустого оператора в теле цикла 0; i
0)
А. В.
Г. В. Фролов. Язык
Самоучитель
Единственный результат выполнения этого запись нулевого значения во все ячейки массива n u m s . Больше от цикла ничего не требуется, поэтому в теле цикла мы использовали пустой оператор. Пустой оператор может быть помечен, например, для использования в операторе goto.
Составной оператор Несколько операторов можно объединить в блок, заключив их в фигурные скобки. В результате получится оператор, называемый составным. Вот пример составного оператора: int nums
new i n t i
10;
i++)
Исполнение составного оператора сводится к последовательному исполнению всех содержащихся в нем операторов (если, конечно, среди них нет операторов условной или безусловной передачи управления). Внутри составного оператора можно объявлять переменные, причем они будут доступны только внутри блока составного оператора. Говорят, что это локальные пе ременные блока составного оператора. Ранее мы уже сталкивались с составными операторами. Эти операторы обычно применяются в составе итерационных и условных операторов, i 10) i++ Здесь в составной оператор входит вызов метода W r i t e , а также инкремент значе ния переменной
Глава
Управляющие операторы
Глава 3. Объектно-ориентированное программирование В этой главе мы рассмотрим важнейшее концепцию объектноориентированного программирования (ООП). Мы уже говорили во Введении, что язык С# представляет собой средство объектно-ориентированного и компонентно-ориенти рованного программирования. И действительно, в языке С# все программы и данные представляют собой объек ты, а все обрабатывающие их алгоритмы являются методами. Оба этих понятия име ют самое непосредственное отношение к ООП и будут предметом изучения в этой главе. Владение методикой ООП абсолютно необходимо для успешного программирова ния на языке С#. Фактически, не разобравшись в этом, вы не сможете создавать на С# хоть сколько-нибудь сложные программы и системы.
Первые шаги к ООП Объектно-ориентированное программирование тесно связано с нашим обычным жи тейским опытом. В повседневной жизни мы встречаемся со многими понятиями ООП, даже не задумываясь над этим. Прежде всего это такое понятие, как объект. Описания объектов, с которыми мы имеем дело в жизни, можно представить себе как совокупность некоторых данных об объекте и набора действий, которые можно выполнять над данным объектом. Возьмем, например, обычный телевизор. Ниже мы перечислили некоторые пара метры, которые могут характеризовать телевизор как объект: •
марка телевизора;
•
название компании-изготовителя;
•
габаритные размеры;
• •
количество принимаемых каналов;
•
возможность работы с пультом дистанционного управления;
•
наличие выхода для подключения видеомагнитофона.
•
Если телевизор включен, то приобретают значение и другие параметры, например: номер принимаемого канала;
•
громкость звука;
•
стандарт видеосигнала (PAL, SECAM, NTSC).
Можно сказать, что набор перечисленных выше характеристик представляет собой данные Такие данные полностью характеризуют объект сам по се бе, а также его текущее состояние. Помимо данных существуют еще и методы работы с телевизором, т. е. действия, выполняемые в процессе эксплуатации телевизора: •
включение электропитания;
•
отключение электропитания;
•
включение канала с заданным номером с панели телевизора;
•
включение канала с заданным номером с пульта дистанционного управления;
•
увеличение громкости;
•
уменьшение громкости;
•
временное отключение звука во время рекламных вставок;
•
включение звука после временного отключения.
Возможность использования тех или иных методов работы с телевизором зависит от его характеристик, а также от текущего состояния. Например, если телевизор не может работать с пультом дистанционного управления, вы не сможете использо вать часть методов, например включение канала с помощью пульта. Если телевизор выключен, его нельзя выключить еще раз, так как эта операция не имеет никакого смысла. Если телевизор в состоянии принимать только 6 каналов, никаким образом не удастся включить канал с номером 40 и т. д. Таким образом, возможность и способы выполнения тех или иных операций с те левизором определяются его характеристиками и текущим состоянием, т. е. данными о телевизоре.
Программная модель телевизора Изучать ООП лучше всего на каком-то конкретном примере. В этом разделе мы поста вим перед собой задачу создания сильно упрощенной программной модели телевизо ра. Вначале эта модель будет реализована с применением средств, уже знакомых вам по предыдущим главам нашей книги, а затем мы применим для ее решения объектноориентированный
Данные Характеристики и текущее состояние телевизора мы будем хранить в наборе перемен ных различных типов. Ниже мы перечислили эти переменные и привели их краткое описание: bool byte byte
isPowerOn;
включен или выключен максимальный номер канала текущий номер канала текущая громкость звука
В типа b o o l мы будем хранить состояние выключателя электропитания телевизора. Если телевизор включен, в переменной бу дет находиться значение а если выключен — значение Глава 3. Объектно-ориентированное программирование
Максимальное количество каналов, принимаемых телевизором, будет храниться в переменной maxChannel, а номер текущего канала, принимаемого в настоящий момент времени, — в переменной currenr.Chann.el. Кроме того, в переменной мы будем хранить уровень громкости, выражаемый в процентах. При этом, если звук выключен, уровень громкости будет равен 0 а если включен на максимальную громкость —
Методы Наша модель телевизора сможет выполнять несколько операций. Это операция инициали зации, включения и выключения телевизора, операции установки номера принимаемого канала и уровня громкости, а также определение состояния телевизора (включен или вы ключен), определение текущего номера канала и текущего уровня громкости. Для выполнения перечисленных операций в нашей программной модели преду смотрено несколько конструкций, называемых методами. Метод объединяет в одном блоке программные строки, имеющие отношение к вы полнению той или иной операции. Метод может получать параметры и возвращать значения. Вот, например, как выглядит метод, включающий электропитание телевизора: void
Ключевое слово void означает, что метод не возвращает никакого значения. Мы считаем, что включение телевизора обязательно заканчивается успехом (хотя на практике это не всегда так), поэтому результат выполнения данной операции нас не интересует. После ключевого слова void идет название метода SetPowerStateOn. Мы вы брали его произвольно, однако с тем условием, чтобы это название отражало действие, выполняемое методом. Далее в фигурных скобках располагаются операторы метода. Включение телевизо ра сводится к записи в переменную isPowerOn значения true. Пока мы отвлечемся от способа определения этой переменной и будем считать, что методы имеют доступ ко всем переменным, описывающим состояние нашего виртуального телевизора. Чтобы выключить телевизор, мы определили метод void isPowerOn
false;
Выключение телевизора сводится к записи в переменную isPowerOn значения Чтобы определить, включен или выключен обычный телевизор, нам достаточно взглянуть на его экран. Для определения состояния виртуального телевизора придется создать несколько методов. А. В. Фролов, Г. В. Фролов. Язык С#. Самоучитель
Метод ременной isPowerOn:
позволяет получить текущее значение, хранящееся в пе
bool r e t u r n isPowerOn; Обратите внимание, что объявление метода начинается с ключевого слова bool. Это означает, что метод должен вернуть логическое значение типа bool. Для возвращения значения используется оператор return, упоминавшийся в кон це предыдущей главы. В данном случае этот оператор возвращает значение перемен ной isPowerOn. Если телевизор включен, метод GetPowerState возвратит значе ние true, в противном случае — значение Итак, мы научились включать и выключать телевизор. Теперь займемся Мы определим методы, позволяющие установить заданный канал и определить номер установленного канала. Переключение виртуального телевизора на заданный канал можно сделать с помо щью метода SetChannel: bool
channel) maxChannel
return else return
channel
0)
true;
false;
В качестве параметра этому методу передается номер канала channel. Метод проверяет номер канала и, если он больше нуля и не превышает максимально допус тимого, записывает новый номер канала в переменную currentchannel. При успешном переключении канала метод SetChannel возвращает значение true, а в случае значение false. Это значение можно использовать для выявления ошибочной попытки установить недопустимый номер канала. В дальней шем мы расскажем вам о более совершенном методе обработки ошибок, основанном на обработке исключений. Получить номер текущего канала можно с помощью метода byte return Этот метод Channel.
просто
возвращает текущее
содержимое переменной
Глава 3. Объектно-ориентированное программирование
current-
111
Для управления громкостью мы предусмотрели метод void 0 currentVolume else currentVolume
volume) volume
100)
0;
Он устанавливает новый уровень громкости, записывая значение v o l u m e в менную c u r r e n t V o l u m e . Перед этим, однако, наш метод проверяет, находится ли новое значение громкости в диапазоне от 0 до 100 Если задан неправильный уровень громкости, звук выключается. Получить текущий уровень можно с помощью метода byte return Он просто возвращает текущее значение уровня громкости, хранящееся в перемен ной c u r r e n t V o l u m e . В завершение мы определим метод, выполняющий инициализацию всех переменных:
Устанавливаем и с х о д н о е с о с т о я н и е т е л е в и з о р а isPowerOn false; выключен в с е г о можно смотреть 4 0 каналов при включении показывать канал 1 currentVolume громкость при включении - 10% Сразу после создания объекта (виртуального телевизора) этот метод выключает электропитание телевизора, определяет максимальное количество каналов (40), уста навливает текущий номер канала, равный единице, а также задает начальный уровень громкости — % от максимального уровня. Мы намеренно не снабдили этот метод ключевым словом v o i d , хотя он и не воз вращает никаких значений. Об этом, а также о выборе имени для метода инициализа ции мы поговорим в следующем разделе.
Объединяем все вместе Итак, мы создали данные — набор переменных, хранящих текущие параметры вирту ального телевизора, а также набор методов, позволяющий получать и изменять эти па раметры. Теперь нам нужно объединить данные и методы вместе, образовав описание нашего объекта — виртуального
112
А.
Фролов, Г. В. Фролов. Язык С#. Самоучитель
В языке
описание объекта
в виде нового типа данных и называ
Вы создаете новый тип данных (класс), определяя для него набор переменных, на зываемых в этом случае полями класса, а также набор методов, предназначенных для работы с этими полями. В общем виде определение класса выглядит достаточно просто: c l a s s <Имя класса 1>] класса 2>] класса N>] класса 1>] класса 2>] класса После ключевого слова c l a s s идет название класса. Например, для нашего теле визора мы выберем название класса class
TelevisionSet
Объявление полей и методов класса находится внутри блока операторов, ограни ченного фигурными скобками. Вы можете размещать поля и методы внутри этого бло ка в произвольном порядке, однако предпочтительнее придерживаться какой-либо стратегии. Мы, например, размещаем внутри объявления класса вначале поля класса, а потом методы, но можно поступить и наоборот. Вот сокращенный пример объявления класса T e l e v i s i o n S e t (полный пример будет рассмотрен позже): class
TelevisionSet
bool public byte byte
byte currentchannel
включен или выключен максимальный номер канала текущий номер канала текущая громкость звука
public isPowerOn maxChannel currentchannel currentVolume
выключен в с е г о можно смотреть 40 каналов при включении показывать канал 1 громкость при включении - 10%
Глава 3. Объектно-ориентированное программирование
public void isPowerOn public void isPowerOn
false;
Как видите, в верхней части объявления класса находятся переменные isPow erOn, maxChannel, currentChannel и currentVolume, описанные нами ранее и отражающие текущее состояние телевизора. В контексте определения класса эти переменные обычно и называют полями класса. Поле maxChannel снабжено так называемым классификатором доступа о котором мы расскажем позже. Ниже полей объявлен метод, имя которого совпадает с именем нашего класса, — public isPowerOn maxChannel currentVolume
false;
выключен
1;
количество каналов при включении показывать канал 1 громкость при включении - 10%
Метод с таким именем называется конструктором класса. На конструктор класса возлагается задача инициализации полей класса, поэтому мы поместили сюда не сколько операторов, определяющих начальное состояние телевизора. Мы передаем конструктору один параметр определяющий максимальное количество телевизионных каналов, которое способен показывать наш телевизор. По определению конструктор не может возвращать никакого значения. В отличие от других методов класса в конструкторе не допускается использование ключевого слова для обозначения этого обстоятельства. Классификатор доступа public определяет один из видов доступа к полю класса или методу. Здесь оно разрешает вызов конструктора из внешней программы. Управление доступом к полям и методам класса мы рассмотрим позже во всех под робностях. Сейчас вы должны запомнить, что если в классе определен методконструктор, то он вызывается при создании объекта, описанного данным классом, с целью его инициализации. Вслед за конструктором мы разместили в классе несколько описанных ранее мето дов, снабдив их ключевым словом Поля и методы, а также другие объекты, объявленные в классе, часто называют членами класса (class members). Мы тоже будем пользоваться этой терминологией.
114
А. В. Фролов, Г. В. Фролов. Язык С#. Самоучитель
Создание объектов класса TelevisionSet Теперь у нас есть класс — описание телевизора, но нет самого теле визора. Пользуясь нашим классом, мы сможем создать необходимое нам количество телевизоров. Допустим, нам нужны два телевизора, большой, способный принимать до 40 кана лов, и маленький, рассчитанный на прием шести каналов. Прежде всего для создания объектов-телевизоров необходимо объявить в програм ме две переменные типа TelevisionSet TelevisionSet
tvSmall; tvLarge;
Как видите, объявление выглядит аналогично объявлению числовых или строчных переменных, с той лишь разницей, что вместо одного из стандартных типов данных С# мы использовали здесь определенный нами тип данных TelevisionSet. На самом деле есть еще одно отличие. В то время как стандартные типы данных можно использовать в программе сразу после объявления, объекты других классов нужно создавать явным образом при по мощи ключевого слова new: tvSmall tvLarge
new new
Здесь мы создали два объекта, вызвав их конструкторы. При первом вызове мы пе редали конструктору значение 6, а при втором — значение 40. В результате всех этих действий программа записала в переменные и tvLarge так называемые ссылки на объекты. Переменная tvSmall ссылается на объект-телевизор, способный принимать 6 каналов, а переменная tvLarge — на 40-канальный телевизор. Важно, что для создания этих разных телевизоров мы использовали один и тот же класс, т. е. одно и то же описание объекта. Отсюда наглядно видно различие между классом и объектом. Класс представляет собой описание объекта, а объект — это реально существующая в памяти компьютера сущность, с которой программа может выполнять какие-либо действия.
Вызов методов класса После того как программа создала объекты, она может вызывать методы, определен ные в классе этих объектов. Вызвать метод очень просто:
Глава 3. Объектно-ориентированное программирование
115
Здесь мы указали имя переменной, ссылку на объект, а затем через точку — имя вызываемого метода. Если метод принимает параметры, эти следует указать в скобках. Вызывая одни и те же методы для разных объектов, мы выполняем с этими объек тами одни и те же действия. Например, метод S e t P o w e r S t a t e O n включает наши те левизоры:
Чтобы переключить телевизоры на разные каналы, достаточно при вызове метода S e t C h a n n e l передать ему разные параметры:
Первый телевизор будет переключен на 6-й канал, а второй — на 27-й канал. Аналогичным образом вы можете изменять громкость каждого телевизора, вызы вая для соответствующих объектов метод
Если метод возвращает значение, оно может быть присвоено переменной или ис пользовано в выражении. Вот, например, как можно узнать текущий номер канала и текущий уровень громкости наших телевизоров: byte b y t e chLarge byte volLarge= После выполнения этих строк в переменных c h S m a l l и будут записа ны текущие значения номера канала и громкости первого телевизора, а в переменных chLarge и — второго. Заметим, что программа, создавшая объект, может обращаться только к таким методам, которые объявлены в описании класса со спецификатором доступа public.
Обращение к полям класса С помощью ссылки на объект программа может не только вызывать методы класса, но и обращаться напрямую к его полям (если, конечно, они определены со спецификато ром доступа разрешающим такое обращение). Вот, например, как можно узнать максимальное количество каналов, предусмот ренное в наших byte byte А. В. Фролов, Г.
Фролов.
Самоучитель
При программа сможет изменить максимальное количество кана лов уже после создания телевизора: 10; 150; Заметим, что максимальное количество каналов можно безболезненно увеличивать, в то время как уменьшение следует выполнять осторожно. Например, если телевизор переключен на 10-й канал, а вы устанавливаете максимальный номер канала, равный, скажем, пяти, то возникнет ошибочная ситуация — телевизор должен показывать ка нал с недопустимым номером. Для избежания логических ошибок подобного рода лучше возложить задачу изме нения полей класса на методы, определенные в классе. Эти методы будут учитывать все особенности работы с В случае нашего телевизора перед уменьшением максимального количества каналов такой метод мог бы, например, установить номер текущего канала равным единице. Лучше всего, если методы класса будут реализовывать всю внутреннюю логику поведения объекта. При этом программист, составляющий программу для работы с объектом, может не отвлекаться на особенности внутреннего устройства объекта и его реализации. Это позволит сконцентрировать внимание на других, более важных задачах и снизить количество ошибок, допускаемых в процессе программирования. Способность объекта скрывать детали своей внутренней реализации и внутренней логики работы от вызывающих программ называется инкапсуляцией. Инкапсуляция — один из китов, на которых базируется ООП.
Пример программы В листинге мы привели полный исходный текст программы, в которой определен рассмотренный выше класс T e l e v i s i o n S e t . Эта программа демонстрирует проце дуру создания объектов на базе определенных классов, обращение к методам и полям класса. Листинг
Файл
using namespace T v S e t class
TelevisionSet
bool byte byte byte
isPowerOn;
включен или выключен максимальный номер канала текущий номер канала текущая громкость звука
Конструктор класса T e l e v i s i o n S e t public Устанавливаем исходное с о с т о я н и е т е л е в и з о р а isPowerOn false; выключен maxChannel количество каналов 1; при включении показывать канал 1 currentVolume громкость при включении - 10% Глава 3. Объектно-ориентированнов программирование
117
Включить т е л е в и з о р public void isPowerOn
true;
Выключить т е л е в и з о р public void isPowerOn
false;
Определить с о с т о я н и е т е л е в и з о р а - включен или выключен public bool return Переключиться на прием з а д а н н о г о канала public bool channel) maxChannel currentChannel return true;
channel
0)
channel
else return Получить номер текущего канала public byte return Установить public void
громкость
0 currentVolume else currentVolume
volume)
volume volume;
100)
0;
Получить текущий уровень громкости public byte return
118
currentVolume;
А. В. Фролов, Г. В. Фролов. Язык С#. Самоучитель
TvSetApp static void
args)
TelevisionSet TelevisionSet tvSmall tvLarge
tvSmall;
new new
tvSmall: канал из "Включен"
громкость
tvLarge: канал
из
громкость
tvSmall: канал из "Включен"
громкость
tvLarge: Глава 3.
программирование
119
канал из "Включен"
громкость
Console
В самом начале листинга находится объявление класса TelevisionSet, о ко тором мы уже рассказывали раньше. Назначение полей и методов этого класса было подробно описано, поэтому мы не будем повторяться. Нам хотелось бы обратить ваше внимание на то, как в этой программе мы вызыва ем методы Write и WriteLine: tvSmall: канал из "Включен"
громкость "Выключен",
Обратите внимание, что при вызове этих методов мы опустили пространство имен System. Это вполне допустимо, так как в начале нашей программы имеется строка с оператором using, компилятору С# просматривать это прост ранство имен: using
System;
В дальнейшем при частом обращении к методам, определенным в рамках какоголибо пространства имен, мы будем опускать имя пространства имен при вызове мето да, указывая его с помощью оператора using. Так наши программы будут короче и проще для понимания. В своих проектах вы можете поступать как угодно в зависимости от собственных предпочтений. Теперь о том, что делает наша программа. Получив управление, метод Main создает два виртуальных телевизора, один из ко торых (маленький) способен принимать 6 каналов, а другой (большой) — 40 каналов: TelevisionSet TelevisionSet tvSmall tvLarge
tvSmall; tvLarge;
new new T e l e v i s i o n S e t
Далее наша программа включает по очереди оба телевизора, устанавливая на пер вом из них 5-й канал, а на втором — 27-й канал. Уровень громкости тоже устанавли вается разный:
120
А. В.
Г. В. Фролов. Язык С#. Самоучитель
После этого программа отображает текущее состояние обоих телевизоров на На следующем этапа программа вновь изменяет номера текущих каналов и уровень громкости обоих телевизоров, а затем выключает их вовсе: SetChannel
И наконец, перед завершением своей работы программа отображает новое состоя ние телевизоров на консоли: Телевизор Включен, канал 5 из громкость 50 Телевизор t v L a r g e : Включен, канал из 4 0 , громкость 30 Телевизор t v S m a l l : Выключен, канал 3 из 6, громкость 80 Телевизор Выключен, канал 39 из 4 0 , громкость Таким образом, разработав класс, описывающий поведение телевизора, мы создали два объекта этого класса, а затем управляли этими объектами совершенно независимо. Этот принцип можно обобщить на данные любого типа. Пользуясь объектноориентированными средствами С#, мы можем описать с помощью классов данные лю бых типов, а затем на базе этих классов создавать объекты.
Наследование После того как мы сделали первые шаги в освоении ООП и разобрались с первой объ ектно-ориентированной программой, настало время дать более строгие определения терминов и понятий ООП. В этом разделе мы займемся классами. Теперь вы знаете, что классы позволяют объединить вместе данные и методы, предназначенные для их обработки. Создав объекты того или иного класса, программа может вызывать методы и обращаться напрямую к полям объекта (если это разрешено при объявлении класса). Работая с объектами, созданными на базе классов, програм мисту нет нужды вникать во внутренние детали устройства достаточно знать, как и какие методы можно вызывать, какие передавать им параметры и какие значения эти методы возвращают. Механизм сокрытия, или, как говорят, инкапсуляции, данных и методов класса об легчает создание программ, особенно сложных, так как позволяет программисту не вникать во все детали реализации программных компонентов. Глава 3. Объектно-ориентированное программирование
121
В этом разделе мы рассмотрим другое базовое понятие ООП, а именно наследова ние. С этим понятием неразрывно связаны два других понятия — базовый класс и производный класс.
Базовый класс Наследование позволяет создавать новые классы на базе уже существующих классов. Создавая новый тип данных на базе типа данных, определенного ранее, можно упро стить работу за счет использования разработанных ранее методов базового класса. Базовым (base), или родительским (parent), классом называется класс, на основе которого создаются другие классы. Чтобы это определение было понятнее, рассмотрим следующий пример. Пусть нам нужно работать в программе с такими объектами, как прямоугольники. Для этого мы создаем класс Rectangle, инкапсулирующий в себе данные и методы, необходимые для размещения прямоугольника на плоскости: class
Rectangle
int int yPos; int int height; public xPos
yPos
width
height
public void xPos yPos
x,
int y)
X; y;
public void width height
0;
w,
i n t h)
h;
Поля класса xPos и хранят координаты левого нижнего угла прямоугольни ка (в прямоугольной системе координат), а поля width и height — соответственно ширину и высоту прямоугольника. Таким образом, содержимое полей класса Rec tangle однозначно расположение прямоугольника на плоскости и его размеры (рис.
А. В. Фролов, Г. В. Фролов. Язык
Самоучитель
Ось Y
Рис.
Расположение прямоугольника на плоскости
Конструктор класса Rectangle записывает в поля класса нулевые значения, в ре зультате чего все новые прямоугольники создаются в центре системы координат с размерами, равными нулю. В классе Rectangle мы также определили методы SetPosition и SetSize, позволяющие установить соответственно расположение прямоугольника на плоскости и его размеры. Работать с этим классом очень просто — нужно сначала создать прямоугольник, а затем установить его расположение и размеры: Rectangle new
Производный класс Итак, мы создали базовый класс, описывающий прямоугольники. А теперь представьте себе, что вам понадобились не простые прямоугольники, а раскрашенные в разные цвета. Вы, конечно, можете класс Rectangle, добавив в него поля и методы для работы с цветом. Однако более красивое решение заключается в создании на базе класса Rectangle нового класса ColorRectangle, дополненного средствами представления цвета: c l a s s ColorRectangle
Rectangle
byte colorR; byte colorG; byte Глава 3. Объектно-ориентированное программирование
public void С colorR
r,
byte g,
byte b)
g; colorB
Обратите внимание, что при объявлении класса после двоеточия мы указали имя базового класса R e c t a n g l e . В результате класс C o l o r R e c t a n g l e на следует все поля и методы своего базового класса, а также добавляет к ним 3 поля: c o l o r R , c o l o r G , c o l o r B — и один метод — S e t C o l o r . Новые поля предназначены для хранения трех основных компонентов цвета (красный, голубой и зеленый), а метод S e t C o l o r позволяет установить значения этих полей, раскрасив наш прямоугольник. Таким образом, мы очень легко добавили новую функциональность — теперь наши прямоугольники стали цветными. При добавлении новой сущности (цвета) нам были несущественны детали реализации базового класса R e c t a n g l e , такие, как способ прямоугольника на плоскости и способ установки его размеров. Более того, создавая свой собственный класс на базе готового класса, программист может даже и не подозревать о существовании в этом классе каких-то еще полей и ме тодов. Например, в базовом классе R e c t a n g l e могло поле для хране ния запаха прямоугольника, а также метод для установки этого запаха. Пока мы имеем дело с простыми классами и простыми программами, эффект от использования наследования может показаться небольшим. Однако в реальности наследование открывает перед программистом возможность создания своих классов на базе огромной библиотеки классов С#. Взяв за основу один из десятков тысяч классов библиотеки, вы можете создать на его собственный класс, наделив его необходимыми вам свойствами. При этом вам потребуется реализовывать полную функциональность, так как она уже реали зована в базовом классе. Достаточно только добавить свои поля и методы (а также, возможно, переопределить существующие поля и методы базового класса, но об этом мы поговорим позже). Класс C o l o r R e c t a n g l e , созданный на базе класса R e c t a n g l e , называется про изводным или дочерним child). В свою очередь, класс R e c t a n g l e играет роль базового (base), или родительского (parent), класса.
Множественное наследование Заметим, что в языке С# дочерний класс может наследовать свойства только одного базового класса. В других языках программирования (например, в С++) допускается так называемое множественное наследование. Множественное наследование позволяет создавать один производный класс на ос нове нескольких базовых классов, что бывает удобно в некоторых случаях, когда про изводный класс нужно наделить свойствами сразу нескольких базовых классов. Язык С# не допускает множественного наследования, однако аналогичная функ циональность может быть достигнута при использовании механизма так называемых Интерфейсы мы рассмотрим в гл. 8. В. Фролов. Г. В.
Язык
Самоучитель
Представление иерархии классов Взаимосвязи между родительскими и дочерними (т. е. между базовыми и производ ными) классами могут быть достаточно сложными. Чтобы сделать эти взаимосвязи на гляднее, их часто иллюстрируют графическими диаграммами. На рис. 3.2 мы показали схему взаимосвязей базового класса R e c t a n g l e и произ водного класса C o l o r R e c t a n g l e .
ColorRectangle
Рис. 3.2. Базовый и производный классы Обратите внимание, что базовый класс располагается в верхней части диаграммы, а производный — в нижней части. Стрелка, обозначающая наследование, идет в на правлении от производного класса к базовому классу. Диаграммы зависимостей классов могут быть достаточно сложными. На рис. 3.3 мы показали пример более сложной схемы, построенной для случая, когда производ ные классы базового класса R e c t a n g l e становятся базовыми для других классов. В этом случае наша схема превращается в дерево иерархии классов. Rectangle
3.3. Дерево иерархии классов Как видно на рис. 3.3, на базе класса R e c t a n g l e созданы два — C o l o r R e c t a n g l e и C a r d . О первом из них мы уже рассказывали — это класс для представления прямоугольников. Второй класс с именем C a r d может быть ван, например, для создания игральных карт. Помимо координат и размеров играль карты имеют и атрибуты, такие, например, как масть. Глава 3 Объектно-ориентированное программирование
125
Производный класс C o l o r R e c t a n g l e служит базовым для классов S m e l l Rectangle и ThickRectangle. Класс предназначен для представления прямоугольников с за пахом, а класс прямоугольников, имеющих не только высоту и ширину, но и толщину. Кстати, если вы думаете, что для программистов работа с запахами — дело отдаленной перспективы, то знайте, что в прессе уже появляются сообщения о разра ботке устройств, генерирующих запахи. Подключив такое устройство к компьютеру, имеющему соединение с Интернетом, вы сможете не только посмотреть на Webсайты, но и понюхать их. Заметим, что не допускается рекурсивное или циклическое наследование классов, когда, например, второй класс наследуется от первого, а первый — от второго. Поэто му такая конструкция будет ошибочной: class
c l a s s MyDerivedClass
MyBaseClass
Важной особенностью библиотеки классов С# является то, что все классы этой библиотеки происходят от одного общего класса с названием O b j e c t . Как мы увидим д а л ь ш е , это имеет далеко идущие последствия. В частности, такое насле дование позволяет выполнять некоторые действия с объектами любых классов, созданных на базе Программист может, например, помещать эти объекты в контейнеры (массивы, списки, словари), получать текстовое описание любого объекта и т. д.
Пример программы В листинге 3.2 мы привели исходный текст программы, в которой используется произ водный класс C o l o r R e c t a n g l e . Базовым для него служит уже знакомый вам класс Rectangle. Листинг
3.2.
Файл
u s i n g System; namespace R e c t a n g l e class
Rectangle
i n t xPos; i n t yPos; int width; int
126
В.
Г. В. Фролов. Язык С#. Самоучитель
xPos
width
height
public void xPos yPos
x,
int y)
x; y;
public void width height
0;
w,
i n t h)
w; h;
c l a s s ColorRectangle
Rectangle
byte colorR; byte colorG; byte colorB; public void colorR colorG colorB
class
r,
byte g,
byte b)
r; g; b;
RectangleApp
s t a t i c void ColorRectangle cr new
args) cr;
cr.SetSize(20, 0,
Получив управление, метод M a i n создает объект производного класса ColorRectangle cr new
Глава 3. Объектно-ориентированное программирование
этот метод вызывает два метода базового класса для изменения расположе прямоугольника:
ния и
10); Обратите внимание: в классе C o l o r R e c t a n g l e нет определения методов S e t P o s i t i o n и S e t S i z e , так как это методы базового класса. Тем не менее мы вы зываем их для объекта производного класса. Модификатор доступа p u b l i c , при мененный при объявлении методов S e t P o s i t i o n и S e t S i z e в базовом классе, до пускает такой вызов. Аналогичным образом мы вызываем метод S e t C o l o r производного класса: ( 0 , 0, Заметим, создав объект базового класса R e c t a n g l e , вы не сможете вызвать для него метод S e t C o l o r : Rectangle rect; rect new 0, Причина очевидна — в классе R e c t a n g l e нет объявления метода S e t C o l o r .
Маскирование методов базового класса Программисты очень часто создают свои классы на базе классов библиотеки Microsoft Framework, а также используют эти классы в неизменном виде, создавая объекты классов. При наследовании классов, созданных другими программистами, вы можете по ошибке определить в производном классе такой метод, который уже имеется в ба зовом классе. Вернемся снова к нашему базовому классу R e c t a n g l e . Теперь мы опять создадим на его основе производный класс C o l o r R e c t a n g l e , но добавим в него помимо всего прочего метод S e t S i z e , объявленный в базовом классе R e c t a n g l e : c l a s s ColorRectangle byte byte byte
Rectangle
colorR; colorG; colorB;
int int public void colorR colorG colorB
128
r, b y t e g, b y t e b) r; b; А. В. Фролов, Г. В. Фролов. Язык С#. Самоучитель
p u b l i c new v o i d width height public
w,
i n t h)
w; h;
int
return width; public
int
return
Обратите что в объявлении метода S e t S i z e мы использовали ключевое слово n e w . Если убрать это слово, то компилятор выведет на экран следующее преду преждающее сообщение: w a r n i n g CS0108: The keyword new is r e q u i r e d on because it hides i n h e r i t e d member говорится о том, что метод S e t S i z e , определенный в классе C o l o r R e c t a n g l e , маскирует, или скрывает, одноименный метод из класса R e c t a n g l e . В такой маскировки метод S e t S i z e базового класса R e c t a n g l e стано вится недоступным программе. Компилятор не знает, возникла такая ли ситуация по ошибке, или же вы намеренно заменили в дочернем классе метод базового класса. Ключевое слово n e w проясняет ситуацию, в результате чего предупреждающее сообщение не появляется. Заметим, что в языке С++ описанная выше ситуация не вызывает появления преду преждающих сообщений. Поэтому если вы по ошибке переопределите подобным об разом метод базового класса в программе С++, то так и не узнаете об этом. Язык С# исключает возникновение подобной ошибки.
Модификаторы доступа Теперь настала очередь разобраться, наконец-то, в модификаторах доступа, таких, как p u b l i c . Что это такое и для чего они нужны? Модификаторы доступа определяют права доступа к полям и методам класса. Ра зумеется, тут речь не идет о том, что программист или пользователь программы будет вводить свой идентификатор и пароль для работы с объектом класса. Механизм моди фикаторов доступа призван облегчить создание программ, вводя дополнительный кон троль на этапе компиляции исходного текста программы. Глава 3. Объектно-ориентированное программирование 5 Язык
Самоучитель
Когда мы создаем производные классы, методы этих классов могут вызывать ды базовых классов или обращаться к полям, объявленным в базовом классе. Мы так же можем обращаться к полям и методам класса, создавая объекты этих классов. Модификаторы доступа позволяют контролировать этот процесс, разрешая или за прещая подобное обращение к полям и методам класса в тех или иных
Модификатор private Если при объявлении класса вы не укажете модификаторы доступа для полей и мето дов класса, то по умолчанию компилятор будет полагать, что для них нужно использо вать модификатор доступа private. Модификатор private запрещает доступ к полям и методам класса извне самого класса. Поля и методы, объявленные с модификатором private, будут доступны только в методах самого класса, т. е. это «личные» поля класса. Приведем пример. Вспомним про наших старых базовый класс Rectangle и производ ный от него класс ColorRectangle. Ранее поля базового класса xPos, yPos, width, height были объявлены без модификатора доступа. Это равносильно тому, как если бы для них был указан модификатор доступа private: class
Rectangle
private private private private
i n t xPos; i n t yPos; int int
Теперь попытаемся определить в производном классе метод GetXPos, возвра щающий текущее значение поля c l a s s ColorRectangle public
Rectangle
int
return xPos;
В результате при компиляции мы получим следующее сообщение об ошибке: i s i n a c c e s s i b l e due t o i t s p r o t e c t i o n l e v e l В этом сообщении говорится, что установленный уровень защиты запрещает дос туп к методу xPos, определенному в классе Rectangle. Аналогичное сообщение появится и при попытке обращения из производного клас са к методам базового класса, определенного с модификатором доступа p r i v a t e или, что равносильно, вообще без какого-либо модификатора доступа. А. В. Фролов, Г. В. Фролов. Язык
Самоучитель
Рассмотрим пример базового класса, в котором метод S e t P o s i t i o n определен с модификатором доступа p r i v a t e : class
Rectangle
i n t xPos; int private void xPos yPos
x,
int y)
x;
Попытаемся вызвать метод S e t P o s i t i o n и з метода C o l o r S e t P o s i t i o n , кото рый объявлен в производном классе C o l o r R e c t a n g l e : c l a s s ColorRectangle
Rectangle
public void
Так как в базовом классе метод S e t P o s i t i o n объявлен как p r i v a t e , в ре зультате этой попытки мы получим следующее сообщение на этапе компиляции программы: i s i n a c c e s s i b l e due t o its
protection
level
Аналогичная ошибка произойдет и при попытке вызвать метод S e t P o s i t i o n для объекта, созданного на базе класса C o l o r R e c t a n g l e : ColorRectangle cr new
cr; Доступ к методу запрещен
Таким образом, с помощью модификатора доступа мы можем полностью запретить доступ методам производных классов к полям и методам базового класса. Заметим, что в языке С++ отсутствие модификатора доступа у поля или метода оз начает, что к этому полю или методу имеется полный доступ со стороны методов про изводных классов или других методов, внешних по отношению к самому классу. В этом смысле язык С# более строг — чтобы предоставить доступ к полю или методу класса, необходимо указать модификатор доступа явным образом. Главе 3.
131
Модификатор public Если поле или метод класса определены с модификатором доступа p u b l i c , они дос тупны базового класса или производных классов. Это, в частности, оз начает, что методы, объявленные вне класса, могут свободно обращаться к таким по лям и методам. Ранее в наших примерах мы часто использовали модификатор доступа p u b l i c при объявлении методов класса. Приведем пример использования этого модификатора для полей класса. Изменим определение базового класса R e c t a n g l e , разрешив доступ к полям класса: class
Rectangle
public public int int
int int
xPos; yPos;
Теперь определим и GetYPos: class
в
производном
ColorRectangle
public
именами G e t X P o s
Rectangle
int
return
public
классе два метода с
xPos;
int
return
yPos;
Первый из этих методов будет возвращать содержимое поля x P o s , а второй — по ля y P o s . Теперь у нас есть способ не только установить текущее расположение пря моугольника на плоскости, но и получить текущие координаты его левого нижнего уг ла (рис. 3.1). Так как поля x P o s и y P o s определены с модификатором доступа p u b l i c , мы можем в своей программе вывести расположение прямоугольника двумя спо собами: ColorRectangle cr
new
10); А. В. Фролов, Г.
Фролов. Язык С#. Самоучитель
прямоугольника прямоугольника способ получение текущих координат с помощью методов G e t X P o s и G e t Y P o s , а второй — прямое обращение к полям x P o s и y P o s .
Модификатор protected Применение модификатора доступа при объявлении полей класса до некото рой степени нарушает принцип инкапсуляции. Действительно, если программа рабо тает с полями класса напрямую, она зависит от типа и назначения этих полей. Между тем разработчик класса (а это не всегда вы) может пожелать внести во внутреннюю реализацию класса какие-либо изменения, затрагивающие поля класса, и оставить не тронутым только набор методов класса. Другое дело — производные классы. Связанные узами «родства», производным классам для увеличения эффективности программы часто приходится обращаться к полям базового класса напрямую. Модификатор доступа p r o t e c t e d позволяет найти некий компромисс. С помощью этого модификатора вы можете предоставить доступ к полям и методам базового класса только для производных классов, но не для внешних по отношению к классу методов. Снова внесем небольшое изменение в класс R e c t a n g l e : class
Rectangle
public int public int protected protected
xPos; yPos i n t width; int
Теперь мы добавили спецификатор доступа к полям w i d t h и h e i g h t . В производном классе C o l o r R e c t a n g l e мы определили методы и GetH e i g h t , с помощью которых можно определить текущие размеры прямоугольника: c l a s s ColorRectangle public
Rectangle
int
return width; p u b l i c i n t GetHeight return
Глава 3. Объектно-ориентированное программирование
Так как поля базового класса w i d t h и h e i g h t объявлены как p r o t e c t e d , мы можем обращаться к ним из методов и G e t H e i g h t производного класса. Для вывода текущих размеров прямоугольника на консоль нам необходимо ис пользовать эти методы: ColorRectangle cr new прямоугольника
высота
Попытка прямого обращения к полям w i d t h и h e i g h t для объекта неудачей: Ошибка!
окончится
высота Доступ запрещен
В результате с помощью модификатора доступа p r o t e c t e d мы скрыли от пользо вателя классов R e c t a n g l e и C o l o r R e c t a n g l e внутренние поля w i d t h и h e i g h t . Доступ к этим полям возможен только при посредстве методов — метод из меняет содержимое полей, а методы G e t W i d t h и G e t H e i g h t позволяют программе узнать это содержимое. При необходимости разработчик базового класса R e c t a n g l e может изменить на звания полей w i d t h и h e i g h t или полностью изменить способ хранения размеров прямоугольника. Это никак не отразится на пользователях производного класса ColorRectangle, как они задают и определяют размеры прямоугольника с по мощью соответствующих методов.
Модификатор internal Модификатор i n t e r n a l представляет собой комбинацию модификаторов p u b l i c и Он ограничивает доступность отмеченных им полей и методов преде лами одной сборки (assembly). При этом внешние классы не имеют к таким полям ни какого доступа. Подробнее о сборках мы поговорим позже. Все модификаторы, кроме i n t e r n a l , должны использоваться по отдельности. В ы н е можете, например, указывать доступ p u b l i c p r o t e c t e d или t e p r o t e c t e d . Тем н е менее модификатор i n t e r n a l допускается использовать вместе с модификатором В этом случае одновременно предоставляются виды доступа p r o t e c t e d (для ис пользования производных классах) и (для использования в пределах текущей сборки). Вот пример использования модификатора i n t e r n a l : class
Rectangle
public int xPos; public int yPos; i n t e r n a l int width; internal int
А. В. Фролов, Г. В. Фролов. Язык С#. Самоучитель
Пример программы В листинге 3.3 мы привели исходный текст программы, демонстрирующей использо вание модификаторов доступа. В этой программе мы работаем с немного модифици рованными классами R e c t a n g l e и C o l o r R e c t a n g l e . Листинг
3.3.
Файл
u s i n g System; namespace R e d e f i n i t i o n class
Rectangle
public int public int protected protected
xPos; yPos; i n t width; int
public xPos
yPos
width
height
public void XPOS
x,
0; int y)
X;
public void width height
w,
i n t h)
w; h;
c l a s s ColorRectangle
Rectangle
byte colorR; byte colorG; byte public void colorR colorG colorB
r,
byte g,
byte b)
r; g; b;
Глава 3. Объектно-ориентированное программирование
135
p u b l i c new v o i d
w,
i n t h)
t
width height
w;
public return xPos; public
int
return yPos; public
int
return public
int
return
class
Redefinition
s t a t i c void ColorRectangle cr new
args) cr; 10);
cr.SetSize(20, 0,
OxFF); прямоугольника прямоугольника
cr.xPos, прямоугольника
высота
i
А. В. Фролов, Г. В. Фролов. Язык
Самоучитель
Прежде всего мы указали спецификаторы доступа для полей базового класса Rectangle: p u b l i c i n t xPos; p u b l i c i n t yPos; p r o t e c t e d i n t width; protected int Модификатор позволил нам напрямую обращаться к этим полям при вы воде текущих координат прямоугольника на консоль. Что же касается модификатора protected, то он открывает доступ к полям width и height только для методов производных классов. В нашем случае это класс ColorRectangle. В производном классе мы добавили методы, с помощью которых можно извлекать значения полей, объявленных в базовом классе: public
int
return xPos; public
int
return yPos; public
int
return width; public
int
return
height;
Метод Main нашей программы демонстрирует способы прямого обращения к полям базового класса, объявленным со спецификатором доступа а так же способы обращения к полям типа protected. В последнем случае доступ осуществляется с применением специально предназначенных для этого методов производного класса.
Статические члены класса Создавая объекты классов в наших программах, мы до сих пор создавали для каждого такого объекта члены экземпляра класса (instance member). Чтобы объяснить, о чем здесь идет речь, вернемся к объявлению класса Tele описанного нами в начале этой главы. Вот сокращенный исходный текст этого класса: Глава 3. Объектно-ориентированное программирование
137
class
TelevisionSet
bool byte byte byte
isPowerOn;
включен или выключен максимальный номер канала текущий номер канала текущая громкость звука
public void isPowerOn public
void
isPowerOn
false;
Как видите, в классе определены 4 поля и несколько методов. При создании объектов на базе класса T e l e v i s i o n S e t (т. е., иными словами, объектов класса T e l e v i s i o n S e t ) мы фактически создаем в оперативной памяти компьютера два набора таких полей и методов: TelevisionSet TelevisionSet tvSmall tvLarge
tvSmall; tvLarge;
new new T e l e v i s i o n S e t
Для объекта t v S m a l l создаются свои поля и методы, а для объекта — свои. Это позволяет управлять нашими двумя виртуальными телевизорами по отдель ности, так как их параметры хранятся в различных областях оперативной памяти ком пьютера:
В данном случае такая особенность важна, так как речь идет о совершенно независи мых друг от друга объектах. В самом деле, каждый телевизор может быть переключен на свою программу и для каждого телевизора можно установить свой уровень громкости. Однако встречаются ситуации, в которых желательно было бы иметь нечто общее, объединяющее все объекты, созданные на базе одного и того же класса. В языке такая возможность реализуется с помощью так называемых статических членов класса.
138
В. Фролов, Г. В. Фролов. Язык С#. Самоучитель
Статические поля класса Статическое поле это такое поле, которое существует в оперативной памяти компьютера в единственном экземпляре, сколько бы объектов класса ни создавалось. Наиболее очевидное применение таких полей — подсчет общего количества соз данных объектов класса. Рассмотрим это на простейшем примере (листинг 3.4). Листинг
3.4.
Файл
ch03\StaticFields\StaticFieldsApp.cs
using namespace class
Point
i n t xPos; i n t yPos; s t a t i c public uint public xPos yPos
class
x,
totalPoints; int y)
x;
StaticFieldsApp
s t a t i c void Point pt; i=0; pt
new
args) i
10;
i++) i) точек:
Здесь мы объявили класс P o i n t , представляющий обыкновенную точку на плос кости. Поля класса x P o s и хранят координаты точки на плоскости. Помимо этих полей в классе P o i n t объявлено статическое поле t o t a l P o i n t s , для подсчета общего количества точек, размещенных на плоскости: s t a t i c public uint
Глава 3. Объектно-ориентированное программирование
139
слово s t a t i c указывает н а то, что поле t o t a l P o i n t s статическим. Числовые статические поля инициализируются нулевым значением. В нашей программе мы воспользуемся этим фактом. Заметим также, что при необ ходимости вы можете статические поля класса при их объ явлении, например: s t a t i c public uint totalPoints
5;
Метод M a i n нашей программы, получающий управление после запуска, создает в цикле Point
объектов класса P o i n t : pt; i=0;
pt
i
10;
new
i++) i)
Здесь в теле метода M a i n мы определили локальную переменную pt класса P o i n t . Эта переменная называется локальной, потому что доступ к ней возможен только в пределах метода Далее в цикле программа создает объекты, по очереди сохраняя ссылки на них в переменной по себе эти объекты нам не нужны, нам интересно только их количество). При создании каждого объекта управление получает конструктор класса: public yPos
х,
int у)
x; у;
Он сохраняет координаты создаваемой точки в соответствующих полях, а также увеличивает содержимое статического поля t o t a l P o i n t s . В то время как для хранения координат каждой создаваемой точки в оперативной памяти компьютера резервируются разные области памяти, статическое поле P o i n t s хранится в единственном экземпляре. В результате при создании каждого но вого объекта конструктор увеличивает на единицу число, хранящееся в этой общей для всех объектов памяти. После завершения цикла мы можем вывести на консоль количество созданных объектов, взяв его из статической переменной точек: Обратите внимание, что для ссылки на это поле мы использовали имя класса P o i n t , а не имя объекта p t , созданного на базе этого класса. Это замечание важно. Для адресации статических членов класса нужно использовать имя класса, а не имя поля или переменной, хранящих экземпляр объекта. Следующая строка будет неверна:
140
Фролов, Г.
Фролов. Язык С#. Самоучитель
точек: Для чего еще можно использовать статические поля класса? Часто их используют для хранения каких-либо общих данных, имеющих отноше ко всей программе в целом. Другие языки программирования, например С и С++, допускают создание с этой целью так называемых переменных. Глобаль ные переменные доступны из любого места программы, однако их применение запу тывает программу и усложняет поиск ошибок. Язык С# не допускает использования глобальных переменных. Еще одно полезное применение статических полей класса — создание статических констант — будет рассмотрено в следующем разделе.
Статические константы Многие программы работают с константами — числами, текстовыми символами и строками, содержимое которых на протяжении работы программы никогда не из меняется. Обычно такие константы содержат значения параметров работы про граммы. Для того чтобы определить константу в программе С#, необходимо использовать ключевое слово c o n s t . Приведем пример использования этого ключевого слова для создания константы в области локальных переменных метода Main. В гл. 1 мы рассказывали об инициализации переменных. Вот фрагмент про граммы, в которой в переменную записывается приблизительное зна чение числа я: f l o a t piNumber
3.1415926F;
Хотя переменная p i N u m b e r и может быть использована в роли константы, напри мер при вычислении длины окружности или при других операциях с числом я, этот способ не очень-то хорош. Программист по ошибке может изменить значение, храня щееся в переменной p i N u m b e r , из-за чего все вычисления с использованием этой пе ременной будут выполнены неверно. Определив переменную p i N u m b e r как константу, вы гарантируете неизменность ее значения: c o n s t f l o a t piNumber
3.1415926F;
Попытка выполнить, например, следующую операцию присваивания будет пресе еще на этапе компиляции программы: c o n s t f l o a t piNumber 3.1415926F; piNumber Ошибка, т. к. переменная piNumber — константа Если в программе имеются группы логически связанных между собой констант, удобно оформить их как классы, содержащие только константные статические поля. Рассмотрим программу, исходный текст которой приведен в листинге 3.5. Глава 3. Объектно-ориентированное программирование
141
Листинг
3.5.
Файл
using System; namespace S t a t i c C o n s t a n t class public const string public const int port class
StaticConstant
static void
args)
"Адрес сайта службы восстановления
В этой программе мы объявили класс G l o b a l P a r a m s , содержащий только два — константные поля u r l и p o r t . Первое из них универсальный адрес ресурса (Universal Resource Locator, URL) Web-сайта службы восстановления данных DataRecpvery.Ru, созданной одним авторов этой книги, а номер порта протокола ТСРЛР для доступа к данному Web-сайту. Задача программы — отображение на консоли полного адреса сайта, состоящего из адреса URL и номера порта ТСРЛР, разделенного двоеточием: "Адрес с а й т а службы восстановления данных: Обратите внимание, что, хотя при объявлении полей u r l и p o r t мы не использо вали ключевое слово s t a t i c , обращение к полям класса G l o b a l P a r a m s выполняет ся как к статическим членам класса, т. е. с использованием имени класса. Это возможно потому, что в контексте объявления полей класса ключевое слово автоматически делает описанные с его помощью поля статическими. При этом дополнительное использование ключевого слова s t a t i c не только излишне, но и допускается компилятором.
Статические методы класса В предыдущем разделе мы создавали класс, состоящий из одних только полей. Такие классы удобны, например, для объединения логически связанных между собой констант. и другая возможность — можно создать классы, состоящие из одних только методов. Такие классы могут объединять методы, реализующие алгоритмы, так или иначе связанные между собой. А
Фролов, Г. В. Фролов, Язык С#, Самоучитель
В программе, исходный текст которой представлен в листинге 3.6, мы создали класс В этом классе есть только методы для выполнения некоторых арифме тических операций над целыми числами, но нет ни одного поля. Листинг
3.6.
Файл
u s i n g System; namespace c l a s s MathOp public s t a t i c int
x)
r e t u r n ++x; public static int
x)
return public static int
x)
r e t u r n x * x; class
StaticMethod
static void
args)
i n t op квадрат числа:
Все методы класса MathOp — статические. Метод I n c r e m e n t возвращает значение переданного ему параметра, увеличенное на единицу, метод D e c r e m e n t — уменьшенное на единицу, а метод P o w e r O f 2 возвращает квадрат переданного ему значения. Так как в классе M a t h O p нет полей, не имеет смысла создавать на его базе объект и размещать данный объект в оперативной памяти компьютера. Вы можете использо вать его методы и без создания объекта, ссылаясь на них при помощи имени класса. Так мы и поступили в методе M a i n приведенной выше программы. Если в вашей программе имеются методы, предназначенные для какой-либо алго ритмической обработки данных, логически связанные между собой и не имеющие от ношения к какому-либо объекту то вы можете оформить их в виде класса с набором статических методов. Если объявить эти методы с модификатором доступа они будут доступны в любом месте программы. Для ссылки на них требуется указать имя класса. Главе 3.
программирование
143
Перегрузка методов Неотъемлемой
возможностью, во всех современных объектноязыках программирования, является так называемая перегрузка
методов. Чтобы понять, что это такое, рассмотрим следующую задачу. Пусть нам нужно создать класс, содержащий статические методы для вывода чений типа i n t , b o o l и s t r i n g на консоль. Вот один из возможных вариантов ре шения этой проблемы: class s t a t i c public void
x) int:
x)
s t a t i c public void
s) string:
s t a t i c public void
s)
b) значение b o o l :
b)
Здесь мы объявили 3 метода с различными именами, каждый из которых можно использовать для вывода значения своего типа:
Как видите, для выполнения однотипных действий (вывода значения на консоль) нам приходится вызывать 3 различных метода. Намного удобнее было бы придумать такой способ вывода, когда метод с одним и тем же именем мог бы выводить на консоль значения различных типов. При этом нам не пришлось бы запоминать име на методов, предназначенных для работы с различными типами данных. В языке С# существует возможность определения в одном классе нескольких мето дов с одинаковыми именами, но с различными списками параметров. Такие методы называются перегруженными. Взгляните на исходный текст программы, приведенный в листинге 3.7. Листинг
Файл
u s i n g System; namespace class
ValuePrinter
1
public void
x) А
Фролов, Г. В Фролов.
Самоучитель
x); s t a t i c public void Print(string s) string: s t a t i c public void
b) значение b o o l :
class s t a t i c void
args)
Как видите, в классе V a l u e P r i n t e r мы определили 3 статических метода с именем P r i n t . Эти методы отличаются друг от друга только типом значения пара и решают одну и ту же задачу — вывода полученного значения на консоль. Пользоваться нашими перегруженными методами очень просто — достаточно пе редать им значение, отображаемое на консоли:
Теперь для вывода значений любого из перечисленных выше типов мы можем ис пользовать с одним и тем же именем. При формировании кода компилятор под ставит вызов нужного варианта метода P r i n t в зависимости от типа значения, пере даваемого методу в качестве параметра. Заметим, что перегруженные методы должны отличаться списком параметров, при этом возвращаемое значение не играет никакой роли. Следующее определение класса будет неверным: class
ValuePrinter
s t a t i c public void
x)
Глава 3. Объектно-ориентированное программирование
145
public int
x) x)
return x;
Здесь мы попытались создать перегруженный метод P r i n t с тем же списком па раметров, но, в отличие от исходного метода, возвращающий значение. Такая попытка будет рассматриваться компилятором как ошибка. Вы можете перегружать не только обычные но и конструкторы. Перегру женные конструкторы, как и перегруженные методы, должны отличаться друг от дру га списком параметров. Рассмотрим следующий пример: class Point int int yPos; public xPos yPos
0; 0;
public
x, int y) X;
yPOS
Здесь в классе P o i n t мы определили два конструктора, один из которых имеет па раметры, а другой — нет. При помощи конструктора с параметрами мы можем при создании объекта (точки) задать координаты этого объекта на плоскости. Конструктор без параметров всегда располагает создаваемый в начале системы координат: Point ptl, pt2; new pt2 new Point Объект p t l будет размещен в точке с координатами (10, 20), а объект p t 2 — в на чале системы координат, т. с. в точке (0, 0).
Конструктор Как мы уже говорили, класс представляет собой описание объекта, а не сам объект. Чтобы создать объект на базе класса, необходим оператор new: cr; cr - new В. Фролов, Г В.
Язык
Самоучитель
В этом разделе мы рассмотрим процесс создания объекта класса подробнее. В ча стности, мы опишем работу конструктора класса. Конструктор — это специальный метод, получающий управление при создании экземпляра объекта. Обычно этот метод используется для инициализации полей класса или выполнения других инициализирующих действий. Имя метода, играющего роль конструктора класса, должно совпадать с названием класса. Например, для класса R e c t a n g l e мы создали конструктор следующего вида: class
Rectangle
public int public int protected protected
xPos; yPos; i n t width; int
public xPos
Задачей
yPos
width
height
этого конструктора является нулевыми значениями.
инициализация
всех
полей
класса
Обратите внимание на использование спецификатора доступа p u b l i c при объяв лении конструктора. Если его не указать или применить вместо него спецификатор доступа p r i v a t e , программа не сможет создать объект данного класса. Зачем же нужны классы, которые нельзя использовать для создания объектов? Конструкторы такого вида встречаются в классах, содержащих только статиче ские члены. Такие классы не используются для создания объектов. Разработчик класса может запретить создание объектов класса, содержащего только статиче ские члены, так как, кроме непроизводительной потери памяти, это ни к чему не приведет.
Конструктор по умолчанию Если в классе нет конструктора, для него будет создан так называемый конструктор по умолчанию (default constructor). Задача конструктора по умолчанию — начальная инициализация полей класса. При этом в числовые поля записываются нулевые значения. Рассказывая о статических полях и методах, мы говорили, что если класс содержит только статические члены, то программе не следует создавать объекты такого класса. Как же запретить создание объектов класса? Если не объявлять в классе конструктор, то для него будет использован конструк тор по умолчанию. Поэтому программа сможет создавать на основе класса, не имеющего собственного конструктора. Глава 3. Объектно-ориентированное программирование
147
Но вы создать в конструктор с модификатором доступа p r i v a t e , в чего создание объектов данного класса станет невозможным. Этот конст руктор, называемый закрытым, может не содержать ни одного оператора, так как он никогда не будет выполняться. class
ValuePrinter
private ValuePrinter
t a t i c public void
закрытый конструктор
x)
t a t i c public void
s) string:
tatic public void
s)
b) значение b o o l :
Компилятор не позволит создать объект класса, объявленного с закрытым конст руктором.
Конструкторы и наследование В языке С# конструкторы классов не наследуются. Это означает, что при создании производного объекта сначала вызывается конструктор производного класса, а за тем — конструктор базового класса. Рассмотрим следующий пример (листинг 3.8). Листинг
3.8.
Файл
u s i n g System; namespace C o n s t r u c t o r s class
BaseClass
public конструктор б а з о в о г о
c l a s s DerivedClass f
BaseClass
А В. Фролов, Г. В. Фролов. Язык С#. Самоучитель
конструктор производного
class static void
args)
DerivedClass derived new
Здесь мы объявили базовый класс B a s e C l a s s и производный от него класс D e r i v e d C l a s s . В каждом из классов мы объявили собственный конструктор. Полу чив управление, этот конструктор идентифицирует себя, отображая на консоли свое сообщение. Вот что мы там увидим, запустив эту программу: Вызван к о н с т р у к т о р б а з о в о г о к л а с с а конструктор производного класса
Таким образом, при создании производного класса вначале вызывается конструк тор базового класса. А что будет, в базовом определить несколько перегруженных конст рукторов? Теперь, когда конструкторов базового класса несколько, встает вопрос выбора кон структора при создании объекта производного класса. Компилятор С# решает этот вопрос следующим образом: •
в базовом классе объявлен конструктор без параметров, то при создании объ екта производного класса вначале вызывается конструктор без параметров базово го класса, а затем конструктор производного класса:
•
в том случае, когда в базовом классе нет конструктора без параметров, компилятор выдает сообщение об ошибке.
В некоторых случаях такое поведение может вызывать определенные неудобства. Например, при создании объекта производного класса может потребоваться вызвать один из перегруженных конструкторов базового класса с параметром. Чтобы разрешить эту проблему, в языке С# предусмотрены так называемые ини конструкторов b a s e и
Инициализатор конструктора base Вначале мы рассмотрим инициализатор конструктора b a s e . Этот инициализатор по зволяет конструктор базового класса перед выполнением конструктора произ водного класса. Рассмотрим использование этого инициализатора на примере про граммы (листинг 3.9). Глава 3. Объектно-ориентированное программирование
149
Листинг
3.9.
Файл
using System; namespace class
BaseClass
public "Конструктор public
б а з о в о г о класса б е з i)
"Конструктор
c l a s s DerivedClass
базового класса,
параметр
BaseClass
public конструктор производного
class
ConstructorsOverloadedApp
s t a t i c void
args)
DerivedClass derived; derived new
Здесь мы определили в базовом классе B a s e C l a s s два один из ко торых не имеет параметров, а другой получает один параметр. Оба конструктора вы водят сообщения на консоль, причем второй конструктор включает в текст этого со значение полученного параметра. Конструктор производного класса тоже выводит сообщение на консоль, но перед этим он вызывает конструктор базового класса, передавая ему в качестве параметра число public
Фролов, Г. В. Фролов. Язык
Самоучитель
Если в базовом классе объявлено несколько конструкторов, то по количеству и ти пу параметров, указанных в скобках после ключевого слова b a s e , компилятор может выбрать нужный конструктор. И действительно, после запуска приведенной выше программы на консоль выво дится следующее: Конструктор б а з о в о г о к л а с с а , параметр 5 Вызван конструктор производного класса Это означает, что при создании объекта производного класса был вызван конструк тор базового класса с параметром.
Инициализатор конструктора this С помощью инициализатора конструктора t h i s , который вызывается аналогично только что рассмотренному инициализатору b a s e , существует возможность вызова одного конструктора из другого в пределах класса. То есть если в классе имеется не сколько перегруженных конструкторов, то инициализатор t h i s позволяет вызвать один такой конструктор из другого. Заметим, что возможность вызова одного конструктора класса из другого конст руктора этого же класса отсутствует в языке программирования С++.
Статический конструктор Если в классе определены статические поля, то для их инициализации используется статический конструктор. При объявлении статического конструктора необходимо указывать ключевое слово Статические конструкторы аналогичны обычным конструкторам. В них, например, вы можете вызвать инициализаторы конструкторов. Статические конструкторы базо вого класса вызываются перед статическими конструкторами производного класса.
Деструктор Как мы говорили во Введении, для удаления объектов из оперативной памяти Micro soft Framework использует специальную систему сборки мусора. Поэтому объек ты и локальные переменные уничтожаются автоматически, как только в них отпадает необходимость. Тем не менее встречаются такие ситуации, когда перед удалением объекта класса из памяти необходимо выполнить какие-либо заключительные действия. Например, если объект работает с файлами, перед удалением такого объекта все открытые файлы следует закрыть. Как контрпара конструктору, создающему объект, во многих объектноориентированных языках программирования предусматривается специальный метод с названием деструктор, отвечающий за выполнение заключительных действий при ликвидации объекта. Деструктор объявляется таким же образом, каким объявляется и конструктор, од нако перед его именем с именем класса) нужно поместить символ ~. Глава
3.
Объектно-ориентированное программирование
Следует заметить, что из-за асинхронного принципа работы системы сборки мусо ра момент вызова трудно (если вообще возможно). Поэтому, в частности, разработчикам программ С# деструкторы использовать не рекомендуется.
Еще о классах и полях В этом разделе мы рассмотрим несколько особенностей языка программирования С#, касающихся классов и полей. Это поля r e a d o n l y , параметры o u t и др.
Поля readonly Выше, в разделе «Статические константы», мы рассказывали вам о том, что програм мам часто приходится иметь дело с константами — полями и переменными, содержи мое которых не изменяется на протяжении работы программы. Значения констант кодируются непосредственно в исходном тексте программы, поэтому они должны быть известны разработчику. Но так бывает далеко не всегда. Некоторые параметры работы программа узнает только после запуска, причем в про цессе работы программы эти параметры остаются неизменными. В качестве примера таких параметров можно привести, например, тип и версию ОС, под управлением которой работает программа, экранное разрешение, максималь ное количество цветов, которое способен отображать видеоадаптер, локальный адрес IP, полученный от администратора локальной сети или от специального сервера, распределяющего такие адреса автоматически. Для хранения всех этих параметров вам не удастся использовать поля, определенные с ключевым словом s t a t i c , как их значения будут известны только после запуска программы. Использование же для хранения параметров обычных полей хотя и возможно, но небезопасно, так как программист по ошибке может изменить содержимое таких полей. Чтобы выйти из этой затруднительной ситуации, в языке С# разрешается объявлять поля с ключевым словом r e a d o n l y . Содержимое таких полей можно изменять только в конструкторе объекта, а после того, как объект создан, они превращаются в настоящие константы. Попытка изменить содержимое поля r e a d o n l y в каком-либо методе класса, кроме конструктора, будет рассматриваться компилятором как ошибочная. Таким образом, поля, определенные с ключевым словом r e a d o n l y , доступны только на чтение. Пример использования ключевого слова r e a d o n l y мы привели в программе, ис ходный текст которой вы найдете в листинге Листинг
3.10.
Файл
u s i n g System; namespace class public
static static
readonly s t r i n g readonly uint А
ip;
Фролов,
В. Фролов. Язык
Самоучитель
static ip port
class
ReadOnlyFieldsApp
static void
args) IP:
порт:
Здесь в классе N e t w o r k P a r a m s мы определили два поля, ip и p o r t , как доступ ные только для чтения. Так как класс N e t w o r k P a r a m s предназначен для хранения констант, значение которых определяется в момент работы программы, нет никакой создавать этого класса. Поэтому наш класс содержит только статические поля. Но статические поля нельзя проинициализировать обычным конструктором, пред полагающим создание объектов класса. Проблема решается при помощи статического конструктора, на который и возлагается инициализация наших полей: static ip port
80;
Только здесь, в статическом конструкторе, мы можем изменять значение констант типа r e a d o n l y . Статический конструктор работает только один раз при запуске про граммы и до момента обращения программы к любому статическому члену класса. Поэтому наша программа может обращаться с полями ip и p o r t как с обычными ста тическими константами, получая их значение с помощью имени класса N e t w o r k Params: IP:
(0),
Разумеется, вы можете создавать поля типа r e a d o n l y не только статическими, но и обычными. В этом случае для работы с этими полями придется создавать объект класса, а обращение к полям нужно будет выполнять с применением имени объекта, а не имени класса. Глава 3. Объектно-ориентированное программирование
Передача параметров по ссылке До сих пор мы имели дело с методами, способными вернуть только одно значение. Но иногда это неудобно. Было бы иметь возможность получить при вызове метода сразу несколько значений. Рассмотрим типичный пример. Пусть у нас имеется определение класса R e c tangle: Rectangle public i n t xPos; public int yPos; protected int protected int public xPos
yPos
public void xPos yPos
height x,
0; int y)
x; y;
public void width height public
width
i n t h) w; h;
int
return xPos; public
int
return yPos; public
int
return width; public
int
return
А В. Фролов, Г. В. Фролов, Язык
Самоучитель
Здесь методы S e t P o s i t i o n и S e t S i z e устанавливают сразу два из них задаст координаты нижнего угла прямоугольника, а второй — ширину и высоту прямоугольника. Однако для того чтобы получить координаты и размеры прямоугольника, нам нуж но не 2, а целых 4 метода, так как каждый метод принципиально вернуть только одно значение. Чтобы объяснить, как можно найти выход из данной ситуации, нам придется разо браться в том, как методы получают параметры. Во многих языках программирования существует два метода передачи парамет ров — по значению и по ссылке. Когда значение параметра передается методу по значению, то метод получает это значение и сохраняет его в своей локальной памяти (в локальном стеке). Хотя метод и может попытаться изменить значение переменной, передаваемой ему по значению, это закончится безрезультатно. Если же параметр передается методу по ссылке, то ме тод может изменять значение соответствующей переменной. Приведем конкретный пример. Пусть наша программа создает объект класса R e c t a n g l e и вызывает методы для установки его расположения и размера: Rectangle rect new 30); Мы создадим метод предназначенный для того, чтобы получить оба размера прямоугольника (ширину и высоту) за один прием: public void
w,
i n t h)
w h Этот метод мы попытаемся использовать следующим образом: i n t wCurrent,
hCurrent;
Здесь мы вызываем метод G e t S i z e в надежде на то, что он изменит содержимое переменных, переданных ему в качестве параметров. Однако этого не произойдет, так как метод G e t S i z e копирует значения полученных параметров в свой стек и мо дифицирует в своей локальной памяти. При этом все изменения пропадут после того, как управление возвратится вызывающему методу. В результате значения переменных w C u r r e n t и h C u r r e n t останутся неизменными. Чтобы заставить метод G e t S i z e изменить содержимое переменных w C u r r e n t и h C u r r e n t , нам нужно передать методу не значения переменных, а ссылки на них. Упрощенно можно представить себе ссылку как адрес переменной в оперативной па мяти. Такой способ передачи параметров и называется передачей по Глава
Объектно-ориентированное программирование
Пользуясь ссылкой, сможет изменить содержимое переменных. В мы получим то, что хотели. Вызвав один метод, мы получим от сра зу два значения — ширину и высоту прямоугольника. Для передачи параметров по ссылке в языке С# предусмотрено два ключевых сло r e f и o u t . Первое из них требует предварительной инициализации объекта, на который передается ссылка, а второе — не требует. Использование ключевого слова o u t демонстрируется в программе, исходный текст которой приведен в листинге Листинг
Файл
u s i n g System; namespace class
Rectangle
int xPos; int int width; int public xPos
yPos
width
height
public void
x,
0; int y)
yPos public void width height public
w,
i n t h)
w; h;
int
return xPos; public
int
return yPos; public
int
return width;
А
Г. В.
Язык С#. Самоучитель
int f
return
height;
public void
int
out i n t h)
w h public void
int x,
out i n t y )
X у
y P o s ;
class s t a t i c void
args)
Rectangle rect; rect new
"Ширина i n t xCurrent,
высота yCurrent,
wCurrent,
xCurrent, out wCurrent, out
"Ширина wCurrent,
высота
Здесь мы использование как уже знакомого нам метода получения координат и размеров прямоугольника, предполагающего объявление четырех мето дов, так и нового, основанного на параметров по ссылке. Глава 3. Объектно-ориентированное программирование
Вот старый способ:
"Ширина
высота
Здесь все понятно: мы вызываем 4 метода и выводим на консоль возвращенные ими значения. Новый способ предполагает использование методов G e t S i z e и G e t P o s i t i o n , следующим образом: public void
int
out i n t h)
h public void x у
int x,
out i n t y )
xPos; yPos;
Параметры этих методов мы отметили ключевым словом o u t , чтобы указать ком пилятору, что это выходные параметры функции, передаваемые по ссылке. Как пользоваться этими методами? Очень просто. Достаточно объявить 4 переменные, в которые наши методы запи шут координаты и размеры прямоугольника, а затем передать их нашим методом, снабдив ключевым словом i n t xCurrent,
yCurrent,
wCurrent,
hCurrent;
xCurrent, out wCurrent, o u t Далее нам остается только вывести значения этих переменных на консоль: xCurrent, "Ширина wCurrent, h C u r r e n t )
высота
Заметим, что если в приведенной выше программе заменить ключевое слово o u t на ключевое слово r e f , то на этапе трансляции исходного текста мы получим сооб щение об ошибке. Компилятору «не понравится», что переменные x C u r r e n t , yCurrent, wCurrent и hCurrent перед использованием. А В. Фролов,
В. Фролов. Язык
Самоучитель
Ситуацию можно было бы исправить, выполнив инициализацию этих переменных произвольным значением, например: i n t xCurrent
yCurrent
wCurrent
hCurrent
Но в нашем случае этот прием выглядит несколько искусственно, так как до вызова методов G e t S i z e и G e t P o s i t i o n в перечисленных переменных не может на ходиться никакого осмысленного значения. Задача методов G e t S i z e и G e t P o s i как раз и заключается в инициализации этих четырех переменных! Сказанное, однако, не означает, что ключевое слово r e f бесполезно и его все гда следует заменять ключевым словом Описание параметра с ключевым сло вом r e f используется обычно тогда, когда метод должен с помощью этого пара метра не только возвращать значение, но и получать его. Вот, например, метод, по зволяющий передвинуть точку одновременно по оси X и Y, вернув при этом новые значения координат: public void xPos yPos x у
int x,
ref i n t
x; y;
Здесь текущие координаты точки увеличиваются на значения, переданные методу через параметры. Затем метод возвращает через те же самые пара метры новые координаты точки. Примененное здесь ключевое слово r e f не только обеспечивает передачу пара метров по ссылке, но и позволяет убедиться на этапе компиляции программы в том, что методу передаются ссылки на проинициализированные пере менные. Это облегчает отладку программы, потому что программисты часто забывают инициализировать переменные, передаваемые подобным образом.
Запрет наследования классов Если вы создали класс, который не должен использоваться для создания на его основе производных классов, т. е. который не может выступать в роли базового для других классов, его можно отметить ключевым словом В следующем примере попытка создания производного класса C o l o r R e c t a n g l e на базе класса пресечена компилятором: sealed
c l a s s Rectangle
int int int int Глава
3. Объектно-ориентированное
программирование
public xPos
yPos
width
c l a s s ColorRectangle byte byte byte
height
Rectangle
colorR;
слово s e a l e d позволяет застраховаться от ошибочного наследования классов, не предназначенных для выступления в роли базовых классов.
Фролов, Г. В.
Язык С# Самоучитель