Предисловие

Задача этой книги — кратко, доступно и строго изложить основы С#, одного из самых перспективных современных языков программирования. Книга содержит описание версии С# 2.0 (2005) и предназначена для студентов, изучающих язык «с нуля», но будет полезна и опытным программистам, желающим освоить но­вый язык, не тратя времени на увесистые переводные фолианты.

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

Язык С# как средство обучения программированию обладает рядом несомнен­ных достоинств. Он хорошо организован, строг, большинство его конструкций логичны и удобны. Развитые средства диагностики и редактирования кода дела­ют процесс программирования приятным и эффективным. Мощная библиотека классов платформы .NET берет на себя массу рутинных операций, что дает возможность решать более сложные задачи, используя готовые «строительные блоки». Все это позволяет расценивать С# как перспективную замену языков Паскаль, BASIC и C++ при обучении программированию.

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

Мощь языка С# имеет и оборотную сторону: во-первых, он достаточно требовате­лен к ресурсам компьютера1, во-вторых, для осмысленного написания простейшей программы, вычисляющей, «сколько будет дважды два», требуется изучить дос­таточно много материала, но многочисленные достоинства языка и платформы .NET перевешивают все недостатки.

Дополнительным достоинством является то, что компания Microsoft распро­страняет версию С# Express 2005, по которой можно познакомиться с языком бесплатно (http://msdn.microsoft.com/vstudio/express/visualCsharp/), что дает нам долгожданную возможность обойтись без пиратских копий программного обес­печения и почувствовать себя законопослушными гражданами.

 

ПРИМЕЧАНИЕ ---------------------------------------------------------------------------------------------

Название языка вообще-то следовало бы произносить как «Си-диез». Компания Microsoft неоднократно подчеркивала, что, несмотря на некоторое различие в на­чертании и коде символов «решетки» и «диеза», в названии языка имеется в виду именно музыкальный диез и обыгрывается тот факт, что символом С в музыке обозначается нота «до». Таким образом, создатели языка вложили в его название смысл «на полтона выше С». Однако в нашей стране укоренилась калька с английского — «Си-шарп». Поклонники этого произношения должны иметь в виду, что язык C++ они должны называть тогда исключительно «Си-плас-плас», потому что программист прежде всего обязан следовать логике!

 

Все возможности языка С# описаны в соответствии с его спецификацией (http:// www.ecma-international.org/publications/standards/Ecma-334.htm) и сопровождают­ся простыми примерами, к которым даны исчерпывающие пояснения. Большин­ство примеров представляют собой консольные приложения, однако в главе 14 дается введение в программирование под Windows, а в главе 15 — введение в соз­дание веб-форм и веб-служб. Возможности, появившиеся во второй версии языка, рассматриваются не в отдельной главе, а по логике изложения материала, при этом они всегда снабжены соответствующей ремаркой для тех, кто пользуется платформой .NET предыдущих версий.

В книге приведено краткое введение в интересные и полезные темы, которые обычно не освещаются в базовом курсе С#, например, многопоточные приложе­ния, асинхронные делегаты, асинхронный ввод-вывод и регулярные выражения. Определения синтаксических элементов языка выделены в тексте книги полу­жирным шрифтом. Все ключевые слова, типы, классы и большинство методов, описанных в книге, можно найти по алфавитному указателю, что позволяет ис­пользовать ее и в качестве справочника. Планируется интернет-поддержка кни­ги на сайте http://ips.ifmo.ru в виде конспекта лекций, тестовых вопросов и про­верки выполнения лабораторных работ.

Традиционного раздела «Благодарности» в этом предисловии не будет: при напи­сании этой книги мне никто не помогал, и вся ответственность за ошибки лежит исключительно на мне. У меня нет собаки или жены, чтобы поблагодарить их, как это принято у зарубежных авторов, а мой попугай постоянно мешал мне своими громкими неорганизованными выкриками.

Ваши замечания, пожелания, дополнения, а также замеченные ошибки и опечат­ки не ленитесь присылать по адресу mux@tp2055.spb.edu — и тогда благодаря вам кто-то сможет получить более правильный вариант этой книги.

 

От издательства

 

Ваши замечания, предложения и вопросы отправляйте также по адресу элек­тронной почты comp@piter.com  (издательство «Питер»). Мы будем рады узнать ваше мнение! Подробную информацию о наших книгах вы найдете на веб-сайте издательства www.piter.com.

 

Глава 1

Первый взгляд на платформу .NET

 

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

Среда разработки обычно содержит:

□   текстовый редактор, предназначенный для ввода и корректировки текста программы;

□  компилятор, с помощью которого программа переводится с языка, на котором она написана, в машинные коды;

□   средства отладки и запуска программ;

□  общие библиотеки, содержащие многократно используемые элементы программ;

□   справочную систему и другие элементы.

Под платформой понимается нечто большее, чем среда разработки для одного язы­ка. Платформа .NET (произносится «дотнет») включает не только среду разработ­ки для нескольких языков программирования, называемую Visual Studio.NET, но и множество других средств, например, механизмы поддержки баз данных, электронной почты и коммерции.

В эпоху стремительного развития Интернета — глобальной информационной сети, объединяющей компьютеры разных архитектур, важнейшими задачами при соз­дании программ становятся:

□  переносимость — возможность выполнения на различных типах компьютеров;

□  безопасность — невозможность несанкционированных действий;

□  надежность — способность выполнять необходимые функции в предопреде­ленных условиях; средний интервал между отказами;

использование готовых компонентов — для ускорения разработки;

межъязыковое взаимодействие — возможность применять одновременно не­сколько языков программирования.

 

Платформа .NET позволяет успешно решать все эти задачи. Для обеспечения пе­реносимости компиляторы, входящие в состав платформы, переводят программу не в машинные коды, а в промежуточный язык (Microsoft Intermediate Language, MSIL, или просто IL), который не содержит команд, зависящих от языка, операционной системы и типа компьютера. Программа на этом языке выполняется не самостоятельно, а под управлением системы, которая называется общеязыковой средой выполнения (Common Language Runtime, CLR).

Среда CLR может быть реализована для любой операционной системы. При выполнении программы CLR вызывает так называемый JIT-компилятор, пере­водящий код с языка IL в машинные команды конкретного процессора, которые немедленно выполняются. JIT означает «just in time», что можно перевести как «вовремя», то есть компилируются только те части программы, которые требует­ся выполнить в данный момент. Каждая часть программы компилируется один раз и сохраняется в кэше для дальнейшего использования. Схема выполнения программы при использовании платформы .NET приведена на рис. 1.1.

 

 

Компилятор в качестве результата своего выполнения создает так называе­мую сборку — файл с расширением ехе или dll, который содержит код на языке IL и метаданные. Метаданные представляют собой сведения об объектах, ис­пользуемых в программе, а также сведения о самой сборке. Они позволяют организовать межъязыковое взаимодействие, обеспечивают безопасность и об­легчают развертывание приложений, то есть установку программ на компьютеры пользователей.

ПРИМЕЧАНИЕ -----------------—-------------------------------------------------------------------------

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

---------------------------------------------------------------------------------------------------------------------

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

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

 

 

 

Над этим слоем находится набор классов, позволяющий работать с базами дан­ных и XML XML вы познакомитесь в последней главе этой книги). Классы самого верхнего уровня поддерживают разработку распределенных приложений, а также веб- и Windows-приложений. Программа может использовать классы любого уровня.

Подробное изучение библиотеки классов .NET — необходимая, но и наиболее трудоемкая задача программиста при освоении этой платформы. Библиотека

классов вместе с CLR образуют каркас (framework), то есть основу платформы. Назначение остальных частей платформы мы рассмотрим по мере изучения материала.

 

ПРИМЕЧАНИЕ----------------------------------------------------------------------------------------------

Термин «приложение» можно для начала воспринимать как синоним слова «програм­ма». Например, вместо фразы «программа, работающая под управлением Windows», Говорят «Windows-приложение» или просто «приложение».

------------------------------------------------------------------------------------------------------------------

 

Платформа .NET рассчитана на объектно-ориентированную технологию созда­ния программ, поэтому прежде чем начинать изучение языка С#, необходимо познакомиться с основными понятиями объектно-ориентированного програм­мирования (ООП).

 

Объектно-ориентированное программирование

 

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

Понятие объекта в программе совпадает с обыденным смыслом этого слова: объ­ект представляется как совокупность данных, характеризующих его состояние, и функций их обработки, моделирующих его поведение. Вызов функции на выпол­нение часто называют посылкой сообщения объекту.

При создании объектно-ориентированной программы предметная область пред­ставляется в виде совокупности объектов. Выполнение программы состоит в том, что объекты обмениваются сообщениями. Это позволяет использовать при про­граммировании понятия, более адекватно отражающие предметную область.

При представлении реального объекта с помощью программного необходимо выде­лить в первом его существенные особенности. Их список зависит от цели модели­рования. Например, объект «крыса» с точки зрения биолога, изучающего миграции, ветеринара или, скажем, повара будет иметь совершенно разные характеристики. Выделение существенных с той или иной точки зрения свойств называется абст­рагированием. Таким образом, программный объект — это абстракция.

Важным свойством объекта является его обособленность. Детали реализации объ­екта, то есть внутренние структуры данных и алгоритмы их обработки, скрыты

от пользователя объекта и недоступны для непреднамеренных изменений. Объект используется через его интерфейс — совокупность правил доступа.

Скрытие деталей реализации называется инкапсуляцией (от слова «капсула»). Ничего сложного в этом понятии нет: ведь и в обычной жизни мы пользуемся объектами через их интерфейсы. Сколько информации пришлось бы держать в голове, если бы для просмотра новостей надо было знать устройство телевизора!

Таким образом, объект является «черним ящиком», замкнутым по отношению к внешнему миру. Это позволяет представить программу в укрупненном виде — на уровне объектов и их взаимосвязей, а следовательно, управлять большим объ­емом информации и успешно отлаживать сложные программы.

Сказанное можно сформулировать более кратко и строго: объект — это инкапсу­лированная абстракция с четко определенным интерфейсом.

Инкапсуляция позволяет изменить реализацию объекта без модификации основ­ной части программы, если его интерфейс остался прежним. Простота модифи­кации является очень важным критерием качества программы, ведь любой про­граммный продукт в течение своего жизненного цикла претерпевает множество изменений и дополнений.

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

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

Наследование является мощнейшим инструментом ООП и применяется для сле­дующих взаимосвязанных целей:

□   исключения из программы повторяющихся фрагментов кода;

□  упрощения модификации программы;

□  упрощения создания новых программ на основе существующих.

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

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

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

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

 Подведя итог сказанному, сформулирую достоинства ООП:

□   использование при программировании понятий, близких к предметной области;

□  возможность успешно управлять большими объемами исходного кода благо­даря инкапсуляции, то есть скрытию деталей реализации объектов и упроще­нию структуры программы;

□   возможность многократного использования кода за счет наследования;

□  сравнительно простая возможность модификации программ;                   

□   возможность создания и использования библиотек объектов.

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

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

 

Классы

 

Для представления объектов в языках С#, Java, C++, Delphi и т. п. используется понятие класс, аналогичное обыденному смыслу этого слова в контексте «класс членистоногих», «класс млекопитающих», «класс задач» и т. п. Класс является обобщенным понятием, определяющим характеристики и поведение некоторого множества конкретных объектов этого класса, называемых экземплярами класса. «Классический» класс содержит данные, задающие свойства объектов класса, и функции, определяющие их поведение. В последнее время в класс часто добавля­ется третья составляющая — события, на которые может реагировать объект класса. Все классы библиотеки .NET, а также все классы, которые создает программист в среде .NET, имеют одного общего предка — класс object и организованы в единую иерархическую структуру. Внутри нее классы логически сгруппированы в так называемые пространства имен, которые служат для упорядочивания имен классов и предотвращения конфликтов имен: в разных пространствах имена мо­гут совпадать. Пространства имен могут быть вложенными, их идея аналогична знакомой вам иерархической структуре каталогов на компьютере.

Любая программа, создаваемая в NET,  использует пространство имен System. В нем определены классы, которые обеспечивают базовую функциональность, напри­мер, поддерживают выполнение математических операций, управление памятью и ввод-вывод.                                        

Обычно в одно пространство имен объединяют взаимосвязанные классы. Напри­мер, пространство System. Net содержит классы, относящиеся к передаче данных по сети, System.Windows.Forms — элементы графического интерфейса пользовате­ля, такие как формы, кнопки и т. д. Имя каждого пространства имен представля­ет собой неделимую сущность, однозначно его определяющую.

Последнее, о чем необходимо поговорить, прежде чем начать последовательное изучение языка С#, — среда разработки Visual Studio.NET.

 

Среда Visual Studio.NET

 

Среда разработки Visual Studio.NET предоставляет мощные и удобные средства написания, корректировки, компиляции, отладки и запуска приложений, исполь­зующих NET-совместимые языки. Корпорация Microsoft включила в платфор­му средства разработки для четырех языков: С#, VB.NET, C++ и J#.

Платформа .NET является открытой средой. Это значит, что компиляторы для нее могут поставляться и сторонними разработчиками. К настоящему времени разработаны десятки компиляторов для .NET, например, Ada, COBOL, Delphi, Eiffel, Fortran, Lisp, Oberon, Perl и Python.

Bee .NET-совместимые языки должны отвечать требованиям общеязыковой спе­цификации (Common Language Specification, CLS), в которой описывается набор общих для всех языков характеристик. Это позволяет использовать для разра­ботки приложения несколько языков программирования и вести полноценную межъязыковую отладку. Все программы независимо от языка используют одни и те же базовые классы библиотеки NET.

Приложение в процессе разработки называется проектом. Проект объединяет все необходимое для создания приложения: файлы, папки, ссылки и прочие ре­сурсы. Среда Visual Studio.NET позволяет создавать проекты различных типов, например:

□   Windows-приложение использует элементы интерфейса Windows, включая формы, кнопки, флажки и пр.;

□   консольное приложение выполняет вывод «на консоль», то есть в окно команд­ного процессора;

□   библиотека классов объединяет классы, которые предназначены для исполь­зования в других приложениях;

□   веб-приложение — это приложение, доступ к которому выполняется через браузер (например, Internet Explorer) и которое по запросу формирует веб-­страницу и отправляет ее клиенту по сети;

веб-сервис — компонент, методы которого могут вызываться через Интернет.

Несколько проектов можно объединить в решение (solution). Это облегчает со­вместную разработку проектов.

                                                               

Консольные приложения

 

Среда Visual Studio.NET работает на платформе Windows и ориентирована на соз­дание Windows- и веб-приложений, однако разработчики предусмотрели работу и с консольными приложениями. При запуске консольного приложения операци­онная система создает так называемое консольное окно, через которое идет весь ввод-вывод программы. Внешне это напоминает работу в операционной системе в режиме командной строки, когда ввод-вывод представляет собой поток символов.

Консольные приложения наилучшим образом подходят для изучения языка, так как в них не используется множество стандартных объектов, необходимых для создания графического интерфейса. В первой части курса мы будем создавать только консольные приложения, чтобы сосредоточить внимание на базовых свойствах языка С#. В следующем разделе рассмотрены самые простые действия в среде: создание и запуск на выполнение консольного приложения на С#. Более полные сведения, необходимые для работы в Visual Studio.NET, можно получить из документации или книг [8], [16].

 

 

ПРИМЕЧАНИЕ----------------------------------------------------------------------------------------------

Большинство примеров, приведенных в книге, иллюстрируют базовые возможно­сти С# и разрабатывались в интегрированной среде версии 7.1 (библиотека NET Framework 1.1), однако вы можете работать и в более новых версиях. Программы, ко­торые используют новые средства языка, появившиеся в спецификации версии 2.0, проверялись в Visual C# 2005 Express Edition (библиотека .NET Framework 2.0).

------------------------------------------------------------------------------------------------------------------

 

Создание проекта. Основные окна среды

 

Для создания проекта следует после запуска Visual Studio.NET в главном ме­ню выбрать команду FileNewProject.... В левой части открывшегося диало­гового окна нужно выбрать пункт Visual C# Projects, в правой — пункт Console Application. В поле Name можно ввести имя проекта, а в поле Location — место его сохранения на диске, если заданные по умолчанию значения вас не устраивают. После щелчка на кнопке ОК среда создаст решение и проект с указанным име­нем. Примерный вид экрана приведен на рис. 1.3.

В верхней части экрана располагается главное меню (с разделами File, Edit, View и т. д.) и панели инструментов (toolbars). Панелей инструментов в среде великое множество, и если включить их все (ViewToolbars...), они займут половину экрана.

 

 

 

 

В верхней левой части экрана располагается окно управления проектом Solution Ex­plorer (если оно не отображается, следует воспользоваться командой ViewSolution Explorer главного меню). В окне перечислены все ресурсы, входящие в проект: ссыл­ки на библиотеку (System, System.Data, System.XML), файл ярлыка (App.ico), файл с исходным текстом класса (Class1.cs) и информация о сборке (Assemblylnfo.cs).

В этом же окне можно увидеть и другую информацию, если перейти на вкладку Class View, ярлычок которой находится в нижней части окна. На вкладке Class View представлен список всех классов, входящих в приложение, их элементов и предков.

ПРИМЕЧАНИЕ ---------------------------------------------------------------------------------------------

Небезынтересно полюбопытствовать, какие файлы создала среда для поддержки проекта. С помощью проводника Windows можно увидеть, что на заданном диске появилась папка с указанным именем, содержащая несколько других файлов и вло­женных папок. Среди них — файл проекта (с расширением csproj), файл решения , (с расширением sin) и файл с кодом класса (Class 1 .cs).

----------------------------------------------------------------------------------------------------------------

 

В нижней левой части экрана расположено окно свойств Properties (если окна не видно, воспользуйтесь командой ViewProperties главного меню). В окне свойств

отображаются важнейшие характеристики выделенного элемента. Например, чтобы изменить имя файла, в котором хранится класс Classl, надо выделить этот файл в окне управления проектом и задать в окне свойств новое значение свой­ству FileName (ввод заканчивается нажатием клавиши Enter). Основное пространство экрана занимает окно редактора, в котором располагает­ся текст программы, созданный средой автоматически. Текст представляет собой каркас, в который программист добавляет код по мере необходимости. Ключевые (зарезервированные) слова1 отображаются синим цветом, коммента­рии различных типов — серым и темно-зеленым, остальной текст — черным. Слева от текста находятся символы структуры: щелкнув на любом квадратике с минусом, можно скрыть соответствующий блок кода. При этом минус превра­щается в плюс, щелкнув на котором, можно опять вывести блок на экран. Это средство хорошо визуально структурирует код и позволяет сфокусировать вни­мание на нужных фрагментах.

 

Заготовка консольной программы

 

Рассмотрим каждую строку заготовки программы (листинг 1.1). Не пытайтесь сра­зу понять абсолютно все, что в ней написано. Пока что ваша цель — изучить прин­ципы работы в оболочке, а досконально разбираться в программе мы будем позже.

Листинг 1.1. Заготовка консольной программы

using System;

 

namespace ConsoleApplicationl

{                                                                                                                      

/// <summary>

    /// Summary description for Classl.

/// </summary> class Classl

 

{

/// <summary>

/// The main entry point for the application.

/// </summary>

[STAThread]

static void Main(string[] args)

{

     //

     // TODO: Add code to start application here

//

 

              }

      } 

}

 

Директива using System разрешает использовать имена стандартных классов из пространства имен System непосредственно (без указания имени пространства).

Ключевое слово namespace создает для проекта собственное пространство имен, названное по умолчанию ConsoleApplicationl. Это сделано для того, чтобы можно было давать программным объектам имена, не заботясь о том, что они могут сов­пасть с именами в других пространствах имен.

Строки, начинающиеся с двух или трех косых черт, являются комментариями и предназначены для документирования текста программы.

С# — объектно-ориентированный язык, поэтому написанная на нем программа представляет собой совокупность взаимодействующих между собой классов. В нашей заготовке программы всего один класс, которому по умолчанию задано имя Classl. Описание класса начинается с ключевого слова class, за которым сле­дуют его имя и далее в фигурных скобках — список элементов класса (его дан­ных и функций, называемых также методами).

 

ВНИМАНИЕ -------------------------------------------------------------------------------------------------

Фигурные скобки являются важным элементом синтаксиса. Каждой открывающей скобке соответствует своя закрывающая, которая обычно располагается ниже по тексту с тем же отступом. Эти скобки ограничивают блок, внутри которого могут располагаться другие блоки, вложенные в него, как матрешки. Блок может приме­няться в любом месте, где допускается отдельный оператор.

---------------------------------------------------------------------------------------------------------------------

 

В данном случае внутри класса только один элемент — метод Main. Каждое при­ложение должно содержать метод Main — с него начинается выполнение програм­мы. Все методы описываются по единым правилам.

Упрощенный синтаксис метода:

[ спецификаторы ] тип имя_метода ( [ параметры ] )  

{

тело метода: действия, выполняемые методом

}

ВНИМАНИЕ--------------------------------------------------------------------------------------------------

Для описания языка программирования в документации часто используется некото­рый формальный метаязык, например, формулы Бэкуса—Наура или синтаксические диаграммы. Для наглядности и простоты изложения в этой книге используется широ­ко распространенный неформальный способ описания, когда необязательные части синтаксических конструкций заключаются в квадратные скобки, текст, который необ­ходимо заменить конкретным значением, пишется по-русски, а возможность выбо­ра одного из нескольких элементов обозначается вертикальной чертой. Например:

 [ void | int ] имя_метода();

Эта запись означает, что вместо конструкции имя метода необходимо указать кон­кретное имя в соответствии с правилами языка, а перед ним может находиться либо слово void, либо слово int, либо ничего. Символ подчеркивания используется для связи слов вместо пробела, показывая, что на этом месте должен стоять один син­таксический элемент, а не два. В тех случаях, когда квадратные скобки являются элементом синтаксиса, это оговаривается особо.

------------------------------------------------------------------------------------------------------------------

 

 

Таким образом, любой метод должен иметь тип, имя и тело, остальные части описания являются необязательными, поэтому мы их пока проигнорируем. Мето­ды подробно рассматриваются в разделе «Методы» (см. с. 106).

ПРИМЕЧАНИЕ----------------------------------------------------------------------------------------------Наряду с понятием «метод» часто используется другое — функция-член класса. Метод является частным случаем функции — законченного фрагмента кода, кото­рый можно вызвать по имени. Далее в книге используются оба эти понятия.

--------------------------------------------------------------------------------------------------------------------

 

Среда заботливо поместила внутрь метода Main комментарий:

// TODO: Add code to start application here

 

Это означает: «Добавьте сюда код, выполняемый при запуске приложения». Последуем совету и добавим после строк комментария (но не в той же строке!)  строку

Console.WriteLineC"Ур-ра! Зар-работало!(с)Кот Матроскин");

Здесь Console — это имя стандартного класса из пространства имен System. Его метод WriteLine выводит на экран заданный в кавычках текст. Как видите, для обращения к методу класса используется конструкция

 

имя_класса.имя_метода

 

Если вы не сделали ошибку в первом же слове, то сразу после ввода с клавиату­ры следом за словом Console символа точки среда выведет подсказку, содержа­щую список всех доступных элементов класса Console. Выбор нужного имени выполняется либо мышью, либо клавишами управления курсором, либо вводом одного или нескольких начальных символов имени. При нажатии клавиши Enter выбранное имя появляется в тексте программы.

СОВЕТ --------------------------------------------------------------------------------------------------------

Не пренебрегайте возможностью автоматического ввода — это убережет вас от опе­чаток и сэкономит время. Если подсказка не появляется, это свидетельствует об ошибке в имени или в месте расположения в программе вводимого текста.

------------------------------------------------------------------------------------------------------------------

 

Программа должна приобрести вид, приведенный в листинге 1.2 (для того чтобы вы могли сосредоточиться на структуре программы, из нее убраны все коммента­рии и другие пока лишние для нас детали). Ничего ужасного в этом листинге нет, не правда ли?

Обратите внимание на то, что после внесения изменений около имени файла на ярлычке в верхней части окна редактора появился символ * — это означает, что текст, сохраненный на диске, и текст, представленный в окне редактора, не совпадают. Для сохранения файла воспользуйтесь командой FileSave главного меню или кнопкой Save на панели инструментов (текстовый курсор должен при этом находиться в окне редактора). Впрочем, при запуске программы среда со­хранит исходный текст самостоятельно.

На рис. 1.4 приведен экран после создания консольного приложения в Visual С# 2005 Express Edition. Как видите, текст заготовки приложения более лако­ничен, чем в предыдущей версии, и практически совпадает с листингом 1.1 без учета комментариев.

Листинг 1.2. Первая программа на С#

using System;

namespace ConsoleApplicationl

 

 

 

{                                                                                                                                                                                           

class Classl

{

static void Main()

{                                     

Console.WriteLine( "Ур-ра! Зар-работало! (с) Кот Матроскин"

     }

 }

          

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


Запуск программы

Самый простой способ запустить программу — нажать клавишу F5 (или выбрать в меню команду DebugStart). Если программа написана без ошибок, то фраза Ур-ра! Зар-работало! (с) Кот Матроскин промелькнет перед вашими глазами в кон­сольном окне, которое незамедлительно закроется. Это хороший результат, но для того чтобы пронаблюдать его спокойно, следует воспользоваться клавишами Ctrl+F5 (или выбрать в меню команду DebugStart Without Debugging).

После внесения изменений компилятор может обнаружить в тексте программы синтаксические ошибки. Он сообщает об этом в окне, расположенном в нижней части экрана.

Давайте внесем в программу синтаксическую ошибку, чтобы впервые увидеть то, что впоследствии вам придется наблюдать многие тысячи раз. Уберите точку с запятой после оператора Console.WriteLine(...) и запустите программу заново. Вы увидите диалоговое окно с сообщением о том, что при построении приложения обнаружены ошибки, и вопросом, продолжать ли дальше (There were build errors. Continue?). Сурово ответив No, обратимся к окну вывода ошибок за разъяснениями.

ПРИМЕЧАНИЕ ---------------------------------------------------------------------------------------------

Если сообщения об ошибке не видно, просмотрите содержимое окна с помощью по­лосы прокрутки, пытаясь найти в нем слово «error».

---------------------------------------------------------------------------------------------------------------------

Двойной щелчок на строке с сообщением об ошибке error CS1002 ; expected (означающее, что здесь ожидалась точка с запятой) подсвечивает неверную стро­ку в программе. Для получения дополнительных пояснений можно нажать кла­вишу F1, при этом в окне редактора появится новая вкладка с соответствующей страницей из справочной системы. Для возвращения к коду класса следует щелк­нуть на ярлычке с именем файла в верхней строке редактора.

Другой способ получения справки — использовать окно Dynamic Help, распо­ложенное на том же месте, что и окно свойств Properties (его ярлычок находится в нижней строке окна). Содержимое этого окна динамически меняется в зависи­мости от того, какой элемент выделен. Для получения справки надо щелкнуть на соответствующей ссылке, и страница справочной системы будет отображена на отдельной вкладке окна редактора (в Visual C# 2005 Express Edition справочная информация появляется в отдельном окне).

Теперь, когда вы получили общее представление о платформе .NET, можно, на­конец, приступить к планомерному изучению языка С#.

 

Рекомендации по программированию

 

В этой главе дается очень краткое введение в интересную и обширную тему — платформу .NET. Для более глубокого понимания механизмов ее функциониро­вания настоятельно рекомендуется изучить дополнительную литературу (напри­мер, [5], [19], [26], [27]) и публикации в Интернете.

Среда разработки Visual Studio.NET предоставляет программисту мощные и удоб­ные средства написания, корректировки, компиляции, отладки и запуска приложе­ний, описанию которых посвящены целые книги. В процессе изучения языка С# желательно постепенно изучать эти возможности, ведь чем лучше вы будете вла­деть инструментом, тем эффективнее и приятнее будет процесс программирования.

 

Глава 2

Основные понятия языка

 

В этой главе рассматриваются элементарные «строительные блоки» языка С#, вводится понятие типа данных и приводится несколько классификаций типов.

 

Состав языка

 

Язык программирования можно уподобить очень примитивному иностранному языку с жесткими правилами без исключений. Изучение иностранного языка обычно начинают с алфавита, затем переходят к словам и законам построения фраз, и только в результате длительной практики и накопления словарного запа­са появляется возможность свободно выражать на этом языке свои мысли. При­мерно так же поступим и мы при изучении языка С#.

 

 

 

Алфавит и лексемы

 

Все тексты на языке пишутся с помощью его алфавита. Например, в русском языке один алфавит (набор символов), а в албанском — другой. В С# использу­ется кодировка символов Unicode. Давайте разберемся, что это такое. Компьютер умеет работать только с числами, и для того чтобы можно было хра­нить в его памяти текст, требуется определить, каким числом будет представлять­ся (кодироваться) каждый символ. Соответствие между символами и кодирую­щими их числами называется кодировкой, или кодовой таблицей (character set). Существует множество различных кодировок символов. Например, в Windows часто используется кодировка ANSI, а конкретно — СР1251. Каждый символ представляется в ней одним байтом (8 бит), поэтому в этой кодировке можно од­новременно задать только 256 символов. В первой половине кодовой таблицы находятся латинские буквы, цифры, знаки арифметических операций и другие распространенные символы. Вторую половину занимают символы русского алфавита. Если требуется представлять символы другого национального алфавита (например, албанского), необходимо использовать другую кодовую таблицу.

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

 

Алфавит С# включает:

□  буквы (латинские и национальных алфавитов) и символ подчеркивания (_), который употребляется наряду с буквами;

□  цифры;

□  специальные символы, например +, *, { и &;

□  пробельные символы (пробел и символы табуляции);

□  символы перевода строки.

Из символов составляются более крупные строительные блоки: лексемы, дирек­тивы препроцессора и комментарии.

Лексема (token) —это минимальная единица языка, имеющая самостоятельный смысл. Существуют следующие виды лексем:

□  имена (идентификаторы);

□  ключевые слова;

□  знаки операций;

□  разделители;

□  литералы (константы).

 

Лексемы языка программирования аналогичны словам естественного языка. На­пример, лексемами являются число 128 (но не его часть 12), имя Vasia, ключевое слово goto и знак операции сложения +. Далее мы рассмотрим лексемы подробнее.

Директивы препроцессора пришли в С# из его предшественника — языка C++. Препроцессором называется предварительная стадия компиляции, на которой формируется окончательный вид исходного текста программы. Например, с по­мощью директив (инструкций, команд) препроцессора можно включить или вы­ключить из процесса компиляции фрагменты кода. Директивы препроцессора не играют в С# такой важной роли, как в C++. Мы рассмотрим их в свое вре­мя—в разделе «Директивы препроцессора» (см. с. 287).

Комментарии предназначены для записи пояснений к программе и формирова­ния документации. Правила записи комментариев описаны далее в этом разделе.

Из лексем составляются выражения и операторы. Выражение задает правило вычисления некоторого значения. Например, выражение а + b задает правило вычисления суммы двух величин.

 

ПРИМЕЧАНИЕ ---------------------------------------------------------------------------------------------

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

---------------------------------------------------------------------------------------------------------------------

 

Оператор задает законченное описание некоторого действия, данных или эле­мента программы. Например:

 

int а;

 

Это — оператор описания целочисленной переменной а;

 

Идентификаторы

 

Имена в программах служат той же цели, что и имена в мире людей, — чтобы об­ращаться к программным объектам и различать их, то есть идентифицировать. Поэтому имена также называют идентификаторами. В идентификаторе могут использоваться буквы, цифры и символ подчеркивания. Прописные и строчные буквы различаются, например, sysop, SySoP и SYSOP — три разных имени.

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

В идентификаторах С# разрешается использовать помимо латинских букв бук­вы национальных алфавитов. Например, Пёсик или çç являются правильными идентификаторами. Более того, в идентификаторах можно применять даже так называемые escape-последовательности Unicode, то есть представлять символ с помощью его кода в шестнадцатеричном виде с префиксом \и, например, \u00F2.

 

ПРИМЕЧАНИЕ----------------------------------------------------------------------------------------------

Примеры неправильных имен: 21 ate, Big gig, Б#г; первое начинается с цифры, вто­рое и третье содержат недопустимые символы (пробел и #).

---------------------------------------------------------------------------------------------------------------------

Имена даются элементам программы, к которым требуется обращаться: перемен­ным, типам, константам, методам, меткам и т. д. Идентификатор создается на этапе объявления переменной (метода, типа и т. п.), после этого его можно ис­пользовать в последующих операторах программы. При выборе идентификатора необходимо иметь в виду следующее:

□ идентификатор не должен совпадать с ключевыми словами (см. следующий раздел);

□ не рекомендуется начинать идентификаторы с двух символов подчеркивания,

поскольку такие имена зарезервированы для служебного использования. Для улучшения читабельности программы следует давать объектам осмысленные имена, составленные в соответствии с определенными правилами. Понятные и согласованные между собой имена — основа хорошего стиля программирова­ния. Существует несколько видов так называемых нотаций — соглашений о пра­вилах cоздания имен.

В нотации Паскаля каждое слово, составляющее идентификатор, начинается с прописной буквы, например, MaxLength, MyFuzzyShooshpanchik. Венгерская нотация (ее предложил венгр по национальности, сотрудник компа­нии Microsoft) отличается от предыдущей наличием префикса, соответствующе­го типу величины, например, MaxLength, TpfnMyFuzzyShooshpanchik. Согласно нотации Camel, с прописной буквы начинается каждое слово, составляю­щее идентификатор, кроме первого, например, maxLength, myFuzzyShooshpanchik. Человеку с богатой фантазией абрис имени может напоминать верблюда, откуда и произошло название этой нотации.

Еще одна традиция — разделять слова, составляющие имя, знаками подчеркива­ния: max_length, my_fuzzy_shooshpanchik, при этом все составные части начинаются со строчной буквы.

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

 

Ключевые слова

 

Ключевые слова — это зарезервированные идентификаторы, которые имеют специ­альное значение для компилятора. Их можно использовать только в том смыс­ле, в котором они определены. Список ключевых слов С# приведен в табл. 2.1.

 

Знаки операций и разделители

 

Знак операции — это один или более символов, определяющих действие над операндами. Внутри знака операции пробелы не допускаются. Например, в вы­ражении а += b знак += является знаком операции, а а и b — операндами. Симво­лы, составляющие знак операций, могут быть как специальными, например, &&, |   и <, так и буквенными, такими как as или new.

Операции делятся на унарные, бинарные и тернарную по количеству участвую­щих в них операндов. Один и тот же знак может интерпретироваться по-разному в зависимости от контекста. Все знаки операций, за исключением [ ], ( ).и ? :, представляют собой отдельные лексемы.

 

 

 

 

 

ПРИМЕЧАНИЕ ---------------------------------------------------------------------------------------------

Знаки операций С# описаны в разделе «Операции и выражения» (см. с. 42). Боль­шинство стандартных операций может быть переопределено (перегружено). Пере­грузка операций рассматривается в разделе «Операции класса» (см. с. 161).

--------------------------------------------------------------------------------------------------------------------

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

 

{}   []   (). . ::    +    - * /   *  % &    |    ˆ    !    ˜   =

 

<   >   ?    ++    --    &&    ||    «   »   ==    != <=>= +=    - =   .*=   

 

/=   %=

 

&=    |=    ˆ=   <<=   >>=    ->

 

Литералы

 

Литералами, или константами, называют неизменяемые величины. В С# есть логические, целые, вещественные, символьные и строковые константы, а также константа null. Компилятор, выделив константу в качестве лексемы, относит ее к одному из типов данных по ее внешнему виду. Программист может задать тип константы и самостоятельно.

Описание и примеры констант каждого типа приведены в табл. 2.2. Примеры, иллюстрирующие наиболее часто употребляемые формы констант, выделены полу­жирным шрифтом (при первом чтении можно обратить внимание только на них).

 

 

Рассмотрим табл. 2.2 более подробно. Логических литералов всего два. Они широ­ко используются в качестве признаков наличия или отсутствия чего-либо. Целые литералы могут быть представлены либо в десятичной, либо в шестнадцатеричной системе счисления, а вещественные — только в десятичной системе, но в двух формах: с фиксированной точкой и с порядком. Вещественная константа с порядком представляется в виде мантиссы и порядка. Мантисса записывается слева от знака экспоненты (Е или е), порядок — справа от знака. Значение кон­станты определяется как произведение мантиссы и возведенного в указанную в порядке степень числа 10 (например, 1.3е2 = 1,3 • 102 = 130). При записи вещест­венного числа могут быть опущены либо целая часть, либо дробная, но, конечно, не обе сразу.

 

ВНИМАНИЕ -------------------------------------------------------------------------------------------------

Пробелы внутри числа не допускаются. Для отделения целой части от дробной ис­пользуется не запятая, а точка. Символ Е не представляет собой знакомое всем из математики число е, а указывает, что далее располагается степень, в которую нужно возвести число 10.

_____________________________________________________________________________

Если требуется сформировать отрицательную целую или вещественную констан­ту, то перед ней ставится знак унарной операции изменения знака (-), например:

-218, -022, -0х3С, -4.8, -0.1е4.

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

 

 

Допустимые диапазоны значений целых и вещественных констант в зависимо­сти от префикса мы рассмотрим немного позже в этой главе. Символьная константа представляет собой любой символ в кодировке Unicode. Символьные константы записываются в одной из четырех форм, представлен­ных в табл. 2.2:

□   «обычный» символ, имеющий графическое представление (кроме апострофа и символа перевода строки), — 'А','ю','*';

□   управляющая последовательность — '\0', '\n';

□   символ в виде шестнадцатеричного кода — '\xF', ' \х74';

□   символ в виде escape-последовательности Unicode '\uA81B'.

 

Управляющей последовательностью, или простой escape-последовательностью, называют определенный символ, предваряемый обратной косой чертой. Управ­ляющая последовательность интерпретируется как одиночный символ и исполь­зуется для представления:

□   кодов, не имеющих графического изображения (например, \n — переход в на­чало следующей строки);

□  символов, имеющих специальное значение в строковых и символьных лите­ралах, например, апострофа .

В табл. 2.4 приведены допустимые значения последовательностей. Если непосред­ственно за обратной косой чертой следует символ, не предусмотренный таблицей, возникает ошибка компиляции.

 

 

 

Символ, представленный в виде шестнадцатеричного кода, начинается с пре­фикса \0х, за которым следует код символа. Числовое значение должно нахо­диться в диапазоне от 0 до 216 - 1, иначе возникает ошибка компиляции. Escape-последовательности Unicode служат для представления символа в ко­дировке Unicode с помощью его кода в шестнадцатеричном виде с префиксом \и или \U, например, \u00F2, \U00010011. Коды в диапазоне от \U10000 до \U1OFFFF представляются в виде двух последовательных символов; коды, превышающие \U10FFFF, не поддерживаются.

Управляющие последовательности обоих видов могут использоваться и в стро­ковых константах, называемых иначе строковыми литералами. Например, если требуется вывести несколько строк, можно объединить их в один литерал, отде­лив одну строку от другой символами \n:

 

"Никто не доволен своей\пвнешностью, но каждый доволен\псвоим умом"

 

Этот литерал при выводе будет выглядеть так:

 

Никто не доволен своей внешностью, но каждый доволен своим умом

 

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

 

"Издательский дом \"Питер\""

 

Как видите, строковые литералы с управляющими символами несколько теряют в читабельности, поэтому в С# введен второй вид литералов — дословные лите­ралы (verbatim strings). Эти литералы предваряются символом @, который отклю­чает обработку управляющих последовательностей и позволяет получать строки в том виде, в котором они записаны. Например, два приведенных выше литерала в дословном виде выглядят так:

@"Никто не доволен своей

внешностью, но каждый

доволен своим умом"

@"Издательский дом "Питер""

 

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

 

"С: \\app\\bin\\debugWa.exe"

 @"С:\app\bin\debug\a.ехе"

 

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

Константа null  представляет собой значение, задаваемое по умолчанию для вели­чин так называемых ссылочных типов, которые мы рассмотрим далее в этой главе.

 

Комментарии

 

Комментарии предназначены для записи пояснений к программе и формирова­ния документации. Компилятор комментарии игнорирует. Внутри комментария можно использовать любые символы. В С# есть два вида комментариев: одно­строчные и многострочные.

Однострочный комментарий начинается с двух символов прямой косой черты (//) и заканчивается символом перехода на новую строку, многострочный заклю­чается между символами-скобками /* и */ и может занимать часть строки, целую строку или несколько строк. Комментарии не вкладываются друг в друга: симво­лы // и /* не обладают никаким специальным значением внутри комментария.

Кроме того, в языке есть еще одна разновидность комментариев, которые начи­наются с трех подряд идущих символов косой черты (///). Они предназначены для формирования документации к программе в формате XML. Компилятор извлекает эти комментарии из программы, проверяет их соответствие правилам и записывает их в отдельный файл. Правила задания комментариев этого вида мы рассмотрим в главе 15.

 

Типы данных

 

Данные, с которыми работает программа, хранятся в оперативной памяти. Есте­ственно, что компилятору необходимо точно знать, сколько места они занимают, как именно закодированы и какие действия с ними можно выполнять. Все это задается при описании данных с помощью типа. Тип данных однозначно определяет:                                                                        

□   внутреннее представление данных, а следовательно, и множество их возмож­ных значений;

□  допустимые действия над данными (операции и функции).

Например, целые и вещественные числа, даже если они занимают одинаковый объем памяти, имеют совершенно разные диапазоны возможных значений; целые числа можно умножать друг на друга, а, например, символы — нельзя. Каждое выражение в программе имеет определенный тип. Величин, не имеющих никакого типа, не существует. Компилятор использует информацию о типе при проверке допустимости описанных в программе действий. Память, в которой хранятся данные во время выполнения программы, делится на две области: стек (stack) и динамическая область, или хип (heap). Стек ис­пользуется для хранения величин, память под которые выделяет компилятор, а в динамической области память резервируется и освобождается во время вы­полнения программы с помощью специальных команд. Основным местом для хранения данных в С# является хип.

 

 

Классификация типов

 

Любая информация легче усваивается, если она «разложена по полочкам». По­этому, прежде чем перейти к изучению конкретных типов языка С#, рассмотрим их классификацию. Типы можно классифицировать по разным признакам. Если принять за основу строение элемента, все типы можно разделить на простые (не имеют внутренней структуры) и структурированные (состоят из элементов других типов). По своему «создателю»   типы можно разделить на встроенные

(стандартные) и определяемые программистом (рис. 2.1). Для данных статиче­ского типа память выделяется в момент объявления, при этом ее требуемый объ­ем известен. Для данных динамического типа размер данных в момент объявле­ния может быть неизвестен, и память под них выделяется по запросу в процессе выполнения программы.

 

 

Встроенные типы

 

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

Встроенные типы С# приведены в табл. 2.5. Они однозначно соответствуют стан­дартным классам библиотеки .NET, определенным в пространстве имен System. Как видно из таблицы, существуют несколько вариантов представления целых и вещественных величин. Программист выбирает тип каждой величины, исполь­зуемой в программе, с учетом необходимого ему диапазона и точности представ­ления данных.

Целые типы, а также символьный, вещественные и финансовый типы можно объединить под названием арифметических типов.

Внутреннее представление величины целого типа — целое число в двоичном коде. В знаковых типах старший бит числа интерпретируется как знаковый (0 — положительное число, 1 — отрицательное). Отрицательные числа чаще всего представляются в так называемом дополнительном коде. Для преобразования числа в дополнительный код все разряды числа, за исключением знакового, инвертируются, затем к числу прибавляется единица, и знаковому биту тоже присваивается единица. Беззнаковые типы позволяют представлять только положительные числа, поскольку старший разряд рассматривается как часть кода числа.

 

 

ПРИМЕЧАНИЕ----------------------------------------------------------------------------------------------

Если под величину отведено и двоичных разрядов, то в ней можно представить 2n различных сочетаний нулей и единиц. Если старший бит отведен под знак, то диа­пазон возможных значений величины — [-2n-1, 2n-1  - 1], а если все разряды исполь­зуются для представления значения, диапазон смещается в область положительных чисел и равен [0, 2n - 1] (см. табл. 2.5).

---------------------------------------------------------------------------------------------------------------------

Вещественные типы, или типы данных с плавающей точкой, хранятся в памяти компьютера иначе, чем целочисленные. Внутреннее представление вещественного числа состоит из двух частей — мантиссы и порядка, каждая часть имеет знак. Дли­на мантиссы определяет точность числа, а длина порядка — его диапазон. В первом приближении это можно представить себе так: например, для числа 0,381 • 104

хранятся цифры мантиссы 381 и порядок 4, для числа 560,3 • 102 — мантисса 5603 и порядок 5 (мантисса нормализуется), а число 0,012 представлено как 12 и 1. Конечно, в этом примере не учтены система счисления и другие особенности.

Все вещественные типы могут представлять как положительные, так и отрица­тельные числа. Чаще всего в программах используется тип double, поскольку его диапазон и точность покрывают большинство потребностей. Этот тип имеют ве­щественные литералы и многие стандартные математические функции.

 

ПРИМЕЧАНИЕ ---------------------------------------------------------------------------------------------

Обратите внимание на то, что при одинаковом количестве байтов, отводимых под величины типа float и int, диапазоны их допустимых значений сильно различают­ся из-за внутренней формы представления. То же самое относится к  Long и double.

________________________________________________________________

Тип decimal предназначен для денежных вычислений, в которых критичны ошиб­ки округления. Как видно из табл. 2.5, тип float позволяет хранить одновре­менно всего 7 значащих десятичных цифр, тип double — 15-16. При вычислениях ошибки округления накапливаются, и при определенном сочетании значений это даже может привести к результату, в котором не будет ни одной верной значащей цифры! Величины типа decimal позволяют хранить 28-29 десятичных разрядов.

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

Любой встроенный тип С# соответствует стандартному классу библиотеки .NET, определенному в пространстве имен System. Везде, где используется имя встроенно­го типа, его можно заменить именем класса библиотеки. Это значит, что у встро­енных типов данных С# есть методы и поля. С их помощью можно, например, получить минимальные и максимальные значения для целых, символьных, фи­нансовых и вещественных чисел:

□   double. MaxValue (или System. Double. MaxValue) — максимальное число типа double;

□   uint.MinValue (или System.UInt32.MinValue) — минимальное число типа uint.

 

ПРИМЕЧАНИЕ ---------------------------------------------------------------------------------------------

Интересно, что в вещественных классах есть элементы, представляющие поло­жительную и отрицательную бесконечности, а также значение «не число» — это Positivelnfinity, Negativelnfinity и NaN соответственно. При выводе на экран, например, первого из них получится слово «бесконечность». Все доступные эле­менты класса можно посмотреть в окне редактора кода, введя символ точки сразу после имени типа.

_____________________________________________________________________________

 

Типы литералов

 

Как уже говорилось, величин, не имеющих типа, не существует. Поэтому литера­лы (константы) тоже имеют тип. Если значение целого литерала находится внутри диапазона допустимых значений типа int, литерал рассматривается как int, иначе он относится к наименьшему из типов uint, long или ulong, в диапазон значений которого он входит. Вещественные литералы по умолчанию относятся к типу double.

Например, константа 10 относится к типу int (хотя для ее хранения достаточно и байта), а константа 2147483648 будет определена как uint. Для явного задания типа литерала служит суффикс, например, l. lf, 1UL, 1000m (все суффиксы пере­числены в табл. 2.3). Явное задание применяется в основном для уменьшения количества неявных преобразований типа, выполняемых компилятором.

 

Типы-значения и ссылочные типы

 

Чаще всего типы С# разделяют по способу хранения элементов на типы-значения и ссылочные типы (рис. 2.2). Элементы типов-значений , или значимых типов (value types), представляют собой просто последовательность битов в памяти, необходимый объем которой выделяет компилятор. Иными словами, величины значимых типов хранят свои значения непосредственно. Величина ссылочного типа хранит не сами данные, а ссылку на них (адрес, по которому расположены данные). Сами данные хранятся в хипе.

 

ВНИМАНИЕ ---------------------------------------------------------------------------------

Несмотря на различия в способе хранения, и типы-значения, и ссылочные типы яв­ляются потомками общего базового класса object.

 

 

 

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

одни и те же данные (на рисунке b и с равны, но а не равно b даже при одинако­вых значениях). Из этого следует, что если изменить значение одной величины ссылочного типа, это может отразиться на другой.

 

 

Обратите внимание на то, что не все значимые типы являются простыми. По дру­гой классификации структуры и перечисления относятся к структурированным типам, определяемым программистом. Мы рассмотрим эти типы в главе 9. Ссы­лочные типы мы будем изучать в главе 5 и последующих главах, после основных операторов С#, когда вы освоите синтаксис языка, а до этого ограничимся исполь­зованием встроенных типов-значений. Типы nullable введены в версию С# 2.0 и рассматриваются в главе 13.

 

Упаковка и распаковка

 

Для того чтобы величины ссылочного и значимого типов могли использоваться совместно, необходимо иметь возможность преобразования из одного типа в дру­гой. Язык С# обеспечивает такую возможность. Преобразование из типа-значе­ния в ссылочный тип называется упаковкой (boxing), обратное преобразование — распаковкой (unboxing).

Если величина значимого типа используется в том месте, где требуется ссылоч­ный тип, автоматически выполняется создание промежуточной величины ссылоч­ного типа: создается ссылка, в хипе выделяется соответствующий объем памяти и туда копируется значение величины, то есть значение как бы упаковывается в объект. При необходимости обратного преобразования с величины ссылочно­го типа «снимается упаковка», и в дальнейших действиях участвует только ее значение.

Рекомендации по программированию

 

Понятия, введенные в этой главе, являются базой для всего дальнейшего мате­риала. На первый взгляд, изучение видов лексем может показаться излишним (пусть их различает компилятор!), однако это совершенно не так. Для того чтобы

читать программы, необходимо понимать, из каких элементов языка они состоят. Это помогает и при поиске ошибок, и при обращении к справочной системе, и при изучении новых версий языка. Более того, изучение любого нового языка реко­мендуется начинать именно с лексем, которые в нем поддерживаются. Понятие типа данных лежит в основе большинства языковых средств. При изу­чении любого типа необходимо рассмотреть две вещи: его внутреннее представ­ление а следовательно, множество возможных значений величин этого типа), а также что можно делать с этими величинами. Множество типов данных, реа­лизуемых в языке, является одной из его важнейших характеристик. Выбор наиболее подходящего типа для представления данных — одно из необходимых условий создания эффективных программ.

Новые языки и средства программирования появляются непрерывно, поэтому программист вынужден учиться всю жизнь. Следовательно, очень важно сразу научиться учиться быстро и эффективно. Для этого надо подходить к освоению каждого языка системно: выделить составные части, понять их организацию и взаи­мосвязь, найти сходства и отличия от средств, изученных ранее, — короче го­воря, за минимальное время разложить все в мозгу «по полочкам» так, чтобы новые знания гармонично дополнили имеющиеся. Только в этом случае ими будет легко и приятно пользоваться. Программист-профессионал должен уметь:

□ грамотно поставить задачу;

□ выбрать соответствующие языковые средства;

□  выбрать наиболее подходящие для представления данных структуры;

□  разработать эффективный алгоритм;

□  написать и документировать надежную и легко модифицируемую программу;

□ обеспечить ее исчерпывающее тестирование.

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

 

Глава 3

Переменные, операции и выражения

 

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

 

Переменные

 

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

Пример описания целой переменной с именем а и вещественной переменной х:

 

int a; float x;

 

Имя переменной служит для обращения к области памяти, в которой хранится значение переменной. Имя дает программист. Оно должно соответствовать пра­вилам именования идентификаторов С#, отражать смысл хранимой величины и быть легко распознаваемым. Например, если в программе вычисляется коли­чество каких-либо предметов, лучше назвать соответствующую переменную quantity или, на худой конец, kolich, но не, скажем, A, tl7_xz или prikol.

 

СОВЕТ --------------------------------------------------------------------------------------------------------

Желательно, чтобы имя не содержало символов, которые можно перепутать друг с другом, например 1 (строчная буква L) и I (прописная буква i).

 

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

При объявлении можно присвоить переменной некоторое начальное значение, то ест инициализировать ее, например:

 

int a, b = 1;

float x - 0.1, у = 0.lf;

Здесь описаны:

 

□  переменная а типа Int, начальное значение которой не присваивается;

□  переменная b типа int, ее начальное значение равно 1;

□  переменные х и у типа f I oat, которым присвоены одинаковые начальные значе­ния 0.1. Разница между ними состоит в том, что для инициализации перемен­ной х сначала формируется константа типа double (это тип, присваиваемый по умолчанию литералам с дробной частью), а затем она преобразуется к типу float; переменной у значение 0.1 присваивается без промежуточного преобра­зования.

При инициализации можно использовать не только константу, но и выражение — главное, чтобы на момент описания оно было вычисляемым, например:

 

int b = 1. а = 100;

int x = b * а. + 25:

 

Инициализировать переменную прямо при объявлении не обязательно, но перед тем, как ее использовать в вычислениях, это сделать все равно придется, иначе компилятор сообщит об ошибке.

ВНИМАНИЕ -------------------------------------------------------------------------------------------------Рекомендуется всегда инициализировать переменные при описании.

--------------------------------------------------------------------------------------------------------------------

Впрочем, иногда эту работу делает за программиста компилятор, это зависит от местонахождения описания переменной. Как вы помните из главы 1, программа на С# состоит из классов, внутри которых описывают методы и данные. Пере­менные, описанные непосредственно внутри класса, называются полями класса. Им автоматически присваивается так называемое «значение по умолчанию» — как правило, это 0 соответствующего типа.

Переменные, описанные внутри метода класса, называются локальными перемен­ными. Их инициализация возлагается на программиста.

ПРИМЕЧАНИЕ----------------------------------------------------------------------------------------------

В этой главе рассматриваются только локальные переменные простых встроенных типов данных.

 

 

Так называемая область действия переменной, то есть область программы, где можно использовать переменную, начинается в точке ее описания и длится до конца блока, внутри которого она описана. Блок — это код, заключенный в фигурные скобки/ Основное назначение блока — группировка операторов. В С# любая переменная описана внутри какого-либо блока: класса, метода или блока внутри метода.

Имя переменной должно быть уникальным в области ее действия. Область дей­ствия распространяется на вложенные в метод блоки, из этого следует, что во вложенном блоке нельзя объявить переменную с таким же именем, что и в охва­тывающем его, например:

 

 

Давайте разберемся в этом примере. В нем описан класс X, содержащий три элемента: поле А, поле В и метод Y. Непосредственно внутри метода Y заданы две локальные переменные — С и А.

Внутри метода класса можно описывать переменную с именем, совпадающим с полем класса, потому что существует способ доступа к полю класса с помо­щью ключевого слова this (это иллюстрирует строка, отмеченная символами ***). Таким образом, локальная переменная А не «закрывает» поле А класса X, а вот по­пытка описать во вложенном блоке другую локальную переменную с тем же именем окончится неудачей (эти строки закомментированы).

Если внутри метода нет локальных переменных, совпадающих с полями класса, к этим полям можно обратиться в методе непосредственно (см. строку, помеченную

символами **). Две переменные с именем 0 не конфликтуют между собой, по­скольку блоки, в которых они описаны, не вложены один в другой.

 

СОВЕТ -------------------------------------------------------------------------------------------------------

Как правило, переменным с большой областью действия даются более длинные имена, а для переменных, вся «жизнь» которых — несколько строк исходного тек­ста, хватит и одной буквы с комментарием при объявлении.

-------------------------------------------------------------------------------------------------------------------

В листинге 3.1 приведен пример программы, в которой описываются и выводят­ся на экран локальные переменные.

 

Как вы догадались, метод Write делает то же самое, что и WriteLine, но не перево­дит строку. Более удобные способы вывода рассмотрены в конце этой главы в разделе «Простейший ввод-вывод» (см. с. 59).

 

ВНИМАНИЕ -------------------------------------------------------------------------------------------------

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

---------------------------------------------------------------------------------------------------------------------

 

Именованные константы

 

Можно запретить изменять значение переменной, задав при ее описании ключе­вое слово const, например:

 

const int b = 1;

const float x = 0.1, у = O.lf;  // const распространяется на обе переменные

 

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

ПРИМЕЧАНИЕ----------------------------------------------------------------------------------------------Улучшение читабельности происходит только при осмысленном выборе имен кон­стант. В хорошо написанной программе вообще не должно встречаться иных чисел, кроме 0 и 1, все остальные числа должны задаваться именованными константами с именами, отражающими их назначение.

---------------------------------------------------------------------------------------------------------------------

Именованные константы должны обязательно инициализироваться при описа­нии. При инициализации можно использовать не только константу, но и выра­жение — главное, чтобы оно было вычисляемым на этапе компиляции, например:

const int b = 1, а = 100;

 const int x = b * а + 25;

 

Операции и выражения

 

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

Например, а + 2 — это выражение, в котором + является знаком операции, а а и 2 — операндами. Пробелы внутри знака операции, состоящей из нескольких сим­волов, не допускаются. По количеству участвующих в одной операции опе­рандов операции делятся на унарные, бинарные и тернарную. Операции С# при­ведены в табл. 3.1.

 

 

 

 

 

ПРИМЕЧАНИЕ ---------------------------------------------------------------------------------------------

В версию С# 2.0 введена операция объединения ??, которая рассматривается в гла­ве 13 (см. раздел «Обнуляемые типы», с. 309).

--------------------------------------------------------------------------------------------------------------------

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

Результат вычисления выражения характеризуется значением и типом. Напри­мер, пусть а и b — переменные целого типа и описаны так:

 

int а = 2. b = 5;

 

Тогда выражение а + b имеет значение 7 и тип int, а выражение а = b имеет значе­ние, равное помещенному в переменную а (в данному случае — 5), и тип, совпа­дающий с типом этой переменной.

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

Например, а + b + с означает (a + b)+c, a a = b = c означает а = (b = с). То есть  сначала вычисляется выражение b = с, а затем его результат становится правым f операндом для операции присваивания переменной а.                                             

 

ПРИМЕЧАНИЕ-----------------------------------------------------------------------------------------        

Часто перед выполнением операции требуется вычислить значения операндов.       

Например, в выражении F (i) + G (i ++) * Н (i) сначала вызываются функции F, G и Н, а затем выполняются умножение и сложение. Операнды всегда вычисляются  слева направо независимо от приоритетов операций, в которых они участвуют.  Кстати, в приведенном примере метод Н вызывается с новым значением i (увеличенным на 1).                                                                                                               

-------------------------------------------------------------------------------------------------------------------

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

 

Преобразования встроенных арифметических типов-значений

 

При вычислении выражений может возникнуть необходимость в преобразовании типов. Если операнды, входящие в выражение, одного типа и операция для этого типа определена, то результат выражения будет иметь тот же тип. Если Операнды разного типа и/или операция для этого типа не определена, пе­ред вычислениями автоматически выполняется преобразование типа по прави­лам, обеспечивающим приведение более коротких типов к более длинным для сохранения значимости и точности. Автоматическое (неявное) преобразование возможно не всегда, а только если при этом не может случиться потеря значимости. Если неявного преобразования из одного типа в другой не существует, програм­мист может задать явное преобразование типа с помощью операции (тип)х. Его  результат остается на совести программиста. Явное преобразование рассматри­вается в этой главе немного позже.

ВНИМАНИЕ--------------------------------------------------------------------------------------------------

Арифметические операции не определены для более коротких, чем int, типов. Это означает, что если в выражении участвуют только величины типов sbyte, byte, short и ushort, перед выполнением операции они будут преобразованы в int. Таким образом, результат любой арифметической операции имеет тип не менее int.

--------------------------------------------------------------------------------------------------------------------

 

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

 

 

Преобразование более коротких, чем int, типов выполняется при присваивании. Обратите внимание на то, что неявного преобразования из float и double в decimal не существует.

 

ПРИМЕЧАНИЕ----------------------------------------------------------------------------------------------

Преобразование из типов int, uint и long в тип float и из типа long в тип double может вызвать потерю точности, но не потерю значимости. В процессе других вари­антов неявного преобразования никакая информация не теряется.

---------------------------------------------------------------------------------------------------------------------

Введение в исключения

 

При вычислении выражений могут возникнуть ошибки, например, переполне­ние, исчезновение порядка или деление на ноль. В С# есть механизм, который позволяет обрабатывать подобные ошибки и таким образом избегать аварийного завершения программы. Он так и называется: механизм обработки исключитель­ных ситуаций {исключений).

Если в процессе вычислений возникла ошибка, система сигнализирует об этом с помощью специального действия, называемого выбрасыванием (генерированием) исключения. Каждому типу ошибки соответствует свое исключение. Поскольку С# — язык объектно-ориентированный, исключения являются классами, которые имеют общего предка — класс Exception, определенный в пространстве имен System.

Например, при делении на ноль будет выброшено (сгенерировано) исключение с длинным, но понятным именем DivideByZeroException, при недостатке памяти — ис­ключение OutOfMemoryException, при переполнении — исключение OverflowException.

ПРИМЕЧАНИЕ----------------------------------------------------------------------------------------------

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

_____________________________________________________________________________

Программист может задать способ обработки исключения в специальном блоке ко­да, начинающемся с ключевого слова catch («перехватить»), который будет автома­тически выполнен при возникновении соответствующей исключительной ситуации. Внутри блока можно, например, вывести предупреждающее сообщение или скор­ректировать значения величин и продолжить выполнение программы. Если этот блок не задан, система выполнит действия по умолчанию, которые обычно заключа­ются в выводе диагностического сообщения и нормальном завершении программы.

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

 

 

Проверка не распространяется на функции, вызванные в блоке. Если проверка переполнения включена, говорят, что вычисления выполняются в проверяемом контексте, если выключена — в непроверяемом. Проверку переполнения вы­ключают в случаях, когда усечение результата операции необходимо в соответ­ствии с алгоритмом.

Можно задать проверку переполнения во всей программе с помощью ключа компилятора /checked, это полезно при отладке программы. Поскольку подобная проверка несколько замедляет работу, в готовой программе этот режим обычно не используется.

Мы подробно рассмотрим исключения и их обработку в разделе «Обработка исключительных ситуаций» (см. с. 89).

 

Основные операции С#

 

В этом разделе кратко описаны синтаксис и применение всех операций С#, кро­ме некоторых первичных, которые рассматриваются в последующих главах при изучении соответствующего материала.

 

Инкремент и декремент

 

Операции инкремента (++) и декремента (--), называемые также операциями увеличения и уменьшения на единицу, имеют две формы записи — префиксную, когда знак операции записывается перед операндом, и постфиксную. В префикс­ной форме сначала изменяется операнд, а затем его значение становится резуль­тирующим значением выражения, а в постфиксной форме значением выражения является исходное значение операнда, после чего он изменяется. Листинг 3.2 ил­люстрирует эти операции.

 

Результат работы программы:

Значение префиксного выражения: 4

Значение х после приращения: 4

Значение постфиксного выражения: 3

Значение у после приращения: 4

 

Стандартные операции инкремента существуют для целых, символьных, вещест­венных и финансовых величин, а также для перечислений. Операндом может быть переменная, свойство или индексатор (мы рассмотрим свойства и индекса­торы в свое время, в главах 5 и 7).

 

Операция new

Операция new служит для создания нового объекта. Формат операции:

 

new тип ( [ аргументы ] )                                                                                                              

 

С помощью этой операции можно создавать объекты как ссылочных, так и зна­чимых типов, например:

object z = new object ();

int i = new int();                     // то же самое, что int i = 0;

 

Объекты ссылочного типа обычно формируют именно этим способом, а пере­менные значимого типа чаще создаются так, как описано ранее в разделе «Пере­менные».

При выполнении операции new сначала выделяется необходимый объем памяти (для ссылочных типов в хипе, для значимых — в стеке), а затем вызывается так называемый конструктор по умолчанию, то есть метод, с помощью которого ини­циализируется объект. Переменной значимого типа присваивается значение по умолчанию, которое равно нулю соответствующего типа. Для ссылочных типов стандартный конструктор инициализирует значениями по умолчанию все поля объекта.

Если необходимый для хранения объекта объем памяти выделить не удалось, генерируется исключение OutOfMemoryException.

 

Операции отрицания

 

Арифметическое отрицание (унарный минус -) меняет знак операнда на противоположный. Стандартная операция отрицания определена для типов int, long, float, double и decimal. К величинам других типов ее можно применять, если для них возможно неявное преобразование к этим типам (см. рис. 3.1). Тип результа­та соответствует типу операции.

ПРИМЕЧАНИЕ ---------------------------------------------------------------------------------------------

Для значений целого и финансового типов результат достигается вычитанием ис­ходного значения из нуля. При этом может возникнуть переполнение. Будет ли при этом выброшено исключение, зависит от контекста.

 

Логическое отрицание (!)   определено для типа boo!. Результат операции —

значение false, если операнд равен true, и значение true, если операнд равен

false.

Поразрядное отрицание (~), часто называемое побитовым, инвертирует каждый

разряд в двоичном представлении операнда типа int, uint, long или ulong.

Операции отрицания представлены в листинге 3.3.

 

 

Явное преобразование типа

 

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

Формат операции:

 

( тип ) выражение

 

Здесь тип — это имя того типа, в который осуществляется преобразование, а вы­ражение чаще всего представляет собой имя переменной, например:

 

long b = 300;

int   a = (int) b;           // данные не теряются

byte d = (byte) a:       // данные теряются                        

 

Преобразование типа часто применяется для ссылочных типов при работе с ие­рархиями объектов.

 

Умножение, деление и остаток от деления

 

 Операция умножения (.*) возвращает результат перемножения двух операндов. Стандартная операция умножения определена для типов int, uint, long, ulong, float, double и decimal: К величинам других типов ее можно применять, если для них возможно неявное преобразование к этим типам (см. рис. 3.1). Тип результа­та операции равен «наибольшему» из типов операндов, но не менее int.

Если оба операнда целочисленные или типа decimal и результат операции слиш­ком велик для представления с помощью заданного типа, генерируется исключе­ние System. OverflowExcepti on.

Все возможные значения для вещественных операндов приведены в табл. 3.2. Символами х и у обозначены конечные положительные значения, символом z — результат операции вещественного умножения. Если результат слишком велик для представления с помощью заданного типа, он принимается равным значению «бесконечность», если слишком мал, он принимается за 0. NaN (not a number) означает, что результат не является числом.

 

 

Операция деления (/) вычисляет частное от деления первого операнда на второй. Стандартная операция деления определена для типов int, uint, long, ulong, float, double и decimal. К величинам других типов ее можно применять, если для них существует неявное преобразование к этим типам. Тип результата определяется правилами преобразования (см. рис. 3.1), но не меньше int.

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

System.Di videByZeroExcepti on.

Если хотя бы один из операндов вещественный, дробная часть результата деления не отбрасывается, а все возможные значения приведены в табл. 3.3. Символами х и у обозначены конечные положительные значения, символом z — результат

операции вещественного деления. Если результат слишком велик для представ­ления с помощью заданного типа, он принимается равным значению «бесконеч­ность», если слишком мал, он принимается за 0.

 

Для финансовых величин (тип decimal) при делении на 0 и переполнении генериру­ются соответствующие исключения, при исчезновении порядка результат равен 0.

Операция остатка от деления (%) также интерпретируется по-разному для це­лых, вещественных и финансовых величин. Если оба операнда целочисленные, результат операции вычисляется по формуле х-(х /у) * у. Если делитель равен нулю, генерируется исключение System.DivideByZeroException. Тип результата опе­рации равен «наибольшему» из типов операндов, но не менее int (см. рис. 3.1).

Если хотя бы один из операндов вещественный, результат операции вычисляется по формуле х - n * у, где n — наибольшее целое, меньшее или равное результа­ту деления х на у. Все возможные комбинации значений операндов приведены в табл. 3.4. Символами х и у обозначены конечные положительные значения, символом z — результат операции остатка от деления.

 

 

Для финансовых величин (тип decimal) при получении остатка от деления на 0 и при переполнении генерируются соответствующие исключения, при исчезно­вении порядка результат равен 0. Знак результата равен знаку первого операнда.

Пример применения операциий умножения, деления и получения остатка пред­ставлен в листинге 3.4.

 

Еще раз обращаю ваше внимание на то, что несколько операций одного приори­тета выполняются слева направо. Для примера рассмотрим выражение 2 / х  у. Деление и умножение имеют один и тот же приоритет, поэтому сначала 2 делит­ся на х, а затем результат этих вычислений умножается на у. Иными словами, это выражение эквивалентно формуле

Если же мы хотим, чтобы выражение х у было в знаменателе, следует заклю­чить его в круглые скобки или сначала поделить числитель на х, а потом на у, то есть записать как

2 / {х у) или 2 / х / у.

 

Сложение и вычитание

 

Операция сложения (+) возвращает сумму двух операндов. Стандартная опе­рация сложения определена для типов int, uint, long, ulong, float, double и decimal. К величинам других типов ее можно применять, если для них существует неяв­ное преобразование к этим типам (см. рис. 3.1). Тип результата операции равен «наибольшему» из типов операндов, но не менее int.

Если оба операнда целочисленные или типа decimal и результат операции слиш­ком велик для представления с помощью заданного типа, генерируется исключе­ние System. OverflowExcepti on.

Все возможные значения для вещественных операндов приведены в табл. 3.5. Символами х и у обозначены конечные положительные значения, символом z — результат операции вещественного сложения. Если результат слишком велик для представления с помощью заданного типа, он принимается равным значе­нию «бесконечность», если слишком мал, он принимается за 0.

 

 

Операция вычитания (-) возвращает разность двух операндов. Стандартная опера­ция вычитания определена для типов int, uint, long, ulong, float, double и decimal. К величинам других типов ее можно применять, если для них существует неяв­ное преобразование к этим типам (см. рис. 3.1). Тип результата операции равен «наибольшему» из типов операндов, но не менее int.

Если оба операнда целочисленные или типа decimal и результат операции слиш­ком велик для представления с помощью заданного типа, генерируется исключе­ние System. OverfiowException.

Все возможные значения результата вычитания для вещественных операндов приведены в табл. 3.6. Символами х и у обозначены конечные положительные значения, символом z — результат операции вещественного вычитания. Если х и у равны, результат равен положительному нулю. Если результат слишком велик для представления с помощью заданного типа, он принимается равным значе­нию «бесконечность» с тем же знаком, что и х - у, если слишком мал, он прини­мается за 0 с тем же знаком, что и х - у.

 

 

 

 

 

Операции сдвига

 

Операции сдвига (« и ») применяются к целочисленным операндам. Они сдви­гают двоичное представление первого операнда влево или вправо на количество двоичных разрядов, заданное вторым операндом.

При сдвиге влево («) освободившиеся разряды обнуляются. При сдвиге вправо (») освободившиеся биты заполняются нулями, если первый операнд беззнакового типа (то есть выполняется логический сдвиг), и знаковым разрядом — в против­ном случае (выполняется арифметический сдвиг). Операции сдвига никогда не приводят к переполнению и потере значимости. Стандартные операции сдвига определены для типов int, uint, long и ulong.

Пример применения операций сдвига представлен в листинге 3.5.

 

Операции отношения и проверки на равенство

 

Операции отношения (<, <=, >, >=, ==, ! =) сравнивают первый операнд со вторым. Операнды должны быть арифметического типа. Результат операции — логического типа, равен true или false. Правила вычисления результатов приведены в табл. 3.7.

 

ПРИМЕЧАНИЕ----------------------------------------------------------------------------------------------

Обратите внимание на то, что операции сравнения на равенство и неравенство име­ют меньший приоритет, чем остальные операции сравнения.

---------------------------------------------------------------------------------------------------------------------

Очень интересно формируется результат операций отношения для особых случаев вещественных значений. Например, если один из операндов равен NaN, результатом для всех операций, кроме !=, будет false (для операции != результат равен true).

Очевиден факт, что для любых операндов результат операции х ! = у всегда равен результату операции ! (х == у), однако если один или оба операнда равны NaN, для операций <, >, <= и >= этот факт не подтверждается. Например, если х или у равны

 NaN, то х < у даст false, а !(х >= у) — true.

Другие особые случаи рассматриваются следующим образом:

□   значения +0 и -0 равны;

□   значение - ∞ меньше любого конечного значения и равно другому значению - ∞;

□   значение + ∞ больше любого конечного значения и равно другому значению + ∞.

 

Поразрядные логические операции

 

Поразрядные логические операции (&, |, ˆ ) применяются к целочисленным опе­рандам и работают с их двоичными представлениями. При выполнении опера­ций операнды сопоставляются побитно (первый бит первого операнда с первым битом второго, второй бит первого операнда со вторым битом второго и т. д.). Стандартные операции определены для типов int, uint, long и ulong.

При поразрядной конъюнкции, или поразрядном И (операция обозначается &), бит ре­зультата равен 1 только тогда, когда соответствующие биты обоих операндов равны 1.

При поразрядной дизъюнкции, или поразрядном ИЛИ (операция обозначается |), бит результата равен 1 тогда, когда соответствующий бит хотя бы одного из опе­рандов равен 1.

При поразрядном исключающем ИЛИ (операция обозначается &) бит результата ра­вен 1 только тогда, когда соответствующий бит только одного из операндов равен 1.

Пример применения поразрядных логических операций представлен в листинге 3.6.

 

 

Условные логические операции

 

Условные логические операции И (&&) и ИЛИ (‌‌‌‌‌‌‌‌‌‌‌‌‌׀ ‌‌‌׀ ‌‌‌‌‌‌) чаще всего используются с опе­рандами логического типа. Результатом логической операции является true или false. Операции вычисляются по сокращенной схеме.

Результат операции логическое И имеет значение true, только если оба операнда имеют значение true. Результат операции логическое ИЛИ имеет значение true, если хотя бы один из операндов имел значение true.

ВНИМАНИЕ -------------------------------------------------------------------------------------------------

Если значения первого операнда достаточно, чтобы определить результат опера­ции, второй операнд не вычисляется. Например, если первый операнд операции И равен false, результатом операции будет false независимо от значения второго операнда, поэтому он не вычисляется.

---------------------------------------------------------------------------------------------------------------------

Пример применения условных логических операций представлен в листинге 3.7.

 

 

Условная операция

 

Условная операция (? :) — тернарная, то есть имеет три операнда. Ее формат:

 

операнд_1 операнд_2 : операнд_3

 

Первый операнд — выражение, для которого существует неявное преобразование к логическому типу. Если результат вычисления первого операнда равен true, то результатом условной операции будет значение второго операнда, иначе — третье­го операнда. Вычисляется всегда либо второй операнд, либо третий. Их тип мо­жет различаться.

Тип результата операции зависит от типа второго и третьего операндов:

□   если операнды одного типа, он и становится типом результата операции;

□   иначе, если существует неявное преобразование типа от операнда 2 к операн­ду 3, но не наоборот, то типом результата операции становится тип операнда 3;

□  иначе, если существует неявное преобразование типа от операнда 3 к операн­ду 2, но не наоборот, то типом результата операции становится тип операнда 2;

□  иначе возникает ошибка компиляции.

Условную операцию часто используют вместо условного оператора if (он рас­сматривается в следующей главе) для сокращения текста программы.

Пример применения условной операции представлен в листинге 3.8.

 

 

Другой пример применения условной операции: требуется, чтобы некоторая це­лая величина увеличивалась на 1, если ее значение не превышает n, а иначе при­нимала значение 1. Это удобно реализовать следующим образом:

 

i = (1 < n) ? i + 1:  1;

 

Условная операция правоассоциативна, то есть выполняется справа налево. На­пример, выражение а ? b : с ? d : е вычисляется как а ? b : (.c?d : е).

 

Операции присваивания

 

Операции присваивания (=, +=, -=, *= и т. д.) задают новое значение переменной. Эти операции могут использоваться в программе как законченные операторы.

Формат операции простого присваивания (=):

 

переменная = выражение

 

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

 

ПеременнаяВыражение

 

Напомню, что константа и переменная являются частными случаями выражения.

 Примеры операторов присваивания:

 

а = b + с / 2;

b = a;

а - b;

х = 1:

х = х + 0.5;

 

Обратите внимание: b = а и а = b — это совершенно разные действия!

 

ПРИМЕЧАНИЕ---------------------------------------------------------------------------------------------

Чтобы не перепутать, что чему присваивается, запомните мнемоническое правило: присваивание — это передача данных «налево».

 

Начинающие часто делают ошибку, воспринимая присваивание как аналог ра­венства в математике. Чтобы избежать этой ошибки, надо понимать механизм работы оператора присваивания. Рассмотрим для этого последний пример (х = х + 0.5). Сначала из ячейки памяти, в которой хранится значение перемен­ной х, выбирается это значение. Затем к нему прибавляется 0.5, после чего полу­чившийся результат записывается в ту же самую ячейку, а то, что хранилось там ранее, теряется безвозвратно. Операторы такого вида применяются в про­граммировании очень широко.

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

 

вещественная_переменная := целоевыражение:

 

Правила преобразований перечислены в разделе «Преобразования встроенных арифметических типов-значений» (см. с. 45).

Результатом операции присваивания является значение, записанное в левый операнд. Тип результата совпадает с типом левого операнда.

В сложных операциях присваивания ( + =, * =, /= и т. п.) при вычислении выраже­ния, стоящего в правой части, используется значение из левой части. Например, при сложении с присваиванием ко второму операнду прибавляется первый, и ре­зультат записывается в первый операнд, то есть выражение а + = b является более компактной записью выражения а = а + b.

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

ПРИМЕЧАНИЕ ---------------------------------------------------------------------------------------------

В документации написано, что тип результата совпадает с типом левого операнда, если он не менее int. Это означает, что если, например, переменные а и b имеют тип byte, присваивание а += b недопустимо и требуется преобразовать тип явным обра­зом: а += (byte)b. Однако на практике компилятор ошибку не выдает.

 

Напомню, что операции присваивания правоассоциативны, то есть выполняют­ся справа налево, в отличие от большинства других операций (а = b = с озна­чает а = (b = с)).

Не рассмотренные в этом разделе операции будут описаны позже.

 

Линейные программы

 

Линейной называется программа, все операторы которой выполняются последова­тельно в том порядке, в котором они записаны. Простейшим примером линейной программы является программа расчета по заданной формуле. Она состоит из трех этапов: ввод исходных данных, вычисление по формуле и вывод результатов. Для того чтобы написать подобную программу, нам пока не хватает знаний о том, как организовать ввод и вывод на языке С#. Подробно этот вопрос рассматрива­ется в главе И, а здесь приводятся только минимально необходимые сведения.

 

Простейший ввод-вывод

 

Любая программа при вводе исходных данных и выводе результатов взаимодей­ствует с внешними устройствами. Совокупность стандартных устройств ввода и вывода, то есть клавиатуры и экрана, называется консолью. Обмен данными с консолью является частным случаем обмена с внешними устройствами, кото­рый подробно рассмотрен в главе 11.

В языке С#, как и во многих других, нет операторов ввода и вывода. Вместо них для обмена с внешними устройствами применяются стандартные объекты. Для работы с консолью в С# применяется класс Console, определенный в пространст­ве имен System. Методы этого класса Write и WriteLine уже использовались в на­ших программах. Поговорим о них подробнее, для чего внесем некоторые изме­нения в листинг 3.1. Результаты этих изменений представлены в листинге 3.9.

 

Результат работы программы:

i=3                              

y = 4.12

d = 600                                         -

s = Вася

До сих пор мы использовали метод WriteLine для выхода значений переменных и ли­тералов различных встроенных типов. Это возможно благодаря тому, что в клас­се Console существует несколько вариантов методов с именами Write и WriteLine, предназначенных для вывода значений различных типов.

Методы с одинаковыми именами, но разными параметрами называются перегру­женными. Компилятор определяет, какой из методов вызван, по типу передавае­мых в него величин. Методы вывода в классе Console перегружены для всех встро­енных типов данных, кроме того, предусмотрены варианты форматного вывода.

Листинг 3.9 содержит два наиболее употребительных варианта вызова методов вывода. Сначала обратите внимание на способ вывода пояснений к значениям переменных в строках 1 и 3. Пояснения представляют собой строковые литера­лы. Если метод WriteLine вызван с одним параметром, он может быть любого встроенного типа, например, числом, символом или строкой. Нам же требуется вывести в каждой строке не одну, а две величины: текстовое пояснение и значе­ние переменной, — поэтому прежде чем передавать их для вывода, их требуется «склеить» в одну строку с помощью операции +.

Перед объединением строки с числом надо преобразовать число из его внутрен­ней формы представления в последовательность символов, то есть в строку. Пре­образование в строку определено во всех стандартных классах С# — для этого служит метод ToString( ). В данном случае он выполняется неявно, но можно вы­звать его и явным образом:

 

Console.WriteLine( "i = " + i.ToString () ):

 

Оператор 2 иллюстрирует форматный вывод. В этом случае используется другой вариант метода WriteLine, который содержит более одного параметра. Первым па­раметром методу передается строковый литерал, содержащий помимо обычных символов, предназначенных для вывода на консоль, параметры в фигурных скоб­ках, а также управляющие последовательности (они были рассмотрены в главе 2).

Параметры нумеруются с нуля, перед выводом они заменяются значениями со­ответствующих переменных в списке вывода: нулевой параметр заменяется зна­чением первой переменной (в данном примере — у), первый параметр — второй переменной (в данном примере — d) и т. д.

ПРИМЕЧАНИЕ ---------------------------------------------------------------------------------------------

Для каждого параметра можно задать ширину поля вывода и формат вывода. Мы рассмотрим эти возможности в разделе «Форматирование строк» (см. с. 146).

-------------------------------------------------------------------------------------------------------------------

Из управляющих последовательностей чаще всего используются символы пере­вода строки (\n) и горизонтальной табуляции (\t).

Рассмотрим простейшие способы ввода с клавиатуры. В классе Console определе­ны методы ввода строки и отдельного символа, но нет методов, которые позволя­ют непосредственно считывать с клавиатуры числа. Ввод числовых данных вы­полняется в два этапа:

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

2.   Выполняется преобразование из строки в переменную соответствующего типа.

Преобразование можно выполнить либо с помощью специального класса Convert, определенного в пространстве имен System, либо с помощью метода Parse, имею­щегося в каждом стандартном арифметическом классе. В листинге 3.10 исполь­зуются оба способа.

 

 

 

 

К этому примеру необходимо сделать несколько пояснений. Ввод строки выпол­няется в операторе 1. Длина строки не ограничена, ввод выполняется до символа перевода строки.

Ввод символа выполняется с помощью метода Read, который считывает один символ из входного потока (оператор 2). Метод возвращает значение типа int, представляющее собой код символа, или — 1, если символов во входном потоке нет (например, пользователь нажал клавишу Enter). Поскольку нам требуется не int, a char, а неявного преобразования от int к char не существует, приходится применить операцию явного преобразования типа, которая описана в разделе «Явное преобразование типа» (см. с. 49).

За оператором 2 записан оператор 3, который считывает остаток строки и нику­да его не передает. Это необходимо потому, что ввод данных выполняется через буфер — специальную область оперативной памяти. Фактически, данные сначала заносятся в буфер, а затем считываются оттуда процедурами ввода. Занесение в буфер выполняется по нажатию клавиши Enter вместе с ее кодом. Метод Read, в отличие от ReadLine, не очищает буфер, поэтому следующий после него ввод будет выполняться с того места, на котором закончился предыдущий.

В операторах 4 и 5 используются методы класса Convert, в операторах 6 и 7 — методы Parse классов Double и Decimal библиотеки .NET, которые используются здесь через имена типов С# double и decimal.

ВНИМАНИЕ -------------------------------------------------------------------------------------------------

При вводе вещественных чисел дробная часть отделяется от целой с помощью запя­той, а не точки. Иными словами, при вводе используются правила операционной системы, а не языка программирования. Допускается задавать числа с порядком, например, 1,95е-8.

 

Если вводимые с клавиатуры символы нельзя интерпретировать как веществен­ное число, генерируется исключение.

 

Ввод-вывод в файлы

 

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

Вывод из программы тоже бывает полезно выполнить не на экран, а в текстовый файл для последующего неспешного анализа и распечатки. Работа с файлами подробно рассматривается в главе 11, а здесь приводятся лишь образцы для использования в программах. В листинге 3.11 приведена версия программы из листинга 3.9, выполняющая вывод не на экран, а в текстовый файл с именем output.txt. Файл создается в том же каталоге, что и исполняемый файл програм­мы, по умолчанию — ...\ConsoleApplication1\bin\Debug.

 

 

Для того чтобы использовать в программе файлы, необходимо:

1.   Подключить пространство имен, в котором описываются стандартные классы для работы с файлами (оператор 1).

2.   Объявить файловую переменную и связать ее с файлом на диске (оператор 2). 3: Выполнить операции ввода-вывода (операторы 3-5).

4. Закрыть файл (оператор 6).

СОВЕТ ---------------------------------------------------------------------------------------------------------

При отладке программы бывает удобно выводить одну и ту же информацию и на экран, и в текстовый файл. Для этого соответствующие операторы дублируют.

--------------------------------------------------------------------------------------------------------------------

 

Ввод данных из файла выполняется аналогично. В листинге 3.12 приведена про­грамма, аналогичная листингу 3.10, но ввод выполняется из файла с именем input.txt, расположенного в каталоге D:\C#. Естественно, из программы убраны все приглашения к вводу.

 

Текстовый файл можно создать с помощью любого текстового редактора, но удоб­нее использовать Visual Studio.NET. Для этого следует выбрать в меню команду File New File... и в появившемся диалоговом окне выбрать тип файла Text File.

 

 

Математические функции — класс Math

 

В выражениях часто используются математические функции, например синус или возведение в степень. Они реализованы в классе Math, определенном в простран­стве имен System. С помощью методов этого класса можно вычислить:

□   тригонометрические функции: Sin, Cos, Tan;

□   обратные тригонометрические функции: ASin, ACos, ATan, ATan2;

□   гиперболические функции: Tanh, Sinh, Cosh;

□   экспоненту и логарифмические функции: Exp, Log, Log10;

□   модуль (абсолютную величину), квадратный корень, знак: Abs, Sqrt, Sign;

□  округление: Ceiling, Floor, Round;

□   минимум, максимум: Mi n, Max;

□   степень, остаток: Pow, IEEEReminder;

□   полное произведение двух целых величин: BigMul;

□   деление и остаток от деления: DivRem.

Кроме того, у класса есть два полезных поля: число п и число е. Описание мето­дов и полей приведено в табл. 3.8.

 

 

 

 

 

В листинге 3.13 приведен пример применения двух методов класса Math. Осталь­ные методы используются аналогично.

 

 

В качестве примера рассмотрим программу расчета по заданной формуле

 

 

Из формулы видно, что исходными данными для программы являются две вели­чины — х и а. Поскольку их тип и точность представления в условии не оговоре­ны, выберем для них тип double. Программа приведена в листинге 3.14.

 

 

 

Итак, к настоящему моменту у вас накопилось достаточно сведений, чтобы писать на С# простейшие линейные программы, выполняющие вычисления по форму­лам. В следующей главе мы займемся изучением операторов, позволяющих реализовывать более сложные алгоритмы.

 

Рекомендации по программированию

 

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

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

Общая тенденция такая: чем больше область действия переменной, тем более длинное у нее имя. Перед таким именем можно поставить префикс типа (одну или несколько букв, по которым можно определить тип переменной). Напротив, для переменных, вся «жизнь» которых проходит на протяжении нескольких строк кода, лучше обойтись однобуквенными именами типа i или к.

Имена переменных логического типа, используемые в качестве флагов, должны быть такими, чтобы по ним можно было судить о том, что означают значения true и false. Например, признак «пусто» лучше описать не как bool flag, а как bool empty.

ПРИМЕЧАНИЕ ---------------------------------------------------------------------------------------------

В С# принято называть классы, методы и константы в соответствии с нотацией Паскаля, а локальные переменные — в соответствии с нотацией Camel (см. раздел «Идентификаторы», с. 24).

-------------------------------------------------------------------------------------------------------------------

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

Избегайте использования в программе чисел в явном виде. Константы должны иметь осмысленные имена, заданные с помощью ключевого слова const. Символическое имя делает программу более понятной, а кроме того, при необходимости изме­нить значение константы потребуется изменить программу только в одном мес­те. Конечно, этот совет не относится к константам 0 и 1.

Ввод с клавиатуры предваряйте приглашением, а выводимые значения — поясне­ниями. Для контроля сразу же после ввода выводите исходные данные на дис­плей (по крайней мере, в процессе отладки).

До запуска программы подготовьте тестовые примеры, содержание исходные данные и ожидаемые результаты. Отдельно проверьте реакцию программы на неверные исходные данные.

При записи выражений обращайте внимание на приоритет операций. Если в одном выражении соседствует несколько операций одинакового приоритета, операции присваивания и условная операция выполняются справа налево, остальные — слева направо. Для изменения порядка выполнения операций используйте круг­лые скобки.

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