Васильев А.Н. ПРОГРАММИРОВАНИЕ НА ОСНОВНЫЕ СВЕДЕНИЯ РОССИЙСКИЙ КОМПЬЮТЕРНЫЙ БЕСТСЕЛЛЕР Москва 2023 УДК 004.43 ББК 32.973.26-018.1 В19 В19 Васильев, Алексей. Программирование на C# для начинающих. Основные сведения / Алексей Васильев. — Москва : Эксмо, 2023. — 592 с. — (Российский компьютерный бестселлер). ISBN 978-5-04-092519-3 В этой книге Алексей Васильев, доктор физико-математических наук и автор популярных российских самоучителей по программированию, приглашает читателей ознакомиться с основами языка C#. Прочитав ее, вы узнаете историю языка, его структуру, ознакомитесь с типами данных и переменными, операторами, циклами и множеством другой полезной информации, необходимой для работы с этим языком. УДК 004.43 ББК 32.973.26-018.1 ISBN 978-5-04-092519-3 © Оформление. ООО «Издательство «Эксмо», 2023 Оглавление Введение. Язык С# и технология .Net Framework . История создания языка С# . . . . . . . . . . . . Особенности языка С# . . . . . . . . . . . . . . . Программное обеспечение . . . . . . . . . . . . . Собственно о книге . . . . . . . . . . . . . . . . . Обратная связь . . . . . . . . . . . . . . . . . . . . Благодарности . . . . . . . . . . . . . . . . . . . . Об авторе . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 . 7 . 9 12 17 19 19 20 Глава 1. Знакомство с языком С# . . . . . . Структура программы . . . . . . . . . . Первая программа . . . . . . . . . . . . . Использование среды разработки . . . Пространство имен . . . . . . . . . . . . Программа с диалоговым окном . . . . Настройка вида диалогового окна . . . Окно с полем ввода . . . . . . . . . . . . Консольный ввод . . . . . . . . . . . . . . Считывание чисел . . . . . . . . . . . . . Форматированный вывод . . . . . . . . Резюме . . . . . . . . . . . . . . . . . . . . Задания для самостоятельной работы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 22 25 27 31 33 42 45 50 54 57 59 60 Глава 2. Базовые типы и операторы . . . . . . . . . . Переменные и базовые типы данных . . . . . . Литералы . . . . . . . . . . . . . . . . . . . . . . . . Управляющие символы . . . . . . . . . . . . . . . Преобразование типов . . . . . . . . . . . . . . . Объявление переменных . . . . . . . . . . . . . . Арифметические операторы . . . . . . . . . . . . Операторы сравнения . . . . . . . . . . . . . . . . Логические операторы . . . . . . . . . . . . . . . Побитовые операторы и двоичные коды . . . . Оператор присваивания . . . . . . . . . . . . . . Сокращенные формы операции присваивания Тернарный оператор . . . . . . . . . . . . . . . . . Приоритет операторов . . . . . . . . . . . . . . . Примеры программ . . . . . . . . . . . . . . . . . Резюме . . . . . . . . . . . . . . . . . . . . . . . . . Задания для самостоятельной работы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 63 68 71 72 77 81 85 86 90 100 102 104 105 106 112 113 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Оглавление Глава 3. Управляющие инструкции . . . . . . Условный оператор if . . . . . . . . . . . . Вложенные условные операторы . . . . . Оператор выбора switch . . . . . . . . . . Оператор цикла while . . . . . . . . . . . . Оператор цикла do-while . . . . . . . . . . Оператор цикла for . . . . . . . . . . . . . Инструкция безусловного перехода goto Перехват исключений . . . . . . . . . . . . Резюме . . . . . . . . . . . . . . . . . . . . . Задания для самостоятельной работы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 116 123 130 142 147 150 156 159 166 168 Глава 4. Массивы . . . . . . . . . . . . . . . . Одномерные массивы . . . . . . . . . . . Инициализация массива . . . . . . . . . Операции с массивами . . . . . . . . . . Цикл по массиву . . . . . . . . . . . . . . Двумерные массивы . . . . . . . . . . . . Многомерные массивы . . . . . . . . . . Массив со строками разной длины . . Массив объектных ссылок . . . . . . . . Параметры командной строки . . . . . Резюме . . . . . . . . . . . . . . . . . . . . Задания для самостоятельной работы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171 171 181 183 194 198 208 213 218 223 226 227 Глава 5. Статические методы . . . . . . . . . . . . . . . . . Знакомство со статическими методами . . . . . . . . Перегрузка статических методов . . . . . . . . . . . . Массив как аргумент метода . . . . . . . . . . . . . . Массив как результат метода . . . . . . . . . . . . . . Механизмы передачи аргументов методу . . . . . . Рекурсия . . . . . . . . . . . . . . . . . . . . . . . . . . . Методы с произвольным количеством аргументов Главный метод программы . . . . . . . . . . . . . . . . Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . Задания для самостоятельной работы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230 231 238 242 247 254 266 271 277 278 280 Глава 6. Знакомство с классами и объектами . . . . Базовые принципы ООП . . . . . . . . . . . . . . Классы и объекты . . . . . . . . . . . . . . . . . . Описание класса и создание объекта . . . . . . Использование объектов . . . . . . . . . . . . . . Закрытые члены класса и перегрузка методов Конструктор . . . . . . . . . . . . . . . . . . . . . . Деструктор . . . . . . . . . . . . . . . . . . . . . . . Статические члены класса . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282 282 286 289 294 299 303 309 314 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Оглавление Ключевое слово this . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 321 Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 328 Задания для самостоятельной работы . . . . . . . . . . . . . . . . . . 330 Глава 7. Работа с текстом . . . . . . . . . . . Класс String . . . . . . . . . . . . . . . . . Создание текстового объекта . . . . . . Операции с текстовыми объектами . . Методы для работы с текстом . . . . . . Метод ToString() . . . . . . . . . . . . Резюме . . . . . . . . . . . . . . . . . . . . Задания для самостоятельной работы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 333 334 336 344 356 373 378 379 Глава 8. Перегрузка операторов . . . . . . . . . Операторные методы . . . . . . . . . . . . . Перегрузка арифметических и побитовых операторов . . . . . . . . . . . Перегрузка операторов сравнения . . . . . Перегрузка операторов true и false . . Перегрузка логических операторов . . . . Перегрузка операторов приведения типов Команды присваивания и перегрузка операторов . . . . . . . . . . . Резюме . . . . . . . . . . . . . . . . . . . . . . Задания для самостоятельной работы . . . . . . . . . . . . . . . . . . 441 . . . . . . . . . . . . . . . . 443 . . . . . . . . . . . . . . . . 445 Глава 9. Свойства и индексаторы . . . . . . Знакомство со свойствами . . . . . . . . Использование свойств . . . . . . . . . . Знакомство с индексаторами . . . . . . Использование индексаторов . . . . . . Двумерные индексаторы . . . . . . . . . Многомерные индексаторы . . . . . . . Перегрузка индексаторов . . . . . . . . Резюме . . . . . . . . . . . . . . . . . . . . Задания для самостоятельной работы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 448 448 456 475 481 493 506 510 518 520 Глава 10. Наследование . . . . . . . . . . . . . . Знакомство с наследованием . . . . . . . . Наследование и уровни доступа . . . . . . Наследование и конструкторы . . . . . . . Объектные переменные базовых классов Замещение членов при наследовании . . . Переопределение виртуальных методов . Переопределение и замещение методов . Переопределение и перегрузка методов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 523 524 529 535 543 549 553 558 562 . . . . . . . . . . . . . . . . . . . . . . . . . . 381 . . . . . . . . . . . . . . . . 381 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 385 404 423 427 432 5 Оглавление Наследование свойств и индексаторов . . . . . . . . . . . . . . . . . . 565 Резюме . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 575 Задания для самостоятельной работы . . . . . . . . . . . . . . . . . . 576 Заключение. Что будет дальше . . . . . . . . . . . . . . . . . . . . . . . . . 580 Предметный указатель . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 581 Вв ед ен и е ЯЗЫК С# И Т ЕХН ОЛОГИ Я .NET FRAMEWORK Русская речь не сложнее других. Вон Марга­ дон - дикий человек - и то выучил. из к/ф «Формула лю бви» Язык С# уже многие годы неизменно входит в список языков програм­ мирования, самых востребованных среди разработчиков программного обеспечения. Язык является базовым для технологии .Net Framework, разработанной и поддерживаемой корпорацией Microsoft. Именно язы­ ку С# посвящена книга, которую читатель держит в руках. И стория создания я з ы ка С# История , леденящая кровь. Под маской овцы скрывался лев. из к/ф «Покровские ворота» Язык С# создан инженерами компании Microsoft в 1998-2001 годах. Руководил группой разработчиков Андерс Хейлсберг, который до того трудился в фирме Borland над созданием компилятора для языка Pascal и участвовал в создании интегрированной среды разработки Delphi. Язык С# появился после языков программирования С++ и Java. Бога­ тый опыт их использования бьш во многом учтен разработчиками С#. G) Н А ЗАМ ЕТ КУ Си нтаксис языка С# похож н а синтаксис языков С++ и Java. Но сход­ ство внешнее. У языка С# своя ун и кал ьная кон це п ция . В месте с тем м ногие управля ющие инструкци и в языке С# будут знакомы тем , кто п рограм м и рует в С++ и Java . 7 Вв едение Вообще же из трех язы ков п рогра м м и рования С++ , Java и С# исто­ рически первым поя вился язы к С++ . Затем на сцену вы шел язык Java. И уже после этого появился язы к п рограм м и рования С# . Для понимания места и роли языка С# на современном рынке про­ граммных технологий разумно начать с языка программирования С, ко­ торый в свое время стал мощным стимулом для развития программных технологий как таковых. Именно из языка С обычно выводят генеало­ гию языка С#. Мы тоже будем придерживаться классического подхода. Язык программирования С появился в 1 972 году, его разработал Де­ нис Ритчи. Язык С постепенно набирал популярность и в итоге стал одним из самых востребованных языков программирования. Этому способствовал ряд обстоятельств. В первую очередь, конечно, сыграл роль лаконичный и простой синтаксис языка. Да и общая концепция языка С оказалась исключительно удачной и живучей. Поэтому ког­ да встал вопрос о разработке нового языка, который бы поддерживал парадигму объектно ориентированного программирования (ООП), то выбор естественным образом пал на язык С: язык программирова­ ния С++, появившийся в 1 983 году, представлял собой расширенную версию языка С, адаптированную для написания программ с привлече­ нием классов, объектов и сопутствующих им технологий. В свою оче­ редь, при создании языка программирования Java отправной точкой стал язык С++. Идеология языкаjаvа отличается от идеологии языка С++, но при всем этом базовые управляющие инструкции и операторы в обоих языках схожи. G) Н А ЗАМ ЕТ КУ Язы к п рограм м и рования Java официально появился в 1 995 году и стал популярн ы м благодаря ун иверсал ьности п рограм м , нап и ­ санных на этом языке . Технология , испол ьзуемая в Java, позволяет п исать переноси м ы е п рограм м н ы е коды , что искл юч ител ьно важно при разработке п р ил ожени й для испол ьзован ия в lпternet. Нет ничего удивительного, что при создании языка программирова­ ния С# традиция была сохранена: синтаксис языка С# во многих мо­ ментах будет знаком тем, кто уже программирует на С++ и Java. Хотя такое сходство - внешнее. Языки очень разные, поэтому расслабляться не стоит. Да и базовые синтаксические конструкции в языке С# имеют свои особенности. Все это мы обсудим в книге. 8 Я зык С# и технология .Net Framework Особен ности я з ы ка С# Обо мне придумано столько небылиц, что я устаю их опровергать. из к/ф �Формула лю бви» Язык программирования С# - простой, красивый, эффективный и гибкий. С помощью программ на языке С# решают самые разные задачи. На С# можно создавать как небольшие консольные программы, так и програм­ мы с графическим интерфейсом. Код, написанный на языке С#, лаконичен и понятен (хотя здесь, конечно, многое зависит от программиста). В этом отношении язык программирования С# практически не имеет конкурентов. G) Н А ЗАМ ЕТ КУ Язык С# создавался после появления языков С++ и Java. В С# были учтен ы и по возможности устранены «недостатки » и «недоработки » , которые есть в С++ и Java. Иногда язык С # упоминается как усовер­ шенствованная версия языков С++ и Java, хотя концепции у них со­ вершенно разн ые, так что утверждение довол ьно поверхностно . Кроме собственно преимуществ языка С # , немаловажно и то, что язык поддерживается компанией Microsoft. Поэтому он идеально подходит, чтобы писать программы для выполнения под управлением операцион­ ной системы Windows. � ПОДРОБН ОСТИ Операционной системой Windows и технологией . Net Framework ком ­ п а н и и Microsoft область применения языка С # не огран ичи вается . Существуют ал ьтернати вные п роекты (такие, например, как М опо ) , позволя ющие выпол нять п рограм м ы , написанные н а языке С#, под управлением операционных систем , отличных от Windows. Вместе с тем мы в кн и ге будем исходить из того , что испол ьзуются « родн ые» средства разработки и операционная система Windows. Как отмечалось выше, язык С# является неотъемлемой частью тех­ нологии (или платформы) .Net Framework. Основу платформы .Net Framework составляет среда исполнения CLR (сокращение от Common Language Runtime) и библиотека классов, которая используется при программировании на языке С#. 9 Вв едение G) Н А ЗАМ ЕТ КУ Платформа .Net Framework позволяет испол ьзовать и иные языки программирования, а не только С# - нап ример, С++ или Visual Basic . Возможности платформы .Net Framework позволяют объединять «В одно целое» п рограммные коды , написанные на разных языках программирования. Это очень мощная технологи я , но для нас инте­ рес представляет написание програм мных кодов на языке С#. Имен­ но особенности и возможности языка С# м ы будем обсуждать в кн иге . При компилировании программного кода, написанного н а языке С # , соз­ дается промежуточный код. Это промежуточный код реализован на языке M SIL (сокращение от Microsoft Intermediate Language ). Промежуточный код выполняется под управлением системы CLR. Система CLR запускает JIТ-компилятор (сокращение от Just In Time ), который, собственно, и пе­ реводит промежуточный код в исполняемые инструкции. � ПОДРОБН ОСТИ Файл с программным кодом на языке С# сохраняется с расширени­ ем cs. После компиляции п рограм мы создается файл с расширени­ ем е х е . Но выполнить этот файл можно тол ько на комп ьютере, где установлена система .Net Framework. Такой код называется управля­ емым ( поскол ьку он выполняется под управлением системы CLR) . . . Описанная нетривиальная схема компилирования программ с привле­ чением промежуточного кода служит важной цели. Дело в том, что тех­ нология .Net Framework ориентирована на совместное использование программных кодов, написанных на разных языках программирования. Базируется эта технология на том, что программные коды с разных язы­ ков программирования «переводятся� (в процессе компиляции) в про­ межуточный код на общем универсальном языке. Проще говоря, коды на разных языках программирования приводятся «К общему знаменате­ лю�, которым является промежуточный язык M SIL. � ПОДРОБН ОСТИ П рограм м ы , написанные на Java, тоже компилируются в промежуточ ­ ный байт- код. Байт- код выполняется под управлением ви ртуальной машины Java. Но по сравнени ю с языком С# имеется принци пиал ьное отличие. Байт- код, в который переводится при компиляции Jаvа- про­ грам ма, имеет при вязку к одному языку программирования - языку 10 Я зык С# и технология .Net Framework Java. И в Java схема с промежуточным кодом нужна для обеспечения универсальности программ , поскольку промежуточный байт- код не за­ висит от типа операционной системы (и поэтому переносим ) . Особен­ ность операционной системы учитывается той виртуальной машиной Java, которая установлена на компьютере и выполняет промежуточный байт- код. Промежуточный код, который испол ьзуется в технологии .Net Framework, не привязан к конкретному языку программирования. Например, что при компиляции программы на языке С# , что при ком­ пиляции программы на языке Visual Basic получаются наборы инструк­ ций на одном и том же промежуточном языке MSIL. И нужно это, еще раз подчеркнем , для обеспечения совместимости разных программ­ ных кодов, реализованных на разных языках. Весь этот процесс для нас важен исключительно с познавательной точки зрения. Более принципиальными являются вопросы, касающиеся осо­ бенностей собственно языка С#. Первое, что стоит отметить: язык С# полностью объектно ориентиро­ ванный. Это означает, что даже самая маленькая программа на языке С# должна содержать описание хотя бы одного класса. G) Н А ЗАМ ЕТ КУ Здесь есть методологическая проблема. Состоит она в том , что не­ подготовленному ч итател ю сложно восприни мать концепцию ООП сразу, без п редварител ьной подготовки . М ы найдем выход в том , чтобы испол ьзовать определенный шаблон п р и написании п ро­ грам м . Затем , по мере знакомства с языком С# , м ногие моменты станут просты м и и п онятн ы м и . Как отмечалось выше, базовые синтаксические конструкции языка С # напоминают (или просто совпадают) соответствующие конструкции в языках С++ и/или Jаvа. Но, кроме них, язык С# содержит множество интересных и полезных особенностей, с которыми мы познакомимся в книге. � ПОДРОБН ОСТИ Знакомым с языками п рограммирован ия С++ и/или Java будет полезно узнать, что в языке С# , как и в языке С++ , испол ьзуются п ространства имен , указатели , существует переопределение опе­ раторов. Также в С# , как и в Java, имеются и нтерфейсы , объекты реал изуются через ссыл ки , испол ьзуется система автоматической 11 Вв едение «уборки мусора » . А еще в С# испол ьзуются делегаты , кон цепция которых идеологически бл изка к концепции указателей на фун кци и в С++ . М ассивы в С# бол ьше напоми нают массивы Java , но вооб­ ще в С# они достаточ н о специфич н ы е . И ндексаторы в С# позво­ л я ют и ндексировать объекты - подобн ы й механ изм , основа н н ы й на переоп редел е н и и оператора « квадратн ые скобки » , есть в С++ . Свойства, которые испол ьзуются в С#, представл я ют собой нечто среднее между полем и методом , хотя , конеч н о , бол ьше напомина­ ют п ол е с особы м режи мом доступа . Есть в языке С# и �классические� механизмы, характерные для боль­ шинства языков, поддерживающих парадигму ООП. Мы познакомим­ ся с тем, как на основе классов и объектов в языке С# реализуется ин­ капсуляция, узнаем, что такое наследование и как в С# используется полиморфизм. Будет много других тем и вопросов, которые мы изучим. Например, мы узнаем (во второй части книги), как в С# создаются при­ ложения с графическим интерфейсом, и познакомимся с основными подходами, используемыми при создании многопоточных приложений. Но обо всем этом - по порядку, тема за темой. П рограммное обеспе ч ение Показывай свою гравицану. Если фирменная вещь - возьмем ! из к/ф -«Кин-дза-дза» Для работы с языком программирования С# на компьютере должна быть установлена платформа .Net Framework. Компилятор языка С# входит в стандартный набор этой платформы. Поэтому, если на компью­ тере установлена платформа .Net Framework, этого достаточно для нача­ ла программирования в С#. Обычно дополнительно еще устанавливают среду разработки, благодаря которой процесс программирования на С# становится простым и приятным. Если мы хотим написать программу (на языке С#), сначала нам нужно набрать соответствующий программный код. Теоретически сделать это можно в обычном текстовом редакторе. В таком случае набираем в тек­ стовом редакторе код программы и сохраняем файл с расширением . c s (расширение для файлов с программами, написанными н а языке С#). 12 Я зык С# и технология .Net Framework После того как код программы набран и файл с программой сохранен, ее следует откомпилировать. Для этого используют программу-компи­ лятор c s c . ехе, которая устанавливается, как отмечено выше, при уста­ новке платформы .Net Framework. � ПОДРОБН ОСТИ Ал горитм действи й тако й : в командной строке указывается назва­ ние п рограм м ы - ком пилятора csc.ехе , а затем через п робел ука­ зы вается название файла с п рограммой на языке С# . Допусти м , м ы записал и код п рограм м ы в файл MyProgram. cs. Тогда для ком ­ пиляции программы в командной строке испол ьзуем инструкци ю csc. ехе MyProgram. cs или csc MyProgram. cs ( расш ирение ехе ­ файла можно не указы вать ) . Есл и ком п ил я ция проходит нормально, то в резул ьтате получаем файл с расш ирением . ехе , а название файла совпадает с названием исходного файла с п рограммой ( в на­ шем случае это MyProgram. ехе ) . Полученный в резул ьтате ком п ил я ­ ци и ехе-файл запускают на выполнение. Файл csc.exe п о умолчан и ю находится в каталоге C : \ Window s \ Microsoft. NET\ Framework внутри папки с номером верси и - на­ пример, vЗ. 5 или v4. О. Также для ком п ил и рован ия п рограммы из командной строки п ридется , скорее всего , выпол н ить некоторые допол н ител ьные настройки - например, в переменных среды за­ дать путь для поиска ком п илятора csc. ехе . Хотя такая консольная компиляция вполне рабочая, прибегают к ней редко. Причина в том, что не очень удобно набирать код в текстовом редакторе, затем компилировать программу через командную строку и запускать вручную исполнительный файл. Намного проще восполь­ зоваться специальной программой интегрированной средой разработ­ ки (ID E от Integrated Development Environment ). Среда разработки со­ держит в себе все наиболее важные «ингредиенты» , необходимые для «приготовления» такого «блюда», как программа на языке С#. При ра­ боте со средой разработки пользователь получает в одном комплекте ре­ дактор кодов, средства отладки, компилятор и ряд других эффективных утилит, значительно облегчающих работу с программными кодами. Мы тоже прибегнем к помощи среды разработки. Нам понадобится прило­ жение Microsoft Visual Studio. - Загрузить установочные файлы можно с сайта компании Microsoft . m i c r o s o f t . сот (на сайте следует найти страницу загрузок). www 13 Вв еде н ие После установки среды разработки мы получаем все, что нужно для успешной разработки приложений на языке С#. � ПОДРОБН ОСТИ Приложение Microsoft Visual Studio я вляется ком мерчески м . Одна­ ко у него есть неком мерческая «уп рощенная» версия Visual Studio Express , которая впол н е подойдет для изучения языка п рогра м м и ­ рования С#. G) Н А ЗАМ ЕТ КУ Кроме языка программ ирован ия С# среда разработки Visual Studio позволяет создавать п рограм м ы на языках Visual Basic и С++ . Часть настроек, вл ияющих на фун кциональность среды разработки , оп ре­ деляется в процессе установки . Как выглядит окно приложения Visual Studio Express (версия 2 0 1 5), по­ казано на рис. В. 1 . o<J, � р Slмt.P.t:gc·t.�VISUllSt•EqнбS2a1SfOl'Windowso.sttop (diC VfCY/I Sиl\Ptgt • Dtbug J' furn 1ooll. T1t1t Wflftdtм Нdр • An><�. . - й AJaVиil<v • х • 1:11 ; Х Visual Studio Start Optn PrOJЮ.. Discover wha 's new in Express 2015 for V tututa•nbpras10UfDtW LU11'1 '� S« f\.t 1 n«w in thc- NTT fr411'nfWtOt'. &.pkw�•h• tмwin \l�Stu61o ftмn Sa-.>kd AudytaCЬ...d·powetpur�eJ Opm trom 5ol.lrU' ControL Connect to Azure @ Рис. В . 1 . Окно приложения Visual Studio Express 2015 Основные операции, выполняемые при создании приложений с помо­ щью среды разработки Visual Studio Express, мы кратко рассмотрим в ос­ новной части книги, когда будем обсуждать программные коды. 14 Я зык С# и технология .Net Framework Среда разработки Visual Studio хотя и предпочтительная, но далеко не единственная. Существуют другие приложения, используемые при создании программ на языке С#. Причем масштабы использования аль­ тернативных средств разработки постоянно расширяются. Но, в любом случае, имеет смысл знать, какие есть варианты помимо Visual Studio. Нас интересуют в первую очередь среды разработки для программиро­ вания на С#, распространяемые на некоммерческой основе. Среда разработки SharpDevelop является приемлемым выбором и по­ зволяет создавать приложения разного уровня сложности. По сути, данная среда разработки является интегрированным отладчиком, вза­ имодействующим со средой .Net Framework. Окно среды разработки SharpDevelop показано на рис. В.2. 8 ui1d OtЬug • 9 Х St1rth Ant�s Тoo k Window Htlp SшtP>g< Namt � 1 ;Х . 1c:J�1 I МodiftIO 19.01.2017 Optn solution • 9 х Loc.1tion IJ,\Wmpla\Мyl'App\МyFApp.lln 1 ( №w soluьon ) EnorJ loo&тo11l�n91 !«toм....9ts! t..м Ptopcmts • 9 х Descr1ptюn Tooh so Рис. 8.2. Окно среды разработки SharpDevelop Загрузить все необходимые файлы для установки среды разработки SharpDevelop можно на сайте проекта www. i c s h a rp c o de . n e t . Еще один проект, заслуживающий внимания, называется Mono (сайт поддержки проекта www. mo n o - p r o j e c t . c om). Проект раз­ вивается в основном благодаря поддержке компании Xamarin (сайт www. xama r i n . c om). В рамках этого проекта была создана среда 15 Вв еде н ие разработки MonoDevelop (сайт www. mo n o deve l op . сот ) , предназна­ ченная, кроме прочего, для создания приложений на языке С#. На дан­ ный момент разработчикам для установки предлагается среда разработ­ ки Xamarin Studio, которая позиционируется как продолжение проекта MonoDevelop. Окно среды разработки Xamarin Studio показано на рис. В.3. X.m1nn Stud10 Communrty FИ Edit Vw:w Dtl lt - Seмch Pro,ect Ot-fм.itt. Buitd Run V�sion Controe Toots Window Htfp Creating а Fluld Android UI 11ps for Smooth and As developers, we mnt to buld moЬlt Jpps that are not on}'f beautiul 1nd fearure.«h, but alsa hlgh-oerformonce. We often tl>� Юout WJYS we an optmzt our network а15 and background oPtr.llЬOn.s #DocumentDB loves Acqualnt 1- Down�d SoluЬon Xamarln: Planet Scale МоЫlе Арр ln Ave steps Тh.1$ IS J specQI OUest post from tht DcкumentOB tеащ wrtten Ьу Кd G;мytyuk. К111 works •t мo:rosol't on the OO<umentOB te1m. You an fnd Рис. 8.3. Окно среды разработки Xamarin Studio Методы работы с перечисленными выше средами разработки не явля­ ются предметом книги. Отметим лишь следующие обстоятельства: • У читателя есть альтернатива в плане использования среды разра­ ботки. • Среды разработки не являются эквивалентными по своим функци­ ональным возможностям. Наиболее надежный вариант связан с ис­ пользованием среды разработки Microsoft Visual Studio. Все приме­ ры из книги тестировались именно в этой среде разработки. • Нужно иметь в виду, что в силу специфики сред разработки SharpDevelop и Xamarin Studio некоторые программные коды, выполняемые в Microsoft Visual Studio, могут не выполняться в SharpDevelop и Xamarin Studio (и наоборот). 16 Я зык С# и технология .Net Framework • Среды разработки Microsoft Visual Studio и SharpDevelop ориен­ тированы на использование операционной системы Windows. Сре­ да разработки Xamarin Studio позволяет работать с операционными системами Windows, Linux и Мае OS Х. С обственно о кн и ге У меня есть мысль, и я ее думаю. из м/ф -«38 попугаев» Книга предназначена для тех, кто изучает язык С# (самостоятельно, в университете или на курсах по программированию). Она содержит не­ обходимый теоретический материал и примеры программ на языке С#. Книга состоит из двух частей. Предполагается, что каждая из частей книги будет издаваться отдельным томом. В первой части книги рассматриваются все основные вопросы, важные при изучении языка программирования С#. Приведенный далее список не является исчерпывающим и содержит лишь названия основных тем, рассмотренных в первой части книги: • Принципы создания программ на языке С#: структура программы, ее основные блоки, компилирование и запуск на выполнение. • Ввод и вывод данных через консоль и через диалоговые окна. • Переменные, базовые типы данных и основные операторы. • Управляющие инструкции: условный оператор, оператор выбора и операторы цикла, инструкция безусловного перехода. • Массивы и операции с ними. • Создание классов и объектов. Основные принципы ООП. • Перегрузка методов, конструкторы и деструкторы. • Работа с текстом. • Перегрузка операторов. • Свойства и индексаторы. • Наследование, замещение членов класса и переопределение вирту­ альных методов. 17 Введение Освоив материал первой части, читатель сможет создавать эффектив­ ные программы высокого уровня сложности. Во второй части книги рассматриваются такие темы: • Абстрактные классы и интерфейсы. • Делегаты, ссылки на методы, анонимные методы, события и лямбдавыражения. • Перечисления и структуры. • Указатели и операции с памятью. • Обработка исключительных ситуаций. • Многопоточное программирование. • Обобщенные классы и методы. • Создание приложений с графическим интерфейсом. • Работа с файлами. Темы, о которых пойдет речь во второй части книги, расширяют пред­ ставление о возможностях языка С#. Есть и другие вопросы, которым уделяется внимание на страницах книги. После успешного изучения материала книги читатель приобретет необходимые знания и навыки для самостоятельного создания программ на языке С# любого уровня сложности. Понятно, что освоение тонкостей программирования на С# на этом не должно заканчиваться. Вместе с тем есть все основания по­ лагать, что книга поможет заложить надежный фундамент, полезный в дальнейшей профессиональной карьере программиста. Материалы, вошедшие в книгу, многократно апробировались при чтении лекций по программированию для студентов Киевского национального университета имени Тараса Шевченко и Национального технического университета Украины « Киевский политехнический институт», а также на занятиях для слушателей курсов по программированию. Поскольку при изучении программирования первоочередное значение имеют на­ выки, которые получают студенты и слушатели в процессе обучения, то при подборе примеров и формировании тем упор делался на при­ кладных аспектах программирования на языке С#. И помните, что не­ отъемным условием успешного изучения любого языка программиро­ вания является постоянное совершенствование практических навыков. Чтобы научиться программировать, следует постоянно тренироваться. 18 Я зык С# и тех нология .Net Framework Это может быть как набор и отладка программных кодов, так и само­ стоятельное написание пусть и несложных программ. Поэтому в конце каждой главы приводится небольшой список программ для самостоя­ тельного написания. Для лучшего закрепления материала каждая глава в книге заканчивается кратким Резюме с перечислением основных пози­ ций, изложенных в соответствующей главе. Обратная связь - Готовы ли вы ответствовать? - Вопрошайте . - Готовы ли вы сказать нам всю правду? - Ну, всю - не всю . . . А что вас интересует ? из к/ф �Формула лю бви» Свои замечания и комментарии по поводу этой и других книг автора можно отправить по адресу электронной почты alex@va s i l ev . k i ev . ua. Также на сайте www. va s i l ev . k i ev . u a можно найти краткую инфор­ мацию по книге. Там же в случае необходимости размещаются дополни­ тельные материалы к книгам. Общение с читателями - важный этап, который позволяет понять, что в книге удалось, а что - нет. Поэтому любые критические замечания или предложения важны и востребованы. На все письма ответить, к сожале­ нию, невозможно, но все они читаются. Благодарности Сильвунле , дорогие гости ! Сильвунле . . . Ж еву­ при авек плезир. Господи прости, от страха все слова повыскакивали ! из к/ф �Формула лю бви» Хочется искренне поблагодарить своих студентов в университете и слу­ шателей курсов, на которых проверялись представленные в книге при­ меры и отрабатывались методики. Опыт, приобретенный в общении с пытливой и требовательной аудиторией, бесценен. Надеюсь, его уда­ лось хотя бы частично воплотить в книге. 19 Вв едение От написания книги до выхода ее �в свет� лежит огромный путь и вы­ полняется большой объем работ. Все это ложится на плечи сотрудников издательства, выпускающего книгу. Все эти люди заслужили огромную благодарность за их важный и благородный труд. Сердечное спасибо моим родителям Тамаре Васильевне и Николаю Ана­ тольевичу, детям Насте и Богдану, жене Илоне за поддержку и долготер­ пение, которое они проявляли все время, пока писалась эта книга. Об авто ре - Где отличник? - Гуляет отличник . из к/ф -«Кин-дза-дза» Автор книги - Василъев Алексей Николаевич, доктор физико- матема­ тических наук, профессор кафедры теоретической физики физическо­ го факультета Киевского национального университета имени Тара­ са Шевченко. Сфера научных интересов: физика жидкостей и жидких кристаллов, синергетика, биофизика, математические методы в эконо­ мике, математическая лингвистика. Автор книг по математическим па­ кетам Maple, Mathcad, Matlab и Mathematica, офисному приложению Excel, книг по программированию на языкахjаvа, С, С++, С#, Python и] avaScript. Гл ава 1 ЗНАКОМСТВО С ЯЗЫКОМ С# Товарищ, там человек говорит, что он - ино­ планетянин. Надо что-то делать . . . из к/ф -«Кин-дза-дза» Мы начинаем знакомство с языком программирования С#. Сначала рас­ смотрим общие подходы: • определимся со структурой программы; • познакомимся с базовыми синтаксическими конструкциями, ис­ пользуемыми при написании программы на языке С#; • узнаем, как набирается программный код, как программа компили­ руется и запускается на выполнение; • создадим несколько несложных программ; • познакомимся с переменными; • узнаем, как выводятся сообщения в консоль и в специальное диало­ говое окно; • увидим, как в программу могут вводиться данные через консоль и с помощью специального диалогового окна с полем ввода. Начнем с того, что обсудим общие принципы создания программ на языке С#. 21 Глава 1 Структура п рогра м м ы Сердце подвластно разуму. Чувства подвласт­ ны сердцу. Разум подвластен чувствам. Круг замкнулся. из к/ф �Формула лю бви» Язык С# является полностью объектно ориентированным. Поэтому для создания даже самой маленькой программы на языке С# необходимо описать по крайней мере один класс. С классами (и объектами) мы по­ знакомимся немного позже. Концепция классов и объектов является краеугольным камнем в здании ООП. Ее изучение требует определен­ ных навыков в программировании. Здесь мы сталкиваемся с методоло­ гической проблемой: при изучении языка С# необходимо сразу исполь­ зовать классы, а для объяснения того, что такое класс, желательно иметь некоторый опыт в программировании. Получается замкнутый круг. Мы его разорвем очень просто: сначала, пока только знакомимся с азами языка С#, будем использовать некоторые синтаксические конструкции языка С#, не особо вдаваясь в их смысл. В данном конкретном случае мы познакомимся с тем, как описывается класс, а что такое класс и как он используется в общем случае, обсудим немного позже. Итак, для создания программы в С# необходимо описать класс. Описание класса начинается с ключевого слова c l a s s . Далее следует имя класса. G) Н А ЗАМ ЕТ КУ И м я класса выбираем сам и . Это одно слово. Название нашего еди н ­ ственного класса , без особого ущерба для понимания ситуаци и , мо­ жем отождествлять с названием програм м ы . Тело класса заключается в фигурные скобки - после имени класса ука­ зываем открывающую скобку { , а окончание описания класса обознача­ ется закрывающей скобкой } . Таким образом, шаблон описания класса имеет следующий вид (жирным шрифтом выделены обязательные эле­ менты шаблона): class имя_класса { 1 1 Описание класса 22 З нако мство с языком С# Возникает естественный вопрос о том, что же описывать в теле класса. В нашем случае класс будет содержать описание главного метода. Глав­ ный метод - это и есть программа. Выполнение программы в С# озна­ чает выполнение главного метода. Команды, которые содержит главный метод - это как раз те команды, которые выполняются при запуске про­ граммы. G) Н А ЗАМ ЕТ КУ Вообще метод (любо й , н е обязател ьно главн ы й ) - это именованный блок из команд. Для выполнения дан н ых команд метод должен быть вызван . В С# методы оп исы ваются в классах. Есть два типа мето­ дов : статические и обычные ( не статические) . Для вызова обычных ( н е статических) методов необходи мо создавать объект. Статиче­ ские методы можно вызы вать без создания объекта. Главный метод я вляется особ ы м . Он вызы вается автоматически при запуске п рограм м ы на выполнение. В п рограмме может быть оди н и тол ько оди н главный метод. Существует несколько альтернативных способов описания главного ме­ тода. Мы будем использовать наиболее простой шаблон, представленный ниже (жирным шрифтом выделены обязательные элементы шаблона): static void Мain(){ 1 1 Код программы Описание главного метода начинается с ключевого слова s t a t i c . Это ключевое слово означает, что главный метод является статическим, и по­ этому для вызова метода не нужно создавать объект. Далее указывается ключевое слово vo i d, означающее, что метод не возвращает результат. Не возвращающий результат метод - это просто набор инструкций, ко­ торые последовательно выполняются при вызове метода. Называется главный метод Ma i n (только так и никак иначе). После имени метода указываются пустые круглые скобки - пустые, потому что у главного метода нет аргументов. � ПОДРОБН ОСТИ В общем случае метод может возвращать результат. Возвращаемое методом значение подставляется вместо инструкци и вызова ме­ тода после того , как метод завершает выполнение. Также у метода 23 Глава 1 могут быть аргументы - значен ия , которые передаются методу при вызове. Эти значения метод испол ьзует при выполнении своих вну­ трен н их инструкци й . Мы испол ьзуем шаблон оп исан ия главного ме­ тода , в котором главный метод не возвращает резул ьтат и аргумен­ тов у главного метода нет. Но это не еди нствен н ы й способ оп исан ия главного метода . Он может быть оп исан как тако й , что возвращает резул ьтат, и аргументы ему тоже могут передаваться (аргументы ко­ мандной строки ) . Разные способы оп исан ия главного метода обсуж­ даются в одной из следующих глав кн и ги . Также сразу отметим , что нередко кроме кл ючевых слов static и void в оп исан ии главного метода испол ьзуется кл ючевое сло­ во puЫic. Кл ючевое слово puЫic означает, что метод является открытым и доступен за п ределами класса . В этом смысле его ис­ пол ьзование при оп исании главного метода п редставляется впол ­ не логич н ы м . Вместе с тем стандарт языка С# не предусматри вает обязател ьного испол ьзования кл ючевого слова puЫic в оп исан и и главного метода програм м ы . Тело главного метода выделяется с помощью пары фигурных скобок { и } . Напомним, что все это (то есть описание главного метода) разме­ щается внутри тела класса. Таким образом, шаблон для создания наи­ более простой программы в С# выглядит следующим образом: class имя_класса{ static void Мain()( 1 1 Код программы В теле главного метода размещаются команды, которые должны быть выполнены в процессе работы программы. Команды выполняются одна за другой, строго в той последовательности, как они указаны в теле глав­ ного метода. 24 З нако мство с языком С# П ервая п рогра м м а Для того ли я оставил свет, убежал из столицы, чтоб погрязнуть в болоте житейском ! из к/ф �Формула лю бви» Наша самая первая программа будет отображать в консольном окне сооб­ щение. Программный код первой программы представлен в листинге 1 . 1 . � Л истинг 1 . 1 . Первая программа 11 Главный класс программы : c l a s s HelloWorld{ 1 1 Главный метод программы : static vo id Main ( ) { 1 1 Отображение сообщения в консольном окне : System . Console . WriteLine ( "Изyчaeм язык С# " ) ; Если мы введем этот программный код в окне редактора кодов, скомпи­ лируем и запустим программу (как это делается - описывается далее), то в консольном окне появится сообщение, представленное ниже. � Результат выполнения п рограм мы (из листи нга 1 . 1 ) Изучаем язык С# Мы рассмотрим, как программу реализовать в среде разработки, но пе­ ред этим проанализируем программный код. Код в листинге 1 . 1 содер­ жит описание класса с названием He l l oWo r l d. В классе описан глав­ ный метод Ма i n ( ) . В главном методе всего одна команда S у s t em . C o n s o l e . W r i t e L i n e ( " Изучаем я зык С# " ) , которой в консольном окне отображается сообщение. G) Н А ЗАМ ЕТ КУ Все команды в С# заканчиваются точ кой с запято й . Размещать команды в разных строках не обязательно, но так п рограм м н ы й код луч ше ч итается . Также рекомендуется выделять блоки команд 25 Глава 1 отступам и . П робел ы между командами игнорируются , п оэтому на­ личие ил и отсутствие отступов на выполнении п рограм м ного кода не сказы вается , зато наличие отступов знач ител ьно улуч шает ч ита­ бел ьность кода. Команда означает вызов метода Wr i t е L i n e ( ) . Метод предназначен для отображения текста. Текст (в двойных кавычках) передается аргументом методу. Но метод не существует сам по себе. Метод вызывается из клас­ са, который называется C on s o l e. Имя метода отделяется от имени клас­ са точкой. Класс C on s o l e находится в пространстве имен S y s t em. Этот факт отображается в команде: имя класса C on s o l e указывается вместе с названием пространства имен S y s t em, в котором содержится класс, а имя класса и пространства имен разделяется точкой. � ПОДРОБН ОСТИ Метод п редставляет собой и менован н ы й блок команд. Други м и словам и , и меется некоторый набор команд, у этого блока есть и м я ( и м я метода) . При вызове метода команды и з этого метода вы пол ­ ня ются . Методу могут передаваться аргументы (значения , исполь­ зуемые при выполнении команд метода ) . Методы описы ваются в классах и бы вают статическими и нестати­ чески м и . Нестатический метод вызы вается из объекта. Статический метод вызы вается без создания объекта . При вызове статического метода указывается имя класса , в котором метод оп исан . В нашем случае вызы вается статический метод WriteLine ( ) , который опи­ сан в классе Console . Класс ы , в свою очередь, разбиты по груп пам . Каждая такая груп па назы вается пространством имен . У каждого п ространства имен есть и м я . Есл и п ространство имен не вкл ючено в п рограм му специал ьной инструкцией ( как это делается - объясняется позже ) , то имя п ро­ странства имен указы вается перед и менем класса . Класс Console находится в пространстве имен System. Поэтому команда вызова статического метода WriteLine ( ) , описанного в классе Console из п ространства имен System, выглядит как System. Console. WriteLine ( ) . Программный код содержит еще и комментарии. Это пояснения, кото­ рые предназначены для человека и игнорируются при компилирова­ нии программы. Комментарий начинается с двойной косой черты / / . Все, что находится справа от двойной косой черты / / , при компиляции во внимание не принимается. 26 Знакомство с языком С# G) Н А ЗАМ ЕТ КУ Ком ментари и , которые нач и наются с двойной косой черты , называ­ ются одностроч н ы м и , поскол ьку должн ы размещаться в одной стро­ ке . Кроме одностроч ных комментариев, существуют и многостроч ­ ные ком ментари и . Такой комментарий может зан имать нескол ько строк. Начинается многостроч н ы й комментарий с сим волов / * , а заканчи вается сим волами * / . После того как мы разобрали программный код, выясним теперь, что с этим кодом делать для того, чтобы программу можно было откомпили­ ровать и запустить на выполнение. И спол ь зова н и е среды ра зработки Фимка, ну что ж ты стоишь ! Неси бланманже с киселем ! из к/ф <tФормула лю бви'> Далее мы рассмотрим вопрос о том, как с помощью среды разработки Microsoft Visual Studio Express создать программу. Итак, первое, что нам необходимо сделать - запустить среду разработки. В окне, которое от­ кроется, необходимо создать новый проект. Для этого в меню File выби­ раем команду New Project, как это показано на рис. 1 . 1 . f»4 rм р Start:Pavc·М.CIOSoftVisu.!Studio&preu20UforWll'odowJO� Edii VllW OtЬusJ Twn � т� Tcst Ct>t•"""• N w� н.tр � Anкh. - х • Discover what's new in Express 2015 for V futUtn�bprи. lSfOfV! Lwn•� Sн�in...... -.1he.NПfr•IN'WOl'll. Eapo1 ... ll\-tul 1 rlfWJn°I� �>IUdlo fцм S.М.:t1 �...iop Rt:•dy to Cktud·powef your �е? Connect to Azure �<ountSdtJn9S- Ct>t·• @ r.., 1tк....... а P...ctf'lll PtO]«Ь-6Solwoм ш ,...,.... Рис. 1 . 1 . Создание нового проекта в окне среды разработки Microsoft Visual Studio Express 27 Глава 1 G) Н А ЗАМ ЕТ КУ Для создания нового п роекта можно воспол ьзоваться п и ктограм­ мой на панел и инструментов или нажать комбинацию клавиш <Ctrl>+<Shift>+<N > . В результате появляется окно создания нового проекта, показанное на рис. 1 .2 . S..tch lnsulltd ТempllttS (Cul • EJ Windows .tt. Ttmplltti "Vr.iш.IC• Clщн:D..i:top r Ttst V'wsual В.s1с � • \fr.sualC•• fotms Applt<•t1on • • Туре: V"isщJ С• А prOJкt for crut1ng • coml'Nlnd·liм WPF Appl1c1t1on Conю8eдppl1att0n Р •pplкat.ion Vau.11(8 ShartdProJ«t SQLSetv" Dt.buggc.r ProJкts CPSProJ«U C!•<k httc tQ90 onl1nc •nd find Jcmplatn � Onl.int №m<: tosьngOl_Ol Solutюn n1me tosung01_01 D:\Soolcs\8 poбoтe\C_shorp\.,..mpl"\chOI\ в.-.... Crette d1rкtcнy for solutюn О Add to Source Control C.ncd Рис. 1 . 2 . Окно создания нового проекта N ew Project В разделе Templates выбираем Visual С#, а в центральной части окна следует выбрать пункт Console Application. В поле Name указыва­ ем название проекта (мы используем название Listing01_01), а в поле Location выбираем место для сохранения файлов проектов. G) Н А ЗАМ ЕТ КУ Для выбора папки для сохранения п роекта можно воспол ьзоваться кнопкой Browse сп рава от поля Location . После щелчка на кнопке ОК открывается окно проекта с шаблонным кодом, представленное на рис. 1 .3. 28 Знакомство с языком С# luW 0du9 ,_. Tooh: lya·": uirщ lyat.•.Coll..-c-t v•in; •vн•.Linq� uinq Sy•t-.'t'•xt: W..М. • """""'°" х �IJn J.i.ain9 l'.- ,..., • _он..,.... <:i • мric1 ... р �о Н.lр • 11\ ... - -·-·Ш 11 .Jo.м..i.111)-w uin9 S\08t.-..Tt1.1·••d.1n;.'1'••k•1 1Q: $ n...SP'iC8 L.i..8ti.ng01_01 1 ch•• Pr09r.-. ( llUitic void м..inCatrin;(J ar9a) С 1 Рис. 1 . 3 . Окно проекта с шаблонным кодом 00 ПОДРОБН ОСТИ При создании нового проекта п рограм ма содержит начальный шаб­ лонный код. Он содержит наиболее часто используемые инструкции и описание класса с главным методом програм мы (с пустым телом ) . Назначение шаблонного кода - облегч ить п рограм мисту работу. Среда разработки Visual Studio позволяет настраи вать (задавать) шаблонный код, который автоматически добавляется в проект. В окне редактора (окно с программным кодом) заменяем шаблонный код на код нашей программы (см. листинг 1 . 1 ). Результат показан на рис. 1 .4. р - " -·-·Ш ........ х ......,,,_" . , ". .._ - /1 rп".нw:с IUIACC прогр ....... : cUsa H•lloWoi:l С 11 rпaaмw:k мек·.:: npcrp..a.aocw: •t..atic vo.id КA.lr\C) t 11 Оrоера.8КИ• с .uttt • tr:oMC:OIUoMOW oJttte: Sy•t.a.coucl .w�it..tin•(•p3yq.11ew JISUk ct")J tlf· Р· ;:iw..ь..�1_01·0� • U.U.,01.tl . ,,._ • •• Rd..ca:t: ' ........... • с,.��" " Рис . 1 . 4 . Окно проекта с кодом программы 29 Глава 1 После того как код введен, программу можно компилировать и запу­ скать на выполнение. Для этого можем воспользоваться специальной пиктограммой с зеленой стрелкой на панели инструментов (см. рис. 1 .4 ). Но лучше воспользоваться командой Start Without Debugging из меню Debug (комбинация клавиш < Ctrl>+< FS>), как показано на рис. 1 .5. D4 Fik р LICtlnf'J.Ol · Мiaosof't Vl\WI Studю Ь:pfбt. 101..S fotW..ndows[Жl:t.op (d.t v• ' Prctкt lo'I J' hikt ? · D«Ьug � • class Hel.l . .r' � 11 rл•aнwrt � s t a t: i c void О'rобр Syat:e:o . T11rn w....... 1ооЬ Tat SW. °""'9!1>"9 SW. W- D<Ьugglng Sttp lrllo 5ttp Ovtr Нdр А Cttl· A � №twrt 8t11� + 81-�ШOirlЬ ло " lfHe : Ctr!·Wt• B - _ ,.....,, . FU Toggie Brukpolrc t: W1ndow ct- ) ; х r:J Р· Q s.i.ьo. �1.01· � ...,..., Lht.8t01"01 " .._... • • Rdcrcnces • <{) .....,_ � Prognm.a Рис . 1 . 5 . Запуск программы на выполнение Если компиляция прошла успешно, то в консольном окне появляется результат выполнения программы (в данном случае - сообщение), как показано на рис. 1 .6. 3учае 1 я 3 ы к С // я продолжения нажмите любую к n авиwу . . . 8 Рис. 1 . 6 . Результат выполнения программы отображается в консольном окне G) Н А ЗАМ ЕТ КУ Кроме сообщения , которое вы водит в консол ьное окно п рограм ма, там также появляется сообщение Для п родолжения наж м и те л ю­ бую клави шу . Это сообщение добавляется автоматически опера­ ционной системой и к программе не имеет н и какого отношения . 30 З нако мство с языком С# � ПОДРОБН ОСТИ Есл и для ком п ил и рования и запуска воспол ьзоваться командой Start Debugging из меню Debug ил и пиктограммой на панели инструмен­ тов , то п рограм ма будет запущена, но консол ьное окно закроется сразу после завершения выпол нения п рограм м ы . Все происходит настол ько быстро , что пол ьзовател ь, как п равил о , не успевает про­ ч итать содержимое консол ьного окна . Есл и ч итател ь столкнется с подобной проблемой, то последней командой в п рограмму мож­ но добавить инструкци ю System. Console. ReadLine ( ) (это должна быть последняя команда в методе Main ( ) ) . Не искл ючено, что для вывода кирилл ического текста в консол ьное окно п ридется выпол н ить некоторые настройки и для консол ьно­ го окна. Для этого в левом верхнем углу консол ьного окна следует щел кнуть п равой кнопкой м ы ш и и в раскрывшемся списке выбрать команду С войства (дл я выпол нения настроек открытого консол ьно­ го окна) или Ум ол ч ан ия (для выполнения настроек, испол ьзуем ых по умолчан и ю ) . В открывшемся окне С войства кон сол ь но го окна следует задать шрифт, который поддержи вает кирилл ические сим­ вол ы , размер шрифта , фон дл я окна консол и и выпол н ить некоторые другие настройки . П ространство имен Н е надо громких слов , они потрясают воздух , но не собеседника. из к/ф �Формула лю бви» Мы можем немного модифицировать код нашей первой програм­ мы. Дело в том, что команда вида S y s t em . C o n s o l e . W r i t e L i n e ( ) , в которой явно указано пространство имен S y s t em, не очень удоб­ на - она слишком длинная. Вообще, длинные команды в С# - са­ мое обычное дело. Но в данном случае мы имеем дело с длинной ко­ мандой, которую можно немножко сократить. Для этого в программу в самом начале (до описания класса) следует добавить инструкцию u s i n g S y s t em (инструкция в программе заканчивается точкой с за­ пятой). И нструкция означает, что в программе используется простран­ ство имен S y s t em. Как следствие, мы можем обращаться к классам из этого пространства имен без явного указания названия простран­ ства S y s t em. В итоге инструкция S y s t em . C o n s o l e . W r i t e L i n e ( ) 31 Глава 1 трансформируется в инструкцию C o n s o l e . W r i t e L i n e ( ) (простран­ ство имен S y s t em явно не указывается). Новая версия нашей первой программы представлена в листинге 1 .2 . � Л истинг 1 . 2 . Испол ьзование пространства имен 11 Использование пространства имен System : us ing Sys tem; 11 Главный класс программы : c l a s s HelloWorld{ 1 1 Главный метод программы : s tatic vo id Main ( ) { 1 1 Отображение сообщения в консольном окне : Console . WriteLine ( "Изyчaeм язык С# " ) ; Результат выполнения этой программы точно такой же, как и в преды­ дущем случае. G) Н А ЗАМ ЕТ КУ Есл и при запуске п рограммы консол ьное окно закрывается сл и ш ­ ком быстро, т о п рограм м н ы й код метода Main ( ) можно заверш ить командой Console. ReadLine ( ) . В таком случае консол ьное окно не будет закрыто , пока пол ьзовател ь не нажмет клавишу <Enter> . Есл и вместо команды Console. ReadLine ( ) испол ьзовать команду Console. ReadKey ( ) , то дл я закрытия консол ьного окна достаточно будет нажать л юбую клави шу. Вообще же статический метод ReadLine ( ) из класса Console п ред­ назначен для считы вания текстовой строки , которую пол ьзовател ь вводит с помощью клавиатуры . Статический метод ReadKey ( ) из класса Console испол ьзуется для сч иты ван ия сим вола, введен­ ного пол ьзователем с помощью клавиатуры . В дальнейшем мы будем использовать u s i n g-инструкции для подклю­ чения пространств имен. С разу отметим, что таких инструкций в про­ грамме может быть несколько (то есть в программе одновременно раз­ решается использовать несколько пространств имен). 32 З нако мство с языком С# П рограмма с диалоговым окном Ну, барин, т ы задачи ставишь ! За десять деп одному не справиться. Тут помощник нужен хомо сапиенс. из к/ф �Формула лю бви» С ледующая программа, которую мы рассмотрим, выводит сообщение в диалоговом окне: при запуске программы на выполнение появляется диалоговое окно, содержащее текст (сообщение). В окне будет кнопка ОК, и щелчок на ней приводит к закрытию окна и завершению выполне­ ния программы. Для отображения окна мы воспользуемся статическим методом S h o w ( ) из класса Me s s a g e B o x . Класс описан в простран­ стве имен F o rm s , которое входит в пространство имен W i ndow s , кото­ рое, в свою очередь, входит в пространство имен S y s t em. Эта иерархия отображается инструкцией S y s tem . W i ndows . F o rm s . С оответственно, программный код начинается инструкцией u s i n g S y s t em . Wi ndows . F o rm s , подключающей пространство имен F o rm s . Это дает возмож­ ность использовать класс Me s s ageBox и его метод Show ( ) . � ПОДРОБН ОСТИ Мало добавить в п рограм м н ы й код инструкци ю u s ing Sys tem. Window s. Forms . Необходи мо добавить ссылку на соответствую­ щее п ространство имен в п роект. Для этого в окне Solution Explorer (по умолчанию отображается в п равом верхнем углу окна среды раз­ работки ) необходи мо щел кнуть п равой кнопкой м ы ш и на узле п ро­ екта и в раскрывшемся контекстном меню выбрать подменю Add , а в нем команду Reference . В резул ьтате откроется окно, в котором следует выбрать ссыл ку для включения в п роект. Вся эта процедура описы вается и илл юстри руется далее. Если при вызове метода S h ow ( ) ему передать аргументом текстовое значение, то появится диалоговое окно, содержащее данный текст. Как выглядит код программы, в которой отображается диалоговое окно, по­ казано в листинге 1 .3. � Л истинг 1 . 3 . Отображение диалогового окна 11 Использование пространства имен : u s ing Sys tem . Windows . Forms ; 33 Глава 1 1 1 Класс с главным методом программы : c l a s s Dial ogDemo { 1 1 Главный метод программы : s tatic void Main ( ) { 1 1 Отображение диалогового окна : Me s s ageBox . Show ( " Пpoдoлжaeм изучать С # " ) ; Кроме инструкции подключения пространства имен в начале про­ граммы, в главном методе программы имеется команда Me s s ageBox . Show ( " Продолжаем изучат ь С # " ) . Как отмечалось ранее, в результа­ те выполнения этой команды появится диалоговое окно. Но способ созда­ ния проекта в данном случае немного отличается от того, что рассматри­ вался выше. Далее описываются основные этапы реализации программы. (D Н А ЗАМ ЕТ КУ П роект можно создавать и выпол нять его настройки по- разному. Да­ лее мы илл юстри руем один из возможн ых способов . И так, на этот раз будем создавать пустой проект - для этого в окне соз­ дания проекта New Project в качестве типа проекта выбираем Empty Project, как показано на рис. 1 . 7 . . ... ... r..,,pa., (Сьt�Г . ....... -" Temfllolt n � Vn� C• O.UК Dali�op Ты " Vrtшl t.w t V'"IW8! C · · SQI. � ·....... "' '"""" Vniи! Scvdlo Soiut-J s....,... Typc: V� (• rj lii w>• ­ ,...... ..,..._. Р • An cmpty pcoj«t fcw uutlnf • loul ..,,11с,ьоn VllUll C• Sho<....... R:i ,.... ......,. Q. �ш dat<tOty for soЬJOf'I 0 Add to Sourc11 COtllt'Ot �� Рис. 1 . 7 . Создание пустого проекта 34 Знакомство с языком С# 00 ПОДРОБН ОСТИ Для создания п роекта в меню File выбираем команду New Project. В окне New Project в левом сп иске в узле Visual С # выбираем пун кт Classic Deskto p . После того как объект создан, в него нужно добавить файл, в который мы введем программный код. Для этого во внутреннем окне Solution Explorer в правом верхнем углу окна среды разработки выбираем узел проекта и щелкаем правой кнопкой мыши. Откроется контекстное меню для проекта, в котором в подменю Add выбираем команду New ltem, как показано на рис. 1 .8. Shiilt •.Ц.•A S.МC• lltefmnc:L.. Conntcttd Stn1c:•- 111 о ....... NuGd ._ S. • � Pto,ю °""" �• lмtQCtNt: wich Pto,кt Sсмlк• СОЮ'о$ � с" ...." .,, _ SIWt • AJt· C х 1:1 ....... ....... .._ с... х (tti• V ... " Рис . 1 . 8 . Добавление элемента в созданный пустой проект G) Н А ЗАМ ЕТ КУ Вместо контекстного меню можем воспол ьзоваться командой Add New ltem в меню Project. В результате откроется окно Add New ltem для добавления элемента в проект. Окно показано на рис. 1 .9. 35 Глава 1 "' sшl C• h1m1 Codc .... Genull W.Мows fOtfl"d. SQt S..W. - • Оn/ом Sо!\ Ьу. o.fwh �· С&ш --0 Sntcrf.c:t � � • �� � {J �G51 !� W1ndowt. Form Sut'�lnrt•n.d TempUtи (Ct�· () н· := Р" А Ым1с С• code Г!М: ViwtlC" ltttnS 1.k« Controt Uw Conuol (WPF) АЬоu\ В.. VfWll C• l:mu АОО.NП Ent..iy D.u Моd1' Appliution Conf19U1.tttoti Fdt д,911CIOotl Мa.Мtit flle дsиmbly Worm•bon File Curtom (on(rol V1w1I C• ltrms Рис. 1 . 9 . Добавление в проект фай ла с программным кодом В этом окне для типа добавляемого элемента выбираем Code File, а в поле Name указываем название для файла (в данном примере мы на­ зовем файл P r o g r am . c s ). После щелчка на кнопке Add файл добавля­ ется в проект. После этого в проекте есть файл, в который мы собираем­ ся ввести программный код. Как выглядит проект с добавленным в него файлом (еще пустым), показано на рис. 1 . 10. � ПОДРОБН ОСТИ В окне Solution Explorer отображается «нач и н ка» п роекта. В частно­ сти , после добавления файла в п роект в узле п роекта отображается пун кт с названием добавленного файла . Чтобы содержимое фай ­ ла открылось в окне редактора кодов, достаточно в окне Solution Explorer выдел ить м ы ш ью пун кт с названием файла . Есл и окно Solution Explorer по каким -то причинам не отображает­ ся , нужно в меню View выбрать команду Solution Explorer. 36 Знакомство с языком С# о4 UIЬnejGI_QJ • Мiaoюft Vllwl �udto Щiress 1Dtl fot W"6owt Ои11.ор Ь4 't'lf'W � � Ddluf Tutn ТоМ Ytst W...._, о . - li t1' • ...... · • ..,. C9U • ..... . Нt1р . • 1 р , t'/I � ... • ·1 - с - ·- + · )( liD � . " с, • liP " s- W..rt.,. Lo t�·J Р· :;:l - �..9T n ,._., " � Lk6ng01_01 • • • ��М("d Рис . 1 . 1 О. Проект с файлом для ввода кода программы После ввода программного кода окно среды разработки с открытым в ней проектом будет выглядеть так, как показано на рис. 1 . 1 1 . о0 8...id tools tei ..... w Нt4р // Исо�sсuкие nрос-rрАНст-" юм к : u•inq Syste.i:a . � . f'onu1 11 Кп.\t;с с ruaнwм методом nporp&юa.1 : - cl••• D a l i // rп анwй N ':'од nporp&w:l6l: autic void н.ain () i / / О'l'оОр.аж ни• .i:и•лoroaoro окн Mt!!898'-9X - Show ( • npoдomua.м KSY\18":'" C I "' ) р 'fO Us:ьngOl_OJ - Мкrosott Vrш.i!Studto &p1ю2!Dl1fOf Wtrtdows �ор • • х - .... ....... · liD •8 Rdetll:fКCS ... App.<onf""9 С" Pf'nil'.m.a i ... " Рис. 1 1 1 . Программный код введен в окно редактора кодов • Хотя мы уже ввели программный код, проект к работе еще не го­ тов. В него необходимо добавить ссылку на пространство имен. Для 37 Глава 1 этого в контекстном меню проекта в подменю Add выбираем команду Reference. С итуация проиллюстрирована на рис. 1 . 1 2 . °'4 f1'ot L.tw9J1_0J - Моом�h Ysu.81 Sr:Ulflo Ьаwе1' 201) foi W.ndawl Drsttop Uk v- PIClftd ....i О.., turn Toall. Td V..... • li .1' ? . ..... . ""''"' 1 1 ис:nоm.S()а-ан:ие nростракс�u • n.Q Syat.". � . Foлu ; р То Нdр '!1 - .... ...... . ш • юсен : 1 1 Кn.ас::с с rn"•иwм м'*"7ОАС84 nporp� : cl• •• Oial09� 1 11 rлa•wwft N8"t'9A nporp&iOOI : •t..at ic voi.d м.ain ( ) ( 1 Oтo6p.asetut• ди-АЛоrоасrо .с х.н • : � - Shov l "ПpoAOЛ;UeN kJIY\CAT� C f " ) ; u .... ...... 'О ...... ....... - ·....... • Swvкc Rd8"tКC(OflМ(tfdS8Yic: Ю.,. 10 П. №w � &plo№ ...... .... м...g& NuGts Ptc.Ь,.S... 11! о ... " Suotu, - Ctd•Shlft •A. � . .... . " ..... � lnttr.aw. � � � """ ' ,_ _ .....__ а ""' '""""- х "' Ctn• X х ....... ID ....... ... 11 ...... ._. <' � F..w. 11\ r• &,lor• _",..... " ._ 1' - Рис . 1 1 2 . Добавление ссылок в проект • Откроется окно Reference Manager, в котором мы находим пункт System. Windows.Forms и устанавливаем возле него флажок (рис. 1 . 1 3). � • f' Anemh5oes ___� T•rgf1tng! .NEТ Fram� • � . PtcJКtS Shi� PrOJ� t сом 8 Brow-и N.tm• �trn.Wth.o.t•V.su�ьon.ots.,n 5'js.1tm.\'ltЬJ)yn.mte01u System.W.Ь.l>yNm><Dш.Desogn System.W.Ь.Entoty System.Yl.Ь.Ent")'.De<oqn Systtm.WI0.&1� $yi1tm.Yld>Ьttn�gn Systnn.WtЬ.М...,. System.Wtb.R<gulo<&,> ...-• Syst&m.YltЬ.Rowng Systrm.WtЬ.Жvкn Syu-Wi­ Wtndows.Ccnttok.kЬЬon Systun.W1�orms..O.At.Y1sщlintюn Systtm.Windows.Forms.OtuVisu•l1uuon.OtstSyяom.Wondows.lnP"'- м.n.pw"'"" Systtm.Wmdows.91�ion SyRtm.\'ilortflvw�cs System.Y/cмtflow.Componm.Мode/ Syutm.Wo<\flow.l!uoьme Sy.tnn.WcнtdlowScм<ts Sy.t....x.ml Sy>tcm.Xml Syutm.Xml.l.onq Syutm.JCrnt.s.n.kutoon ....... с.о.о.о •.О.О.О •JJJJIJ •JJJJJJ •JJJJJJ 4/J/J/J • JJJJIJ • JJJJIJ •JJJJIJ fChAs'IC'Тlbltcs ((ttf• [) ,..." ., Systttn.\Vvt.dowsl orms C..- �tt!d Ьy. Мauosoft Cotpotttюn v.4/J.O.O fle Vcnion: 4/J.30319J8020 Ь..lt Ьу. FXASRTМGDR •JJJJIJ с.о.о .о 4/J/J/J 4.0/J/J •IJIJIJ •JJJJIJ •JJJJ IJ •JJJJD • .O.OJJ C/J/J/J •JJDIJ 4/J/JIJ <JJDIJ <DIJIJ <DDIJ <DDD Рис . 1 . 1 3 . Вы бор ссылки для добавления в проект 38 Р • Знакомство с языком С# Для подтверждения щелкаем кнопку ОК. 00 ПОДРОБН ОСТИ Пока ссыл ка в п роект не добавл ена, команды , в которых есть обра­ щение к неподкл юченному п ространству имен и классам из этого п ространства имен , подчеркиваются красной волнистой черто й , что означает наличие ошибок в п рограммном коде . П ричина в том , что , пока соответствующая ссылка не добавлена в п роект, испол ьзован­ ные идентификаторы ком п илятору « Не известн ы» . П осле подкл юче­ ния ссылки красное подчеркивание должно исчезнуть. Есл и мы испол ьзуем пространство имен System, ссылка в п роекте на это пространство тоже должна быть. Но при создании консол ьно­ го приложения ссылка на п ространство имен System (и некоторые другие ) в п роект добавляется автоматически . П оэтому раньше у нас не было необходимости выпол нять оп исываемые настрой ки . Также стоит заметить, что добавить ссыл ку в п роект можно с помо­ щью команды Add Reference из меню Project. Технически проект готов к использованию. Осталось один маленький «штрих» добавить. А именно, мы установим тип приложения. Для этого в контекстном меню проекта выбираем команду Properties (рис. 1 . 14) или выбираем одноименную команду в меню Project. �- - То - ·- · = . · -· " .•• 11 Ис:nо.п-1о�ааюнt nрос-транС'1'8А ммен : 1.18.iru; Syst-ei.. Иind.cv• . Foau; // кп..� с rn•a1Qo;N w.-roдow cporp...oa.i : cl�•• u... _. � t 11 rлuмwй �д nporpalOAI: •Utic void М.in С) С // 0:-оСр.аsание .incaлoro•oro окн.а : " •• •9• . shov c •npoдomцe.м и11уЧат1о ct " ) ; ko,e to Тhit Ne- SokitlO'l ь,Aote 'V.... 1 М1N9c NWd PкЬga.. ... " О S. n S..Up ,,.,,.., . ...... � Cut Ctrt•X ,.. " ... п х ....... D .....,... ...... _ � 0,.. F.w. 11\ Нc Ь,W. ... _ � � Рис. 1 . 1 4 . Вы бор команды Properties в контекстном меню проекта для перехода к вкладке свойств проекта 39 Глава 1 Во внутреннем окне редактора кодов откроется вкладка свойств проек­ та. Она показана на рис. 1 . 1 5. �-1)) Jok...ott Y..М � t ..- J:tU foi � � (" - ""'t«1 ... D8llt ,...., ,.... ' "' w...... О · .\J fЙ lil J'I 114 r; . .... ..,. .... .... ._ -........ ._ - Otfюti �-- �l_DJ Jll"ft'I,._.. ....... _ -- ,..... _ Cliмt tllttry ,н(Т f,._., &.S .....8 ....... �� 1141_. ," _, ......, - '-а • • L .... . . 111 " G " Р· ..._ li] _ .....,.,.., . ,..,..., � . ,...,. ,_.) .,,, • •• Ы-• r ....... • • S,.....""""""'1С'" .... -с. s,.dy .... � !ИOW( .S " " "'8lllOJll& 1!!!111111111111111111111111111111111111111111111111"-�-�-·��----- -::.;.•-.=...---- Рис . 1 1 5 . Вы бор типа приложения на вкладке свойств проекта • На вкладке свойств проекта следует выбрать раздел Application, а в рас­ крывающемся списке Output type устанавливаем значение Windows Application (вместо используемого по умолчанию значения Console Application). Чтобы закрыть вкладку свойств проекта, щелкаем систем­ ную пиктограмму (рис. 1 . 1 6). о4 LJl#�.0) ""'"-" '"-1 � (,,_. 20:1) ,.. � 0.Ш., •• "_ � ...., � r.... т .- f nt w...._ О · to lil .ri r" .... -­ ... ... ,. . -­ ._.... ·-­ .НЛ J,_._.,) .,... wм �._ �l_l)J ---� ....... _ ....... ..,...._ Р· li] _ .....,.,..,. • ...,.., " • t�I OJ " .. .._" r ....... .. ,,...._ �_ � ('О ,,.,,-.а ::.;.-:::::.J ---------"--- - --'� -�--------- - Рис . 1 . 1 6 . Закрытие вкладки свойств проекта 40 ·= Знакомство с языком С# Для компилирования и запуска программы на выполнение выбираем, например, команду Start Debugging в меню Debag (рис. 1 . 1 7). о4 �-OJ · "41ootdt 't/" Stlotlloo ..,_ J'JU hlf W� Dбl:loP 1� Р,.а "- О..., т- T80ft 1"8 " .a tl' ? v...,.,._. О • fм • • , -г� ... " u.•i.nq syat-. . МI � // Jtn.acc с r" � clA8 8 D-A• � · )( · �..Q 11 rлаа....а •Ut ic void 11 � ... . ,.. Ъft � StмV. � Attadl • '*� Strp .... S... 0.. 'Oftlll •...,_. ''"' •� ......... о о,е-_ /i' t....,o1 ,J� "_ то ... " [) CtrM� СМ.. Аа�·• ш "" " C.l • !W! " Jt )1 !!!!111 1 111111111111111111111111111111111111111111!11 -а_,� . ����� Рис . 1 . 1 7 . Запуск приложения на выполнение с помощью команды Start Debugging в меню Debug Если программа компилируется без ошибок, появляется диалоговое окно, представленное на рис. 1 . 1 8 . Продоп.жаtм И])'Ч•пь С= ок Рис . 1 . 1 8 . При выполнении программы отображается окно с сообщением Щелчок на кнопке ОК в этом окне (или на системной пиктограмме в строке названия окна) приводит к закрытию окна и завершению вы­ полнения программы. 00 ПОДРОБН ОСТИ Есл и не изменить тип приложения на Windows Application , то при запуске п рограммы кроме диалогового окна откроется еще и кон ­ сол ьное окно . Это не страшно, но не о ч е н ь эстетично. 41 Глава 1 G) Н А ЗАМ ЕТ КУ Вместо добавления файла с кодом в пустой п роект можно было добавить класс . В этом случае в контекстном меню п роекта в под­ меню Add вместо команды New ltem выби рают команду Class (см . рис. 1 . 8 ) . Н о , строго говоря , можно было создавать не пустой п роект, а консол ьное п р ил ожение. Тогда добавлять в п роект файл для ввода п рограм м ного кода не придется - такой файл в п роект добавляется автоматически . Все п рочие настройки выпол н я ются так, как оп исано выше. Н астрой ка вида диалогового окна Да, это о т души. Замечательно . Достойно вос­ хищения. из к/ф �Формула лю бви» Окно, которое отображалось в предыдущем примере, было очень про­ стым (см. рис. 1 . 1 8). Оно было настолько простым, что не содержало даже названия. Эту досадную оплошность легко исправить. Настрой­ ка диалогового окна выполняется передачей методу Show ( ) из класса Ме s s ageBox дополнительных аргументов. Ниже описывается назначе­ ние аргументов метода Show ( ) . • Первый аргумент, как мы уже знаем, определяет сообщение, отобра­ жаемое в окне. • Если передать методу второй текстовый аргумент, то такой аргумент будет определять заголовок диалогового окна (отображается в стро­ ке названия). • Третий аргумент определяет тип пиктограммы, отображаемой в диа­ логовом окне (по умолчанию пиктограмма в окне не отображается). • Четвертый аргумент определяет кнопки, которые отображаются в диа­ логовом окне (по умолчанию такая кнопка одна, и это кнопка ОК). Таким образом, методу S h ow ( ) можно передавать от одного д о четы­ рех аргументов. С первыми двумя аргументами (сообщение и заголовок окна) все просто - это текстовые значения. Третий и четвертый аргу­ менты не так просты, а именно: третий аргумент должен быть констан­ той из перечисления Me s s ageBoxBu t t o n s . 42 З нако мство с языком С# � ПОДРОБН ОСТИ Перечисления рассматриваются нем ного позже . Пока же п росто отмети м , что перечисление представляет собой набор констант (фикси рованных, неизменных значен и й ) . Каждая константа и меет название. Название константы указы вается вместе с названием пе­ реч исления : после имени перечисления ставится точ ка , и затем ука­ зы вается название константы . Например, константа ОК из перечис­ ления Mes sageBoxBu t tons , переданная третьи м аргументом методу Show ( ) , означает, что в диал оговом окне должна отображаться кноп­ ка с названием О К. Константа ОК передается аргументом в виде и н ­ струкци и Mes sageBoxBu t tons. О К . Константы из перечисления Me s s ageBoxBu t t on s , определяющие ко­ личество и названия отображаемых в диалоговом окне кнопок, перечис­ лены в табл. 1 . 1 . Табл . 1 . 1 . Константы для определения кнопок в диалоговом окне К онстанта О писание ок Отображение кнопки О К OKC a n c e l Отображение кнопок О К и Cancel ( Отмена) Ye sNo Отображение кнопок Yes (Д а ) и No ( Нет ) YesNoCancel Отображение кнопок Yes (Д а ) , N o ( Нет ) и Cancel ( Отмена) Re t r y C a n c e l Отображение кнопок Retry (П овтор ) и Cancel ( О тмена) Ab o r t Re t r y i gn o r e Отображение кнопок Abort (Прервать), Retry (П овто р ) и Ignore (Пр опустить) Еще одна константа, на этот раз из перечисления Me s s ageBox i c on, определяет тип пиктограммы, отображаемой в диалоговом окне. Кон­ станты, используемые в качестве четвертого аргумента метода Show ( ) , представлены в табл. 1 .2. Табл . 1 . 2 . Константы для определения пиктограммы в диалоговом окне К онстанта Пиктогр амма Asteri s k Отображается пиктограмма с изображением прописной буквы i вну­ три синего круга E rror Буква Х белого цвета внутри красного круга E x c l ama t i o n Восклицательный знак 1 внутри желтого треугольника Hand Буква Х белого цвета внутри красного круга 43 Глава 1 I n f o rma t i o n Отображается пиктограмма с изображением прописной буквы i вну­ три синего круга None Пиктограмма отсутствует Que s t i o n Знак вопроса ? внутри синего круга S t op Буква Х белого цвета внутри красного круга Wa r n i n g Восклицательный знак 1 внутри желтого треугольника Как и в случае констант из перечисления Me s s ageBoxBu t t o n s (тре­ тий аргумент), константа из перечисления Me s s a g e B o x i c o n ука­ зывается вместе с названием перечисления. Например, константа I n f о rmа t i о n передается аргументом методу Show ( ) в виде инструкции Me s s ageBox i c o n . I n fo rmat i o n . Небольшой пример использования констант из перечислений Me s s ageBoxBut t o n s и Me s s a geBox i c o n представлен в листинге 1 .4. � Листинг 1 . 4 . Диалоговое окно с названием и пиктограммой us ing Sys tem . Windows . Forms ; c l a s s AnotherDial ogDemo { s tatic void Main ( ) { / / Отображение диалогового окна : Me s s ageBox . Show ( " Bceм привет ! " , / / Сообщение " Окно с названием" , / / Название окна Mes s ageBoxButton s . OK , / / Кнопки ( одна ОК ) Mes s ageBoxi con . I n formation / / Пиктограмма )i При выполнении программы отображается диалоговое окно с сообще­ нием. Как выглядит окно, показано на рис. 1 . 19. Окно с на.sаанием Всем npш1tr! ок 44 Рис . 1 . 1 9 . Диалоговое окно с пи ктограммой и названием З нако мство с языком С# Особенность этого окна в том, что, помимо сообщения и кнопки ОК, у окна есть название Окно с названием и информационная пиктограм­ ма (буква i внутри синего круга). Окно, которое отображалось в преды­ дущем примере (см. рис. 1 . 1 8), не имело ни пиктограммы, ни названия. Для определения заголовка диалогового окна мы передаем вторым аргу­ ментом методу Show ( ) текст " Окно с н а з в а нием " (см. листинг 1 .4). Третий аргумент Me s s a ge B o xBu t t o n s . ОК означает, что в диалого­ вом окне отображается всего одна кнопка ОК. Четвертый аргумент Me s s ageBox i c on . I n f o rmat i o n определяет пиктограмму, отобража­ емую в диалоговом окне. � ПОДРОБН ОСТИ В нашем случае диалоговое окно содержит всего одну кнопку. В принципе, можно сделать так, чтобы кнопок было несколько (две ил и три ) . В таком случае пол ьзовател ь может нажать одну из кнопок в диалоговом окн е , и нам важно уметь оп ределять, какая именно кнопка была нажата . Такая возможность есть. Метод Show ( ) возвращает резул ьтат: инструкция с вызовом мето­ да и меет значение. Это значение - одна из констант перечисления DialogResult. Константы определя ют кнопку, нажатую пол ьзова­ телем : Yes ( нажата кнопка Yes , или Да ) , No ( нажата кнопка No, ил и Н е т) , ОК (нажата кнопка О К) , Cancel ( нажата кнопка Cance l , ил и Отмена ) , Retry (нажата кнопка Retry, или П овтор ) , Abort ( нажа­ та кнопка Abort, или П р ерват ь ) , I gnore ( нажата кнопка lgnore , ил и П роп уст и ть) . Как испол ьзовать эти значения для оп ределения на­ жатой в окне кнопки , м ы рассмотрим немного позже . Окно с полем ввода Ну, ежели , конечно , кроме железных предме­ тов, еще и фарфор можете употребить - тог­ да . . . просто слов нет. из к/ф �Формула лю бви» В следующем примере мы научимся отображать диалоговое окно с по­ лем ввода. С помощью такого окна в программу можно вводить данные, которые программа сможет использовать. 45 Глава 1 Диалоговое окно с полем ввода отображается так же просто, как и диало­ говое окно с сообщением. Правда, для отображения окна с полем ввода нам придется прибегнуть к средствам языка Visual Basic. Дело в том, что в языке С# нет встроенных средств для отображения окна с полем ввода. Зато такое окно можно отобразить с привлечением средств языка Visual Basic. Технология .Net Framework позволяет нам прибегнуть к помо­ щи библиотеки Visual Basic при написании программного кода на язы­ ке С#. Чтобы воспользоваться этой возможностью, мы должны подклю­ чить пространство имен Mi c ro s o ft . V i s u a l Ba s i c . Если пространство подключено, то в программе будет доступен класс I nt e r a c t i on, в ко­ тором есть статический метод I nputBox ( ) . Этот метод отображает ди­ алоговое окно с полем ввода, а результатом возвращает текст, введенный пользователем. Небольшой пример программы, в которой использованы описанные выше утилиты для отображения диалогового окна с полем ввода, представлен в листинге. 1 .5. [1i!] л истинг 1 . 5 . Отображение окна с полем ввода / / Исполь зуем ресурсы Visual Basic : us ing Micros oft . Vi sualBas i c ; us ing Sys tem . Windows . Forms ; c l a s s I nputDial ogDemo { s tatic vo id Main ( ) { / / Текстовая переменная : s t ring name ; / / Отображение окна с полем ввода : name=Interaction . InputBox ( " Как Вас зовут ? " , / / Текст над полем ввода " Давайте познакомимся". " // Название окна ); / / Еще одна текстовая переменная : s t ring tхt= " Очень приятно , " +name+ " ! " ; / / Окно с сообщением : Me s s ageBox . Show ( txt , " Знaкoмcтвo состоялось " ) ; 46 Знакомство с языком С# При запуске программы сначала появляется окно, представленное на рис. 1 .20. /J,/Jаайте nо:sнакоr.шмся." Р ис . 1 . 2 0 . Диалоговое окно с полем ввода Окно имеет название Давайте познакомимся , определяемое в про­ грамме, и текст Как Вас зовут?, также определяемый в программе. В поле диалогового окна следует ввести текст и щелкнуть кнопку ОК (см. рис. 1 .20). После этого появляется диалоговое окно с сообщени­ ем, причем сообщение содержит текст, введенный ранее пользователем в поле предыдущего диалогового окна. Как может выглядеть окно с со­ общением, показано на рис. 1 .2 1 . . . . � Знакомство состомск:ь Очеttь nршnно. Алисtй Вас•t.11 ь е.1! ок Рис . 1 . 2 1 . Диалоговое окно с сообщением, содержащим введенный пользователем текст Теперь проанализируем программный код, который позволяет полу­ чить такие результаты. В начале программы подключаются простран­ ства имен Mi c r o s o f t . V i s u a l B a s i c и S y s t e m . W i n do w s . F o rm s . Первое из них необходимо для использования статического метода I np u t B o x ( ) из класса I n t e r a c t i o n , отображающего окно с полем ввода, а второе пространство имен необходимо для использования ста­ тического метода S h o w ( ) из класса Me s s ageBox. G) Н А ЗАМ ЕТ КУ Напо м н и м , что мало добавить в п рограм м н ы й код и н струкции подкл юч е н и я пространств и м е н . Н еобходи м о также добавить 47 Глава 1 соответствующие ссылки в п роект. Дл я этого сл едует в м е н ю Project выбрать команду Add Reference и в откр ы в ш емся окне установить флажки у пунктов с назван и я м и п одкл ючаемых п р о ­ странств и м е н . В главном методе программы есть команда s t r i n g n ame . Этой ко­ мандой объявляется переменная n am e , относящаяся к текстовому типу s t r i n g . Проще говоря, значением переменной n ame может быть текст. Переменная - это блок памяти, к которому обращаются по имени (на­ звание переменной). Переменную удобно представлять в виде ячейки с надписью на дверце. В эту ячейку можно что-то положить (записать значение) или посмотреть, что там находится (прочитать значение). У каждой переменной есть тип. Тип переменной влияет на объем памя­ ти, выделяемой под переменную, а также на то, какие значения можно записывать в переменную и какие операции с этой переменной можно выполнять. Каждая переменная в языке С# должна быть объявлена. Для этого ука­ зывается тип переменной и ее название. В данном случае в качестве идентификатора типа указано ключевое слово s t r i ng, означающее, что переменная является текстовой: значением такой переменной разреша­ ется присвоить текст. Объявлять переменные можно фактически в лю­ бом месте программы (главного метода), но обязательно до того, как пе­ ременная первый раз используется. � ПОДРОБН ОСТИ Идентифи катор string является псевдонимом ( ил и синонимом) для инструкци и System. String. Это обращение к классу String из п ространства имен System. Есл и п ространство имен System под­ кл ючено, то вместо идентифи катора string можно испол ьзовать название класса String. Класс String п редназначен для реал изаци и текста. Текст реал изу­ ется в виде объекта класса String . Но то , как выше описы вался спо­ соб реал изаци и переменных, относится к базовым типам , а не к объ­ ектам . Други м и словам и , способ реал изаци и объектов сложнее, чем было оп исано выше. Тем не менее пока что это не стол ь важно , и м ы можем думать о текстовой переменной как об обычной переменной базового ти па. Базовые типы данных, классы и объекты обсуждают­ ся п озже . 48 З нако мство с языком С# Для отображения диалогового окна с полем ввода из класса I n t e r a c t i on вызывается статический метод I np u t B o x ( ) . Аргументами методу пе­ редается текст " Как В а с з о вут ? " (первый аргумент) и " Д а в а й т е п о з н а комимся " (второй аргумент). Первый аргумент метода опреде­ ляет текст, отображаемый в диалоговом окне сверху над полем ввода, в то время как второй аргумент задает название окна. ... Метод I np u t B o x ( ) возвращает результат. Это позволяет отож­ дествлять инструкцию вызова метода с некоторым значением. Для метода I np u t B o x ( ) возвращаемое значение - это введенный пользователем в поле диалогового окна текст. Данное значение присва­ ивается переменной name. Поэтому в результате выполнения коман­ ды n ame= I n t e r a c t i on . I np u t B o x ( " Как В а с зовут ? " , " Давайте п о з н а комимся " ) открывается диалоговое окно с полем ввода, а пе­ ременной n ame значением присваивается (после того как пользователь нажмет кнопку ОК) текст из поля ввода. ... � ПОДРОБН ОСТИ В С# оператором п рисваиван ия я вляется сим вол равенства = . Выражение вида п ереме нн а я =зн а че ние обрабаты вается так: в п еременн ую , указан ную слева от оператора присваивания , зап и ­ сывается зн а чени е , указан ное сп рава о т оператора п рисваивания . Командой s t r i n g t х t = " Оче н ь прия т н о , " + n ame + " ! " объявляет­ ся еще одна текстовая переменная, которая называется t x t . При объ­ явлении переменной ей сразу присваивается значение. Так можно де­ лать. Значением, присваиваемым переменной t x t , указано выражение " Оче н ь прия т н о , " + n ame + " ! " . Это сумма (использован оператор сложения +) трех текстовых значений: к текстовому литералу " Оче н ь прия т н о , " прибавляется значение переменной n ame , и к тому, что по­ лучилось, прибавляется текстовый литерал " ! " (состоящий всего из од­ ного символа, но все равно это текст). Если операция сложения при­ меняется к текстовым значениям, то происходит формирование новой текстовой строки путем объединения (конкатенации) складываемых фрагментов. В данном случае переменной t x t присваивается текст, получающийся объединением текста " Оче н ь прия т н о , " , значения текстовой переменной n ame (то, что ввел пользователь) и текста " ! " . После того как значение переменной t x t определено, переменная пе­ редается первым аргументом методу S h ow ( ) из класса Me s s a g e B o x . Вторым аргументом передается текст " З н а комс т в о с о с т о я л о с ь " , 49 Глава 1 определяющий заголовок диалогового окна. В результате выполнения команды Me s s ageBox . Show ( t xt , " Зна комс т в о с о с т ояло с ь " ) по­ является диалоговое окно с сообщением, содержащим введенный поль­ зователем текст. Консол ь н ы й ввод - Вы получите то, что желали , согласно наме­ ченным контурам. - К черту контуры ! Я их уже ненавижу. из к/ф �Формула лю бви» Информацию можно вводить не только через окно с полем ввода, но и через консольные устройства: набирая значение с помощью кла­ виатуры (с отображением в консольном окне). Далее мы рассмотрим пример, в котором показано, как в программе может быть реализован консольный ввод. Программа похожа на предыдущую (см. листинг 1 .5): пользователь вводит текст, и затем появляется сообщение, содержащее этот текст. Но в предыдущем примере текст вводился в поле диалогово­ го окна, и сообщение появлялось в диалоговом окне. Теперь для ввода и вывода информации мы используем консольное окно. Нам понадобит­ ся статический метод Re adL i n e ( ) из класса C on s o l e для считывания введенного пользователем текста. Для вывода сообщений в консоль­ ное окно мы используем статические методы W r i te ( ) и W r i t e L i n e ( ) из класса Соn s о l е . Различие между методами W r i t е ( ) и Wr i t e L i n e ( ) в том, что методом Wr i t e ( ) в консольном окне отображается значение аргумента (например, текст) и курсор остается в той же строке. То есть следующее сообщение появится в той же строке, в которую выводилось предыдущее сообщение. Методом W r i t e L i n e ( ) отображается значе­ ние аргумента в консольном окне, и по завершении отображения выпол­ няется переход к новой строке: следующее сообщение появится в новой строке. Далее рассмотрим программный код в листинге 1 .6. G) Н А ЗАМ ЕТ КУ В данном случае мы создаем консол ьный п роект. Для ком пилирова­ ния и запуска п рограм м ы на вы полнение испол ьзуем команду Start Without Debugging из меню Debug или нажи маем комбинацию кла­ виш <Ctrl>+<F5> . 50 З нако мство с языком С# [1i!]л истин г 1 . 6 . Консольный ввод u s ing Sys tem; c l a s s I nputConsoleDemo { s tatic vo id Main ( ) { 1 1 Текстовая переменная : s t ring name ; 1 1 Заголовок консольного окна : Console . Ti tlе="Давайте познакомимся"." ; 1 1 Сообщение в консоли : Console . Write ( " Kaк Вас зовут ? " ) ; 1 1 Считывание текста : name=Console . ReadLine ( ) ; 1 1 Еще одна текстовая переменная : s t ring tхt= " Очень приятно, " +name+ " ! " ; 1 1 Заголовок консольного окна : Соnsоlе . Тitlе=" Знакомство состоялось " ; 1 1 Сообщение в консоли : Console . WriteLine ( txt ) ; В программе используется класс C o n s o l e , поэтому мы подключаем пространство имен S y s t em, к которому относится этот класс. � ПОДРОБН ОСТИ Также мы испол ьзуем класс String из п ространства имен Sys tem для реал изаци и текстовых переменных. Но вместо назван и я класса String п р и объя влении текстовых переменных задействован иден­ тифи катор string как псевдоним для инструкци и Sys tem. String. В последующих примерах м ы также в основном будем испол ьзовать идентификатор string. В главном методе программы командой s t r i n g n ame объявляет­ ся текстовая переменная n ame. Эта переменная используется для за­ поминания текста, введенного пользователем. Командой C o n s o l e . 51 Глава 1 T i t l е= " Дав айт е п о з н акомимся . . . " задается заголовок для консоль­ ного окна. � ПОДРОБН ОСТИ У класса Con s o l e есть свойство T i t l e , оп ределя ющее название, отображаемое в консол ьном окне . П о умолчанию при запуске п ро­ граммы на выполнение в названии консол ьного окна отображается путь к файлу п рограммы . П рисвоив значение свойству, мы оп реде­ ляем название, которое отображается в строке названия консол ь­ ного окна. Более детал ьно свойства обсуждаются в контексте изу­ чения классов и объектов . Koмaндoй C on s o l e . Wr i t e ( " Kaк В а с з овут ? " ) в консоли печатается со­ общение. Курсор остается в той же строке. Ситуация проиллюстрирова­ на на рис. 1 .22. 1 ДаNйте n03Hll(O MИM(Jll ... как вас зовут? • "' Рис. 1 . 2 2 . Сообщение в консольном окне и ожидание ввода пользователя Программа ожидает, пока пользователь введет значение (текст). Сле­ дует ввести текст (имя пользователя) и нажать клавишу < Enter>. На рис. 1 .23 показана ситуация, когда пользователь ввел текст, но еще не нажал клавишу < Enter>. как вас зовут? АЛ е к с е й В ас и л ь е в• 111 1 Рис . 1 . 2 3 . Консольное окно с введенным текстом перед нажатием клавиши < Eпter> 52 Знакомство с языком С# Считывание введенного пользователем текста выполняется командой n ame = C o n s o l e . Re a d L i n e ( ) . Здесь из класса C o n s o l e вызывается статический метод Re a dL i n e ( ) . Метод считывает введенное пользова­ телем текстовое значение и возвращает это значение результатом. Текст запоминается с помощью текстовой переменной n ame. Еще одна тек­ стовая переменная объявляется и инициализируется командой s t r i n g t х t = " Очен ь прия т н о , " + n ame + " ! " . Значение переменной t х t фор­ мируется объединением текста " Оче н ь прия т н о , " , значения тексто­ вой переменной n ame и текста " ! " . G) Н А ЗАМ ЕТ КУ И н и циализация переменной означает первое (после объя вления или во время объявления) п рисваиван ие значения этой переменной. После этого командой C on s o l e . Ti t l е= " Знакомс т в о с о с т о ял о с ь " консольному окну присваивается новое название, а затем с помощью команды Co n s o l e . W r i t e L i n e ( t xt ) в консольном окне отображается значение переменной t x t . Как может выглядеть в этом случае консоль­ ное окно, показано на рис. 1 .24. а! Зн1комстsо состоялось к а к Вас з о вут? Ал е к с е й В ас и л ь е в ч е н ь п р и я т н о , Ал е к с ей Васил ьев ! л я п р одолже н и я нажмите любую к л а в ишу Р IЦ ' \.. 111 �(ШJ� . . . • С: J Рис. 1 .24 . Консольное окно после отображения сообщения с текстом, введенным пользователем G) Н А ЗАМ ЕТ КУ Сообщение Для продолжен ия наж м и те л юбую клави шу , кото­ рое появляется в консол ьном окне в последней строч ке , не и меет отношения к п рограмме и добавляется автоматически . . . . Следовательно результат выполнения программы может быть таким, как показано ниже (жирным шрифтом выделено значение, введенное пользователем). 53 Глава 1 [1i!J Результат выполнения п рограммы (из листинга 1 . 6) Как Вас зовут ? Аnехсей Васильев Очень приятно , Алексей Василь ев ! В этом и рассмотренном ранее примере с вводом значения через окно с полем ввода мы считывали текст. Но часто возникает необходимость считывать значения и других типов. С ч иты вание ч и сел Ни одной буквочки ! Ни одной 4МЭЙд ин". >.> . и з к/ф -«Кин-дза-дза» При считывании введенного пользователем значения с помощью мето­ да Re adL i n e ( ) (при консольном вводе) или метода I nputBox ( ) (при вводе через поле в диалоговом окне) значение, введенное пользовате­ лем, считывается как текст - даже если это число. Проще говоря, если пользователь введет, например, целочисленное значение, то считано оно будет как текст, состоящий из цифр (это называется текстовое пред­ ставление числа). Чтобы «извлечь>.> из такого текста число, необходимо использовать специальные методы. Для получения целочисленного зна­ чения на основе его текстового представления используют статический метод P a r s e ( ) из структуры I nt 3 2 (структура относится к простран­ ству имен S y s t em ) . G) Н А ЗАМ ЕТ КУ В языке С# структура я вляется аналогом класса. Во всяком случае, различия между ними в основном «техн ические» . М ы будем детал ь­ но обсуждать и класс ы , и структуры . Аргументом методу передается текстовое представление числа, а ре­ зультатом метод возвращает собственно число, «спрятанное>.> в тексте. Небольшой пример считывания целочисленного значения с помощью диалогового окна с полем ввода представлен в листинге 1 .7. Программа, которая представлена там, при выполнении отображает диалоговое окно с полем ввода, в которое пользователю предлагается ввести год своего рождения. Год рождения считывается, и на основе этой информации вы­ числяется возраст пользователя. А теперь рассмотрим код программы. 54 З нако мство с языком С# [1i!] л истинг 1 . 7 . Считывание числа u s ing Sys tem; u s ing Micros oft . VisualBas i c ; u s ing Sys tem . Windows . Forms ; c l a s s Enteringi ntege r { s tatic vo id Main ( ) { 1 1 Текстовые переменные : s t ring re s , txt ; 1 1 Целочисленные переменные : int year=2 0 1 7 , age , born ; 1 1 Отображение окна с полем ввода : res=Interaction . I nputBox ( " B каком году Вы родились ? " , " Год рождения " ) ; 1 1 Преобразование текста в число : born=Int32 . Parse ( re s ) ; 1 1 Вычисление возраста : age=year - born ; tхt=" Тогда Вам " +age+" лет " ; 1 1 Окно с сообщением : Me s s ageBox . Show ( txt , " Boзpacт " ) ; В программе подключаются пространства имен S y s t em, Mi c r o s o f t . Vi s u a l B a s i c и S y s t em . W i ndows . F o rm s . В главном методе объявля­ ются текстовые переменные r e s и txt (команда s t r i ng r e s , txt ) . Также объявляются три целочисленные переменные ye a r , age и b o r n (команда i n t ye a r= 2 0 1 7 , age , b o r n ) , причем переменной ye a r сразу присваивается значение 2 О 1 7 . Для определения типа мы исполь­ зовали идентификатор i n t , обозначающий целочисленный тип: пере­ менные такого типа значением могут принимать целые числа. G) Н А ЗАМ ЕТ КУ Можно объя влять сразу нескол ько перемен ных одного ти па. В таком случае указы вается идентификатор типа переменных, и затем через 55 Глава 1 запятую переч исляются эти переменные . В случае необходи мости части из н их ( ил и всем перемен н ы м ) могут п рисваи ваться значения . Командой r e s = I nt e ra c t i on . I nputBox ( " В каком г оду Вы роди­ ли с ь ? " , " Г о д р ожде ния " ) отображается окно с полем ввода. Первый аргумент метода I np u t B o x ( ) определяет текст, отображаемый в диа­ логовом окне сверху над полем ввода, а второй аргумент метода задает название диалогового окна. Как выглядит окно, показано на рис. 1 .25. ГОД ро ждеtШЯ 1 97111 Рис. 1 .25 . Диалоговое окно с полем для ввода целого числа Введенное пользователем число в виде текстового значения запоминает­ ся с помощью переменной r e s . Для получения числового значения мы используем команду b o r n= I n t 3 2 . P a r s e ( re s ) . В результате в пере­ менную b o r n записывается целое число, введенное пользователем, при­ чем сохраняется это значение именно как число, поэтому с ним можно выполнять арифметические операции. G) Н А ЗАМ ЕТ КУ Есл и пол ьзовател ь введет в поле диалогового окна не числ о , то при поп ытке п реобразовать введенный текст в целое ч исло возни кнет ошибка . Она также возни кает, есл и пол ьзовател ь нажмет вместо кнопки О К кнопку Отмена . 8о1раст То гдо В ом 39 лот ок Рис . 1 . 2 6 . Ди алоговое окно с сообщением, содержащим возраст пользователя 56 З нако мство с языком С# Для вычисления возраста пользователя выполняется команда a g e = ye a r -b o r n . Переменной a g e , находящейся слева от оператора присваивания, присваивается значение выражения, находящегося в пра­ вой части от оператора присваивания. А в правой части вычисляется раз­ ность значений переменных ye a r (текущий год) и b o r n (год рождения). После вычисления значения переменной age она используется в команде tхt= " Т о гда Вам " +age+ " ле т " , которой определяется текстовое зна­ чение, запоминаемое с помощью переменной t x t . В правой части от опе­ ратора присваивания указано выражение " Т о гда Вам " + age + " ле т " . Значением выражения является текст, получающийся объединением текстового фрагмента " Т огд а Вам " , значения переменной age и тек­ ста " ле т " . Наконец, командой Ме s s аgеВох . Show ( t xt , " Возра с т " ) отображается диалоговое окно с названием Возраст, в котором есть со­ общение с текстом, определяемым переменной t x t . Как может выгля­ деть такое окно, показано на рис. 1 .26. После закрытия диалогового окна выполнение программы завершается. Форматирова н н ы й вывод - В таком виде я не могу. Мне нужно сначала принять ванну, выпить чашечку кофе . . . - Будет тебе там и ванна, будет и кофе, будет и какава с чаем. Поехали ! из к/ф «Брwu� иантовая рука» В завершении главы сделаем небольшой комментарий относительно ис­ пользования методов W r i t e L i n e ( ) и W r i te ( ) и W r i te ( ) . Мы уже знаем, что методы предназначены для отображения в консольном окне значения, переданного аргументом методу. G) Н А ЗАМ ЕТ КУ Напом н и м , что разница между методами WriteLine ( ) и Write ( ) состоит в том , что при испол ьзовании метода Wri teLine ( ) п осле отображения значения в консол ьном окне вы пол няется автоматиче­ ский переход к новой строке. При испол ьзовании метода Wri te ( ) такой переход не вы пол няется . 57 Глава 1 Но аргументы методу W r i t e ( ) или W r i t e L i n e ( ) можно передавать и несколько иначе. Первым аргументом передается текстовая строка. Эта строка содержит специальные блоки инструкций, которые будем на­ зывать блоками форматирования. В самом простом случае блок форма­ тирования представляет собой пару фигурных скобок с целочисленным индексом внутри: например { О } , { 1 } , { 2 } и так далее. После текстовой строки с блоками форматирования через запятую указываются значе­ ния, которые должны быть вставлены вместо блоков форматирования при отображении строки в консоли. Допустим, мы используем коман­ ду вида Wr i t е L i n е ( 11 Знач е ния { О } , { 1 } и { 2 } 11 , А , В , С ) . В таком случае в консольном окне отображается текст 11 З н ачения { О } , { 1 } и { 2 } 11 , но только вместо блока { О } вставляется значение пере­ менной А, вместо блока { 1 } вставляется значение переменной В, а вме­ сто блока { 2 } будет вставлено значение переменной С. Фактически ин­ декс в фигурных скобках - это индекс аргумента из списка аргументов, указанных после текстовой строки (первый по порядку элемент после текстовой строки имеет нулевой индекс). В текстовой строке ссылку на один и тот же аргумент можно выполнять несколько раз. G) Н А ЗАМ ЕТ КУ Допускается, чтобы перемен ные или вы ражения , указанные п осле первого текстового аргумента в методе WriteLine ( ) или Write ( ) , был и разного типа. Что касается собственно блоков форматирования, то они могут содер­ жать больше, чем просто индекс аргумента для вставки. Кроме индекса аргумента, в фигурных скобках можно указать способ выравнивания со­ держимого и формат отображения значения. Общий шаблон для блока форматирования выглядит так (жирным шрифтом выделены ключевые элемента блока форматирования): { индекс , ширина : формат } Сначала в фигурных скобках указывается индекс аргумента, который подставляется в соответствующее место при отображении строки. Че­ рез запятую после индекса указывается целое число, определяющее ширину поля, выделяемую для отображения значения аргумента. По­ ложительное число означает выравнивание содержимого по право­ му краю, а отрицательное число означает выравнивание содержимого по левому краю. Также через двоеточие можно указать инструкцию, 58 З нако мство с языком С# определяющую способ (формат) отображения значения аргумента. Для определения формата отображения числового значения используют символ #, обозначающий цифру. Символ Х используют как индикатор для отображения числа в шестнадцатеричном формате, символ Е явля­ ется индикатором для использования экспоненциального формата, сим­ вол N используют как индикатор десятичного числа, символ С позволя­ ет применять денежный формат. Например, инструкция { О , 2 О : # . # # } означает, что при отображении первого аргумента (аргумента с нулевым индексом) следует использовать поле шириной в 2 О символов (не мень­ ше) и выравниваться значение будет по правому краю (поскольку ши­ рина поля задана положительным числом). Код # . # # означает, что в дробной части числа будет отображаться не больше двух цифр. Если мы хотим, чтобы параметры отображения были те же, но выравнивание выполнялось по левому полю, то используем код { О , - 2 О : # . # # } ( ши­ рина поля определяется отрицательным числом). Блок { О : Е } означает, что число отображается в экспоненциальном формате, а для использова­ ния шестнадцатеричного формата отображения числа используем блок { О : Х } . Далее в книге по мере необходимости мы еще будем возвращать­ ся к этому вопросу и комментировать способ передачи аргументов мето­ дам W r i te ( ) и W r i t e L i n e ( ) . Р ез ю ме Нет денег. И деньги , и документы , и валюта все осталось у экскурсовода . Ну, так получи­ лось. Отошли на секундочку, и затерялись в песках. из к/ф -«Кин-дза-дза» • Программа на языке С# обязательно содержит класс с главным ме­ тодом. Выполнение программы - это выполнение программного кода главного метода. • Описание класса начинается с ключевого слова c l a s s , после кото­ рого указывается имя класса и, в фигурных скобках, описывается тело класса - в данном случае тело класса содержит описание глав­ ного метода. • Описание главного метода начинается с ключевого слова s t a t i c, далее указывают название метода Ma i n ( ) с пустыми круглыми 59 Глава 1 скобками. Команды главного метода размещаются в блоке из фигур­ ных скобок. • При необходимости с помощью инструкции u s i n g подключаются пространства имен. Часто используется пространство имен S y s t em. • Для консольного вывода используются статические методы Wri te ( ) и W r i t e L i n e ( ) класса C on s o l e из пространства имен S y s t em. Для отображения диалогового окна используют статический метод S h o w ( ) класса Me s s a g e B o x (доступен после подключения про­ странства имен S y s t em . W i ndows . F o rms ) . • Для консольного считывания текста используют метод Re adL i n e ( ) из класса C o n s o l e . Текст также можно вводить через окно с по­ лем ввода. Окно отображается статическим методом I np u t B o x ( ) из класса I n t e ra c t i o n . Класс доступен после подключения про­ странства имен Mi c r o s o ft . Vi s u a l Ba s i c . • Для работы с текстом используют переменные типа s t r i n g (псев­ доним для выражения S ys t em . S t r i n g ) . Целые числа реализуются с помощью переменных типа i n t . • Для преобразования текстового представления числа в целое число используют статический метод Р а r s е ( ) из структуры I n t 3 2 (про­ странство имен S y s t em ) . Зада н ия для самостоятел ьной работы Как говорит наш дорогой шеф, в нашем деле главное - этот самый реализм ! из к/ф «Бриллиантовая рука» 1 . Напишите программу, в которой пользователь вводит сначала имя, а затем фамилию. Программа выводит сообщение с информацией об имени и фамилии пользователя. Предложите версию программы, в которой ввод и вывод текста осуществляется с помощью диалоговых окон. Также предложите консольную версию программы. 2. Напишите программу, в которой пользователь вводит имя и возраст. Программа отображает сообщение об имени и возрасте пользователя. Предложите консольную версию программы и версию, в которой дан­ ные вводятся и выводятся с помощью диалоговых окон. 60 З нако мство с языком С# 3. Напишите программу, в которой пользователь последовательно вво­ дит название текущего дня недели, название месяца и дату (номер дня в месяце). Программа выводит сообщение о сегодняшней дате (день недели, дата, месяц). Используйте консольный ввод и вывод данных. Предложите версию программы, в которой для ввода и вывода данных используются диалоговые окна. 4. Напишите программу, в которой пользователю предлагается ввести название месяца и количество дней в этом месяце. Программа выводит сообщение о том, что соответствующий месяц содержит указанное коли­ чество дней. Предложите версии программы для ввода/вывода данных через консоль и с помощью диалоговых окон. 5. Напишите программу, в которой по году рождения определяется воз­ раст пользователя. Используйте консольный ввод и вывод данных. 6. Напишите программу, в которой пользователь водит имя и год рожде­ ния, а программа отображает сообщение, содержащее имя пользователя и его возраст. Предложите консольную версию программы, а также вер­ сию программы, в которой ввод и вывод данных выполняется с помо­ щью диалоговых окон. 7. Напишите программу, в которой по возрасту определяется год рожде­ ния. Возраст пользователь вводит в окно с полем, а вычисленный год рождения отображается в другом диалоговом окне. Предложите вариант программы, в которой используется консольный ввод и вывод данных. 8. Напишите программу для вычисления суммы двух чисел. Оба чис­ ла вводятся пользователем. Для вычисления суммы используйте опера­ тор + . Предложите два варианта программы: программу, в которой дан­ ные вводятся и выводятся с помощью диалоговых окон, и программу, в которой используется консольный ввод и вывод данных. 9. Напишите программу, в которой пользователь вводит число, а про­ граммой отображается последовательность из трех чисел: число, на еди­ ницу меньшее введенного, введенное число и число, на единицу боль­ шее введенного. Предложите версию программы с консольным вводом и выводом данных, а также версию программы, в которой ввод и вывод выполняется с помощью диалоговых окон. 1 О. Напишите программу, в которой пользователь вводит два числа, а про­ граммой вычисляется и отображается сумма и разность этих чисел. Пред­ ложите варианты программы с использованием консольного ввода/выво­ да данных и ввода и вывода данных с помощью диалоговых окон. Гл ава 2 БАЗОВ Ы Е Т И ПЫ И О П Е РАТО РЫ Я представитель цивилизованной планеты и требую , чтобы вы проследили бы за своим лексиконом ! из к/ф -«Кин-дза-дза» В этой главе мы рассмотрим базовые типы данных и основные операто­ ры, используемые в языке С#. В частности: • мы узнаем, какие базовые (или примитивные) типы данных используются в С# ; • разберемся с особенностями объявления переменных ; • выясним, каким образом в языке С# реализуются литералы ; • познакомимся с автоматическим преобразованием типов и узнаем, как выполняется явное приведение типов ; • узнаем, какие базовые операторы есть в языке С#; • составим представление о способе двоичного кодирования чисел ; • выясним особенности оператора присваивания ; • познакомимся с тернарным оператором. И это не самый полный перечень тем, которые освещаются в главе. Нач­ нем с базовых типов данных, используемых в языке С# . 62 Базовые типы и операто ры П еремен ные и базовые ти п ы дан н ых Милый, это ж театр ! Это тебе не в об а надо ! в гл азок смо­ треть. Тут из к/ф -« 0 бедном гусаре замолвите слово» С переменными мы кратко познакомились в предыдущей главе. Напом­ ним, что переменной называется именованный блок памяти. Этот блок может хранить определенное значение. Мы в программе имеем возмож­ ность прочитать это значение и изменить его. Для получения доступа к блоку памяти используется переменная. Перед тем как переменную использовать, ее необходимо объявить. Объявление переменной подра­ зумевает, что указывается имя переменной и ее тип. При выполнении команды, которой объявляется переменная, для этой переменной в па­ мяти выделяется место. Каждый раз, когда мы будем обращаться к этой переменной, в действительности будет выполняться обращение к соот­ ветствующему месту в памяти. При этом совершенно не важно, где имен­ но в памяти выделено место под переменную. Нам достаточно знать имя переменной, чтобы прочитать ее текущее значение или присвоить новое. Тип переменной определяет, какие значения могут присваиваться пере­ менной. Также тип переменной влияет на то, какие операции можно вы­ полнять с переменной. В языке С# для каждой переменной должен быть указан тип. После объявления тип переменной не может быть изменен. Тип переменной определяется с помощью специального идентифика­ тора. Проще говоря, для каждого типа данных имеется свой идентифи­ катор, который указывают при объявлении переменной. Например, мы уже знаем, что если переменная объявлена с идентификатором типа i n t , т о значением такой переменной может быть целое число. Кроме целых чисел существуют другие типы данных. С ними мы и познакомимся. G) Н А ЗАМ ЕТ КУ М ы познакоми мся с базовыми ти пами дан н ых ( и ногда их еще назы­ вают п росты м и или п римити вными ти пам и ) . Способы реал изаци и переменных, описываемые здесь, и меют отношение к перемен н ы м базовых т и п о в . Помимо базовых ти пов, существуют еще и ссылоч­ ные типы дан н ых. Дан ные ссылочного типа реал изуются нескол ько иначе по сравнению реал изацией данных базовых ти пов. Ссылоч­ ные типы м ы рассмотри м п р и знакомстве с классам и и массивам и . 63 Глава 2 В языке С# большинство базовых типов предназначено для реализации числовых значений (здесь имеются в виду целые числа и числа с плава­ ющей точкой), символьных значений (значением символьной перемен­ ной может быть отдельный символ) и логических значений (переменная логического типа может принимать одно из двух логических значений истина или ложъ ). Наибольшую группу составляют целочисленные типы - то есть типы данных, предназначенных для реализации целых чисел. Мы уже знакомы с одним из целочисленных типов (речь о типе i n t ) . Но кроме типа i n t в языке С# существуют и другие целочислен­ ные типы. На первый взгляд может показаться странным, что для реали­ зации целых чисел используется несколько типов данных. Но странного здесь ничего нет: хотя каждый раз имеем дело с целыми числами, запи­ сываются они по-разному. Если кратко, то есть две �позиции�, по кото­ рым отличаются способы реализации целых чисел (при использовании разных типов): • объем памяти, выделяемой для хранения числа ; • возможность или невозможность использовать отрицательные числа. � ПОДРОБН ОСТИ Для записи цел ых ч исел в пам яти ком п ьютера испол ьзуется б и ­ товое представл е н и е . В с я память, выдел я емая для записи ч исла , разбита на отдел ь н ы е бл оки - биты . Кажды й б и т м ожет содержать в кач естве значения О ил и 1 . Блок из 8 битов называется байтом . Для хранения дан н ых оп редел е н ного типа выдел я ется одно и то же кол ичество бито в . Напри ме р , дл я записи знач е н и й типа int в язы ­ ке С# в ыдел яется 32 бита , ил и 4 байта . Таки м образом , значение типа int представляет собой п оследовател ьность из 32 нул ей и еди н и ц . Такой код и нтерп рети руется как дво и ч ное п редставле­ н и е ч исла . Ч ем бол ьше в ыделя ется битов дл я записи ч исла , тем бол ьше ч и сел можно закоди ровать. Есл и ч е рез n обознач ить ко­ л и ч ество бито в , с п ом ощью которых коди руются ч исла , то всего " с п омо щью такого кол ичества битов м ожно реал изовать 2 раз н ых ч и сел . Для значени й типа int кол ичество ч исел , которые м ожно 2 реал изовать через перемен ную дан н ого ти п а , равно 2 3 • Диапазон возможн ых знач е н и й для перем е н н ой типа int огран ичен ч и слам и 1 1 -2 3 ( ч исло - 2 1 4 7 4 8 3 64 8 ) и 2 3 - 1 ( ч исл о 2 14 7 4 8 3 64 7 ) в ключ ител ь­ 2 но. Всего ( с учетом нул я ) получается 2 3 раз н ых ч исел . Поскол ьку в общем случае п риходится кодировать не тол ько по­ ложител ьные, но и отри цател ьные ч исла , то оди н из битов ( стар­ ш ий бит) испол ьзуется как и ндикатор того , п ол ожител ьное ч и сло 64 Базовые типы и операто ры ил и отри цател ьное . У пол ожител ьн ых ч и сел знаковы й бит нулево й , а у отри цател ьных чисел - еди н и ч н ы й . Более детал ьно способы ко­ дирования цел ых чисел рассматри ваются в разделе , посвя щенном побитовы м операторам . Для реализации целых чисел, кроме типа i n t , используются типы byte, sbyte, s h o r t , u s h o r t , u i n t , long и u l o n g . Значения типов b y t e и sbyte кодируются с помощью 8 битов. Различие между типами в том, что значение типа b y t e - это неотрицательное число. Значение типа sbyte может быть и положительным, и отрицательным. G) Н А ЗАМ ЕТ КУ Начал ьная буква s в назван и и типа sbyte - это «остаток» от слова «signed» ( что означает «СО знаком » ) . С помощью 8 битов можно записать 2 8 2 5 6 разных чисел. Поэтому возможное значение для переменной типа byte лежит в пределах от О до 2 5 5 включительно. Значение переменной типа s b y t e находится в пределах от - 1 2 8 до 1 2 7 включительно. = Для хранения значения типов s h o r t и u s h o r t выделяется 1 6 битов. То есть «мощность» этих типов одинакова. Но тип s h o r t использует­ ся для работы с отрицательными и положительными числами, а тип u s ho r t используют при работе с неотрицательными числами. G) Н А ЗАМ ЕТ КУ Начал ьная буква и в назва н и и типа u s hort, а также в названиях рассматри ваем ых далее ти пов uint и ulong поя вилась из слова « u nsigned» ( что означает «без знака » ) . Следующая пара типов i n t (числа с о знаком) и u i n t (неотрицательные числа) использует 32 бита для хранения числовых значений. Наконец, самые «мощные» целочисленные типы - это l o n g и u l o n g. Перемен­ ные данных типов хранятся в ячейках памяти объемом в 64 бита. Значе­ ния типа l o n g интерпретируются как числа со знаком, а значения типа u l o n g обрабатываются как неотрицательные числа. Более подробная информация не только о целочисленных типах, но и о прочих базовых типах языка С# приведена в табл. 2 . 1 . 65 Глава 2 Табл . 2 . 1 . Базовые типы языка С# Тип Память Диапаз он в битах з пачепий Описапие Структур а byte 8 от О до 255 Целое неотрицательное число Byte sbyte 8 от - 1 28 до 1 2 7 Целое число SByte short 16 о т -32768 д о 32767 Целое число I nt l б u s ho r t 16 о т О д о 65535 Целое неотрицательное число U i nt l б int 32 от - 2 1 47483648 до 2 1 47483647 Целое число Int32 uint 32 от о до 4294967295 Целое неотрицательное число Uint32 long 64 от -9223372036854775808 до 9223372036854775807 Целое число I nt 6 4 u l ong 64 от о до 1 844674407370955 1 6 1 5 Целое неотрицательное число Uint 64 float 32 о т 1 .5Е-45 д о 3.4Е+38 Действительное число (с плавающей точкой) Single douЫ e 64 от 5Е-324 до 1 .7Е+308 Действительное число (с плавающей точкой) двойной точности DouЫe dec imal 128 от 1 Е-28 до 7.9Е+28 Действительное число для выполнения особо точных (финансовых) расчетов D e c im a l char 16 о т о д о 65535 Символьное значение Char bool 8 значения t ru e и f a l s e Логическое значение Boolean Для удобства в табл. 2.1 кроме идентификаторов для базовых типов указано количество битов, выделяемых для значения соответствующего типа, а также приведен диапазон возможных значений для переменной соответствующего типа. G) Н А ЗАМ ЕТ КУ В де йствител ьности значения базовых типов реал изуются как эк­ зем пляры структур . Структуры м ы рассмотрим нем ного позже . В табл . 2 . 1 для каждого базового типа указана структура , которая соответствует дан н ому ти пу. Фактически идентификаторы базовых 66 Базовые типы и операто ры ти пов являются псевдо н и мам и для назван и й структур . Напри ме р , для работы с цел ы м и ч ислам и можно создать перемен ную базово­ го типа int, что означает создание экзем пля ра структуры Int3 2 . То есть ситуация н е самая тривиал ьная , но в большинстве случаев это не и грает н и какой рол и . Кроме целых чисел часто приходится иметь дело с действительными числами, или числами с плавающей точкой. Такие числа имеют целую часть и дробную, которая указывается через точку после целой части. G) Н А ЗАМ ЕТ КУ Для ч и сел с плавающей точкой также можно испол ьзовать экспонен­ циал ьное ( ком п ьютерное) п редставление в виде мантиссы и пока­ зателя степен и . Действител ьное ч исло представляется в виде п ро­ изведения мантиссы (действител ьное ч и сло) на 1 0 в целочисленной степе н и . Зап исать ч исло в таком п редставл е н и и можно следующи м образом : указать мантиссу, затем сим вол Е ( ил и е ) и целое ч и сл о , оп ределяющее показател ь степе н и . Напри ме р , вы ражение 1. SE-45 означает ч исло 1 , 5 х 1 0-45 , а вы ражение 3. 4Е+ 38 соответствует ч ис­ лу 3 ,4 х 1 0 38 • Есл и показател ь степени положител ьн ы й , то знак + можно не указы вать. Для реализации действительных чисел используются типы f l o a t , douЫ e и de c ima l . Для значения типа f l o a t выделяется 32 бита, а под значение типа douЫ e выделяется 64 бита. Поэтому числа, реализован­ ные как значения типа douЬ l e , имеют большую «точность�. Наимень­ 45 шее по модулю f l о а t-число равно 1 ,5 х 1 0 - , а самое маленькое по мо­ 24 -3 дулю dоuЫ е -число равно 5 х 1 0 • Верхняя граница для значений типа 0 douЫ e составляет величину 1 ,7 х 1 0 3 8 , а f l о а t-значения ограничены сверху числом 3,4 х 1 03 8 • В этом смысле тип douЫ e является более предпочтительным. G) Н А ЗАМ ЕТ КУ Есть и другая п р и ч и на для приоритетного испол ьзования типа douЫe по сравнению с типом float . Ее м ы обсудим нем ного позже . Помимо типов f l o a t и douЫ e в языке С# есть тип de c ima l . Под значение типа de c ima l выделяется 1 2 8 битов, что в два раза боль­ ше, чем для типа d o uЬ l e . Но главное назначение такой мощной 67 Глава 2 �поддержки� - обеспечить высокую точность вычислений, выполняе­ мых со значениями типа de c ima l . Обычно необходимость в высокой точности операций возникает при финансовых расчетах. Поэтому обыч­ но тип de c ima l упоминают как специальный тип, предназначенный для поддержки высокоточных финансовых вычислений. Для работы с символьными значениями предназначен тип char. Значе­ нием переменной типа c ha r может быть отдельный символ (буква). Для хранения такого значения в памяти выделяется 1 6 битов. � ПОДРОБН ОСТИ Значения типа char коди руются так же , как и цел ые ч исла - и меем дело с 1 6 битам и , запол н е н ны м и нуля м и и еди н и цам и . Аналоги ч н ы м образом коди руются , напри ме р , значения типа u s hort. Поэтому техн ически сhаr - значение можно рассматри вать как неотри цател ь­ ное числовое значение, которое попадает в диапазон цел ых ч исел от О до 6 5 5 3 5 . Но обрабатывается такое значение нескол ько и наче (по сравне н и ю с обработкой целочисленных значени й ) , а и менно : по двоич н ому коду оп ределяется ч и сл о , и затем из кодовой табл и цы берется сим вол с соответствующи м кодом . Наконец, тип b o o l предназначен для работы с логическими значения­ ми. Поэтому этот тип обычно называют логическим. Переменная логи­ ческого типа может принимать одно из двух значений: t ru e (истина) или f a l s e (ложь). Значения логического типа используются в управ­ ляющих инструкциях - таких, как условный оператор или операторы цикла. Л итерал ы - Ч то они хотят ? - Ку они хотят" . из к/ф -«Кин-дза-дза» Под литералами подразумевают константные (такие, которые нель­ зя изменить) значения, используемые в программном коде. Приме­ рами литералов являются число 1 2 3 (целочисленный литерал), текст " Изуч а ем С # " (текстовый литерал) или буква ' ы ' (символьный 68 Базовые типы и операто ры литерал). То есть это некоторые фиксированные значения, понятные для программиста, которые могут использоваться в программе. Но пи­ кантность ситуации в том, что такие значения в программе реализуются по тому же принципу, что и переменные. Другими словами, каждый ли­ терал имеет определенный тип (или относится к определенному типу), и обрабатывается литерал в соответствии с правилами работы со зна­ чением данного типа. Возникает вопрос: к какому типу относится, на­ пример, целочисленный литерал 1 2 3 ? Понятно, что это целое число. Но для реализации целочисленных значений существует несколько ти­ пов, и в данном конкретном случае любой из них теоретически подходит для реализации числа 1 2 3 . Ответ же состоит в том, что число 1 2 3 реали­ зуется как i n t -значение. Вообще есть несколько правил, определяющих способ реализации литералов. • Целочисленный литерал реализуется как значение �наименьшего� типа, начиная с типа с i n t , достаточного для сохранения значения литерала. • Действительные числовые литералы (число с плавающей точкой, как, например, число 1 2 . 3 ) реализуются в виде значений типа douЬ l e . • Символьные литералы заключаются в одинарные кавычки (напри­ мер, ' F ' или ' я ' ) и реализуются как значения типа c h a r . • Текстовые литералы (текст) заключаются в двойные кавычки (на­ пример, " Язык С # " или " А " ) и реализуются как объекты класса S t r i n g из пространства имен S y s tem (мы используем идентифи­ катор s t r i ng для выражения S y s t em . S t r i n g). G) Н А ЗАМ ЕТ КУ Си м вол ьн ый л итерал закл ючается в оди нарные кавыч ки , а тексто­ вый - в двой н ы е . Си м вол ьн ы й л итерал - это оди н символ . Текст может содержать м ного символо в . Но есл и оди н сим вол заключ ить в двой н ые кавыч ки , то это будет текст, а не си мвол ь н ы й л итерал . И ны м и словам и , вы ражение ' А ' п редставля ет собой символьны й л итерал , а вы ражение " А " я вляется текстовы м л итерал ом (состоя­ щи м из одного символа) . И хотя с формал ьной точки зрения в обоих случаях м ы и меем дело с одн и м и тем же сим волом , с техн ической точки зрения м ы и меем дело со значения м и разных ти п о в , реал изо­ ванн ых совершенно п о - разному. 69 Глава 2 Описанные выше правила применяются при реализации литералов по умолчанию. Но мы можем вносить в этот процесс разумные коррек­ тивы. Так, если мы хотим, чтобы целочисленный литерал реализовался как l оng-значение, надо использовать суффикс L или 1 . Например, ли­ терал 1 2 3 L означает число 1 2 3 , реализованное как значение типа l o ng. Если воспользоваться суффиксом U или и , то целочисленный литерал будет реализован как значение типа u i n t . Суффикс U L (а также u l , U l и u L ) позволяет определить литерал типа u l ong - например, выра­ жение 1 2 3 U L определяет число 1 2 3 , реализованное как значение типа u l ong. Если необходимо, чтобы числовой литерал реализовался как f 1 оа t-зна­ чение, используют суффикс F или f. Например, выражение 1 2 . 3 F опре­ деляет число 1 2 . 3 как значение типа f l o a t . Если использовать суффикс М или m в числовом литерале, получим чис­ ло, реализованное как значение типа de c ima l . Примером может быть выражение 1 2 . 3М. G) Н А ЗАМ ЕТ КУ Целочисленн ые значения м ожно представлять шестнадцатерич­ н ы м и л итералам и . Шестнадцатерич н ы й л итерал нач и н ается с сим­ волов О х или О Х . В п редставл е н и и шестнадцатерич ного л итерала испол ьзуются цифры от О до 9 , а также лати нские буквы от А до F для обозначения ч и сел от 1 0 до 1 5 соответствен н о . Буквы можно испол ьзовать как строч н ы е , так и прописные. Есл и шестнадцате­ рич н ы й л итерал записан в виде последовател ьности цифр и букв anan_ 1 а 2а1 а0 , В которой ak (k=O, 1 , 2 , . . . , П) ЭТО цифры ОТ 0 ДО 9 ил и буквы от А до F, то в десятич н ой системе сч исления такое ч ис­ 1 2 ло выч исляется как а " а "_ 1 а 2а 1 а0 = а0 х 1 6 ° + а 1 х 1 6 + а 2 х 1 6 + . . . " 1 " + а " _ 1 х 1 6 - + а" х 1 6 . П р и этом вместо букв А , В . . . F подставля ются ч исла 1 О , 1 1 . . . 1 5 . Например, л итералу O x 2 F 9 D соответствует деся ­ 1 2 тичное ч исло 1 3 х 1 6° + 9 х 1 6 + 1 5 х 1 6 + 2 х 1 63 = 1 2 1 89 . • • • - • • • 70 Базовые типы и операто ры У п равля ю щие с и м вол ы - И быть тебе з а это рыбой , мерзкой и скольз­ кой ! - Д а , но обещали котом ! - Недостоин ! из к/ф �Формула лю бви» В С# есть группа символов, которые называются управляющими. Фор­ мально это символы, но при их «печатании� обычно выполняется опре­ деленное действие. Все управляющие символы начинаются с обратной косой черты \ . После обратной косой черты указывается некоторый символ. Например, выражение \ n является инструкцией перехода к но­ вой строке, а инструкция \ t представляет собой инструкцию для выпол­ нения горизонтальной табуляции. G) Н А ЗАМ ЕТ КУ Хотя вы ражение \ n или \ t формал ьно состоит и з двух символов, интерп рети руется каждое из них как оди н символ . Поэтому такое выражение можно присвоить значен ием сим вол ьной переменной : выражение как сим вол закл ючается в оди нарные кавычки ( п олучаем л итерал ы ' \ n ' и ' \ t ' ), и такой л итерал п рисваи вается значением переменной типа char ( ил и испол ьзуется другим образом ) . Если в текстовом литерале присутствует инструкция \ n и такой лите­ рал передан аргументом методу Wr i t e L i n e ( ) , то при отображении это­ го литерала в соответствующем месте будет выполнен переход к новой строке. Инструкция \ t в процессе отображения литерала обрабатывает­ ся так: курсор оказывается в ближайшей позиции табуляции. G) Н А ЗАМ ЕТ КУ Строку, в которую выводится текст, можно условно раздел ить на « позици и » , п редназначенные для отображения символов. Среди этих « позици й » есть «особые» или « реперные» - обычно это каж­ дая восьмая « позици я » . То есть «особыми» я вляются 1 -я , 9-я , 1 7-я , 25-я и так далее позици и . Когда нужно « напечатать» инструкци ю \ t , то курсор «прыгает» в бл ижай шую « реперную» позици ю . Следующи й сим вол будет отображаться именно там . 71 Глава 2 Кроме перечисленных выше, можно выделить управляющие символы \ а (звуковой сигнал), \Ь (инструкция перемещения курсора на одну позицию назад) и \ r (инструкция перемещения курсора в начало текущей строки). � ПОДРОБН ОСТИ Нередко возникает потребность испол ьзовать в текстовых и сим­ вол ьных л итералах двойные и одинарные кавычки или обратную косую черту. П роблема в том , что просто так добавить соответству­ ющи й сим вол не получ ится : обратная косая черта я вляется п р и ­ знаком управляющего сим вола, а двойные и оди нарные кавычки испол ьзуются для выделения л итералов (текстовых и сим вол ьных соответствен н о ) . Решение п роблемы состоит в том , чтобы исполь­ зовать допол нител ьную обратную косую черту. Например, двойные кавычки в л итерал добавля ются в виде вы ражения \ " , вы ражение \ ' можно испол ьзовать для добавления в л итерал оди нарной кавыч­ ки , а вы ражение \ \ полезно , есл и необходи мо добавить в л итерал ( одну) обратную косую черту. G) Н А ЗАМ ЕТ КУ Есл и перед текстовым л итералом указать сим вол @ , то такой л итерал становится «буквал ьн ы м » или «не форматируемым» : он отображает­ ся так, как записан. Такой л итерал не подлежит форматированию, он испол ьзуется « как есть» . Например, есл и м ы передадим аргумен­ том методу WriteLine ( ) текстовый л итерал " Изуч аем \ n я зык С # " , то из-за наличия в л итерале инструкци и \ n в первой строке кон­ сол ьного окна появится текст " Изуч ае м " , а во второй - текст " я зык С # " . Но есл и передать аргументом буквал ьный текстовый л итерал @ " Изуч аем \ n я зык С # " , то в консол ьном окне появится текст " Изуч аем \ n я зык С # " . П реобра зова н и е типов - Может, его за Нерона нам выдать - к ак будто ? - Где же ты видел Неронов в лаптях и онучах ? из к/ф -« 0 бедном гусаре замолвите слово» Нередко в выражения входят значения разных типов. Скажем, может возникнуть необходимость к значению типа f l o a t прибавить значение 72 Базовые типы и операто ры типа i n t и полученный результат присвоить переменной типа douЬ l e . Математически здесь все корректно, поскольку речь идет о б арифмети­ ческой операции с числами, результат этой операции представляется в виде действительного числа, а целые числа являются подмножеством чисел действительных. Но в плане программной реализации подобной операции мы имеем дело со значениями трех разных типов. К счастью, в языке С# есть встроенные механизмы, позволяющие сводить к мини­ муму неудобства, связанные с использованием в выражениях значений разных типов. В ситуациях, подобных описанной выше, выполняется ав­ томатическое преобразование типов. Обычно речь идет о преобразова­ ниях различных числовых типов. Есть несколько правил выполнения таких преобразований. И так, допустим, имеются два операнда, которые относятся к разным числовым типам. Алгоритм выполнения преобразо­ ваний такой: • Если один из операндов относится к типу de c ima l , то другой ав­ томатически преобразуется к типу de c ima l . Но если другой опе­ ранд - значение типа f l o a t или douЬ l e , то возникает ошибка. • Если один операнд - типа douЬ l e , то другой автоматически преоб­ разуется в тип douЬ l e . • Если один операнд относится к типу f l o a t , то другой операнд будет преобразован в значение типа f l o a t . • Если в выражении есть операнд типа u l ong, т о другой операнд авто­ матически приводится к типу u l ong. Ошибка возникает, если дру­ гой операнд относится к типу sbyte, s h o r t , i n t или l o ng. • Если в выражении есть операнд типа l o ng, то другой операнд будет преобразован в тип l ong. • Если один из операндов относится к типу u i n t , а другой операнд значение типа sbyte, s h o r t или i n t , то оба операнда расширяются до типа l ong. • Если в выражении оба операнда целочисленные, то они преобразу­ ются в тип i n t . Эти правила применяются последовательно. Например, если в выраже­ нии один из операндов относится к типу de c ima l , то выполняется по­ пытка преобразовать второй операнд к тому же типу, и если попытка успешная, то вычисляется результат. Он также будет относиться к типу de c ima l . До выполнения прочих правил дело не доходит. Но если 73 Глава 2 в выражении нет операндов типа de c ima l , тогда в игру вступает вто­ рое правило: при наличии операнда типа douЫ e другой операнд пре­ образуется к этому же типу, такого же типа будет результат. Далее, если в выражении нет ни операнда типа de c ima l , ни операнда типа douЬ l e , т о применяется третье правило (для типа f l o a t ) , и так далее. В этой схеме стоит обратить внимание на два момента. Во-первых, об­ щий принцип состоит в том, что автоматическое преобразование выпол­ няется к большему (в плане диапазона допустимых значений) типу, так, чтобы потеря значения исключалась - то есть автоматическое преоб­ разование выполняется всегда с расширением типа. Отсюда становит­ ся понятно, почему при попытке преобразовать значения типа douЫ e и f l o a t к значению типа de c ima l возникает ошибка. Хотя для значе­ ний типа de c imal выделяется 1 2 8 битов (и это больше, чем 64 бита для типа douЫ e и 32 бита для типа f l o a t ) , но диапазон допустимых зна­ чений для типа de c ima l меньше, чем диапазоны допустимых значений для типов douЫ e и f l o a t . Поэтому теоретически преобразуемое зна­ чение может быть потеряно, а значит, такое автоматическое преобразо­ вание не допускается. G) Н А ЗАМ ЕТ КУ Подчеркнем , что здесь речь идет об автоматическом преобразовании типов - то есть о преобразовании, которое в случае необходимости выполняется автоматически . Помимо автоматического преобразова­ ния типов существует еще и явное приведение типа, которые выпол ­ няется с помощью специальной инструкции . Там правила другие. Сама инструкция явного приведения типа имеет вид ( тип ) выр аже ни я : иден­ тификатор т и п а , к которому следует привести значение выр аже ни я , указывается в круглых скобках перед этим выр аже ни е м. Во-вторых, если в выражении оба операнда целочисленные, то даже если ни один из них не относится к типу i n t (например, два операнда типа s h o rt), то каждый из операндов приводится к типу i n t , и, соот­ ветственно, результат также будет типа i n t . Это простое обстоятель­ ство имеет серьезные последствия. Допустим, мы командой s h o r t a = l О объявили переменную а типа s h o r t со значением 1 0 . Если после это­ го мы попытаемся использовать команду a=a + l , то такая команда бу­ дет некорректной и программный код не скомпилируется. Причина в том, что числовой литерал 1 , использованный в команде, относится к типу i n t . Поэтому в выражении a + l текущее значение переменной 74 Базовые типы и операто ры а преобразуется к типу i n t , и результат выражения a + l также вычис­ ляется как i n t -значение. Таким образом, получается, что мы пытаемся присвоить значение i n t -типа переменной типа s ho r t . А это запрещено, поскольку может произойти потеря значения. � ПОДРОБН ОСТИ При выч ислении вы ражения а + 1 считы вается значен ие переменной а , и это число преобразуется к типу int . Сама перемен ная как была типа s h o r t , так переменной типа short и остается . Решение проблемы может состоять в том, чтобы использовать явное приведение типа. Корректная команда выглядит так: а= ( s h o r t ) ( a+ l ) . Здесь идентификатор short в круглых скобках перед выражением ( а+ 1 ) означает, что после вычисления значения выражения полученный резуль­ тат (значение типа int) следует преобразовать в значение типа s h o r t . � ПОДРОБН ОСТИ Для значен ия типа i n t в памяти выделяется 32 бита . Значение типа short запоминается с помощью 1 6 битов. П реобразован ие значе­ ния типа int в значение типа s h o r t выполняется путем отбрасы­ вания содержи мого в старших 1 6 битах i n t -значен ия . Есл и int ­ ч исло не очень бол ьшое (не выходит за диапазон s h о r t -значен ия ) , то старшие биты заполнены нул я м и (для положител ьного числа) , и при их отбрасы вании значение п отеряно не будет. Но даже есл и в старших битах не нул и , то биты все равно отбрасываются , и значе­ ние может быть потеряно. Поэтому п реобразован ие значения типа int в значение типа s h o r t - это потен циал ьно опасное п реобразо­ ван ие. Такие преобразования автоматически не выполняются . В не­ котором смысле инструкция ( s h o r t ) свидетел ьствует о том , что п рограм м ист понимает риск и берет ответственность на себя . Есть еще одно важное обстоятел ьство : при ини циал изации пере­ менной s h o r t командой short a= l O перемен ной типа short при­ сваи вается л итерал 1 0 , который я вляется значением типа i n t . Хотя формал ьно здесь тоже вроде бы проти вореч ие, но при п р исваива­ нии целочисленной переменной целочислен ного л итерала, есл и по­ следн и й не выходит за допусти мые предел ы для переменной данно­ го типа, при ведение ти пов выполняется автоматически . Еще один пример: командой b y t e a = l , Ь=2 , с объявляем три пере­ менные ( а , Ь и с ) типа b yt e . Переменные а и Ь инициализируются 75 Глава 2 со значениями 1 и 2 соответственно. Но если мы захотим воспользо­ ваться командой с = а +Ь, то такая команда также будет некорректной. В отличие от предыдущего случая здесь нет литерала типа i n t . Здесь вычисляется сумма а+Ь двух переменных типа byte, и результат при­ сваивается (точнее, выполняется попытка присвоить) переменной типа byte. Проблема же в том, что, хотя в выражении а+ Ь оба операнда отно­ сятся к типу byte, они автоматически преобразуются к типу i n t (в со­ ответствии с последним пунктом в списке правил автоматического пре­ образования типов), и поэтому результатом выражения а +Ь является значение типа i n t . Получается, что мы пытаемся присвоить Ьуtе -пе­ ременной in t -значение, а это неправильно. Чтобы исправить ситуацию, необходимо использовать явное приведение типа. Корректная версия команды присваивания выглядит как с= ( b yte ) ( а +Ь ) . G) Н А ЗАМ ЕТ КУ Есл и п реобразование цел ых чисел в действител ьные в случае необ­ ходимости выполняется автоматически , то обратное п реобразова­ ние требуется выполнять в я вном виде . Например, есл и нам нужно п реобразовать значение типа douЫ e в значен ие типа i n t , то пе­ ред соответствующим вы ражением указы вается инструкция ( i nt ) . П реобразование выполняется отбрасыванием дробной части дей ­ ствител ьного ч исла . Например, есл и командой douЫ e х= 1 2 . 6 объ­ является перемен ная х типа douЫ e со значением 1 2 . 6 , а командой int у объя вляется переменная у типа i n t , то в резул ьтате вы полне­ ния команды у= ( i nt ) х переменная у получ ит значение 1 2 . При этом значение переменной х не меняется . Арифметическое выражение может содержать не только числовые, но и символьные операнды. В таких случаях сhа r-операнд автомати­ чески преобразуется к i n t-типу. Например, выражение ' А ' + ' в ' име­ ет смысл. Результат такого выражения - целое число 1 3 1 . Объяснение простое: значения типа c h a r (то есть символы) хранятся в виде целых чисел. Это последовательность из 16 битов. Вопрос только в том, как ее интерпретировать. Для целых чисел такая последовательность ин­ терпретируется как двоичное представление числа. Для символов �пре­ образование� двоичного кода выполняется в два этапа: сначала по дво­ ичному коду определяется число, а затем по этому числу определяется символ в кодовой таблице. Проще говоря, 1 6 битов для с h а r-значе­ ния - это код символа в кодовой таблице. Если символ нужно преобра­ зовать в целое число, то вместо символа используется его код из кодовой 76 Базовые типы и операто ры таблицы. У буквы ' А ' код 6 5 , а у буквы ' В ' код 6 6 , поэтому сумма сим­ волов ' А ' + ' в ' дает целочисленное значение 1 3 1 . Преобразование типа c h a r в тип i n t в случае необходимости выпол­ няется автоматически. Обратное преобразование (из целого числа в тип char) выполняется в явном виде. Например, результатом команды char s = ( ch a r ) 65 является объявление символьной переменной s со значе­ нием ' А ' . Принцип преобразования целого числа в символ такой: пре­ образуемое целочисленное значение определяет код символа в кодовой таблице. Так, поскольку у буквы ' А ' код 6 5 , то значением выражения ( cha r ) 6 5 будет символ ' А ' . G) Н А ЗАМ ЕТ КУ Уч итывая п равила автоматического п реобразования типов и спо­ соб реал изаци и числовых л итералов, можно дать следующую реко­ мендаци ю : есл и нет объекти вной необходимости поступать иначе, то для работы с целочисленными значен иями разумно испол ьзовать переменные типа int , а для работы с действител ьн ы м и значен иями имеет смысл испол ьзовать перемен ные ти па douЫ e . Об ъ я вление перемен н ых Огонь тоже считался божественным, пок а Прометей не выкрал его. Тенерь мы кипятим на нем воду. из к/ф �Формула лю бви» Мы уже знаем (в общих чертах), что такое переменная и как она определя­ ется. Здесь сделаем некоторое обобщение. И так, напомним, что переменная предназначена для хранения в памяти некоторого значения. Переменная предоставляет пользователю доступ к области памяти, где хранится зна­ чение. С помощью переменной это значение можно прочитать и, в случае необходимости, изменить. Значение переменной присваивается в формате переменная=значение, то есть используется оператор присваивания =, слева от которого указывается переменная, которой присваивается зна­ чение, а справа значение, присваиваемое переменной. - Перед использованием переменная должна быть объявлена. Сделать это можно практически в любом месте программного кода, но обязательно 77 Глава 2 до того, как переменная начинает использоваться. Объявляется пере­ менная просто: указывается ее тип и название. При этом можно: • Объявлять сразу несколько переменных, если они относятся к од­ ному типу. В таком случае после идентификатора типа названия переменных указываются через запятую. Например, инструкцией i n t A , В , С объявляются три переменные ( А, В и С), каждая типа i n t . • При объявлении переменной можно сразу присвоить значение (ини­ циализировать переменную). Так, командой s h o r t х= 1 2 объявля­ ется переменная х типа s h o r t , и этой переменной присваивается значение 1 2 . Командой i n t А= 5 , В , C= l O объявляются перемен­ ные А, В и С типа i n t , переменной А присваивается значение 5 , а пе­ ременной С присваивается значение 1 О . • Разрешается выполнять динамическую инициализацию переменной: это когда переменная инициализируется на основе выражения, в ко­ торое входят другие переменные. Главное условие для динамической инициализации состоит в том, что переменные, входящие в выра­ жение, на момент вычисления выражения должны иметь значения. Проще говоря, для динамической инициализации можно использо­ вать только те переменные, которым уже присвоены значения. На­ пример, сначала с помощью команды i n t x= l O , у=2 0 мы можем объявить переменные х и у со значениями 1 О и 2 О соответственно, а затем командой i n t z =x + y объявляем и динамически инициали­ зируем переменную z . Значение этой переменной будет равно 3 0 , поскольку на момент вычисления выражения х + у переменные х и у имеют значения 1 О и 2 О . Важный момент: между переменными z , с одной стороны, и х и у, с другой стороны, функциональная связь не устанавливается. Если после инициализации переменной z мы изменим значение переменной х или у, то значение переменной z не изменится. • При объявлении переменной вместо идентификатора типа мож­ но использовать ключевое слово v a r . С помощью одной vа r ­ инструкции может объявляться одна переменная (то есть нельзя использовать одну vаr-инструкцию для объявления сразу несколь­ ких переменных). Переменная, объявляемая в v а r - инструкции, должна сразу инициализироваться, и тип такой переменной опре­ деляется типом литерала (или типом значения выражения), ини­ циализирующим переменную. Например, командой v a r z = l O 78 Базовые типы и операто ры объявляется переменная z . Переменная z относится к типу i n t , поскольку значение 1 О , которое присваивается переменной, пред­ ставляет собой целочисленный литерал, а целочисленные литера­ лы реализуются как значения типа i n t . Но если мы воспользуемся командой v a r z = l O . О , то в таком случае переменная z будет типа douЬ l e , поскольку литерал 1 О . О реализуется как значение типа douЬ l e . Наконец, команда v a r z = l O F определяет переменную z типа f l o a t . Причина проста: литерал l O F (из-за суффикса F) ре­ ализуется как f l о а t -значение, и это определяет тип перемен­ ной z. Ситуация может быть и более сложной. Скажем, командой b y t e x= l , у=2 объявляется две Ь у t е -переменные (х и у со зна­ чениями 1 и 2 соответственно) . Если после этого мы объявим пере­ менную z командой v a r z =x + y, то значением переменной z будет число 3 , а сама переменная будет типа i n t . Почему? Потому что при вычислении выражения х + у, несмотря на то что оба операнда являются Ь у t е - переменными, по правилам автоматического пре­ образования типов их значения преобразуются в числа типа i n t , и результат (число 3 ) также представлен числом типа i n t . Поэтому тип переменной z - целочисленный тип i n t . • Наряду с переменными могут использоваться и константы. Прин­ ципиальное отличие константы от переменной состоит в том, что значение константы изменить нельзя. Как и переменная, констан­ та объявляется. Но кроме идентификатора типа и имени констан­ ты, указывается еще и ключевое слово c o n s t . При объявлении константа инициализируется: указанное при инициализации зна­ чение константы впоследствии изменить не получится. Например, командой c o n s t i n t num= 1 2 3 объявляется константа num типа i n t , и значение этой константы равно 1 2 3 . Командой c o n s t c h a r f i r s t = ' А ' , s e c ond= ' в ' объявляются две символьные константы f i r s t и s e c o n d со значениями ' А ' и ' В ' соответственно. Существует такое понятие, как областъ доступности (или область види­ мости) переменной. Это место в программном коде, где переменная мо­ жет использоваться. Правило очень простое: переменная доступна в том блоке, в котором она объявлена. Блок, в свою очередь, определяется па­ рой фигурных скобок. Поэтому если переменная объявлена в главном методе программы, то она доступна в этом методе (начиная с того места, где переменная объявлена). 79 Глава 2 � ПОДРОБН ОСТИ Далее мы узнаем , что главным методом область испол ьзован ия пе­ ремен н ых не ограничи вается . Есть и другие хорошие места в про­ грамме, где могут испол ьзоваться переменные . Более того , даже в главном методе могут быть внутренние блоки . Скажем , часть ко­ манд в главном методе мы можем закл юч ить в фигурные скобки . Други м и словам и , блоки кода могут содержать внутренние блоки кода , которые, в свою очередь, также могут содержать внутренние блоки , и так далее. Переменная , объя вленная в некотором блоке (например, в главном методе ) , доступна и во всех внутренних бло­ ках. Но не наоборот: есл и перемен ная объя влена во внутреннем блоке , то во внешнем она не доступ на. Допусти м , главн ый метод п рограм м ы и меет такую структуру: s t a t i c vo i d Ma i n ( ) { / / Переме н н а я о б ъ я в л е н а в г л а в н о м ме т оде : int А ; / / Команды { / / Начало в нутр е н н е г о блока / / Переменна я объявлена в о внутренне м блоке : int В ; / / Команды } / / З а в ершение в нутре н н е г о блока / / Команды Здесь мы имеем дело с перемен ной А, объя вленной в главном ме­ тоде , и переменной В , объя вленной во внутреннем блоке . П ричем внутрен н и й блок - это часть команд, заключенны х в фигурн ые скоб­ ки . В этом случае перемен ная А доступ на везде в главном методе , вкл ючая внутрен н и й блок. Но вот перемен ную В можно испол ьзовать тол ько во внутреннем блоке . Вне этого блока переменная «не суще­ ствует» . Название перемен ной, объя вленной во внутреннем блоке , не может совпадать с названием перемен ной, объя вленной во внешнем бло­ ке . Это означает, что есл и м ы объя вили в главном методе перемен­ ную А, то объя вить во внутреннем блоке главного метода перемен­ ную с таким же именем м ы не може м . 80 Базовые типы и операторы А рифметичес кие операторы - Что они кричат ? - Прод ают. - Ч то продают ? - Все продают. из к/ф -«Кин-дза-дза » Далее мы рассмотрим основные операторы, используемые в языке С#. Начнем с арифметических операторов, используемых при выполнении арифметических операций. Вообще же все операторы в языке С# обыч­ но разделяют на четыре группы: • арифметические операторы ; • логические операторы ; • операторы сравнения ; • побитовые операторы. Также в языке С# есть тернарный оператор. Да и оператор присваива­ ния не такой простой, как может показаться на первый взгляд. Все эти вопросы мы обязательно обсудим. Ну а пока - арифметические опера­ торы. � П ОД РО Б Н ОСТИ Оператор - это некий сим вол , обозначающий оп ределен ную опе­ раци ю . Оператор испол ьзуется с операндом ил и операндам и . Опе­ раци я , оп ределяемая оператором , выполняется с операндам и . Есл и оператор испол ьзуется с одн и м операндо м , то такой оператор на­ зы вается унарн ы м . Есл и оператор испол ьзуется с двумя операнда­ м и , то такой оператор назы вается бинарн ы м . В языке С# также есть один оператор, который испол ьзуется аж с тремя операндам и . Этот оператор назы вается тернарн ы м . Назначение большинства арифметических операторов интуитивно по­ нятно каждому, кто хотя бы в минимальном объеме сталкивался с ма­ тематикой. В табл. 2 . 2 представлен полный перечень арифметических операторов языка С#. Там же дано краткое их описание. 81 Глава 2 Табл . 2 . 2 . Арифметические операторы Оператор Описание + Оператор сложения. Значением выражения вида А+В является сумма зна­ чений переменных А и В Оператор вычитания. Значением выражения вида А- В является разность значений переменных А и В * Оператор умножения. Значением выражения вида А * В является произве­ дение значений переменных А и В / Оператор деления. Значением выражения вида А / В является частное значений переменных А и В. Если оба операнда А и В целочисленные, то деление выполняется нацело. Для выполнения обычного деления с це­ лочисленными операндами перед выражением указывают инструкцию - ( douЬ l e ) % Оператор вычисления остатка от деления. Значением выражения вида А% В является остаток от целочисленного деления переменных А и В ++ Оператор инкремента. При выполнении команды вида А + + или ++А значе­ ние переменной А увеличивается на 1 Оператор декремента. При выполнении команды вида А - - или - - А значе­ ние переменной А уменьшается на 1 Описание операторов дано в предположении, что операнд (или операн­ ды) является числовым. Вместе с тем это не всегда так. Так, в операции сложения с использованием оператора + один или оба операнда могут быть, например, текстовыми. В таком случае выполняется объединение текстовых строк. Скажем, если к тексту прибавить число, то числовой операнд автоматически преобразуется в текстовый формат и результа­ том выражения является текст, получающийся объединением �сумми­ руемых» текстовых фрагментов. Имеет свои особенности и оператор деления. Дело в том, что если де­ лить два целых числа, то выполняется целочисленное деление. Напри­ мер, результатом выражения 1 2 / 5 будет не 2 . 4 , как можно бьшо бы ожидать, а целое число 2 (целая часть от деления 1 2 на 5 ) . Но вот ре­ зультат выражения 1 2 . 0 / 5 или 1 2 / 5 . О это число 2 . 4 . Объяснение простое: в выражении 1 2 / 5 оба операнда целочисленные, а в выраже­ ниях 1 2 . О / 5 и 1 2 / 5 . О целочисленный только один операнд. Поэто­ му в выражении 1 2 / 5 речь идет о целочисленном делении, а значения выражений 1 2 . О / 5 и 1 2 / 5 . О вычисляются с использованием обычно­ го деления. Если а и Ь целочисленные переменные (например, типа i n t ) , то при вычислении выражения а /Ь используется целочисленное деление. Если нужно, чтобы деление было обычным, используют коман­ ду вида ( douЬl e ) а / Ь. - - 82 Базовые типы и операто ры Оператор % позволяет вычислить остаток от целочисленного деления. Причем операнды могут быть не только целыми, но и действительными числами. В таком случае результатом выражения вида А % В может быть не только целое, но и действительное число. � ПОДРОБН ОСТИ Есл и А и в некоторые числа (действител ьные, в том числе они могут быть и отрицательн ы м и ) , то остаток от деления А на в (обознач им эту операцию как А%В ) вычисляется следующим образом . Сначала нахо­ дится наибол ьшее по модул ю целое число n , такое, что 1 nв 1 :5: 1 А 1 при условии совпадения знаков А и nв , и резул ьтат операции А%В вы­ А - nв . Например, есл и А 13, 7 и в 3, 1, ч исля ется как А%В 4 ( nB 1 2 , 4 ) И А% В А - nB 13, 7 - 12 , 4 1, 3. то n -13 , 7 и в - 4 ( nB - 1 2 , 4 ) и А%В 3 , 1 , то n А Есл и А nв -13 , 7 + 12 , 4 - 1 , 3 , и так далее . Есл и А и В целочисленные перемен ные, то резул ьтат вы ражения А%В совпадает с разностью значен и й переменной А и вы ражения В * ( А / В ) (то есть значения вы ражений А%В и А-В * ( А / В ) в этом слу­ чае совпадают) . = = = = = = = = = = = = = = = - Если все предыдущие операторы ( +, - , * , / и % ) были бинарными, то опера­ торы инкремента + + и декремента - - являются унарными - они исполь­ зуются с одним, а не с двумя операндами. У каждого из этих операторов есть префиксная и постфиксная формы: в префиксной форме операто­ ры инкремента и декремента указываются перед оператором (например, + +А или - -А), а в постфиксной форме сначала указывается операнд, а за­ тем - оператор (например, А++ или А- - ). В плане влияния на значение операнда префиксная и постфиксная формы эквивалентны. При выпол­ нении команды вида ++А или А++ значение переменной А увеличивается на 1 . Команда вида - -А и А- - означает уменьшение значения перемен­ ной А на 1 . Но разница между префиксными и постфиксными формами все же есть. Дело в том, что выражения вида + +А, А++, - -А и А- - име­ ют значения. Проще говоря, инструкцию ++А или, скажем, А - - мы мо­ жем интерпретировать как некоторое число (если операнд А числовой). То есть, помимо прямых последствий для операнда, само выражение на основе оператора инкремента или декремента может использовать­ ся в более сложном выражении. Правило такое: значением выражения с оператором инкремента или декремента в префиксной форме является новое (измененное) значение операнда, а значением выражения с опера­ тором инкремента или декремента в постфиксной форме является старое 83 Глава 2 (до изменения) значение операнда. Например, командой i n t А= 1 О , В объявляются две переменные А и В, и переменная А получает значение 1 О . Если затем выполняется команда В=А+ +, то переменная В получит зна­ чение 1 0 , а значение переменной А станет равным 1 1 . Почему? Потому что при выполнении инструкции А++ значение переменной А увеличива­ ется на 1 и становится равным 1 1 . Но переменной В значением присваи­ вается не значение переменной А, а значение выражения А++. Значение выражения А++, поскольку использована постфиксная форма оператора инкремента, - это старое (исходное) значение переменной А (то есть зна­ чение 1 О ). А вот если бы мы вместо команды В=А+ + использовали коман­ ду В=+ +А, то и переменная А, и переменная В получили бы значение 1 1 . Причина в том, что значением выражения + +А с оператором инкремента в префиксной форме является новое значение переменной А (число 1 1 ). G) Н А ЗАМ ЕТ КУ После выполнения команд i n t A= l O , В и В=А- - у перемен ной А будет значение 9 , а у перемен ной В будет значение 1 0 . Есл и коман­ ду В=А - - замен ить на В=--А, то обе перемен ные А и В будут иметь значение 9 . При выполнении арифметических операций, помимо числовых опе­ рандов, могут использоваться и символьные значения (значения типа c h a r ). В таком случае выполняется автоматическое преобразование значения типа c h a r в числовое значение (используется код соответ­ ствующего символа из кодовой таблицы). В этом смысле мы вполне мо­ жем вычислить, например, разность ' D ' - ' А ' (результат равен 3 - раз­ ность кодов символов ' D ' и ' А ' ). � ПОДРОБН ОСТИ Операторы ин кремента и декремента также могут испол ьзоваться с символ ьным операндом . Но здесь есть одна особен ность. Допу­ сти м , командой char s ymb= ' А ' мы объя вили сим вол ьную пере­ мен ную s ymb со значением ' А ' . Есл и м ы испол ьзуем вы ражение s yrnЬ + 1 , то его значением я вляется целое ч исло 66 ( к коду 65 симво­ ла ' А ' прибавляется 1 ) . Поэтому, чтобы с помощью такого вы раже­ ния в переменную s yrnЬ зап исать следующи й символ после сим вола ' А ' , нужно испол ьзовать команду s ymb= ( char ) ( s yrnЬ + l ) . Буквы ал ­ фавита в кодовой табл и це идут одна за друго й , поэтому следующи м после сим вола ' А ' будет сим вол ' В ' , затем сим вол ' С ' и так далее. 84 Базовые типы и операто ры Значен ие выражения s yrnЬ + l - ч исло 6 6 , которое является кодом сим вола ' В ' в кодовой табл и це . Резул ьтат вы ражения s ymb+ 1 явно при водится к сим вол ьному виду, и полученное ч исло и нтерп рети­ руется как код сим вола. В результате значением переменной s ymb будет сим вол ' В ' . А вот есл и мы воспол ьзуемся выражением s yrnЬ + + ( ил и + + s yrnЬ ) , то значение переменной s yrnЬ станет равным ' В ' . Дело в том , что в плане последствий для операнда А выражение А++ (или + +А) экви­ валентно команде вида А= ( тип А) ( А+ 1 ) . То есть к исходному значе­ нию операнда А прибавляется еди н и ца , полученный резул ьтат при­ водится к типу операнда А , и это значение п рисваи вается операнду А. Есл и операнд А - ч ислово й , то факт при ведения типа не стол ь ва­ жен . Но вот есл и операнд символ ьн ы й - то момент с при ведением типа важен . Это же замечан ие относится и к оператору декремента . G) Н А ЗАМ ЕТ КУ Помимо бинарных операторов сложения + и выч итания - есть еще унарные операторы « ПЛ ЮС» + и « м и нус» - . Унарн ый « м и нус» пишется перед отри цател ьны м и числами (то есть испол ьзуется для обозна­ чения отри цател ьных ч исел ) . Унарн ы й « ПЛЮС» обычно не испол ьзу­ ется , поскол ьку числа без знака по умолчанию интерп рети руются как положител ьн ые, но в п ринципе его можно писать. Операторы сравнен ия Когда у общества нет цветовой дифференци­ ации штанов , то нет цели ! А когда нет цели нет будущего ! из к/ф -«Кин-дза-дза» Операторы сравнения все бинарные и используются для сравнения зна­ чений переменных или литералов. Результатом выражения на осно­ ве оператора сравнения является логическое значение (значение типа b o o l ). Если соотношение истинно, то результатом является значение t ru e , а если соотношение ложно, то результатом является значение fa l s e . В табл. 2.3 перечислены и кратко описаны операторы сравнения, используемые в языке С#. 85 Глава 2 Табл . 2 . 3 . Операторы сравнения Оператор Описание < Оператор «меньше». Значение выражения вида А<В равно t ru e , если значение операнда А меньше значения операнда В. В противном случае значение выражения А<В равно f a l s e <= Оператор «меньше или равно ». Значение выражения вида А<=В равно t ru e , если значение операнда А меньше или равно значению операнда В. В противном случае значение выражения А<=В равно f a l s e > Оператор «больше». Значение выражения вида А > В равно t ru e , если значение операнда А больше значения операнда В. В противном случае значение выражения А>В равно f a l s e >= Оператор «больше ил и равно». Значение выражения вида А>= В равно t ru e , если значение операнда А больше или равно значению операнда В. В противном случае значение выражения А>=В равно f a l s e Оператор «равно ». Значение выражения вида А==В равно t ru e , если значение операнда А равно значению операнда В. В противном случае значение выражения А==В равно f a l s e != Оператор «Не равно ». Значение выражения вида А ! =В равно t ru e , если значение операнда А не равно значению операнда В. В противном случае значение выражения А ! =В равно f а 1 s е Обычно выражения на основе операторов сравнения используются в управляющих инструкциях или в качестве операндов в выражениях на основе логических операторов (если нужно записать сложное условие). G) Н А ЗАМ ЕТ КУ Есл и в вы ражении на основе оператора сравнения испол ьзован символьный операнд, то он п реобразуется к числ овому виду. Осо­ бенности испол ьзования операторов сравнения с текстовыми опе­ рандами обсуждаются в главе , посвя щен ной работе с текстом . Л огич еские операторы - Встань-ка вон там , у той липы. - В аше сиятельство, это не липа, это дуб. - Да ? А выглядит как липа . . . из к/ф « 0 бедном гусаре замолвите слово» Операндами в выражениях на основе логических операторов являют­ ся значения логического типа ( t r u e или f a l s e ) , и результатом таких 86 Базовые типы и операто ры выражений также являются значения логического типа. Логические операторы полезны для того, чтобы на основе некоторых простых ус­ ловий (логических значений) получить новое более сложное условие (логическое). Логические операторы перечислены и кратко описаны в табл. 2.4. Табл . 2 . 4 . Логические операторы Оператор Описапие & Оператор �логическое и». Результатом выражения А & В является зна­ чение t ru e , если оба операнда А и В равны t ru e . Если хотя бы один из операндов равен f a l s e , то результатом выражения А & В является значение f a l s e && Оператор �логическое и» (упрощенная форма). Значение выражения вида А& &В равно t ru e , если оба операнда А и В равны t ru e . В против­ ном случае результат выражения равен f a l s e . Если при вычислении первого операнда А оказалось, что он равен f a l s e , то значение второго операнда В не вычисляется Оператор �логическое или». Результатом выражения А 1 в является зна­ чение t ru e , если хотя бы один из операндов А или В равен t ru e . Если оба операнда равны f a l s e , то результатом выражения А 1 В является значение f a l s e 1 1 Оператор �логическое или» (упрощенная форма). Значение выраже­ ния вида А 1 1 В равно t ru e , если хотя бы один из операндов А или В равен t ru e . В противном случае результат выражения равен f a l s e . Если при вычислении первого операнда А оказалось, что о н равен t ru e , то значение второго операнда В не вычисляется Оператор �логическое исключающее или». Результатом выражения А л в является значение t ru e , если один и з операндов А или В равен t ru e , а другой равен f a l s e . Если оба операнда равны f a l s e или оба опе­ ранда равны t ru e , то результатом выражения д л в является значение fal se Оператор �логическое отрицание». Значение выражения ! А равно t ru e , если значение операнда А равно f a l s e . Если значение операнда А равно t ru e , то значение выражения ! А равно f a l s e Логические операторы позволяют выполнять четыре операции: «логи­ ческое и», «логическое или», «логические исключающее или» и «логическое отрицание». Более сложные логические операции выполняются путем комбинированного использования логических операторов. Для выпол­ нения операций «логическое и» и «логическое или» можно использовать две формы операторов (основную и упрощенную). Правила вычисления результата для операции «логическое и» проиллю­ стрированы в табл. 2.5. 87 Глава 2 Табл . 2 . 5. Оп ерация «логическое И» ( А & В или А & & В ) � t ru e fal se t ru e t ru e fal s e false false fa l s e Если операнды А и В отождествлять с некоторыми условиями, то «слож­ ное� условие А & В (или А & & В ) состоит в том, что истинно и условие А, и условие В. Другими словами, значение выражения А & В (или А & & В) бу­ дет равно t ru e , только если равно t ru e значение операнда А и одновре­ менно равно t ru е значение операнда В. Очевидно, что если при проверке значения операнда А окажется, что его значение равно f а 1 s е, то резуль­ татом логической операции будет f а 1 se вне зависимости от того, како­ во значение операнда В. Этот факт принимается во внимание в работе упрощенной формы оператора & & : если при вычислении значения вы­ ражения вида А & & В оказалось, что значение операнда А равно f a l s e , то значение операнда В вообще не будет вычисляться. При вычислении значения выражения вида А & В значение операнда В вычисляется вне за­ висимости от того, какое значение операнда А. (D Н А ЗАМ ЕТ КУ На первый взгляд может показаться , что разл ичие между операто­ рами & и & & «косметическое» . Но это не так. П роилл юстрируем это на небол ьшом примере. Допусти м , имеются две целочисленные пе­ ременные х и у. Рассмотрим выражение (х ! = 0 ) & ( y/ x > l ) . П роанал и­ зируем, как будет вычисляться значение такого вы ражения . Сначала вычисляется значение вы ражения х ! = О . Оно равно t rue , если зна­ чение переменной х отлично от нул я . Затем вычисляется значение выражения y / x> l . Оно равно t rue , есл и частное от деления у на х больше 1 . П роблема в том , что есл и значение переменной х равно О , то при выч ислении выражения у / х возникнет ошибка (деление на нол ь ) . А вот есл и вместо вы ражения (х ! = 0 ) & ( y/ x > l ) взять выра­ жение (х ! = 0 ) & & ( y/ x > l ) , то п роблема с делением на нол ь сни мает­ ся автоматически : есл и значение переменной х равно О , то значение выражения х ! = О равно f a l s e и условие y / x > l вообще вычисляться не будет. 88 Базовые типы и операто ры Как вычисляется результат операции «логические или», демонстрирует табл. 2.6. Табл . 2 . 6 . Операция «логическое или» (А 1 в ил и А 1 1 в ) � true false t ru e t ru e t ru e false true fal se « Сложное» условие А 1 В (или А 1 1 В ) состоит в том, что истинно хотя бы одно условие, А или В. Значением выражений А 1 В или А 1 1 В является fa l s e только в том случае, если оба операнда А и В равны f a l s e. Поэ­ тому если при вычислении значения операнда А окажется, что его зна­ чение равно t ru e , то в принципе значение второго операнда В можно не вычислять - результат от этого не зависит. Именно по такой схеме «работает» упрощенная форма оператора 1 1 . При использовании опера­ тора 1 значение второго операнда вычисляется вне зависимости от зна­ чения первого операнда. Для операции «логическое исключающее или» можно дать такую интер­ претацию: результат является истинным, если у операндов разные зна­ чения. Более детально это показано в табл. 2.7. Табл . 2 . 7 . Оп ерация «логическое исключающее или» ( А л в ) � true false true false t ru e false true false При одинаковых значениях операндов А и В значением выражения А л в является f а 1 s е , а если значения операндов А и В разные, то результатом выражения является значение t ru e . 89 Глава 2 G) Н А ЗАМ ЕТ КУ Операци ю «логическое исключающее или " можно выпол н ить с по­ мощью операторов & , 1 и ! . Желающие могут п роверить, что для логи­ ческих значений А и В резул ьтаты выражений д л в и ( А 1 В ) & ( ! ( А & В ) ) совпадают. Оператор ! для выполнения операции <tлогическое отрицание» - един­ ственный логический унарный оператор. Правила выполнения этой операции иллюстрируются в табл. 2.8. Табл . 2 . 8 . Операция «логическое отрицание» ( 1 А) fa l s e t ru e Здесь все просто. Если операнд А истинный (значение t rue ), т о результат выражения ! А ложный (значение fal s e ) . Если операнд А ложный (зна­ чение fa l s e), то результат выражения ! А истинный (значение t ru e ) . И в том и в другом случае значение операнда А остается неизменным то есть выполнение команды вида ! А не меняет значение операнда А. G) Н А ЗАМ ЕТ КУ М ы еще будем обсуждать логические операторы в главе, посвящен­ ной перегрузке операторов. П обитовые операторы и двоич ные коды Варварская игра, дикая местность - меня тя­ нет на родину. из к/ф <tФормула л юбви» Побитовые операторы предназначены для выполнения операций с це­ лочисленными значениями на уровне их побитового (двоичного) пред­ ставления. Перед тем как приступить к обсуждению собственно по­ битовых операторов, кратко рассмотрим способы кодирования чисел с помощью бинарных представлений. 90 Базовые типы и операто ры Итак, с некоторой натяжкой можно полагать, что в памяти компьютера числа представлены в виде двоичного кода, в котором есть нули и едини­ цы. Идея записи числа с помощью нулей и единиц принципиально мало чем отличается от способа записи числа посредством цифр от О до 9. До­ пустим, параметры ао , а1" . an - это некоторый набор цифр (то есть каж­ дый такой параметр - это какая-то из цифр О, 1 и так далее до 9). Мы можем записать с их помощью число anan_1 ". а2 а1а0• Данная запись озна­ 1 2 чает буквально следующее: anan_ 1 ". а2 а1а0 = а0 х 1 0 ° + а 1 х 1 0 + а2 х 1 0 ". + n l n 1 2 an_1 х 1 0 - + an х 1 0 . Например, число 1 2 3 = 3 х 1 0 ° + 2 х 1 0 + 1 х 1 0 . А теперь воспользуемся теми же принципами, но вместо десяти цифр (от О до 9) будем использовать всего две: О и 1. То есть теперь параметры а0, а1 ". an могут принимать одно из двух значений (О или 1 ) . Если число записано в виде последовательности anan_1 ". а2 а1а0 , то это будет означать n n 1 0 1 2 anan- 1 ". а2 а1ао = ао х 2 + а 1 х 2 + а2 х 2 + ". + an- 1 х 2 - + an х 2 . Н апример, если имеется двоичный код 1 0 1 1 0 1 1 , то он соответствует десятичному 1 6 числу 1 0 1 1 0 1 1 = 1 х 2 ° + 1 х 2 + о х i + 1 х 2 3 + 1 х 2 4 + о х 2 5 + 1 х 2 = 1 + 2 + 8 + 1 6 + 64 = 9 1 . Чтобы выполнить обратное преобразование (де­ сятичное число записать двоичным кодом), достаточно число записать в виде суммы слагаемых, являющихся степенями двойки. Например, определим двоичный код для числа 29. Имеем следующее: 29 = 16 + 8 + 2 4 3 4 + 1 = 24 + 23 + 2 + 2 ° = 1 х 2° + 1 х i + 1 х 2 + 1 х 2 = 1 1 1 о 1 . G) Н А ЗАМ ЕТ КУ В при веденных формулах код из нулей и еди н и ц - это двоичное представление ч исла . Все прочие числа зап исаны в десятичной си­ стеме. Операции, которые выполняются с десятичными числами, можно вы­ полнять и в двоичном представлении. Например, можно складывать числа в столбик. Для этого достаточно учесть простые правила сложе­ ния двоичных чисел: О + О = О, 1 + О = 1 , О + 1 = 1 , 1 + 1 = 1 0 . Например, десятичное число 13 имеет двоичный код 1 1 0 1 , а число 9 имеет двоич­ ный код 1 00 1 . Складываем эти двоичные коды: + ���� 101 10 Двоичный код 1 О 1 1 О соответствует десятичному числу 2 2 . Так и должно быть, поскольку 13 + 9 = 22. 91 Глава 2 Все описанное выше касалось положительных чисел. Есть еще один важ­ ный вопрос. Связан он с тем, как в памяти компьютера кодируются от­ рицательные числа. Проблема в том, что для записи отрицательного чис­ ла на бумаге достаточно перед таким числом поставить знак �минус». Но компьютер не умеет �писать минус» . Он понимает только двоичные коды. Чтобы понять принципы кодирования отрицательных чисел, про­ ведем небольшой мысленный эксперимент. Допустим, имеется некоторое положительное число А, для которого из­ вестен бинарный код. Попробуем выяснить, какой бинарный код дол­ жен быть у противоположного по знаку числа -А. Мы будем исходить из того, что код числа -А должен быть таким, чтобы выполнялось усло­ вие А + (-А) О. = Выполним побитовую инверсию: в бинарном представлении числа А все нули заменим на единицы, а единицы заменим на нули. То, что получи­ лось, обозначим как -А. Сложим значения А и -А. Операцию будем вы­ полнять на уровне бинарного кода. Учтем также, что в компьютере для записи числа выделяется определенное количество битов. Обозначим это количество битов как п. Несложно догадаться, что при вычислении суммы А + (-А) мы будем складывать два битовых кода, каждый длиной в п битов. И при складывании битов, находящихся на одинаковых пози­ циях, один бит будет единичный, а другой нулевой. Поэтому в результа­ те вычисления суммы А + (-А) получим бинарный код, который состоит из п битов, и каждый бит равен единице. К тому, что получилось, приба­ вим единицу - то есть вычислим сумму А + (-А ) + 1 . Но если к коду из п единиц добавить единицу, то получим бинарный код, в котором самый левый бит единичный, а после него следует п нулей. И вот здесь нуж­ но вспомнить, что речь идет о компьютере, который запоминает только п битов. Поэтому старший единичный бит теряется (для него в памяти просто нет места), и остается код из п нулей. А это код числа О. Таким образом, компьютер вычисляет указанную сумму как А + (-А) + 1 О. Следовательно, код отрицательного числа -А должен совпадать с кодом выражения -А + 1 . Отсюда мы получаем правило записи двоичного кода отрицательного числа. = • • 92 Берем код соответствующего положительного числа и выполняем побитовую инверсию: меняем единицы на нули и нули на единицы. К полученному бинарному коду прибавляем единицу. Это и есть код отрицательного числа. Базовые типы и операто ры Аналогичным образом выполняется и обратное преобразование, когда по коду отрицательного числа необходимо определить это число в де­ сятичной системе счисления. • В бинарном коде отрицательного числа выполняем побитовую инверсию. • К полученному коду прибавляем единицу. • Переводим полученный код в десятичную систему. • Дописываем перед полученным числом знак �минус� . Это и есть ис­ комое число. Все эти операции для нас имеют скорее теоретический интерес. Но есть важное практическое последствие. Если взять код положительного чис­ ла, то старший бит в таком числе будет нулевым. А вот в коде отрица­ тельного числа старший бит единичный. Таким образом, самый старший бит в бинарном коде числа определяет, положительное это число или отрицательное: у положительных чисел старший бит нулевой, у отри­ цательных чисел - единичный. Поэтому старший бит обычно называют знаковым битом (он определяет знак числа). G) Н А ЗАМ ЕТ КУ В прикладном плане, кроме двоичной , достаточно популярными я вля­ ются восьмеричная и шестнадцатеричная системы счисления . В вось­ меричной системе число записывается с помощью цифр от О до 7 включительно. Если в восьмеричной системе число записано в виде последовательности апап_1 " . а2а1а0 (параметры а0, а1 " . ап ЭТО цифры от О до 7), то в десятичной системе счисления это число определяется 1 1 2 так: апап_1 " . а2а1 а0 = а0 х 8° + а1 х 8 + а2 х 8 + " . + ап_1 х 8"- + ап х 8" . В шестнадцатеричной системе счисления ч исло коди руется с помо­ щью шестнадцати сим волов : это цифры от О до 9 и буквы от А до F, которые обозначают десятичные ч исла от 1 О до 1 5 . Для шестнадца­ теричной системы справедл и ва такая формула перевода в десятич ­ 1 2 ную систему: апап_ 1 " . а2 а1а0 = а0 х 1 6 ° + а1 х 1 6 + а2 х 1 6 + " . + ап_1 х n n- 1 1 6 + ап х 1 6 . В данном случае параметры а0 , а1 ап ЭТО цифры от О до 9 и буквы от А до F ( в место букв при вычислении сум м ы под­ ставля ются числа от 1 О до 1 5 ) . - • • • - После того как м ы кратко познакомились с о способами бинарного ко­ дирования чисел, можем приступить к обсуждению побитовых операто­ ров. Они перечислены в табл. 2.9. 93 Глава 2 Табл . 2 . 9 . Побитовые операторы Оператор Описапие & Оператор <1'nобитовое И». Результатом выражения А & В является чис­ ло, которое получается сравнением соответствующих битов в пред­ ставлении чисел А и в. На выходе получается единичный бит, если оба сравниваемых бита единичные. Если хотя бы один из сравнивае­ мых битов нулевой, в результате получаем нулевой бит Оператор <1'nобитовое или». Значение выражения вида А 1 В есть чис­ ло, получаемое сравнением соответствующих битов в представлении чисел А и в. На выходе получается единичный бит, если хотя бы один из сравниваемых битов единичный. Если оба сравниваемых бита ну­ левые, в результате получаем нулевой бит Оператор <1'nобитовое исключающее или». Значением выражения д л в является число - результат сравнения соответствующих битов в представлении чисел А и в. На выходе получается единичный бит, если сравниваемые биты различны (один единичный, а другой ну­ левой). Если биты одинаковые (оба единичные или оба нулевые), то в результате получаем нулевой бит Оператор <1'nобитовая инверсия». Значение выражения вида -А есть число, битовое представление которого получается заменой в бито­ вом представлении числа А нулей на единицы и единиц на нули. Зна­ чение операнда А при этом не меняется << Оператор "сдвиг влево ». Результатом выражения A<<n является чис­ ло, которое получается путем сдвига бинарного представления чис­ ла А на n позиций влево. Младшие биты при этом заполняются нуля­ ми, а старшие теряются. Значение операнда А остается неизменным >> Оператор "сдвиг вправо ». Значение выражения A>>n равно числу, ко­ торое получается путем сдвига бинарного представления числа А на n позиций вправо. Младшие биты при этом теряются, а старшие запол­ няются значением старшего (знакового) бита. Значение операнда А остается неизменным Вообще побитовые операторы напоминают логические, с поправкой на то, что операции выполняются с битами, истинному значению соот­ ветствует 1, а ложному значению соответствует О. Например, при вычис­ лении выражения А & В на основе оператора <<побитового И» в результате получается целое число. Его бинарный код вычисляется так: сравнива­ ются биты, находящиеся на одинаковых позициях в представлении чи­ сел А и В. Правила сравнения даются в табл. 2. 10. Табл . 2 . 1 0 . Операция «побитовое и" ( а & Ь ) для б итов а и Ь � 1 о 1 1 о о о о 94 Базовые типы и операто ры Если оба бита единичные, то в соответствующем месте в коде для чис­ ла-результата записывается единица. Если хотя бы один из двух срав­ ниваемых битов нулевой, то в число-результат записывается нуле­ вой бит. G) Н А ЗАМ ЕТ КУ Оператор «логические И» и «побитовое И» записываются одн и м и тем ж е символом & . О каком операторе идет реч ь, можно понять по операндам . Есл и операнды ч исловые, то это побитовый опера­ тор . Есл и операнды логические, то это логически й операто р . Это же замечан ие относится и к рассматриваемым далее операторам 1 и л . Как пример простой операции с использованием оператора «побитового и» & рассмотрим следующий блок программного кода: byte А=l З , В=2 5 , С ; С = (byte ) (А&В ) ; В данном случае имеем дело с тремя переменными А, В и С типа byt e . Это значит, что значение каждой из переменных запоминается с помо­ щью 8 битов. G) Н А ЗАМ ЕТ КУ Хотя переменные А и В объя влены как относя щиеся к типу byte , при выполнении операции А & В из-за автоматического при ведения типа результат выч исляется как i n t -значение. Это значение м ы п ытаем­ ся зап исать в перемен ную С типа byte . Поэтому здесь необходи ма инструкция ( byte ) явного при ведения типа. Это замечание отно­ сится и к нескол ьким следующим м и н и - п ри мерам . Десятичное число 1 3 в двоичном 8-битовом коде записывается как 0 0 0 0 1 1 0 1 , а числу 2 5 соответствует бинарный код 0 0 0 1 1 0 0 1 . � ПОДРОБН ОСТИ Оп редел ить бинарные коды для чисел 1 3 и 25 можно , есл и учесть 2 что 1 3 = 8 + 4 + 1 = 2 3 + 2 + 2 ° = о х 27 + о х 2 6 + о х 2 5 + о х 24 + 1 х 23 + 1 х 2 4 2 + О х i + 1 х 2 ° , а также 25 = 1 6 + 8 + 1 = 2 + 2 3 + 2 ° = О х 27 + О х 1 2 4 3 6 5 2 + о х 2 + 1 х 2 + 1 х 2 + о х 2 + о х 2 + 1 х 2° . 95 Глава 2 Операция «:побитовое И» выполняется так: & 0000 1 1 0 1 000 1 1001 0000 1001 Следовательно, в результате получаем бинарный код 0 0 0 0 1 0 0 1 . Не­ сложно проверить, что это код числа 2 3 + 2 ° 9. Таким образом, при вы­ полнении указанного выше программного кода значения переменных будут следующими: значение переменной А равно 1 3 , значение перемен­ ной В равно 2 5 , а значение переменной С равно 9 . = Если используется оператор «побитовое или» 1 , то при сравнении двух битов в результате получаем единичный бит, если хотя бы один из срав­ ниваемых битов единичный. Правила выполнения этой операции про­ иллюстрированы в табл. 2. 1 1 . Табл . 2 . 1 1 . Операция «побитовое или» ( а 1 Ь) для б итов � 1 о 1 1 1 о 1 о а иЬ Рассмотрим, как операция «побитовое или» выполняется с уже знако­ мыми для нас переменными: byte А=l З , В=2 5 , С ; C= (byte ) (А [ В ) ; В соответствии с правилами выполнения операции получаем следую­ щий результат: 0000 1 1 0 1 000 1 1001 000 1 1 1 0 1 Бинарный код О О О 1 1 1 О 1 соответствует десятичному числу 2 9 , посколь­ ку имеет место соотношение 2 4 + 2 3 + i + 2 ° 29. Таким образом, пере­ менная С в данном случае будет иметь значение 2 9 . = Если выполняется операция «побитовое исключающее или», т о двоичные коды для двух исходных чисел сравниваются с использованием такой 96 Базовые типы и операто ры схемы: если на одинаковых позициях в битовых представлениях чисел зна­ чения разные (то есть один бит единичный, а другой нулевой), то на «Выхо­ де� получаем единицу. Если у сравниваемых чисел на одинаковых позици­ ях находятся одинаковые биты (то есть биты с одинаковыми значениями), в числе-результате на соответствующем месте будет нулевой бит (бит с ну­ левым значением). Правила выполнения операции «:побитовое исключаю­ щее или» проиллюстрированы в табл. 2 . 1 2 для двух отдельных битов. Табл . 2. 1 2 . Операция «побитовое исключающее или» ( а л Ь) для б итов а и Ь � 1 о 1 о 1 о 1 о Резу льтат применения операции «побитовое исключающее или» к число­ вым значениям 1 3 и 2 5 приводит к следующему результату: л 0000 1 1 0 1 000 1 1001 000 1 0 1 00 Мы получаем бинарный код О О О 1 О 1 О О , который соответствует десятич­ ному числу 2 О (поскольку 2 4 + i 20). Допустим, мы имеем дело, на­ пример, с таким блоком программного кода: = byte А=l З , В=2 5 , С ; С = (byte ) (А'В ) ; В результате его выполнения переменная С получит значение 2 0 . Оператор «побитовая инверсия» является унарным. Побитовая инвер­ сия выполняется просто: в битовом представлении числа нули меняют­ ся на единицы, а единицы меняются на нули. Это правило, по которому вычисляется результат. Операнд при этом не меняется. В табл. 2 . 1 3 по­ казано, как операция побитовой инверсии выполняется для одного от­ дельного бита. � Табл . 2 . 1 3 . Операция «побитовая инверсия» ( - а ) дл я б ита а 1 :а 1 � 1 � 1 97 Глава 2 Например, имеется такой блок программного кода: byte А, В ; А= l З ; B= (byte ) �A ; Вопрос состоит в том, какие значения будут у целочисленных пере­ менных А и В после выполнения указанных команд? Ответ, в принци­ пе, простой: значение переменной А равно 1 3 , а значение переменной В равно 2 4 2 . Самая простая ситуация с переменной А. Ей присваивает­ ся значение 1 3 . Это число в десятичной системе. В двоичной системе код значения переменной А имеет вид 0 0 0 0 1 1 0 1 . Но в данном случае это не столь важно, поскольку при выполнении команды В= ( byt e ) -А значение переменной А не меняется: побитовая инверсия применяется для вычисления результата выражения -А, но сам операнд А не меняет­ ся. Поэтому переменная А остается со своим, ранее присвоенным значе­ нием (число 1 3 ). � ПОДРОБН ОСТИ Напом н и м , что значение типа byte записы вается с испол ьзовани­ ем 8 битов и записанное таким образом ч исло и нтерп рети руется как неотри цател ьное ч исло . Поэтому старший бит в п редставлении Ьуtе-числа знаковым не я вляется . Если применить операцию побитового инвертирования к коду О О О О 1 1 О 1 числа 1 3 , то в бинарном 8-битовом представлении полу1 6 5 1 4 чим код 1 1 1 1 0 0 1 0 . Этот код соответствует числу 2 + 2 + 2 + 2 + 2 1 2 8 + 64 + 32 + 1 6 + 2 242. Таким образом, переменная В получает зна­ чение 2 4 2 . Но еще раз подчеркнем, что в данном случае важно, что тип b y t e предназначен для реализации неотрицательных чисел. Если мы вместо типа byte воспользуемся типом sbyt e , результат побитовой ин­ версии будет иным. Точнее, он будет по-другому интерпретироваться. Допустим, имеется такой код: = = sbyte А, В ; А= l З ; B= ( sbyte ) �A ; Вопрос состоит в том, каково значение переменной В? Строго говоря, как и в предыдущем случае, значение переменной В будет представлено 98 Базовые типы и операто ры кодом 1 1 1 1 0 0 1 0 . Но теперь, поскольку это s Ь у t е -значение, код ин­ терпретируется как число со знаком. Так как старший бит единичный, то число отрицательное. Чтобы понять, что это за число, мы должны: • выполнить побитовую инверсию ; • прибавить к результату единицу ; • перевести код в десятичное число ; • дописать знак «минус» перед числом. Поскольку нам предстоит инвертировать то, что было получено инвер­ тированием, мы сначала получим бинарный код для числа 1 3 , затем прибавим к этому числу 1 (получим 1 4 ) и, дописав знак «минус», полу­ чаем значение - 1 4 для переменной В. Еще две побитовые операции, которые нам осталось обсудить - сдвиг влево и сдвиг вправо. Например, рассмотрим следующий программный код: byte А=l З , В , С ; В = (byte ) (А« 2 ) С = (byte ) (А»2 ) ; Чтобы получить значение переменной В, мы должны взять код О О О О 1 1 О 1 для числа 1 3 (значение переменной А) и выполнить сдвиг этого кода влево на две позиции. В результате получаем код 0 0 1 1 0 1 0 0 . Это код числа 5 2 - значение переменной В. Значение переменной А остается не­ изменным. G) Н А ЗАМ ЕТ КУ Сдви г влево на одну позицию бинарного кода ч исла экви валентен ум ножению ч исла на 2 . Сдви г бинарного кода положител ьного ч ис­ ла на одну позицию вправо экви валентен целочислен ному делению этого ч исла на 2 . Для вычисления значения переменной С бинарный код значения пе­ ременной А сдвигается вправо на две позиции. В результате из кода 0 0 0 0 1 1 0 1 получаем код 0 0 0 0 0 0 1 1 , что соответствует числу 3 . Это и есть значение переменной С. 99 Глава 2 Оператор п р исва и ва н ия - Нет ! На это я пойтить не могу ! - Товарищ старший лейтенант . . . - Нет ! М н е надо посоветоваться с шефом ! С начальством ! из к/ф «Бриллиантовая рука» С оператором присваивания = мы уже знакомы: он используется для присваивания значений переменным. Обычно используются команды вида переменная=значение. Напомним, что в результате выполнения такой команды переме н н а я , указанная слева от оператора присваива­ ния =, получает значение, указанное справа от оператора присваива­ ния. Но особенность оператора присваивания в том, что он возвращает значение. Поэтому у выражения вида переменная=з начение есть зна­ чение, и такое выражение можно использовать в более сложном выра­ жении. Проще говоря, в одной команде может быть больше одного опе­ ратора присваивания. Самая простая ситуация - когда сразу нескольким переменным присва­ ивается значение. Скажем, следующий блок программного кода имеет смысл и является корректным: int А, В, С ; А=В=С= 1 2 3 ; Здесь три целочисленные переменные А, В и С значением получают чис­ ло 1 2 3 . Объяснение простое. Команда А=В=С= 1 2 3 представляет собой инструкцию, которой переменной А значением присваивается выраже­ ние В=С= 1 2 3 . Выражение В=С= 1 2 3 - это команда, которой перемен­ ной В присваивается значением выражение С= 1 2 3 . Командой С= 1 2 3 пе­ ременной С присваивается значение 1 2 3 . И само это выражение С = 1 2 3 имеет значение - это значение, присваиваемое переменной С (то есть 1 2 3 ). Следовательно, переменная В получает значение 1 2 3, и значением выражения В=С= 1 2 3 также является число 1 2 3 . Это значение присва­ ивается переменной А. В итоге все три переменные (А, В и с) получают значение 1 2 3 . Команда с несколькими операторами присваивания может быть более замысловатой. Как пример рассмотрим следующий блок программного кода: 100 Базовые типы и операто ры int А, В , С ; А= (А= (А=3 ) + ( В=5 ) - ( С=7 - В ) ) + (А=А+ ( В=В+2 ) * ( С=С - 3 ) ) ; После выполнения этого кода у переменной А будет значение 5 , у пе­ ременной В будет значение 7 , а переменная С получит значение - 1 . Чтобы понять, почему так происходит, проанализируем коман­ ду А= ( А= ( А= 3 ) + ( В = 5 ) - ( С = 7 - В ) ) + ( А=А+ ( В = В + 2 ) * ( С = С - 3 ) ) . В этой команде значением переменной А присваивается выражение ( А= ( А= 3 ) + ( В= 5 ) - ( С= 7 - В ) ) + ( А=А+ ( В=В+ 2 ) * ( С= С - 3 ) ) . Данное вы­ ражение представляет собой сумму двух операндов: ( А= ( А= 3 ) + ( В = 5 ) ( С= 7 - В ) ) и ( А=А+ ( В=В+ 2 ) * ( С= С - 3 ) ) . Операнды вычисляются слева направо. Первым вычисляется операнд ( А= ( А= 3 ) + ( В= 5 ) - ( С= 7 - В ) ) . После него вычисляется операнд ( А=А+ ( В=В+ 2 ) * ( С= С - 3 ) ) . Операнд ( А= ( А= 3 ) + ( В= 5 ) - ( С = 7 - В ) ) представляет собой выраже­ ние, в котором переменной А значением присваивается выражение ( А= 3 ) + ( В= 5 ) - ( С= 7 - В ) . В нем три операнда: А= 3 , В=5 и С= 7 - В. Они вычисляются последовательно слева направо. В результате сначала пе­ ременная А получает значение 3 (и это же значение выражения А= 3 ), переменная В получает значение 5 (значение выражения В=5 ), а пере­ менная С получает значение 2 (оно же - значение выражения С = 7 - В ). Тогда результат выражения ( А= 3 ) + ( В= 5 ) - ( С= 7 - В ) равен 6 ( 3 плюс 5 минус 2 ). А поскольку это выражение присваивается значением пере­ менной А (операнд ( А= ( А= 3 ) + ( В= 5 ) - ( С= 7 - В ) ) ), то значение пере­ менной А становится равным 6 . При вычислении значения второго операнда ( А=А+ ( В=В+2 ) * ( С= С - 3 ) ) значением переменной А присваивается сумма двух выражений: А и ( В=В + 2 ) * ( С= С - 3 ) . Как мы выяснили выше, текущее значение пе­ ременной А равно 6 . При вычислении выражения В=В + 2 (учитывая, что текущее значение переменной в равно 5) получается, что значение пе­ ременной В равно 7 и это же будет значение выражения В=В + 2 . Для вы­ ражения С=С - 3 получаем значение - 1 (оно же значение переменной с): в данном случае нужно учесть, что на предыдущем этапе переменная С получила значение 2 . В итоге значение выражения ( В=В+ 2 ) * ( С= С - 3 ) равно - 7 . Тогда второй операнд ( А=А+ ( В=В + 2 ) * ( С= С - 3 ) ) равен - 1 ( 6 плюс - 7 ). Следовательно, значение выражения ( А= ( А= 3 ) + ( В= 5 ) ( С= 7 - В ) ) + ( А=А+ ( В=В+ 2 ) * ( С= С - 3 ) ) равно 5 ( 6 плюс - 1 ). Это новое значение переменной А. Таким образом, переменная А получила значение 5, переменная В получила значение 7 , а переменная С получила зна­ чение - 1 . 101 Глава 2 G) Н А ЗАМ ЕТ КУ Сложн ые команды и вы ражения луч ше разби вать на нескол ько п ро­ стых. Такой программный код легче ч итать, и меньше вероятность сделать ошибку. С окраще н н ы е формы опера ц и и п рисва и ва н ия Отлично, отлично ! Скромненько и со вкусом ! из к/ф «Бриллиантовая рука» Допустим, что Q - это некоторый бинарный оператор (арифметический или побитовый). Если нам необходимо вьшолнить операцию вида А=А О В (то есть вычислить значение выражения А О В на основе оператора О, а затем результат выражения присвоить значением переменной А), то та­ кая операция может быть реализована с помощью команды А0=В. На­ пример, вместо выражения А=А+В можно использовать команду А+=В. Аналогично команду А=А< <В можно записать как А<<=В или вместо вы­ ражения А=А л в использовать выражение А л =в. Во всех перечисленных случаях говорят о сокращенной форме операции присваивания. Помимо очевидного «эстетического» удобства, сокращенные формы операции присваивания имеют еще одно несомненное преимущество. Чтобы понять, в чем оно состоит, рассмотрим небольшой блок про­ граммного кода, в котором переменная А в итоге получает значение 1 5 : byte A=l O , В=5 ; А= (byte ) (А+В ) ; В нем проиллюстрирована уже знакомая нам ситуация: чтобы присво­ ить переменной А новое значение, необходимо использовать автомати­ ческое приведение типа. G) Н А ЗАМ ЕТ КУ Переменные А и В объя влены как относя щиеся к типу byte . Но при вычислении вы ражения А+В вы полняется автоматическое расшире­ ние до типа i n t . Поэтому, есл и м ы хотим вы ражение А+В присво­ ить значением переменной А (типа byte ) , то перед вы ражением А+В 1 02 Базовые типы и операто ры следует указать инструкцию ( byte ) - то есть выпол н ить при веде­ ние i n t -значения к Ьуtе-значению. Но если мы используем сокращенную форму операции присваивания, то явное приведение типа выполнять не нужно: byte A=l O , B=S ; А+=В ; В данном случае переменная А также получит значение 1 5 . Причем инструкцию ( by t e ) мы не использовали - в этом нет необходимости. Объяснение состоит в том, что команда вида А0=В в действительности вычисляется немного сложнее, чем выполнение команды А=А О В . Точнее, если результат выражения А О В может быть автоматически преобразован к типу переменной А, то проблемы нет вообще. Но если для результата выражения А О В автоматического преобразования к типу переменной А нет, то схема иная. А именно если результат вы­ ражения А О В может быть явно преобразован к типу переменной А, а тип переменной В при этом может быть неявно преобразован к типу переменной А, то преобразование результата выражения А О В к типу переменной А выполняется автоматически (без явного указания ин­ струкции приведения типа). Проще говоря, если результат выражения А О В можно преобразовать (с помощью процедуры явного приведения типа) к типу переменной А (первое условие) и если тип переменной В такой, что допускается неявное преобразование к типу переменной А (второе условие), то выражение АО=в вычисляется как А= ( т ип А ) ( А О В ) . После вычисления выражения А О В полученное значе­ ние автоматически преобразуется к типу переменной А, а затем выполняется операция присваивания. Например, проанализируем команду А+=В, в которой переменные А и В относятся к типу b yt e . Значение выражения А+В - это число типа i n t . А присваивать значение нужно переменной типа byt e . Тип in t в тип b y t e автоматически не преобра­ зуется. Но такое преобразование можно выполнить явно. То есть пер­ вое условие выполнено. Далее, переменная В относится к типу b yt e . Необходимо, чтобы для типа переменной В существовала возможность неявного преобразования к типу переменной А. Но в данном случае эти типы просто совпадают, поэтому второе условие тоже выполнено. Следовательно, результат выражения А+ В автоматически преобразует­ ся к типу переменной А (тип byt e ) . 1 03 Глава 2 Тернарн ы й оператор - Лелик, это же не эстетично . . . - Зато дешево, надежно и практично ! из к/ф «Бриллиантовая рука» В языке С# есть один оператор, который используется с тремя операн­ дами. Такой оператор называют тернарным . По большому счету, это �миниатюрная� версия условного оператора, поскольку значение выра­ жения на основе тернарного оператора зависит от истинности или лож­ ности некоторого условия. G) Н А ЗАМ ЕТ КУ Здесь и далее под условием будем подразумевать л юбое логиче­ ское вы ражение - то есть вы ражение, которое может иметь значе­ ние t rue ( исти нное условие) или fa l s e (ложное услови е ) . Синтаксис тернарного оператора такой (жирным шрифтом выделены ключевые элементы шаблона): условие ?значение : значение Сначала указывается условие (это первый операнд) - любое логиче­ ское выражение. Затем следует вопросительный знак ? - это синтак­ сический элемент тернарного оператора. После вопросительного знака указывается некоторое значение (второй операнд). Далее следует дво­ еточие : (синтаксический элемент тернарного оператора) и после дво­ еточия - еще одно значение (третий операнд). Если условие истинно (значение первого операнда равно t ru e ) , то значением всего выраже­ ния является значение второго операнда (значение, указанное после во­ просительного знака ? ) . Если условие ложно (значение первого операн­ да равно f а 1 s е ), то значение всего выражения определяется значением третьего операнда (значение, указанное после двоеточия : ). G) Н А ЗАМ ЕТ КУ Тернарный оператор иногда упоми нают как оператор ? : . Например, при выполнении команды num= ( х< О ) & 1 : 2 переменная num получает значение 1, если значение переменной х меньше О, и значе­ ние 2 , если значение переменной х не меньше О . 1 04 Базовые типы и операторы П риоритет операторов Дайте мне поручение, а у ж особым я его как-нибудь и сам сделаю. из к/ф « 0 бедном гусаре замолвите слово» При вычислении сложных выражений, в которых используются различ­ ные операторы, порядок выполнения операций определяется приори­ тетом операторов. Подвыражения на основе операторов с более высо­ ким приоритетом вычисляются ранее по сравнению с подвыражениями на основе операторов с более низким приоритетом. Если у операторов одинаковый приоритет, то подвыражения вычисляются в естествен­ ном порядке, слева направо. В табл. 2 . 1 4 основные операторы языка С# сгруппированы по уровню приоритета (от самых приоритетных до наи­ менее приоритетных). Табл . 2. 1 4 . П риоритет операторов Приоритет Операторы 1 Круглые скобки ( ) , квадратные скобки [ ] , оператор «точка» , операто­ ры инкремента + + и декремента -- в постфиксной форме (то есть в выра­ жениях вида А++ и А- - ) 2 Унарный оператор «плюс» + , унарный оператор «минус» - , оператор по­ битовой инверсии - , оператор логического отрицания ! , операторы ин­ кремента + + и декремента -- в префиксной форме (то есть в выражениях вида ++А и - -А), операция явного приведения типа 3 Оператор умножения * , оператор деления / , оператор вычисления остат­ ка от деления % 4 Оператор сложения + , оператор вычитания - 5 Оператор побитового сдвига вправо > > , оператор побитового сдвига вле­ во < < 6 Оператор сравнения «больше» > , оператор сравнения «больше или равно» >=, оператор сравнения «меньше» < , оператор сравнения «меньше или . равно » < = 7 Оператор сравнения «равно » ==, оператор сравнения «не равно» ! = 8 Оператор «побитовое и» & (также «логическое и» в полной форме) 9 10 Оператор «побитовое исключающее или» л (также «логическое исключаю ­ щее или») Оператор «побитовое или» 1 (также «логическое или» в полной форме) 1 05 Глава 2 11 Оператор «логическое и» в упрощенной форме & & 12 Оператор «логическое или» в упрощенной форме 1 1 13 Тернарный оператор ? : 14 Оператор присваивания =, операторы сокращенного присваивания * =, / = , % = , + =, -=, <<=, >>=, & =, л = И 1 = Для изменения порядка вычисления выражений можно использовать круглые скобки. G) Н А ЗАМ ЕТ КУ Помимо своего прямого назначения - изменения порядка выч исле­ ния п одвы ражений - круглые скобки облегчают чтение п рограм м ­ ного кода . Так что и х нередко испол ьзуют и в «декоративных» целях, когда прямой необходимости в этом нет. П римеры п рограмм Я тебя полюбил - я тебя научу. из к/ф «Кин-дза-дза» Далее мы рассмотрим несколько очень простых иллюстраций к тому, как базовые операторы языка С# могут использоваться на практике. Снача­ ла мы создадим программу, предназначенную для проверки введенного пользователем числа на предмет четности/нечетности. G) Н А ЗАМ ЕТ КУ Ч исло назы вается четн ы м , есл и оно дел ится на 2 - то есть есл и при делении на 2 в остатке получаем О . Есл и ч исло на 2 не дел ится , то оно назы вается нечетн ы м . Программа выполняется просто: появляется диалоговое окно с полем ввода, в которое пользователю предлагается ввести целое число. Далее появляется еще одно диалоговое окно с сообщением о том, что пользова­ тель ввел четное или нечетное число. Код программы представлен в лис­ тинге 2 . 1 . 106 Базовые типы и операто ры [1i!] л истинг 2. 1 . Проверка числа на четность/нечетность u s ing Sys tem; u s ing Sys tem . Windows . Forms ; u s ing Micros oft . VisualBas i c ; c l a s s OddEvenDemo { s tatic vo id Main ( ) { 1 1 Целочисленные переменные : int numЬer, reminde r ; 1 1 Считывание целого числа : numbe r=Int 3 2 . Parse ( Interaction . I nputBox ( 1 1 Текст в окне : "Введите целое число : " , 1 1 Название окна : " Проверка " ) ); 1 1 Вычисляется остаток о т деления на 2 : reminder=numЬer% 2 ; s t ring txt= " Bы ввели " ; 1 1 Использован тернарный оператор : txt+= ( reminder==O ? " чeтнoe " : " нeчeтнoe " ) + " число ! " ; Me s s ageBox . Show ( txt ) ; В программе используются две целочисленные переменные numb e r и reminde r - соответственно, для запоминания введенного пользова­ телем числа и остатка от деления этого числа на 2 . Для считывания чис­ ла использован статический метод I nputBox ( ) класса I nt e r a c t i o n (для использования этого класса в заголовке программы взята инструк­ ция u s i n g Mi c r o s o ft . Vi s u a l B a s i c ) . Аргументами методу пере­ дается два текстовых значения: текст, отображаемый над полем ввода, и название (заголовок) для окна. Метод I np u t B o x ( ) значением воз­ вращает введенное пользователем значение, но значение возвращается 107 Глава 2 в текстовом формате. Для преобразования текстового представления числа в целое число используем статический метод P a r s e ( ) из струк­ туры I n t 3 2 (структура доступна после подключения пространства имен S y s t e m командой u s i n g S y s t em). В частности, инструкция вызова метода I nputBox ( ) передана аргументом методу P a r s e ( ) . Результат преобразования присваивается значением переменной n umbe r. Командой remi n de r=numb e r % 2 в переменную reminde r записывает­ ся остаток от деления значения переменной numb e r на 2 . Переменная reminde r в принципе может принимать всего два значения: О при чет­ ном значении переменной numb e r и 1 при нечетном значении перемен­ ной numb e r . Текстовая переменная t x t объявляется с начальным значением " В ы вв ели " . Нам эта переменная нужна для того, чтобы записать в нее текст, который затем будет отображаться во втором диалоговом окне. Следу­ ющей командой t x t + = ( r e m i nde r== O ? " ч e т н o e " : " н е ч е т н о е " ) + " число ! " к текущему значению этой переменной дописывается недо­ стающая часть. Здесь в подвыражении в правой части мы использовали тернарный оператор. Речь об инструкции remi n de r = = O ? " ч e т н o e " : " нече т н о е " . Значение этого выражения определяется так: если зна­ чение переменной r em i n de r равно О, то значение выражения равно " че т ное " . В противном случае (если значение переменной reminde r отлично от О ) значение выражения равно " н ече т н о е " . Таким обра­ зом, в текстовое значение переменной t x t включается слово " че т н о е " или " н ече т н о е " в зависимости от значения переменной r em i n d e r . После того как значение переменной t x t сформировано, командой Me s s ageBox . S how ( txt ) отображается диалоговое окно с соответству­ ющим сообщением (для использования класса Me s s ageBox используем инструкцию u s i n g S ys tem . Wi ndows . F o rms в заголовке программы). При запуске программы на выполнение появляется окно с текстовым полем. На рис. 2 . 1 показано такое окно с числом 1 2 3 в поле ввода. Прооер" Вее.аопе uелое число: 123 Рис. 1 08 2. 1 . В поле введено нечетное ч исло Базовые типы и операто ры После щелчка по кнопке ОК появляется новое окно с сообщением о том, что число нечетное. Окно показано на рис. 2.2. В ы ааищ нечетное число! ок Рис . 2 . 2 . Окно с соо б щением о том , что б ыло введено нечетное ч и сло На рис. 2.3 показано начальное диалоговое окно с числом 1 24 в поле ввода. Прооероtа 1 24 Рис . 2.3. В поле введено четное ч исло Если так, то вторым появится окно с сообщением о том, что пользова­ тель ввел четное число. Окно показано на рис. 2 .4. Вы ваvш четное Ч\IСЛО! ок Рис. 2 . 4 . Окно с соо б щением о том , что б ыло введено четное ч исло При этом стоит также заметить, что если пользователь в поле ввода в первом окне введет не число или вместо кнопки ОК щелкнет кноп­ ку Отмена (или закроет окно с помощью системной пиктограммы), то в программе произойдет ошибка. В принципе, подобные ситуации легко обрабатываются. Но пока что нас этот вопрос занимать не будет. В следующей программе для числа, введенного пользователем, опреде­ ляется количество сотен (в десятичном представлении числа это третья цифра справа). В листинге 2.2 представлен код программы. 109 Глава 2 [1i!] л истинг 2.2 . Количество сотен в числе us ing Sys tem; us ing Sys tem . Windows . Forms ; us ing Micros oft . Vi sualBas i c ; c l a s s Hundreds Demo { s tatic vo id Main ( ) { / / Целочисленные переменные : int numЬe r , hundreds ; / / Считывание целого числа : numbe r=Int 3 2 . Parse ( Interaction . I nputBox ( / / Надпись над полем ввода : " Введите целое число : " , / / Заголовок окна : " Количество сотен " ) ); / / Количество сотен в числе ( для целочисленных / / операндов деление выполняется нацело ) : hundreds=numbe r/ 1 0 0 % 1 0 ; / / Текстовая переменная : s t ring txt= " B этом числе "+hundreds+" сотен ! " ; / / Отображение окна с сообщением // ( аргументы метода - сообщение и заголовок окна ) : Me s s ageBox . Show ( tx t , " Coтни" ) ; Эта программа напоминает предыдущую - как минимум в части счи­ тывания целочисленного значения. Число, введенное пользователем, записывается в переменную numb e r . Затем выполняется команда hundr e d s =numЬe r / 1 0 0 % 1 0 , благодаря чему в переменную hundreds записывается количество сотен в числе. Вычисления выполняются так: значение переменной numb e r делится на 1 0 0 . Здесь нужно учесть, что 110 Базовые типы и операто ры для целочисленных операндов выполняется целочисленное деление. Поэтому деление числа на 1 О О означает фактически отбрасывание двух последних цифр в представлении числа. Как следствие, третья справа цифра в значении переменной numb e r �перемещается� на последнюю позицию (первую справа). G) Н А ЗАМ ЕТ КУ Значение переменной numЬer не меняется . Речь идет о значении вы­ ражения numЬe r / 1 О О по сравнению со значением переменной numЬer . Далее вычисляется остаток от деления на 1 О значения выражения n umbe r / 1 0 0 (выражение numbe r / 1 0 0 % 1 0 ). Результат записывается в переменную hundr e d s . В программе объявляется текстовая переменная t x t с о значением "В э т ом числе " +hundreds+ " сотен ! " , в котором использована пере­ менная hundreds. Переменную txt используем в команде Me s s ageBox . Show ( txt , " Со тни " ) для отображения окна с сообщением (первый аргу­ мент определяет текст сообщения, а второй задает заголовок окна). На рис. 2.5 показано окно с полем, содержащим введенное пользовате­ лем число. Ко11ичкт110 соте:н Рис . 2. 5 . В поле введено целое ч исло для оп ределения кол ичества сотен в ч исле После щелчка по кнопке ОК появляется окно с сообщением, показанное на рис. 2.6. Сотни В ]ТОМ чиСJ'lе S сотен! ок Рис . 2 . 6 . Окно с соо б щением о кол ичестве сотен в ч исле 111 Глава 2 В данном случае мы ввели число 2 5 3 8 и программа вычисляет кор­ ректное значение 5 для количества сотен в числе. Р ез ю ме Только быстро. А т о одна секунда здесь - это полгода там ! из к/ф -«Кин-дза-дза» • При объявлении переменной указывается ее тип и название. Не­ сколько переменных одного типа может быть объявлено одновре­ менно. При объявлении переменной можно указать ее значение. Константы объявляются с ключевым словом c o n s t . • В языке С# существует несколько базовых типов. Целочислен­ ные типы данных b y t e , s b yt e , s h o r t , u s ho r t , i n t , u i n t , l o n g и u l ong. Отличаются эти типы размером выделяемой памяти и воз­ можностью/невозможностью использовать отрицательные чис­ ла. Действительные числа реализуются с помощью типов f l o a t и douЬ l e . Для финансовых расчетов используется тип de c ima l . Символьные значения реализуются через тип char. Логические зна­ чения реализуются как значения типа b o o l . • Целочисленные литералы п о умолчанию реализуются как i n t ­ значения, действительные числовые литералы реализуются как dоuЫ е -значения, символьные литералы реализуются как с h а r ­ значения, текст реализуется в виде объектов класса s t r ing. • Если в выражении используются значения разных (но совмести­ мых) типов, то применяется автоматическое преобразование типов. Можно также выполнять явное приведение типов. • Для выполнения основных операций применяются специальные операции. В С# есть четыре группы операторов: арифметические ( «сложение» +, «вычитание» - , «умножение» * , «деление» / , «оста­ ток от деления» % , «инкремент» + + , «декремент» - - ), логические ( «логические и» & и & &, «логические или» 1 и 1 1 , «логическое исклю­ чающее или» л , «логическое отрицание» ! ), операторы сравнения ( «меньше» <, «меньше или равно» <=, «больше» >, «больше или равно» >=, «равно» ==, «не равно» ! = ), а также побитовые операторы ( «по­ битовое и» & , «побитовое или» 1 , «побитовое исключающее или» л , 1 12 Базовые типы и операто ры «побитовая инверсию> - , «сдвиг влево» < < , «сдвиг вправо» >>). В С# есть один тернарный оператор ? : , представляющий собой упрощен­ ную форму условного оператора. Для арифметических и побито­ вых операторов есть сокращенные формы операции присваивания ( * =, / = , %=, +=, - = , <<=, >>=, & =, л = и 1 =). Оператор присваивания = в языке С# возвращает значение. Задания для самостоятел ьной работы Хочешь поговорить - плати еще чатл. из к/ф -«Кин-дза-дза» 1 . Напишите программу, которая проверяет, делится ли введенное поль­ зователем число на 3. 2. Напишите программу, которая проверяет, удовлетворяет ли введенное пользователем число следующим критериям: при делении на 5 в остатке получается 2, а при делении на 7 в остатке получается 1 . 3 . Напишите программу, которая проверяет, удовлетворяет ли введен­ ное пользователем число следующим критериям: число делится на 4, и при этом оно не меньше 1 0 . 4 . Напишите программу, которая проверяет, попадает л и введенное пользователем число в диапазон от 5 до 1 О включительно. 5. Напишите программу, которая проверяет, сколько тысяч во введен­ ном пользователем числе (определяется четвертая цифра справа в деся­ тичном представлении числа). 6. Напишите программу, которая проверяет вторую справа цифру в вось­ меричном представлении числа, введенного пользователем. Число вво­ дится в десятичном (обычном) представлении. 7. Напишите программу, которая вычисляет третий бит справа в двоич­ ном представлении числа, введенного пользователем. Число вводится в десятичном (обычном) представлении. В программе используйте опе­ ратор побитового сдвига. 8. Напишите программу, в которой для введенного пользователем числа в бинарном представлении третий бит устанавливается равным единице. 1 13 Глава 2 9. Напишите программу, в которой для введенного пользователем чис­ ла в бинарном представлении четвертый бит устанавливается равным нулю. 1 0 . Напишите программу, в которой для введенного пользователем чис­ ла в бинарном представлении значение второго бита меняется на проти­ воположное (исходное нулевое значение бита меняется на единичное, а исходное единичное значение бита меняется на нулевое). Гл ава 3 У П РАВЛ SI Ю ЩИ Е И НСТ РУК ЦИ И З начит, такое предложение : сейчас мы на­ жимаем на контакты и перемещаемся к вам. Но если эта машинка не сработает, тогда уж вы с нами переместитесь, куд а мы вас переме­ стим ! из к/ф -«Кин-дза-дза» В этой главе мы рассмотрим вопросы, связанные с использованием управляющих инструкций. Более конкретно, мы сосредоточим внима­ ние на следующих конструкциях языка С#: • познакомимся с условным оператором i f ; • научимся использовать оператор выбора s w i t ch ; • узнаем, как используется оператор цикла wh i l e ; • выясним, в чем особенности оператора цикла d o - wh i l e ; • убедимся в гибкости и эффективности оператора цикла f o r ; • познакомимся с инструкцией безусловного перехода g o t o ; • составим некоторое представление о системе обработки исключе­ ний. Теперь рассмотрим более детально означенные выше темы. 1 15 Глава 3 Условны й оператор if Или ты сейчас же отдаешь нам эту К Ц , или меньше чем за семь коробков мы тебя на З ем­ лю не положим ! из к/ф «:Кu н-дза-дза'> Условный оператор позволяет выполнять разные блоки команд в зави­ симости от истинности или ложности некоторого условия (выражение со значением логического типа). Работает это так: сначала вычисляется значение некоторого логического выражения (условие). Если значение выражения равно t ru e (условие истинно), выполняется определенный блок команд. Если значение выражения равно f a l s e (условие ложно), выполняется другой блок команд. Условие t r ue , 1 >" " - - , , ... - - - - �. f a l se ... , ... , - - " " .\ , " ' , ' 1 1 \ 1 ' 1 "" \ " ... " , ' ' 1 1 -\ Команды ,-- \ , - - .., ' , '· Команды ,�. . ' ' 1 1 �- 1 , ' - i f - бл о к ' ,_ .... ' 1 , ' ' 1 - - ' ' ' , -' - , ... _ _ _ ... , , ,, ... " _ " , ,' , , ' 1 1 �"- ' е l s е - бл о к \ , , . - - \ ' , ' 1 ' - -: ' , _ ...\ , ... ... _ _ ... ... , ,' ,, ... _ _ _ " Рис. 3. 1 . П р и н ци п ы выпол н е н и я условного оператора Описывается условный оператор достаточно просто: указывается клю­ чевое слово i f, после которого в круглых скобках следует условие, проверяемое при выполнении условного оператора. Блок команд, вы­ полняемых при истинном условии, указывается в фигурных скобках сразу после ключевого слова i f с условием ( i f-блок). Команды, пред­ назначенные для выполнения в случае ложного условия, указываются 116 Управля ющие инструкции в фигурных скобках после ключевого слова e l s e ( е l s е -блок). Шаблон описания условного оператора приведен ниже (жирным шрифтом выде­ лены ключевые элементы шаблона): if (условие ) { 1 1 Команды - если условие истинно else { 1 1 Команды - если условие ложно Принципы выполнения условного оператора также проиллюстрирова­ ны в схеме на рис. 3. 1 . Условие f a l se t r ue 1 1 , , " - - - ...:. . , - - ... ,, ... - - ... ... 1 \ •-, ' К оманды ,'' \ 1 1 \ 1 \ 1 1 1 1 ' � ... _ : \ " ... _ :: ' ' i f - блок 1 " ... ..,. _ _ " ...\ ... " ... _ _ ... ... r. '·', ... , ' ' '._ , - ' / _ _ _ , Рис. 3.2. Выполнение условного оператора в упрощенной форме Если блок состоит всего из одной команды, то фигурные скобки для вы­ деления блока можно не использовать. У условного оператора есть упрощенная форма, в которой отсутству­ ет е l s е -блок. Если так, то оператор выполняется следующим обра­ зом: проверяется условие, и, если оно истинно, выполняются команды в i f-блоке. Если условие ложно, то ничего не происходит - выполня­ ется команда, следующая после условного оператора. Шаблон описания 1 17 Глава З условного оператора в упрощенной форме такой (жирным шрифтом вы­ делены ключевые элементы шаблона): if (условие ) { / / Команды - если условие истинно Как выполняется условный оператор в упрощенной форме, иллюстри­ рует схема на рис. 3.2. Небольшой пример, иллюстрирующий работу условного оператора, представлен в листинге 3. 1 . � Листин г 3. 1 . Использовани е условно го оператора us ing Sys tem . Windows . Forms ; us ing Micros oft . Vi sualBas i c ; c l a s s Usingi fDemo { s tatic vo id Main ( ) { / / Переменная для определения типа пиктограммы : Me s s ageBoxi con icon ; / / Переменные для определения текста сообщения , / / заголовка окна и имени поль зователя : s t ring msg , t i t l e , name ; / / Считывание имени пользователя : name=Interaction . InputBox ( / / Текст над полем ввода : " Как Вас зовут ? " , / / Название окна : " Знакомимся " ) ; / / Проверка введенного поль зователем текста : i f ( name== " " ) { / / Если текст не введен / / Пиктограмма ошибки : icon=Me s s ageBoxi con . Error; / / Текст сообщения : msg= " Очень жаль , что мы не познакомилис ь ! " ; 118 Управля ю щие инструкции 1 1 Заголовок окна : titlе=" Знакомство не состоялось " ; e l s e { / / Если текст введен 11 Информационная пиктограмма : icon=Me s s ageBoxi con . I nformation ; 1 1 Текст сообщения : msg= " Очень приятно , " +name+ " ! " ; 1 1 Заголовок окна : titlе=" Знакомство состоялось " ; 1 1 Отображение сообщения ( аргументы - текст / / сообщения , заголовок, кнопки и пиктограмма ) : Me s s ageBox . Show (msg, title , Mes sageBoxButton s . OK , icon) ; Программа простая: отображается окно с полем ввода, и пользователю предлагается ввести туда имя. Затем появляется новое диалоговое окно с сообщением, которое содержит введенное пользователем имя. Но это, если имя пользователь ввел. Однако пользователь в первом окне может на­ жать кнопку отмены или закрыть окно с помощью системной пиктограммы с крестом. Вот такая ситуация и обрабатывается с помощью условного опе­ ратора. А именно, мы воспользуемся тем, что если пользователь закрывает окно с полем ввода без ввода текста, то соответствующий метод возвращает пустую текстовую строку. Чтобы легче было понять, как организован про­ граммный код, рассмотрим результат выполнения программы. Сначала, как указывалось, появляется окно с полем ввода, представленное на рис. 3.3. Зкакомимс.я IAnoo<""" вас:ШЫаl Рис. З.З. Д иалоговое окно с полем для ввода имени пол ьзователя 119 Глава 3 Если мы вводим в поле ввода текст и щелкаем кнопку ОК, то следую­ щим появляется окно с информационной пиктограммой (буква i в си­ нем круге). Текст сообщения в окне содержит имя, введенное на преды­ дущем этапе. Окно имеет название Знакомство состоялось. Ситуация проиллюстрирована на рис. 3.4. 3не1СОМСТ80 состомось Очень nрш11"0, Anцce.ii Васщ1ьt.а! ок Рис. 3. 4 . Окно, ото б ражаемое в случае, есл и пол ьзовател ь ввел и м я Но если поле для ввода текста оставить пустым или вместо кнопки ОК в окне с полем ввода щелкнуть кнопку Отмена или системную пикто­ грамму (см. рис. 3.3), то появится окно как на рис. 3.5. Знакомство н t состоялось Оче н ь аль, что мы не nо1накомил и с ь! ок Рис. 3. 5 . Окно, ото б ражаемое в случае, есл и пол ьзовател ь не ввел имя Окно содержит сообщение, пиктограмму ошибки (белый косой крест в красном круге) и имеет название Знакомство не состоялось. Подчерк­ нем, что появление такого окна - следствие выполнения программы, а не резу льтат ошибки. Теперь проанализируем программный код. Есть несколько важных мо­ ментов. Один из них связан с подходом, использованным в программе: на финальной стадии отображается окно с сообщением. Для отображе­ ния этого окна необходимо определить три параметра: текст сообщения, тип пиктограммы и название окна. Собственно, в процессе выполнения программы эти параметры определяются, и, после того как они опреде­ лены, отображается окно. При определении указанных характеристик использован условный оператор. 120 Управля ющие инструкции В программе объявляется переменная i c o n , предназначенная для определения типа пиктограммы. Переменная объявлена как относя­ щаяся к типу Ме s s а g е В о х i с о n . Напомним, чтo Me s s a g e B o x i c o n это перечисление. В этом перечислении есть константы, используе­ мые для определения типа пиктограммы в диалоговом окне. Также мы используем три текстовые переменные m s g (текст сообщения) , t i t l e (заголовок окна), n ame (имя пользователя, которое вводится в поле ввода). Для отображения окна с полем ввода используем метод I np u t B o x ( ) класса I n t e r a c t i o n . Результат (введенное пользователем значение) записывается в переменную n ame . А дальше в игру вступает услов­ ный оператор. В условном операторе проверяется условие n ame = = " " . Это выражение возвращает значение t ru e , если в переменную n ame записана пустая текстовая строка " " . Это имеет место, если пользо­ ватель нажал кнопку Отмена, закрыл окно с полем ввода щелчком по системной пиктограмме или если пользователь щелкнул кнопку ОК при пустом поле ввода. В иных случаях значение переменной n ame будет иным и условие в условном операторе окажется ложным (значение f a l s e ) . Если условие истинно, то командой i c on=Me s s a g e B o x i c o n . E r r o r значение переменной i c on определяется константой, соответствующей пиктограмме ошибки. Текст сообщения записывается в переменную ms g командой m s g= " Оче н ь жаль , ч т о мы не п о з н а комилис ь ! " . На­ конец, командой t i t l е = " З н а комс т в о н е с о с т о я л о с ь " задается значение, используемое впоследствии в качестве названия для второго диалогового окна. Напомним, что все эти команды выполняются, если значением переменной name является пустая текстовая строка. В против­ ном случае команды выполняются другие. Они собраны в е l s е -блоке. Там командами i c o n=Me s s a geBox i c on . I n fo rma t i on, m s g= " Оч е н ь прия т н о , " +n ame + " ! " и t i t l е= " Зн акомс т в о с о с т о ял о с ь " опре­ деляются значения (но не такие, как в i f-блоке) для переменных i c on, m s g и t i tle. Пиктограмма в этом случае используется информацион­ ная, а текст сообщения содержит значение, которое указал пользователь в поле ввода. После того как значения означенных переменных определены (а они в лю­ бом случае определяются, но происходит это по-разному в разных случаях), командой Me s s ageBox . Show ( m s g , t i t l e , Me s s ageBoxBu t t o n s . ок , i c on ) отображается диалоговое окно с сообщением. 121 Глава 3 Такую же программу (по функциональным возможностям) можно напи­ сать с использованием условного оператора в упрощенной форме. Соот­ ветствующий программный код представлен в листинге 3.2. � Листинг 3. 2 . Услов ны й оператор в упро щенно й форме us ing Sys tem . Windows . Forms ; us ing Micros oft . Vi sualBas i c ; c l a s s Anothe r i fDemo { s tatic vo id Main ( ) { / / Пиктограмма ( сначала это пиктограмма ошибки ) : Me s s ageBoxi con icon=Me s sageBox icon . E rror; / / Текст сообщения : s t ring msg= " Очень жаль , что мы не познакомились ! " , / / Заголовок окна : titlе=" Знакомство не состоялось " , / / Переменная для записи имени поль зователя : name ; / / Отображение окна с полем ввода : name=Interaction . InputBox ( / / Текст над полем ввода : " Как Вас зовут ? " , / / Заголовок окна : " Знакомимся " ) ; / / Условный оператор в упрощенной форме : i f ( name ! = " " ) { / / Если не пустая текстовая строка / / Новый тип пиктограммы : icon=Me s s ageBoxi con . I n formation ; / / Новый текст сообщения : msg= " Очень приятно , " +name+ " ! " ; / / Новый заголовок окна : titlе=" Знакомство состоялось " ; } / / Завершение условного оператора / / Отображение окна с сообщением : 122 Управля ющие инструкции Me s s ageBox . Show (msg, title , Mes sageBoxButton s . OK , icon) ; Данная программа функционирует точно так же, как и предыдущая програм­ ма. Но организация программного кода иная. Там переменные i con, msg и t i t l e сразу получают такие значения, как в случае, когда пользователь отказывается вводить имя. Другими словами, априори мы исходим из того, что пользователь не введет имя в поле первого диалогового окна. Но после того как определяется значение переменной name, выполняется проверка этого значения. Для проверки использован условный оператор в сокращен­ ной форме. В условном операторе проверяется условие name ! = " " , истин­ ное в случае, если в переменную name записана непустая текстовая строка. Если так, то значения переменных i con, msg и t i t l e переопределяются (переменным присваиваются новые значения). Если условие name ! = " " ложно, то переменные i con, msg и t i t l e остаются с теми значениями, ко­ торые им бьmи присвоены в самом начале (при объявлении). В ложенные условные операторы Ты свои К Ц и м н е показывай. И н е думай о них. Ты мой К Ц покажи. И больше полспич­ ки пе давай, гравицапа полспички стоит. из к/ф -«Кин -дза -дза» Условные операторы могут быть вложенными - то есть в одном услов­ ном операторе (внешний оператор) может использоваться другой услов­ ный оператор (внутренний, или вложенный оператор), а в нем еще один условный оператор и так далее. Причем внутренние условные операто­ ры могут использоваться и в i f-блоке, и в е l s е -блоке внешнего услов­ ного оператора. Например, ниже приведен шаблонный код для случая, когда внутренний условный оператор размещен внутри i f-блок внеш­ него условного оператора (жирным шрифтом выделены ключевые эле­ менты шаблона): 11 Внешний условный оператор : if (условие ) { 1 1 Внутренний условный оператор : 123 Глава 3 if (условие ) { 1 1 Команды i f - блока внутреннего оператора 11 Блок e l s e внутреннего оператора : else{ 1 1 Команды е l s е - блока внутреннего оператора 11 Блок e l s e внешнего оператора : else( 1 1 Команды е l s е - блока внешнего оператора Достаточно популярной является схема, когда внутренний условный оператор размещается в е l s е -блоке внешнего оператора. Если таких вложений несколько, то получается конструкция, позволяющая после­ довательно проверять несколько условий. Ниже приведен подобный код для случая, когда используется четыре вложенных условных оператора (жирным шрифтом выделены ключевые элементы кода): if (условие _ l ) ( 1 1 Команды выполняются , если условие_ l истинно else if (условие_2 ) { 1 1 Команды выполняются , если условие_2 истинно else if (условие_ З ) { 1 1 Команды выполняются , если условие_ З истинно else if (условие_ 4 ) { 1 1 Команды выполняются , если условие_ 4 истинно else( 1 1 Команды выполняются , если все условия ложны 124 Управля ющие инструкции Хотя конструкция может выглядеть несколько необычно, но в действи­ тельности это всего лишь вложенные условные операторы. Просто нуж­ но учесть несколько моментов. В первую очередь, следует обратить вни­ мание, что е l s е -блок каждого из условных операторов формируется всего одним оператором - внутренним условным. Поэтому е 1 s е-блоки, за исключением последнего, описываются без использования фигурных скобок. G) Н А ЗАМ ЕТ КУ Напом н и м , что есл и i f-блок или е l s е -блок состоит из одной коман­ ды , то фигурные скобки можно не испол ьзовать . Условный опера­ тор - это одна «составная » команда . Поэтому есл и е l s е -блок ил и i f-блок состоит из условного оператора, то его в фигурные скобки можно не закл ючать . Условия в условных операторах проверяются последовательно: снача­ ла проверяется условие во внешнем условном операторе (усло вие_ 1 ). Если условие истинно, то выполняются команды в i f-блоке этого оператора. Если условие ложно, то выполняется условный оператор в е l s е -блоке внешнего оператора - и проверяется условие во внутрен­ нем операторе (усло вие_ 2 ). Если это условие истинно, то выполняются команды в i f-блоке данного условного оператора. Если условие ложно, проверяется условие в следующем условном операторе (условие_ З ) , и так далее. В итоге получается следующая схема: проверяется первое условие, и если оно ложно, проверяется второе условие. Если второе ус­ ловие ложно, проверяется третье условие. Если оно ложно, проверяется четвертое условие, и так далее. Самый последний е l s е -блок выполня­ ется, если все условия оказались ложными. В листинге 3.3 представлена программа, в которой используются вло­ женные условные операторы. [1iJ Листинг З.З. Вложенные условные операторы u s ing Sys tem; c l a s s Nestedi fDemo { s tatic vo id Main ( ) { 1 1 Текстовая переменная : s t ring txt ; 1 1 Отображение сообщения : 125 Глава 3 Console . Write ( " Bвeдитe текст : " ) ; 1 1 Считывание текстовой строки : txt=Console . ReadLine ( ) ; 1 1 Е сли введена непустая строка : i f ( txt ! =" " ) { 1 1 Отображение сообщения : Console . WriteLine ( " Cпacибo , что ввели текст ! " ) ; 1 1 Если в строке больше десяти символов : i f ( txt . Length> l O ) { 1 1 Отображение сообщения : Console . WriteLine ( " Oгo, как много букв ! " ) ; 1 1 Если в строке не больше десяти символов : else { 1 1 Отображение сообщения : Console . WriteLine ( " Oгo, как мало букв ! " ) ; 1 1 Е сли введена пустая строка : else { Console . WriteLine ( "Жaль , что не ввели текст ! " ) ; Алгоритм выполнения программы простой: пользователю предлагается ввести текстовое значение, и это значение считывается и записывается в текстовую переменную. Затем в дело вступают вложенные условные операторы. Во внешнем операторе проверяется, не пустая ли введенная пользователем текстовая строка. И если строка не пустая, то во внутрен­ нем условном операторе проверяется, не превышает ли длина строки значение 1 О . В зависимости от истинности или ложности этих условий в консольное окно выводятся разные сообщения. 126 Управля ющие инструкции Для записи текстового значения, введенного пользователем, использу­ ется переменная t x t . Значение считывается командой txt=Co n s o l e . Re adL i n e ( ) . Во внешнем условном операторе проверяется условие t x t ! = " " . Если условие ложно, то выполняется команда C o n s o l e . W r i t e L i n e ( " Жал ь , ч т о не в в ели т е к с т ! " ) в е l s е -блоке внеш­ него условного оператора. Если же условие t x t ! = " " во внешнем ус­ ловном операторе истинное, то в i f-блоке сначала выполняется коман­ да C on s o l e . Wr i t e L i n e ( " Спа сибо , ч т о в в ели т е к с т ! " ) , после чего на сцену выходит внутренний условный оператор. В этом опера­ торе проверяется условие txt . Length> l O . Здесь мы воспользовались свойством L e n gth, которое возвращает количество символов в тексто­ вой строке. Следовательно, условие txt . Length> l O истинно в случае, если текстовая строка, на которую ссылается переменная t x t , содер­ жит больше 1 0 символов. Если так, то выполняется команда Co n s o l e . W r i t e L i n e ( " O г o , как мн о г о букв ! " ) в i f-блоке внутреннего ус­ ловного оператора. Если условие t x t . L e n g t h > 1 О ложно, то выпол­ няется команда C o n s o l e . Wr i t e L i n e ( " О г о , как мало букв ! " ) в е l s е -блоке внутреннего условного оператора. Таким образом, возможны три принципиально разных случая: • пользователь не ввел текст ; • пользователь ввел текст, состоящий больше чем из 1 О символов ; • пользователь ввел текст, состоящий не больше чем из 1 О символов. Результат выполнения программы может быть таким (здесь и далее жирным шрифтом выделено значение, которое вводит пользователь). [1i!J Резул ьтат выполнения программы (из листинга 3.3 ) Введите текст : Изучаем С# Спасибо , что ввели текст ! Ого, как мало букв ! Это пример ситуации, когда введенный пользователем текст содержит не больше 1 О символов. Если пользователь вводит текст, состоящий из более чем 10 символов, то результат может быть следующим. [1i!J Резул ьтат выполнения программы (из листинга 3.3 ) Введите текст : Продоmкаем изучать С# 127 Глава 3 Спасибо , что ввели текст ! Ого, как много букв ! Наконец, если пользователь не вводит текст, то результат выполнения программы такой. � Резул ьтат выпол нения программы (из листинга 3.3 ) Введите текст : Жаль , что не ввели текст ! Еще один пример, рассматриваемый далее, иллюстрирует схему с вло­ женными условными операторами, когда каждый следующий вну­ тренний условный оператор формирует е l s е -блок внешнего условно­ го оператора. Программа, представленная в листинге 3.4, выполняется по простой схеме: пользователь вводит целое число (предположительно, от 1 до 4), а программа выводит текстовое название этого числа. � Листинг 3. 4 . Определение ч исл а us ing Sys tem; c l a s s Anothe rNe stedi fDemo { s tatic vo id Main ( ) { 1 1 Переменная для запоминания введенного числа : int numЬe r ; 1 1 Отображение сообщения : Console . Write ( " Bвeдитe целое число : " ) ; 1 1 Считывание числа : numbe r= Int 3 2 . Parse ( Console . Read1ine ( ) ) ; 1 1 Е сли введена единица : i f ( numЬer==l ) Console . WriteLine ( "Eдиницa " ) ; 1 1 Е сли введена двойка : e l s e i f ( numbe r==2 ) Console . WriteLine ( " Двoйкa " ) ; 1 1 Е сли введена тройка : e l s e i f ( numbe r==З ) Console . WriteLine ( " Tpoйкa " ) ; 1 1 Е сли введена четверка : 128 Управля ющие инструкции e l s e i f ( numbe r==4 ) Console . WriteLine ( "Чeтвepкa " ) ; 1 1 Все прочие случаи : e l s e Console . WriteLine ( " Heизвecтнoe число " ) ; Программа «узнает� числа от 1 до 4 включительно. В начале выполне­ ния программы командой C on s o l e . W r i te ( " Введите целое чис ­ л о : " ) отображается приглашение ввести целое число. Число считы­ вается командой n umb e r = I n t 3 2 . P a r s e ( C o n s o l e . Re a dL i n e ( ) ) и записывается в переменную n umЬe r. Затем задействуется блок из вло­ женных условных операторов, в каждом из которых проверяется введен­ ное значение. При наличии совпадения в консольном окне появляется сообщение соответствующего содержания (название введенного числа). Если пользователь ввел число, не попадающее в диапазон значений от 1 до 4 , то выполняется команда C o n s o l e . W r i t e L i n e ( " Неиз в е с т н о е число " ) . Ниже показано, как может выглядеть результат выполнения программы, если пользователь вводит «знакомое� для программы чис­ ло (здесь и далее жирным шрифтом выделено введенное пользователем значение). [1i!J Резул ьтат выполнения программы (из листинга 3. 4) Введите целое число : 2 Двойка Если программе не удается идентифицировать число, то результат та­ кой. [1i!J Резул ьтат выполнения программы (из л истинга 3. 4) Введите целое число : 5 Неизвестное число Как мы увидим далее, вложенные условные операторы - далеко не единственный способ организовать проверку нескольких условий в программе. 129 Глава 3 Оператор выбора switch Нет-нет, шуба подождет. Я считаю, что глав­ ное - посмотреть на мир. из к/ф «Бриллиан товая рука» Оператор выбора swi tch позволяет выполнить проверку значения не­ которого выражения. В языке С# выражение, которое проверяется с по­ мощью оператора выбора swi t ch, может возвращать значение целочис­ ленного, символьного или текстового типа. Описывается оператор выбора следующим образом. Сначала указывается ключевое слово swi t ch, после которого в круглых скобках следует проверяемое выражение. Тело операто­ ра выбора заключается в фигурные скобки. Там описываются с а s е-блоки. Каждый такой блок начинается с ключевого слова c a s e . После него указы­ вается контрольное значение (заканчивается двоеточием). Каждый с а s е ­ блок содержит набор команд, заканчивающийся инструкцией break. Кон­ трольные значения в с а s е-блоках сравниваются со значением выражения в switсh-инструкции. Если совпадение найдено, то выполняются коман­ ды в соответствующем с а s е-блоке. На случай, если совпадения нет, в опе­ раторе выбора s w i t ch может быть предусмотрен блок de fau l t . Общий шаблон описания оператора выбора swi tch с тремя с а s е-блоками пред­ ставлен ниже (жирным шрифтом выделены ключевые элементы шаблона): switсh(выражение){ case значение 1: 1 1 Команды break ; case значение 2 : 1 1 Команды break ; case значение 3 : 1 1 Команды break ; default : 11 Команды break ; 130 Управля ю щие инструкции Блок de fau l t не является обязательным - его можно не указывать со­ всем. На рис. 3.6 показана схема, иллюстрирующая способ выполнения оператора выбора s w i t c h . ' 1 1 ' , ..,, , - , , ... " - - - - - - - <: - - - ... " " , " - - - - ... ' " s w i t с h - и н с трук ц и я ... " " " ' 1 . . .... , 1, ' ' ' ' 1 ' , . ... _ _ ... ... " , ' ' ' ... ... " -�... 1 -- - ... ... _, , , ' , ... _ _ _ _ _ ... ... _ _ _ _ _ _ _ ... Сра в н е н и е саsе- б Совпадение лок , ' · - - ,' l _ К ом анды О __,] _ _ _ ___ _ Н е с о в п а де н и е break Сра в н е н и е саsе- б совпадение лок К оманды н есовпаде н и е Сра в н е н и е саsе- б С о в п а де н и е лок Команды Н е с о в п а де н и е Сра в н е н и е ,---�----. б са s е - лок сов п а д е н и е К оманды Н е с о в п а де н и е -- -- ( �. (,._.. d_e_f_a__ u l�t--_б_л_o_к--1= --. == A:c:,=::::>(::: , , , ... ' · · - ( --- не ' , ... ... _ , ,, ... ' ' \" " , ' о б я з а тел ь ный _ ... ... ," " ' " ... ... " блок ' ... ... ... , )-- : ' / ' _к _о _м _а.вды _ _ _ _ _ О break ) _ _ " ... ... _ _ _ _ _ , ,' "" - - - ' � Рис. 3. 6. В ы полнение оператора вы б ора 131 Глава 3 Таким образом, при выполнении оператора выбора сначала вычисляется значение выражения в s w i t сh-инструкции. Полученное значение по­ следовательно сравнивается с контрольными значениями в с а s е -бло­ ках. При первом же совпадении выполняются команды в соответству­ ющем с а s е -блоке - вплоть до инструкции b r e a k. Если совпадение не найдено и в операторе выбора есть de fau l t -блoк, то выполняются команды в этом блоке. Если совпадение не найдено и de f a u l t-блока в операторе выбора нет, то ничего не происходит. � ПОДРОБН ОСТИ И усл о в н ы й операто р , и оператор выбора, и рассматриваемые далее операторы цикл а - все переч исленные управл я ющие и н ­ струкци и есть не тол ько в языке С# , но и , скажем , в языках С++ и Java . Даже бол ьше - там они п рактически такие же , как в язы­ ке С# . И в плане си нтаксиса, и в плане п р и н ципов выпол нения . Но вот оператор выбора в С# немного особ ы й . Во-первых, кро­ ме цел оч исленных и сим вол ьных вы раже н и й в операторе выбора мо гут испол ьзоваться и текстовые вы ражения . Такого нет в языке С++ (там п роверяемое в ы ражение может быть целоч исл е н н ы м или символ ь н ы м ) . Во - вторых, в С# в операторе выбора, есл и с а s е ­ блок непусто й , он должен заканчи ваться Ь r е а k - и нструкцие й . В от­ л и ч и е от С#, в языках С++ и Java с а s е -блок может заканчи ваться инструкцией b r e a k , но это не обязател ьно. Вообще , инструкция b r e a k испол ьзуется для завершения работы оп ераторов цикл а или оператора выбора. Само по себе завершение ко манд в с а s е - бл оке оператора выбора работу это го оператора не завершает. Други­ м и сл овам и , есл и в каком -то с а s е - бл оке команды выпол н ил и с ь , т о это к автоматическому завершению в ы п о л н е н и я всего операто ­ ра выбора не п р и водит. Чтобы заверш ить оператор выбора, нужна инструкция b r e a k . Ч исто гипотетически , есл и бы с а s е - бл ок не за­ кан ч и вался инструкцией b r e a k , то п осл е завершения выполнения команд в это м бл оке должно было бы начаться выпол нение команд в следующем с а s е - блоке (даже есл и нет сов падения с контрол ь­ ным значением в этом блоке ) . В языках С++ и Java все так и п роис­ ходит. Что касается языка С # , то здесь п р и про верке знач ения в ы ­ ражения и срав нении е г о с контрол ь н ы м и знач е н и я м и с а s е - бл оки могут переби раться не в том по рядке , как они указан ы в операторе выбора. П оэтому в ы пол н е н и е кода необходимо огранич ить л и ш ь блоком , для которого выявлено сов падение контрол ьного значе­ н и я со значением п роверяемого вы ражения . Как бы там н и был о , но в языке С# непустые с а s е - блоки и блок de fau l t заканчива­ ются инструкцией b r e a k . Как и зачем испол ьзуются пустые с а s е ­ блоки - обсудим немного позже . 132 Управля ющие инструкции Несложно заметить, что в некотором смысле оператор выбора напоми­ нает блок из вложенных условных операторов, хотя полной аналогии здесь, конечно, нет. Небольшой пример, в котором использован опера­ тор выбора, представлен в листинге 3.5. Фактически это альтернативный способ реализации программы с вложенными условными операторами, в которой по введенному пользователем числу определяется название этого числа (см. листинг 3.4). Но на этот раз вместо условных операто­ ров использован оператор выбора s w i t ch. � Листинг 3. 5 . З накомство с оператором выбора u s ing Sys tem; u s ing Sys tem . Windows . Forms ; u s ing Micros oft . VisualBas i c ; c l a s s SwitchDemo { s tatic vo id Main ( ) { / / Переменная для запоминания введенного числа : int numЬer; / / Переменная для записи названия числа : s t ring name ; / / Считывание числа : numbe r=I nt 3 2 . Parse ( Interaction . I nputBox ( / / Текст над полем ввода : "Введите число : " , / / Заголовок окна : "Число " ) ); / / Использование оператора выбора для определения / / названия введенного числа : switch ( numЬe r ) { case 1 : / / Если ввели число 1 nаmе=" Единица " ; / / Название числа break; / / Завершение блока case 2 : / / Если ввели число 2 133 Глава 3 nаmе=" Двойка " ; / / Название числа break; / / Завершение блока case 3 : / / Если ввели число 3 nаmе=" Тройка " ; / / Название числа break; / / Завершение блока case 4 : / / Если ввели число 4 nаmе= "Четверка " ; / / Название числа break; / / Завершение блока de fault : / / Ввели другое число / / Текст сообщения : nаmе= " Неизвестное число " ; break; / / Завершение блока } / / Завершение оператора выбора / / Отображение сообщения : Me s s ageBox . Show ( name , "Чиcлo " ) ; Предыдущая версия программы была консольной. Эта версия для вво­ да числа и вывода сообщения использует диалоговые окна. Но это от­ личие �косметическое» . Идеологически важное отличие состоит в том, что здесь мы используем оператор выбора s w i t c h . Целочисленная переменная numb e r предназначена для записи числа, которое вво­ дит пользователь. Текстовая переменная n ame нужна для того, чтобы сформировать и запомнить текстовое значение - название введенного пользователем числа. Для считывания числа используем статический метод I np u t B o x ( ) класса I n t e r a c t i o n . Для преобразования тек­ стового представления числа в целое число используем статический метод P a r s e ( ) из структуры I n t 3 2 . Результат записывается в пере­ менную numb e r . Для проверки значения переменной numb e r использован оператор выбора. Проверяемым выражением в нем является эта переменная. В с а s е -блоках указаны контрольные значения - целые числа от 1 до 4 включительно (всего четыре с а s е -блока). При наличии совпадения значения переменной numb e r и контрольного значения в с а s е -блоке 134 Управля ю щие инструкции получает значение переменная name (значение переменной - название введенного числа). На случай, если совпадения не будет, предусмотрен de f a u l t-блок. В этом блоке значением переменной name присваивает­ ся текст, сообщающий о невозможности идентифицировать число. Таким образом, было или не было совпадение, переменная n ame по­ лучит значение. Это значение используется в команде Me s s a g e B o x . Show ( name , " Число " ) , которой отображается диалоговое окно с сооб­ щением (название числа или сообщение о том, что число неизвестно). На рис. 3.7 показано окно с полем ввода, в которое введено число 2 . Число Рис. 3. 7. В поле введено ч исло 2 После подтверждения ввода появляется новое диалоговое окно, пока­ занное на рис. 3.8. Число � Двойк1 ок Рис. 3.8. Окно с соо б щением после ввода числа 2 На рис. 3.9 показано окно с полем ввода, в котором пользователь указал число 5 . ЧиU10 Рис. 3. 9 . В поле введено ч исло 5 135 Глава 3 После щелчка по кнопке ОК появляется окно с сообщением о том, что число неизвестно. Окно показано на рис. 3. 1 0. Чием � Нrю�sктн о е ч�tсло ок Рис. 3. 1 О. Окно с соо б щением после ввода ч исла 5 Стоит заметить, что если пользователь вместо ввода числа в окне с по­ лем щелкнет, например, кнопку Отмена, то возникнет ошибка. Жела­ ющие могут подумать, как следует модифицировать программный код, чтобы подобная ситуация обрабатывалась корректно. G) Н А ЗАМ ЕТ КУ В программе испол ьзовано нескол ько пространств имен . П ростран­ ство имен Sys tem . Windows . Forms необходи мо подкл ючить для ис­ пол ьзования статического метода Show ( ) из класса Me s s ageBox . Метод отображает диалоговое окно с сообщением ( в данном слу­ чае назван ие введенного пользователем ч исла) . Пространство имен Mi c ro s o ft . V i s u a l Ba s i c нужно для испол ьзования статического метода I nputBox ( ) из класса Interaction . Метод отображает окно с полем ввода . Напом н и м , что в данном случае мы п рибегаем к по­ мощи средств разработки языка Visual Basic. Наконец, пространство имен Sys tem нам понадобилось, поскол ьку мы используем стати­ ческий метод Parse ( ) из структуры I n t 3 2 . Вообще-то м ы еще ис­ пол ьзуем и текстовый тип S t r i n g. Это класс из п ространства имен Sys tem. Но в программе ссылка на текстовый тип выпол нена через идентификатор s t ring, явля ющийся псевдонимом для инструкции Sys tem . S t r i n g. Так что s t r ing можно было бы испол ьзовать и без подкл ючения пространства имен Sys tem. Как отмечалось ранее, с а s е -блоки в операторе выбора могут быть пу­ стыми. Так поступают в случае, если необходимо, чтобы для несколь­ ких контрольных значений выполнялись одни и те же команды. Если так, то вместо дублирования команд в соответствующих с а s е -блоках (что неудобно и не очень рационально) используют несколько идущих один за другим пустых блоков. Причем в таких пустых с а s е -блоках нет 136 Управля ющие инструкции даже Ь r е а k-инструкций. Пример подобного подхода проиллюстриро­ ван в следующей программе, представленной в листинге 3.6. � Листинг 3.6. Оператор выбора с пустыми са sе -блоками u s ing Sys tem; u s ing Sys tem . Windows . Forms ; u s ing Micros oft . VisualBas i c ; c l a s s Anothe rSwitchDemo { s tatic vo id Main ( ) { / / Переменная для запоминания введенного числа : int numЬer; / / Переменная для текста сообщения : s t ring txt= " " ; / / Началь ное значение переменной / / Считывание числа : numbe r=I nt 3 2 . Parse ( Interaction . I nputBox ( / / Текст над полем ввода : "Введите целое число от 1 до 9 : " , / / Заголовок окна : "Число " ) ); / / Проверка значения переменной numbe r : switch ( numЬe r ) { case 1: case 9 : / / Оператор выбора / / Е сли значение 1 / / Е сли значение 9 / / Текст сообщения : txt=" Bы ввели нечетное , \n но не простое число . " ; break; / / Завершение блока case 2 : / / Е сли значение 2 case 3 : / / Е сли значение 3 case 5 : / / Е сли значение 5 case 7 : / / Е сли значение 7 137 Глава З / / Текст сообщения : txt=" Bы ввели простое число . " ; break; / / Завершение блока case 4 : / / Е сли значение 4 case 8 : / / Е сли значение 8 / / Текст сообщения : txt=" Bы ввели число - степень двойки . " ; break; / / Завершение блока case 6 : / / Е сли значение 6 / / Текст сообщения : txt=" Bы ввели 6 - совершенное число . " ; break; / / Завершение блока / / Отображение диалогового окна с сообщением : Me s s ageBox . Show ( tx t , "Чиcлo " ) ; Данная программа в некотором смысле напоминает предыдущую. Сна­ чала пользователю предлагается ввести целое число (в диапазоне от 1 до 9 ) . Результат записывается в переменную numb e r . После этого с по­ мощью оператора выбора проверяется значение этой переменной. Мы �классифицируем» числа по следующим категориям: • Простые числа - числа, которые не имеют других делителей, кроме единицы и себя самого. В диапазоне чисел от 1 до 9 простыми явля­ ются числа 2 , 3, 5 и 7 . • Числа, которые являются степенью двойки - в диапазоне от 1 до 9 2 3 в эту категорию попадают числа 4 (2 4) и 8 (2 8). = = • Число 6 является совершенным - сумма его делителей (числа 1, 2 и 3 ) равна самому этому числу. Следующее совершенное число - это 2 8 (его делители 1 , 2 , 4 , 7 и 1 4 в сумме дают 2 8 ), но оно не попадает в интервал от 1 до 9 . • Нечетные числа, которые при этом н е являются простыми - в ука­ занном диапазоне это числа 1 и 9 . 138 Управля ю щие инструкции Введенное пользователем число с помощью оператора выбора «припи­ сывается� к одной из перечисленных групп. И в этом операторе выбора мы используем пустые с а s е-блоки. Например, есть в операторе выбо­ ра такая конструкция (комментарии для удобства удалены): case 2 : case 3 : case 5 : case 7 : txt= "Bы ввели простое число . " ; break; Как это работает? Допустим, пользователь ввел значение 2. При срав­ нении этого значения с контрольными значениями в с а s е -блоке со­ впадение имеет место для блока c a s e 2 . Поэтому будут выполнены все команды от места, где идентифицировано совпадение, до первой ин­ струкции b r e a k. В результате выполняется команда t x t = " Bы в в ели про с т о е число . " . Теперь предположим, что пользователь ввел значе­ ние 5 . Совпадение проверяемого (переменная numb e r ) и контрольно­ го значения имеет место в блоке c a s e 5. Он пустой. Но это все равно с а s е-блок. Поэтому выполняются команды от места совпадения до пер­ вой инструкции b r e a k. Итог такой - выполняется команда t x t = " Bы в в ели про с т о е число . " . То же получаем, когда пользователь вводит значение 3 и 7 . Прочие блоки в операторе выбора работают по такому же принципу. Внешне все выглядит следующим образом. При запуске программы на выполнение появляется окно с полем ввода, в которое следует ввести число от 1 до 9. Окно показано на рис. 3. 1 1 . Число Рис. З. 1 1 . Окно с полем для ввода ч исла в диапазоне от 1 до 9 Если пользователь вводит числа 1 или 9 , то следующим появляется окно, представленное на рис. 3. 1 2 . 139 Глава 3 ЧН<J>о Вы е в е1 ш нечflНое.. но не: npocтot чиС/lо. ок Рис. 3. 1 2 . Окно появляется , есл и пол ьзовател ь вводит ч исло 1 или 9 G) Н А ЗАМ ЕТ КУ В операторе выбора в одном из с а s е -блоков (для значений 1 и 9 ) при оп ределении текстового значения переменной txt в текстовом л итерале испол ьзована инструкция \ n . Напом н и м , что это инструк­ ция перехода к новой строке . П ри отображен и и соответствующего текста в том месте , где размещена инструкция \ n , выполняется пе­ реход к новой строке. Резул ьтат можно видеть на рис . 3 . 1 2 , где текст в диалоговом окне отображается в две строки . В случае, если пользователь вводит число 2 , 3 , 5 или 7 , появится окно, представленное на рис. 3. 1 3 . Число Вы в аt..n и простое число. ок Рис. 3. 1 3. Окно появляется , есл и пол ьзовател ь вводит ч исло 2, 3, 5 или 7 Если пользователь вводит число 6 , появляется окно, показанное на рис. 3. 1 4 . Число Вы авvш 6 · совершенное число. ок Рис. 3. 1 4 . Окно появляется , есл и пользовател ь вводит ч исло 6 140 Управля ющие инструкции Наконец, если пользователь вводит число 4 или 8, то появляется окно, показанное на рис. 3 . 1 5 . Число ок Рис. 3. 1 5 . Окно появляется , есл и пол ьзовател ь вводит ч и сла 4 или 8 Хочется обратить внимание на несколько обстоятельств. Во-первых, в операторе выбора мы не использовали de f a u l t - блок. Как упоми­ налось ранее, этот блок не является обязательным, чем мы, собствен­ но, и воспользовались. Но поскольку теперь в s w i t с h-операторе de f а и 1 t-блока нет, то теоретически может получиться, что в операторе переменной txt значение не будет присвоено - например, если пользо­ ватель введет число, которое не попадает в диапазон значений от 1 до 9 . И это проблема, поскольку переменная t x t используется в команде Me s s ageBox . S h o w ( t x t , " Число " ) после оператора выбора. Такие си­ туации компилятором отслеживаются, и выдается ошибка еще на этапе компиляции. Чтобы ее избежать, при объявлении переменной txt мы ей сразу присвоили пустое текстовое значение. В таком случае, даже если пользователь введет значение вне рекомендуемого диапазона и в опера­ торе выбора значение переменной t x t присвоено не будет, переменная останется со своим старым значением. Возможно, это не самый лучший способ решения проблемы, но, во всяком случае, при компиляции не бу­ дет ошибки. Также стоит заметить, что, если пользователь в поле ввода введет не чис­ ло, отменит ввод (щелкнув кнопку Отмена) или закроет окно щелчком по системной пиктограмме, возникнет ошибка. Чтобы ее избежать, мож­ но воспользоваться системой перехвата и обработки исключений. Как это делается, кратко будет показано в одном из разделов в конце главы. Обработке исключений также посвящена одна из глав во второй части книги. 141 Глава З Оператор ц и кла wh i l e З амечательная идея ! Ч то ж она мне самому в голову не пришла ? из к/ф �Ирония судьбы, WlU С легким паром» Оператор цикла позволяет многократно выполнять определенный набор команд. В языке С# существует несколько операторов цикла, и со всеми ними мы познакомимся. Но начнем с оператора цикла whi l e. У него до­ статочно простой синтаксис. Описание оператора начинается с ключевого слова whi l e. В круглых скобках после ключевого слова whi l e указьшает­ ся некоторое условие (выражение со значением логического типа). Затем в фигурных скобках указьшается блок из команд, формирующих тело опе­ ратора цикла. Общий синтаксис оператора цикла wh i l e, таким образом, следующий (жирным шрифтом выделены ключевые элементы шаблона): while (ycлoвиe ) { 1 1 Команды Выполняется оператор цикла wh i l e так. Сначала проверяется условие (в круглых скобках после ключевого слова w hi 1 е ). Если условие истинно (значение t rue ), то выполняются команды в теле оператора цикла. После этого снова проверяется условие. Если оно истинно, то снова выполняют­ ся команды в теле оператора цикла, после чего опять проверяется условие, и так далее. Оператор цикла выполняется до тех пор, пока при проверке условия оно не окажется ложным (значение fa l s e). Если при проверке условия оно оказывается ложным, команды в теле оператора цикла не вы­ полняются, работа оператора цикла завершается и управление передается следующей инструкции после оператора цикла. Схема выполнения опе­ ратора цикла wh i l e проиллюстрирована на рис. 3. 1 6. Стоит заметить, что команды в теле оператора выполняются блоком то есть условие в очередной раз проверяется только после того, как вы­ полнены все команды в теле оператора цикла. Условие, указанное в круг­ лых скобках после ключевого слова wh i l e , должно быть таким, чтобы при выполнении команд в теле оператора цикла оно в принципе могло измениться. Проще говоря, чтобы в операторе цикла начали выполнять­ ся команды, условие в самом начале должно быть равно t ru e . А чтобы оператор цикла в какой-то момент завершил выполнение, условие долж­ но стать равным f a l s e . Иначе получим бесконечный цикл. 142 Управля ющие инструкции ,/ ' ' ' ,. - -1 ' 1 , \ ... - - t r ue ' - - - <' - �- -�::;����:��:;; - -, Условие ... .:- " " " " _ _ _ ... .>, " _ _ _ _ _ _ , /, - ' ... ... ,, .... - - - - , , ' ,' ' .,., ) "' f a l se Команды Рис. 3. 1 6 . Выполнение оператора ци кла wh i l e (1) Н А ЗАМ ЕТ КУ Инструкция b r e a k , с которой м ы встретились при знакомстве с опе­ ратором выбора swi tch, позволяет завершать выполнение опера­ тора цикла. Есл и в теле оператора цикла выполняется инструкция b r e a k , то оператор ци кла п рекращает свое выполнение вне зави ­ симости о т того , какое условие. И н струкция cont inue может ис­ пол ьзоваться в теле оператора цикла для досроч ного завершения текущего ци кла ( итераци и ) . Есл и в теле оператора цикл а всего одна команда , т о фигурн ые скоб­ ки можно не использовать. Далее мы рассмотрим небольшой пример, в котором используется опе­ ратор цикла wh i 1 е. Программа очень простая - в ней вычисляется сум­ ма первых п нечетных чисел 1 + 3 + 5 + . . . + (2п - 1) (значение параме­ тра п в программе определяется с помощью переменной). � ПОДРОБН ОСТИ Для п роверки результатов вычислений можно воспол ьзоваться со­ 2 отношением 1 + 3 + 5 + . . . + ( 2п - 1 ) = п • Поэтому, например, есл и вычисляется сум ма первых 1 О нечетных чисел , то такая сум ма равна 1 00 , то есть 1 + 3 + 5 + . . . + 1 9 = 1 00 . Но в п рограм ме эти форму­ л ы не испол ьзуются - вычисление сум м ы вы полняется непосред­ ственным сум мирован ием слагаем ых. 143 Глава З Интересующий нас программный код представлен в листинге 3. 7. [1i!J Листинг З. 7 . Испол ьзование оператора цикла while us ing Sys tem; c l a s s Whi l eDemo { s tatic vo id Main ( ) { / / Количество слагаемых в сумме , индексная / / переменная и значение суммы : int n= l O , k=l , s=O ; / / Отображение сообщения : Console . Write ( " Cyммa 1 + 3 + 5 +".+ { 0 } = " , 2 * n - 1 ) ; / / Оператор цикла : whi l e ( k<=n ) { s+=2 * k - 1 ; / / Добавляем слагаемое в сумму k++ ; / / Новое значение индексной переменной / / Отображение вычисленного значения : Console . WriteLine ( s ) ; Результат выполнения программы такой. [1i!J Результат выполнения программы (из л истинга З. 7) Сумма 1 + 3 + 5 +".+ 1 9 = 1 0 0 В программе мы объявляем три целочисленные переменные. Перемен­ ная n со значением 1 О определяет количество слагаемых в сумме. Пере­ менная k с начальным значением 1 нам нужна для подсчета количества слагаемых, добавленных к значению суммы. Эту переменную будем на­ зывать индексной. Переменная s с начальным значением О нам пона­ добится для записи в эту переменную значения суммы. Перед началом вычислений командой C o n s o l e . W r i t e ( " Сумма 1 + 3 + 5 + ... + { О } = " , 2 * n - 1 ) в консольном окне выводится текстовое сообщение, формально обозначающее вычисляемую сумму. Текстовое значение 144 Управля ющие инструкции заканчивается знаком равенства, и переход к новой строке не выполня­ ется (поскольку использован метод W r i te ( ) ). � ПОДРОБН ОСТИ Koмaндa C o n s o l e . Wr i t e ( " Cyммa l + 3 + 5 +".+ { 0 } " , 2*n- l ) выполняется так. Отображается текстовое значение " Сумма l + 3 + 5 +".+ { О } " , но тол ько вместо инструкци и { О } подставляется значение вы ражения 2 * n - l , которое указано аргументом метода Wri te ( ) после текстовой строки . Здесь мы учл и , что есл и вычисляется сум ма п нечетных чисел , то по­ следнее слагаемое в такой сум ме равно 2п - 1 . = = Для вычисления суммы использован оператор цикла wh i l e . В операто­ ре цикла указано условие k<=n то есть оператор цикла будет выпол­ няться до тех пор, пока значение переменной k не превышает значения переменной n. Поскольку начальное значение переменной k равно 1 , а значение переменной n равно 1 О , то при первой проверке условия k<=n его значение равно t ru e (условие истинно). Поэтому начинается выполнение команд в теле оператора цикла. Там их всего две. Сначала командой s += 2 * k - 1 к текущему значению суммы прибавляется очеред­ ное слагаемое, после чего командой k + + значение индексной перемен­ ной увеличивается на единицу. - � ПОДРОБН ОСТИ М ы приняли в расчет, что есл и k это порядковый номер нечетного ч исла , то значение этого ч исла может быть выч ислено по формуле 2k - 1 . Поэтому на каждой итерации к переменной s , в которую зап и ­ сывается значение сум м ы нечетных чисел , прибавляется значение 2 * k- l . Чтобы получ ить значение очередного нечетного ч исла , мож­ но воспол ьзоваться той же формулой , но тол ько значен ие перемен­ ной k нужно увел и ч ить на еди н и цу. - После выполнения команд в теле цикла снова проверяется условие k<=n. Для использованных начальных значений переменных при вто­ рой проверке условие опять окажется истинным. Будут выполнены ко­ манды в теле оператора, потом снова начнется проверка условия. И так далее. Последний раз условие k<=n окажется истинным, когда значе­ ние переменной k будет равно значению переменной n. На этой итера­ ции к значению переменной s прибавляется значение 2 * n - 1 , а значение 145 Глава З переменной k станет больше на единицу значения переменной n. В ре­ зультате при проверке условия k<=n оно окажется ложным, и оператор цикла завершит свою работу. По завершении оператора цикла в переменную s записана искомая сум­ ма нечетных чисел. Поэтому командой C o n s o l e . W r i t e L i n e ( s ) вы­ численное значение отображается в консольном окне. G) Н А ЗАМ ЕТ КУ Есл и в рассмотренном выше п рограм мном коде переменной n вме­ сто значения 1 О присвоить значение О , то для сум м ы нечетных чисел п рограм ма выдаст значение О . Объяснение п ростое: при первой же проверке условия k<=n в операторе цикла, поскол ьку начал ьное значение переменной k равно 1 , а значение переменной n равно О , условие будет ложн ы м . Поэтому команды в теле оператора цик­ ла выполняться не будут. Следовател ьно, переменная s останется со своим начал ьным значением О . Оно и будет отображено в кон ­ сол ьном окне как результат вычислен и й . М ы могли реализовать программу для вычисления суммы нечетных чи­ сел немного иначе. Как минимум мы могли бы обойтись без индексной переменной k. Программа с �уменьшенным» количеством переменных приведена в листинге 3.8. � Листинг 3. 8 . Еще один пример использования оператора цикла while us ing Sys tem; c l a s s Anothe rWhileDemo { s tatic vo id Main ( ) { / / Количество слагаемых в сумме и значение суммы : int n= l O , s=O ; / / Отображение сообщения : Console . Write ( " Cyммa 1 + 3 + 5 + + { 0 } = " , 2 * n - 1 ) ; ". / / Оператор цикла : whi l e ( n > O ) { 146 s+=2 * n - 1 ; / / Добавляем слагаемое в сумму n -- ; / / Новое значение переменной n Управля ющие инструкции 1 1 Отображение вычисленного значения : Console . WriteLine ( s ) ; Результат выполнения этой программы точно такой же, как и в преды­ дущем случае (см. листинг 3.7). Но код немного изменился. Как отмеча­ лось выше, мы больше не используем переменную k. В некотором смыс­ ле ее «роль� играет переменная n. Теперь в операторе цикла проверяется условие n> О , а в теле оператора цикла выполняются команды s +=2 * n - 1 и n - - . Фактически здесь вычисляется сумма (2п - 1 ) + (2п - 3) + " . + 5 + 3 + 1 , которая, очевидно, равна сумме 1 + 3 + 5 + ". + (2п - 3) + (2п - 1 ) . Другими словами, м ы вычисляем ту ж е сумму, н о слагаемые добавляем в обратном порядке. При начальном значении переменной n выражение 2 * n - 1 дает значение последнего слагаемого в сумме. После уменьше­ ния значения переменной n на единицу выражение 2 * n - 1 дает значение предпоследнего слагаемого в сумме, и так далее, пока при единичном значении переменной n в соответствии с выражением 2 * n - 1 не получим значение 1 (первое слагаемое в сумме). G) Н А ЗАМ ЕТ КУ К моменту завершения оператора цикла значение переменной n равно О . Таким образо м , начал ьное значение переменной n «теряет­ ся » . В версии п рограммы с индексной переменной (см . листи н г 3 . 7) значение переменной n оставалось неизмен н ы м . Оператор ц и кла d o -wh i le Это в твоих руках все горит, а в его руках все работает ! из к/ф �покровские ворота» Синтаксис оператора цикла do -w h i 1 е напоминает синтаксис операто­ ра цикла wh i l e . Описание начинается с ключевого слова do, после ко­ торого в фигурных скобках указываются команды, формирующие тело оператора цикла. Затем после блока из фигурных скобок следует ключе­ вое слово wh i l e, и в круглых скобках - условие, истинность которого 147 Глава 3 является критерием для продолжения работы оператора цикла. Шаб­ лон описания оператора цикла do -wh i l e представлен ниже (жирным шрифтом выделены ключевые элементы шаблона): do{ 1 1 Команды }while (ycлoвиe ) ; Выполняется оператор цикла do -wh i l e следующим образом. Сначала выполняются команды в теле оператора цикла. После их выполнения проверяется условие, указанное после ключевого слова wh i l e. Если ус­ ловие окажется истинным, будут выполнены команды в теле операто­ ра цикла и проверено условие. Процесс завершается, когда при провер­ ке условия оно окажется ложным. Схема выполнения оператора цикла do - wh i l e проиллюстрирована на рис. 3 . 1 7 . Ко манды � ... - " - - ...,, , ""' - - - - ... ; - - - ... ' , , - ' · - ' t r ue . ' Усл о вие · ' �' ' " ' ' ' fal se ,1- - · w h i l е - и н с трук ц и я 1 , 1 - " _ " _ _ ... ; ,_ _ ... ... _ _ _ _ _ _ _, - - Рис. 3. 1 7 . В ы полнение оператора d o - wh i l e Если сравнивать оператор цикла do -wh i l e с оператором цикла wh i l e , то легко заметить главное отличие этих операторов. Выполнение опе­ ратора w h i 1 е начинается с проверки условия, и если условие истинно, то выполняются команды в теле оператора цикла. В операторе цикла do - wh i l e сначала выполняются команды в теле оператора, и только после этого проверяется условие. Получается, что в операторе цикла 148 Управля ющие инструкции d o - wh i l e команды из тела оператора будут выполнены по крайней мере один раз. С оператором wh i l e дела обстоят иначе: если при пер­ вой проверке условия оно окажется ложным, то команды в теле опера­ тора вообще выполняться не будут. Как пример использования оператора цикла d o - wh i l e мы рассмотрим программу вычисления суммы нечетных чисел - то есть программу, аналогичную той, что реализовалась с использованием оператора цикла whi l e (см. листинг 3.7). Рассмотрим программный код, представлен­ ный в листинге 3.9. [1i!J Листинг 3. 9 . И спользование оператора цикл а do-while u s ing Sys tem; c l a s s DoWhileDemo { s tatic vo id Main ( ) { / / Количество слагаемых в сумме , индексная / / переменная и значение суммы : int n= l O , k=l , s=O ; / / Отображение сообщения : Console . Write ( " Cyммa 1 + 3 + 5 + ". + { 0 } = " , 2 * n - 1 ) ; / / Оператор цикла : do { s+=2 * k - 1 ; / / Добавляем слагаемое в сумму k++ ; / / Новое значение индексной переменной } wh i l e ( k<=n ) ; / / Отображение вычисленного значения : Console . WriteLine ( s ) ; Результат выполнения программы точно такой же, как и для случая, ког­ да использовался оператор цикла whi l e . [1i!J Результат выполнения программы (из л истинга 3. 9) Сумма 1 + 3 + 5 + ". + 19 = 1 0 0 149 Глава З Алгоритм выполнения программы по сравнению с исходной верси­ ей (см. листинг 3.7) практически не изменился. Но отличие все же есть. В программе из листинга 3.7 (использован оператор цикла wh i l e) снача­ ла проверяется условие k<=n, а затем выполняются команды в теле цикла. В программе из листинга 3.9 (использован оператор do -wh i l e) сначала выполняются команды в теле цикла, а затем проверяется условие k<=n. Если значение переменной n не меньше 1, то разницы, в общем-то, нет. Но если значение переменной n меньше 1 , то программы будут выдавать разные результаты. Например, при нулевом значении переменной n пер­ вая программа выдает значение О для суммы чисел, а вторая выдает зна­ чение 1 . Причина в том, что при первой же проверке условия k<=n оно оказывается ложным. Но в случае с оператором цикла do -wh i l e перед проверкой условия один раз будут выполнены команды в теле оператора цикла. Поэтому значение переменной s станет равным 1 . Оператор ц и кла for - Один крокодил. Два крокодила. Три крокодила" . - Бредит, бедняг а. Похоже на тропическую лихорадку ! из м/ф <tПриключения капитана Врунгеля» Синтаксис оператора цикла for немного сложнее по сравнению с тем, как описываются операторы цикла whi l e и do -wh i l e. Описание опера­ тора цикла начинается с ключевого слова f o r . В круглых скобках после ключевого слова размещается три блока инструкций. Блоки инструкций разделяются точкой с запятой, а инструкции внутри блока - запятыми. Затем указывается блок команд в фигурных скобках. Они формируют тело оператора цикла. G) Н А ЗАМ ЕТ КУ Есл и тело оператора цикла содержит всего одну команду, то фигур­ ные скобки можно не испол ьзовать. Шаблон описания оператора цикла f o r представлен ниже (жирный шрифт выделяет основные элементы шаблона): fоr ( первый блок ; второй блок ; третий блок) { 1 1 Команды 150 Управля ющие инструкции Выполняется оператор цикла for следующим образом. Сначала выпол­ няются команды в первом блоке (в круглых скобках после ключевого слова f o r ) . Эти команды выполняются один и только один раз в самом начале работы оператора цикла. Поэтому в первый блок обычно поме­ щают команды, имеющие отношение к инициализации переменных, а сам блок нередко называют блоком инициализации. Во втором блоке размещается условие, поэтому второй блок обычно называют блоком ус­ ловия. Это условие проверяется сразу после выполнения команд в пер­ вом блоке. Если условие ложно, то на этом выполнение оператора цикла заканчивается. Если условие истинно, то выполняются команды в теле оператора цикла. После этого выполняются команды в третьем блоке. Затем проверяется условие во втором блоке. Если условие ложно, ра­ бота оператора цикла прекращается. Если условие истинно, то выпол­ няются команды в теле оператора цикла, затем в третьем блоке и снова проверяется условие. И так далее. Схема выполнения оператора цикла f o r проиллюстрирована на рис. 3. 1 8. - � - - - - - - - - -< , ---���� -----', � /' ( [Первый � ',>-- - - - �:::���-� '>' - - - - - - - - - --\ Тр е тий Второй бnох блох ( условие ) блох . : _ _ - - - -' ' _ ..:, " _/ � _ _ ... ... " ' f a l se ... ... ... _ _ _ , - , _ t r ue ' - - - - ... " .> ... ... " - - - - - . , , " ,с.. ... _ _ _ _ _ _ _ _ _ , Команды Рис. 3. 1 8. В ы полнение оператора цикла for G) Н А ЗАМ ЕТ КУ В третьем блоке обычно размещается команда , которой изменяется значение и ндексной перемен ной . Поэтому третий блок иногда на­ зы вают блоком и н кремента/декремента . 151 Глава 3 Имеет смысл обратить внимание читателя на несколько обстоя­ тельств. Во-первых, легко заметить, что разница между командами в теле оператора цикла и в третьем блоке достаточно условная. Дей­ ствительно, сначала выполняются команды в теле оператора цикла, а затем команды в третьем блоке. Поэтому имеет значение последо­ вательность команд, но не то, в каком месте они находятся (в теле оператора или в третьем блоке) . Во-вторых, блоки могут содержать по несколько команд (в таких случаях команды разделяются запяты­ ми), а могут быть пустыми. Пустым может быть и второй блок. В этом случае он эквивалентен блоку с истинным условием - то есть факти­ чески мы имеем дело с бесконечным циклом. Выход из такого беско­ нечного цикла может быть связан с использованием условного опе­ ратора и инструкции b r e a k (пример рассматривается чуть позже ) . Наконец, первый блок может содержать не просто команды присваи­ вания значений переменным, а команды, объявляющие переменную или переменные. С одной стороны, удобно. С другой стороны, такие переменные доступны только в пределах оператора цикла. Далее мы рассмотрим небольшие примеры, иллюстрирующие работу оператора цикла f o r . Сначала рассмотрим программу в листинге 3 . 1 0. Эта про­ грамма, как несложно догадаться, вычисляет сумму нечетных чисел, но на этот раз используется оператор цикла f о r . � Листинг 3. 1 О . И спол ьзование оператора цикл а for us ing Sys tem; c l a s s ForDemo { static vo id Main ( ) { / / Количество слагаемых в сумме , индексная / / переменная и значение суммы : int n=l O , k , s=O ; / / Отображение сообщения : Console . Write ( " Cyммa 1 + 3 + 5 +".+ { 0 } = " , 2 * n - 1 ) ; / / Оператор цикла : for ( k=l ; k<=n ; k++ ) { s+=2 * k - 1 ; / / Добавляем слагаемое в сумму / / Отображение вычисленного значения : 1 52 Управля ющие инструкции Console . WriteLine ( s ) ; Результат выполнения программы таков. � Резул ьтат выполнения программы (из л истинга З. 1 О ) Сумма 1 + 3 + 5 +".+ 1 9 = 1 0 0 Несложно заметить, что результат такой же, как и в рассмотренных ранее случаях. Что нового в программном коде? Как и ранее, объявляются три целочисленные переменные (n со значением 1 0 , k и s со значением О ). Но на этот раз переменной k значение при объявлении не присваивается. Переменная k получает единичное значение в начале выполнения операто­ ра цикла for, когда вьшолняется команда k= l в первом блоке. Эта коман­ да вьшолняется только один раз в самом начале работы оператора цикла. После ее выполнения проверяется условие k<=n во втором блоке. При тех значениях, которые присвоены переменным k и n, условие истинно. Поэто­ му выполняется команда s +=2 * k - 1 в теле оператора цикла и команда k++ в третьем блоке. Затем снова проверяется условие. То есть, несмотря на из­ менившийся синтаксис программного кода, последовательность выполне­ ния команд, по сути, осталась прежней. Отсюда и результат. Еще одна небольшая вариация программы с оператором цикла f о r пред­ ставлена в листинге 3. 1 1 (для сокращения объема программного кода не­ принципиальные комментарии удалены из программы). � Листинг З. 1 1 . Второй пример испол ьзования оператора цикл а for u s ing Sys tem; c l a s s ForDemoTwo { static vo id Main ( ) { int n=l O , s=O ; Console . Write ( " Cyммa 1 + 3 + 5 +".+ ( 0 ) = " , 2 * n - 1 ) ; for ( int k=l ; k<=n ; k++ ) { s+=2 * k - 1 ; Console . WriteLine ( s ) ; 153 Глава 3 Результат выполнения программы такой же, как и в предыдущем слу­ чае. Особенность программного кода в том, что переменная k объявля­ ется и инициализируется в первом блоке в операторе цикла f o r . Как следствие, такая переменная доступна только в операторе цикла f o r , н о н е з а его пределами. Проще говоря, если б ы м ы захотели проверить значение переменной k после завершения выполнения оператора цикла, то на этапе компиляции возникла бы ошибка. Другой способ организации оператора цикла, когда блоки в f о r ­ инструкции содержат п о несколько команд, представлен в программе в листинге 3. 1 2 . [1i!J Листинг 3. 1 2. Трети й пример использования оператора цикла for us ing Sys tem; c l a s s ForDemoThree { static vo id Main ( ) { int n=l O , k , s ; Console . Write ( " Cyммa 1 + 3 + 5 + + ( 0 ) = " , 2 * n - 1 ) ; ". for ( k=l , s=O ; k<=n ; s +=2 * k - 1 , k++ ) ; Console . WriteLine ( s ) ; В данном случае переменные n , k и s объявляются в главном методе программы, но начальные значения переменным k и s присваиваются в операторе цикла (в первом блоке). Команда s + = 2 * k- 1 из тела опера­ тора цикла перенесена в третий блок, и теперь там две команды (кроме упомянутой команды s + = 2 * k - 1 там еще есть инструкция k + + ). В ре­ зультате тело оператора цикла оказалось пустым, поэтому сразу после fо r-инструкции стоит точка с запятой. � ПОДРОБН ОСТИ Есл и тело оператора цикла пустое , то мы можем л ибо поставить точ ­ ку с запятой после fоr-инструкции , л ибо поставить пустые фигурные скобки . Если ничего этого не сделать, то к оператору цикла будет относиться следующая (после оператора цикла) команда в програм­ ме - в данном случае это была бы команда C on s o l e . W r i t e L i n e ( s ) . 1 54 Управля ющие инструкции Противоположная к предыдущей ситуация реализована в программном коде в листинге 3. 1 3 там два блока (первый и третий) в f о r-инструк­ ции оператора цикла пустые. - � Л истинг З. 1 З. Четвертый пример использования оператора цикла fог u s ing Sys tem; c l a s s ForDemoFour { static vo id Main ( ) { int n=l O , k=l , s=O ; Console . Write ( " Cyммa 1 + 3 + 5 +".+ ( 0 ) = " , 2 * n - 1 ) ; for ( ; k<=n ; ) { s+=2 * k - 1 ; k++ ; Console . WriteLine ( s ) ; Здесь переменные k и s получают начальные значения при объявле­ нии, поэтому в первом блоке нет необходимости выполнять присваи­ вание значений переменным. Что касается третьего блока, то команды из него вынесены в тело оператора цикла: там теперь есть инструкции s += 2 * k- 1 и k ++. Поэтому третий блок тоже пустой. Наконец, совсем �экзотический� случай представлен в программном коде в листинге 3. 1 4. В этой программе в операторе цикла f o r все три блока (включая второй блок с условием) являются пустыми. � Л истинг З. 1 4 . П яты й пример использования оператора цикла for u s ing Sys tem; c l a s s ForDemoFive { static vo id Main ( ) { int n=l O , k=l , s=O ; Console . Write ( " Cyммa 1 + 3 + 5 +".+ ( 0 ) = " , 2 * n - 1 ) ; for ( ; ; ) { / / Все блоки пустые s+=2 * k - 1 ; 155 Глава 3 k++ ; / / Условный оператор : i f ( k>n) break; / / Завершение оператора цикла Console . WriteLine ( s ) ; Напомним, что если второй блок с условием пустой, то это эквивалентно истинному условию во втором блоке. Поскольку такое �пустое� усло­ вие изменить уже нельзя, то формально мы получаем бесконечный цикл. Но его можно остановить. Для этого использована инструкция b r e a k. Она задействована вместе с условным оператором. В теле оператора цикла, после выполнения команд s += 2 * k- 1 и k++, выполняется услов­ ный оператор. В нем проверяется условие k>n. Если условие истинно, то инструкцией b r e a k завершается выполнение оператора цикла. G) Н А ЗАМ ЕТ КУ Условие k<=n , которое м ы размещал и во втором блоке в операторе цикла f o r , было условием продолжения работы оператора цикла. Условие k>n , которое м ы испол ьзовал и в условном операторе, яв­ ляется условием завершения оператора ци кла. То есть условие k>n является « П РОТИ ВОП ОЛОЖН Ы М » к условию k<=n . Во всех рассмотренных примерах результат выполнения один и тот же (см. результат выполнения программы из листинга 3. 1 0). И нструк ц ия безусловн ого перехода g oto Нормальные герои всегда идут в обход. из к/ф �А й болит - 66 » Инструкция безусловного перехода g o t o позволяет выполнить переход к определенному месту программного кода, помеченному с помощью метки. Метка - это обычный идентификатор, размещаемый в програм­ мном коде. Метку не нужно объявлять. Она просто указывается, и по­ сле нее следует двоеточие. Если в программном коде воспользоваться 156 Управля ющие инструкции инструкцией goto, указав после нее метку из программного кода, то вы­ полнение такой команды приведет к тому, что следующей будет выпол­ няться команда в строке, выделенной с помощью метки. Таким образом, инструкция g o t o позволяет �прыгать� из одного ме­ ста программного кода к другому, и это не может не вызывать неко­ торых опасений. Стоит признать, что они небезосновательны. Суще­ ствует устоявшееся мнение, что наличие в программном коде команд с инструкцией g o t o признак дурного тона в программировании. Другими словами, использование инструкции g o t o это не тот путь, которым следует идти. Но прежде чем отказаться от использования какого-либо механизма, интересно хотя бы поверхностно оценить его возможности. - - G) Н А ЗАМ ЕТ КУ Причина такого недоброго отношения к инструкции goto связана с тем , что п рограм мные коды , нап исан ные с использованием этой инструкци и , обычно запутан ы , сложны для анал иза, да и быстрота их выполнения вызы вает нарекан и я . Как иллюстрацию к использованию инструкции g o t o м ы рассмотрим программу, в которой традиционно вычисляется сумма нечетных чи­ сел. Но на этот раз операторы цикла использовать не будем, а прибег­ нем к помощи условного оператора и, как несложно догадаться, ин­ струкции g o t o . Интересующий нас программный код представлен в листинге 3. 1 5 . [1i!J Л истинг З. 1 5 . И спользование инструкции goto u s ing Sys tem; c l a s s GotoDemo { static vo id Main ( ) { 1 1 Переменные : количество слагаемых , индексная 11 переменная и значение суммы : int n=l O , k=l , s=O ; 1 1 Отображение сообщения : Console . Write ( " Cyммa 1 + 3 + 5 +".+ { 0 } " , 2*n-1 ) ; 1 1 Использование метки : 157 Глава 3 mylabe l : / / Добавляем слагаемое в сумму : s+=2 * k - 1 ; / / Изменение значения индексной переменной : k++ ; / / Использование инструкции goto : i f ( k<=n ) goto myl abel ; / / Переход к помеченному коду / / Отображение результата вычислений : Console . WriteLine ( s ) ; Результат выполнения этой программы такой же, как и в случаях, когда мы использовали операторы цикла. � Результат выполнения программы ( из листинга З. 1 5) Сумма 1 + 3 + 5 +".+ 19 = 1 0 0 Как же выполняется этот программный код? В принципе, все достаточно просто. В программе использованы, как и ранее, три целочисленные пе­ ременные n, k и s, назначение которых не изменилось - это количество слагаемых, индексная переменная и значение суммы чисел. Переменные получают начальные значения при объявлении. В программе использо­ вана метка myl abe l . Это простой идентификатор, который заканчива­ ется двоеточием. Само по себе наличие метки в программном коде ни­ каких ощутимых последствий не имеет. То есть факт наличия метки на логику выполнения программного кода не влияет. На выполнение программного кода влияет наличие инструкции g o t o . Более конкретно, код выполняется следующим образом. После объявления и инициализа­ ции переменных n, k и s и отображения сообщения о вычислении сум­ мы (команда C on s o l e . W r i te ( " Сумма 1 + 3 + 5 +".+ { О } = " , 2 * n - 1 ) ) выполняются команды s + = 2 * k - 1 (добавление слагаемого в сумму) и k + + (увеличение значения переменной на единицу). Вооб­ ще, поскольку данные команды не находятся в теле оператора цикла, то они должны бьши бы выполняться только один раз. Интрига появля­ ется, когда дело доходит до выполнения условного оператора. В нем про­ веряется условие k<=n. Если условие истинно, то выполняется коман­ да g o t o myl abe l . Эта команда означает, что продолжать выполнение 158 Управля ющие инструкции программы следует с того места, которое выделено меткой myl abe l . Несложно заметить, что сразу после метки myl abe l находится ко­ манда s += 2 * k- l . Поэтому в результате перехода из-за команды g o t o m у 1 аЬе 1 снова будет выполняться команда s += 2 * k- 1 , а затем и коман­ да k + + . После этого на сцену опять выходит условный оператор, и при истинном условии k < = n выполнение программы будет продолжено с места, выделенного меткой myl abe l . Поскольку каждый раз значение переменной k увеличивается на единицу, то в какой-то момент при про­ верке условия k<=n оно окажется ложным, и команда g o t o myl abe l выполнена не будет. Тогда выполнится следующая команда после ус­ ловного оператора, которой отображается вычисленное значение для суммы нечетных чисел. Таким образом, получается, что с помощью ус­ ловного оператора и инструкции g o t o мы организовали некое подобие оператора цикла, хотя ни один из операторов цикла использован не бьш. G) Н А ЗАМ ЕТ КУ В плане алгоритма выполнения программы с инструкцией goto она аналогична п рограм ме для выч исления сум м ы нечетных чисел , реа­ л изованной с испол ьзованием оператора цикла do -wh i l e (см . л ис­ ти нг 3 . 9 ) . П ерехват искл ю чени й Гр аждане , если вы увидите Б армалея, который собирается делать добрые дела, схватите его и поставьте в угол ! Очень вас об этом прошу. из к/ф �А й болит - 66 » Непосредственного отношения к управляющим инструкциям обработ­ ка исключений не имеет. Вообще, перехват исключений - тема большая и многогранная. Ей посвящена отдельная глава второй части книги. Здесь мы только кратко познакомимся с тем, как в языке С# на наибо­ лее простом уровне может осуществляться перехват и обработка исклю­ чений. Причин для такого �преждевременного� знакомства две. Во-пер­ вых, те приемы, которые мы рассмотрим далее, полезны в прикладном смысле. С их помощью можно значительно повысить надежность и об­ ласть применимости программных кодов. Во-вторых, некоторая анало­ гия между обработкой ошибок и управляющими инструкциями все же 159 Глава 3 есть - в определенном смысле это напоминает синтаксическую кон­ струкцию, подобную условному оператору. Но если в условном опера­ торе проверяется логическое выражение (условие), то при обработке ис­ ключений роль «условия» играет ошибка, которая может возникнуть, а может не возникнуть. G) Н А ЗАМ ЕТ КУ Впоследстви и , когда м ы познакоми мся с искусствен н ы м генериро­ ван ием искл ючен и й , м ы увиди м , что аналогии между управля ющи­ м и инструкциями и обработкой искл ючений еще более глубокие. Так что же такое исключение и что с ним можно делать? Дело в том, что при выполнении программы могут возникать ошибки. Речь не идет об ошибках, связанных с неправильно написанным программным кодом. Речь об ошибках, которые связаны с тем или иным режимом выполне­ ния программы. Например, программа выдает запрос для ввода пользо­ вателем числа. Но ведь нет никакой гарантии, что пользователь введет именно число. В таком случае, когда программа попытается преобра­ зовать введенное пользователем значение в число, может возникнуть ошибка. И возникнет она или нет - всецело зависит от пользователя, а не от программиста. Что может сделать программист? Программист может предусмотреть реакцию программы в случае, если пользователь вводит не число. Вот здесь и окажется полезной обработка исключи­ тельных ситуаций. � ПОДРОБН ОСТИ Есл и при выполнении п рограммы п роисходит ошибка ( говорят, что генери руется искл ючение) , то в соответстви и с типом ош ибки соз­ дается специальный объект, который назы вается объектом ошиб­ ки , ил и искл ючен ием . П рограмму можно «науч ить» реагировать на ошибки . В таких случаях речь идет о перехвате и обработке ис­ кл ючен и й . То есть обработка искл ючений - это « комплекс меро­ прияти й » , выполняемых в связи с тем , что в п рограмме в п роцессе выполнения возн и кла ошибка . Есл и в п рограмме обработка искл ю ­ чений не выпол няется , т о возникновение ош ибки п р иводит к завер­ шени ю выпол нения п рограм м ы . Итак, исходная постановка задачи формулируется следующим образом. Имеется некоторый программный код, при выполнении которого может 160 Управля ющие инструкции возникнуть ошибка. Необходимо так организовать программу, чтобы даже в случае возникновения ошибки она продолжала выполнение. Для решения этой задачи есть хороший рецепт. G) Н А ЗАМ ЕТ КУ Еще раз подчеркне м , что здесь м ы тол ько знакоми мся с механиз­ мом обработки искл ючен и й . М ы рассмотрим наиболее п ростой способ организовать такую обработку и не будем особо вдаваться в подробности . Обработке искл ючений посвящена отдел ьная глава второй части кн иги . Идея состоит в том, чтобы использовать t r y- c a t c h конструкцию. А именно, программный код, при выполнении которого может возник­ нуть ошибка (контролируемый код), помещается в t r у-блок. Делается это так: указывается ключевое слово t r y, а блок программного кода раз­ мещается в фигурных скобках после этого ключевого слова. После t rу­ блока размещается с а t с h -блок: указывается ключевое слово c a t c h и после него в фигурных скобках - программный код, предназначенный для обработки исключений. Вся конструкция выглядит так, как показа­ но ниже (жирным шрифтом выделены ключевые элементы шаблона): try{ 1 1 Контролируемый код catch{ 1 1 Код для обработки исключений Работает t r y - c a t c h конструкция так. Если при выполнении про­ граммного кода в t r у-блоке ошибка не возникает, то программный код в с а t сh-блоке игнорируется и после завершения выполнения кода в t r у-блоке начинает выполняться команда после t r y- c a t c h кон­ струкции. Проще говоря, если ошибок нет, то наличие с а t с h-блока ни­ как не проявляется. Но ситуация резко меняется, если при выполнении кода в t r у-блоке возникает ошибка. Происходит следующее: если при выполнении какой-то команды возникает ошибка, то на этом выполне­ ние команд в t r у-блоке прекращается и начинается выполнение команд в с а t сh-блоке. После того как команды в с а t с h-блоке будут выпол­ нены, начинают выполняться команды после t r y - c a t ch конструкции. 161 Глава 3 Самое важное во всей этой схеме: то, что, как бы ни развивались собы­ тия, программа продолжает работу в штатном режиме. И это не может не радовать. Как описанная схема используется на практике, проиллюстрировано в рассматриваемой далее программе. В этой программе сначала отобра­ жается диалоговое окно с сообщением о начале выполнения программы. Затем пользователю предлагается в окне с полем ввода указать действи­ тельное число. Если пользователь вводит число, то появляется соответ­ ствующее сообщение. Если пользователь не вводит ничего или вводит не число, появляется сообщение о том, что нужно бьшо ввести число. Но в любом случае (ввел пользователь число или нет) перед заверше­ нием выполнения программы появляется еще одно сообщение. Теперь проанализируем программный код из листинга 3. 1 6. � Л истинг 3. 1 6. Знакомство с перехватом искл ючений us ing Sys tem; us ing Sys tem . Windows . Forms ; us ing Micro s o ft . Vi sualBas i c ; c l a s s TryCatchDemo { static vo id Main ( ) { / / Сообщение о начале выполнения программы : Ме s s аgеВох . Shоw ( " Выполняется программа ! " , " Начало " ) ; / / Перехват и обработка исключений : t ry { / / Контролируемый код / / Попытка преобразовать текст в число : DouЫe . Parse ( Interaction . I nputBox ( / / Текст над полем ввода : " Введите действительное число : " , 1 1 Заголовок окна : "Число " ); / / Отображение сообщения : 1 62 Управля ю щие инструкции Mes sageBox . Show ( "Дa , это было число ! " , "Число " ) ; 1 1 Блок обработки исключений : catch ( / / Отображение сообщения : Mes sageBox . Show ( 1 1 Текст сообщения : " Надо было ввести число ! " , 1 1 Заголовок окна : " Ошибка " , 1 1 В окне одна кнопка ОК : Me s sageBoxButton s . OK , 1 1 Используется пиктограмма ошибки : Me s s ageBox i con . Error ); } / / Завершение блока обработки исключений 1 1 Сообщение о завершении выполнения программы : Me s s ageBox . Show ( " Пpoгpaммa завершена ! " , "Завершение " ) ; В программе командой Me s s a geBox . S how ( " Выполняе т с я програм­ ма ! " , " Начало " ) отображается диалоговое окно с сообщением о том, что программа начала выполнение. Окно, которое появится на экране, показано на рис. 3. 1 9. Начало Выnопнявс.я программа! ок Р ис. З. 1 9 . Окн о , которое ото б ражается в начале выполнения п рограм м ы Затем выполняются команды в t r у-блоке (команды выполняются после того, как пользователь закрывает первое диалоговое окно с сообщением 1 63 Глава 3 о начале выполнения программы). Команд в t r у-блоке всего две. Пер­ вой командой отображается диалоговое окно с полем ввода, считывается введенное пользователем значение (в текстовом формате) и выполняет­ ся попытка преобразовать введенное значение в число с плавающей точ­ кой (действительное число). Диалоговое окно с полем для ввода числа показано на рис. 3.20. Число 12.5 Рис. 3 . 20 . Окно с полем для ввода действител ьного числа (1) Н А ЗАМ ЕТ КУ В данном случае речь идет о вводе действител ьного ч исла . Н е бу­ дет ошибко й , есл и пол ьзовател ь введет целое числ о . Но также мож­ но ввести и ч исло в формате с плавающей точ ко й . Используемый в качестве «ТОЧ КИ» символ - точ ка или запятая - оп ределяется на­ стройками операционной системы и среды разработки . На рис. 3 . 20 в качестве «ТОЧ КИ» испол ьзована запятая . Для отображения диалогового окна использован метод I np u t B o x ( ) . Результатом метод возвращает текстовую строку со значением, введен­ ным пользователем. Команда вызова метода I nputBox ( ) указана ар­ гументом статического метода P a r s e ( ) структуры DouЫe (структура из пространства имен S y s t em). Метод пытается выполнить преобразо­ вание текстового представления числа в число. Если попытка удачная, то результатом метода является соответствующее число. Если же поль­ зователь не ввел число (или ввел не число), то возникает ошибка. §§3 ПОДРОБН ОСТИ М ы знакомы со статическим методом P a r s e ( ) из структуры I n t 3 2 . Этот метод позволяет п реобразовать текстовое представление целого ч исла в целое ч исло . Метод с таким же назван ием , но уже из структуры DouЫ e , позволяет п реобразовать текстовое п редстав­ ление для действител ьного числа в действител ьное ч исло . Метод 1 64 Управля ю щие инструкции возвращает резул ьтат - число, получен ное в резул ьтате п реобра­ зован и я . То есть резул ьтат вызова метода можно зап исать в пере­ менную . В рассматриваемом примере мы этого не делаем , посколь­ ку нас само ч исло не и нтересует. И нтерес представл яет тол ько сам факт того , что пол ьзовател ь ввел число. Таким образом, если вызов метода Pa r s e ( ) из структуры DouЫ e про­ шел удачно, то пользователь ввел число. В этом случае в t r у-блоке вы­ полняется следующая команда Me s s ageBox . S h o w ( " Да , э т о было число ! " , " Число " ) , которой отображается окно с сообщением о том, что было введено число. Окно показано на рис. 3.2 1 . Число Дг, 1то быпо число! ок Рис. 3 . 2 1 . Окн о , которое ото б ражается в случае , есл и пол ьзовател ь ввел ч исло Если пользователь закрыл окно с полем ввода, щелкнув кнопку Отмена или системную пиктограмму, либо если он ввел не число, то при по­ пытке получить числовое значение с помощью метода Р а r s е ( ) воз­ никнет ошибка. В этом случае до выполнения команды Me s s ageBox . Show ( " Да , э т о было число ! " , " Число " ) дело не дойдет. Выпол­ нение блока t r y будет прекращено, и начнут выполняться команды в блоке c a t ch. А в этом блоке всего одна команда, которой отобража­ ется диалоговое окно с сообщением об ошибке. Аргументами методу Me s s ageBox . Show ( ) передаются: • текст сообщения " Надо бьmо в в е с т и число ! " ; • текст заголовка для окна " Ошибка " ; • константа Me s s ageBoxBut t o n s . ОК, означающая, что в окне будет всего одна кнопка ОК; • константа Me s s a g e B ox i c o n . E r r o r , означающая, что в окне ото­ бражается пиктограмма ошибки. Как будет выглядеть отображаемое окно, показано на рис. 3.22. 1 65 Глава 3 Оwибu На,цо бЫ11 0 е аести число! ок Рис. 3 . 22 . Окно, которое ото б ражается , есл и пол ьзовател ь ввел не ч исло или отказался от ввода ч и сла Но вне зависимости от того, произойдет или не произойдет ошиб­ ка, в конце, перед завершением выполнения программы, командой Me s s a g e B o x . S h o w ( " Пp o г p aммa з а в ерше н а ! " , " З а в ершение " ) отображается диалоговое окно, представленное на рис. 3.23. Программа :sааершена! ок Рис. 3 . 23 . Окно, которое ото б ражается перед завершением выполнения п рограм м ы Мы видим, что даже в таком упрощенном варианте обработка исключе­ ний - достаточно мощный механизм, значительно повышающий гиб­ кость и надежность программного кода. Как мы узнаем впоследствии, возможности этого механизма в действительности намного шире. На­ пример, можно по-разному выполнять обработку исключений разного типа. Но об этом речь пойдет немного позже. Р ез ю ме П ацак пацака н е обманывает. Это некрасиво, родной. из к/ф «Кин-дза-дза'> • В языке С# существуют специальные управляющие инструкции, по­ зволяющие создавать в программе точки ветвления и блоки повто­ ряемых команд. • 166 Условный оператор i f позволяет выполнять разные блоки команд в зависимости от истинности или ложности некоторого условия. Управля ющие инструкции Проверяемое условие указывается в круглых скобках после ключе­ вого слова i f. Команды, выполняемые при истинном условии, ука­ зываются в блоке после i f-инструкции. Команды, выполняемые при ложном условии, указываются в е l s е -блоке. Существует упрощен­ ная форма условного оператора без е l s е -блока. • Оператор выбора s w i t c h позволяет выполнять разные блоки ко­ манд в зависимости от значения некоторого выражения. Проверя­ емое выражение (целочисленное, символьное или текстовое) ука­ зывается в круглых скобках после ключевого слова s w i t ch. Затем указываются с а s е -блоки с контрольными значениями. Выполняют­ ся команды в с а s е -блоке, в котором контрольное значение совпа­ дает со значением проверяемого выражения. В случае, если значе­ ние выражения не совпадает ни с одним из контрольных значений в с а s е -блоках, выполняются команды в de f a u l t-блоке. Этот блок не является обязательным. Каждый с а s е -блок и de f a u l t-блок за­ канчивается инструкцией b r e a k. В случае необходимости можно использовать пустые с а s е -блоки. • Оператор цикла wh i l e позволяет многократно выполнять блок определенных команд. После ключевого слова wh i l e в круглых скобках указывается условие, при истинности которого выполняют­ ся команды в теле оператора цикла. Каждый раз после выполнения этих команд проверяется условие, и, если оно истинно, команды вы­ полняются снова. • Оператор цикла do-wh i l e похож на оператор цикла wh i l e , но в опе­ раторе do - wh i l e сначала выполняются команды, а затем проверяет­ ся условие. Команды указываются после ключевого слова do. После блока команд следует ключевое слово wh i l e и, в круглых скобках, условие. Оператор цикла выполняется до тех пор, пока при очеред­ ной проверке условия оно не оказывается ложным. • Описание оператора цикла f o r начинается с ключевого слова f o r . В круглых скобках указывается три блока инструкций. Блоки разде­ ляются между собой точкой с запятой. Если блок содержит несколь­ ко инструкций, то они разделяются запятыми. Команды, формиру­ ющие тело оператора цикла, указываются в круглых скобках после f о r -инструкции. Выполнение оператора цикла начинается с вы­ полнения команд в первом блоке. После этого проверяется условие во втором блоке. Если оно ложно, оператор цикла завершает работу. Если условие истинно, то выполняются команды в теле оператора 167 Глава З цикла и в третьем блоке. Затем снова проверяется условие. Если ус­ ловие ложно, работа оператора цикла завершается. Если условие истинно, выполняются команды в теле оператора и в третьем блоке и снова проверяется условие. И так далее, пока при проверке усло­ вия оно не окажется ложным. • Инструкция безусловного перехода g o t o позволяет перейти к вы­ полнению программного кода в том месте, которое выделено меткой. Используя инструкцию g o t o и условный оператор, можно органи­ зовать циклическое выполнение программного кода (симулировать работу оператора цикла). Общая рекомендация состоит в том, чтобы избегать использования инструкции g o t o . • Система обработки исключений позволяет предусмотреть специаль­ ный код, выполняемый при возникновении ошибки. С этой целью используется конструкция t ry- c at ch. Программный код, при вы­ полнении которого может возникнуть ошибка, помещается в t r у­ блок. Программный код, предназначенный для выполнения в случае возникновения ошибки, помещается в с а t с h-блок. Если при выпол­ нении кода в t rу-блоке ошибка не возникает, то с а t сh-блок игно­ рируется. Если при выполнении кода в t rу-блоке возникает ошибка, то выполнение команд в блоке t r y прекращается и начинают выпол­ няться команды в блоке c a t c h . Задания для самостоятел ьной работы Владимир Николаевич, у тебя дома жена, сып-двоечник, з а кооперативпую квартиру не заплачено. А ты тут мозги пудришь. Плохо кончится, родной. из к:/ф -«Кин-дза-дза» 1 . Напишите программу, в которой пользователь вводит число, а про­ грамма проверяет, делится ли это число на 3 и на 7 . Результаты проверки отображаются в сообщении в диалоговом окне. Используйте обработку исключений. 2. Напишите программу, в которой пользователь последовательно вво­ дит два целых числа. Программа определяет, какое из чисел больше или они равны, и выводит сообщение в диалоговом окне. Используйте обра­ ботку исключений. 1 68 Управля ющие инструкции 3 . Напишите программу, в которой вычисляется сумма чисел, которые вводит пользователь. Программа выводит запрос на ввод числа, считы­ вает введенное пользователем число, прибавляет его к сумме и снова выводит запрос на ввод числа. Процесс продолжается до тех пор, пока пользователь не введет нулевое значение. Используйте обработку ис­ ключений. 4. Напишите программу, в которой пользователь вводит целое число в диапазоне от 1 до 7 , а программа определяет по этому числу день не­ дели. Если введенное пользователем число выходит за допустимый диа­ пазон, выводится сообщение о том, что введено некорректное значение. Используйте оператор выбора s w i t ch. Предложите механизм обработ­ ки ошибки, связанной с вводом нечислового значения. 5. Напишите программу, в которой пользователю предлагается ввести название дня недели. По введенному названию программа определяет порядковый номер дня в неделе. Если пользователь вводит неправиль­ ное название дня, программа выводит сообщение о том, что такого дня нет. Предложите версию программы на основе вложенных условных операторов и на основе оператора выбора s w i t ch. 6. Напишите программу, в которой вычисляется сумма нечетных чисел. Для проверки результата воспользуйтесь тем, что 2 + 4 + 6 + ". + 2п = п(п + 1 ). Предложите версии программы, использующие разные опера­ торы цикла. 7. Напишите программу для вычисления суммы квадратов натуральных чисел. Для проверки результата воспользуйтесь тем, что 1 2 + 2 2 + 3 2 + " . 2 n(n + 1 ) (2n + 1) +п = П редложите версии программы, использующие разные 6 операторы цикла. • 8. Напишите программу, которая выводит последовательность чисел Фибоначчи. Первые два числа в этой последовательности равны 1 , а ка­ ждое следующее число равно сумме двух предыдущих (получается по­ следовательность 1 , 1 , 2 , 3, 5 , 8 , 1 3 , 2 1 , 3 4 , 5 5 , 8 9 и так далее). Количе­ ство чисел в последовательности вводится пользователем. Предложите версии программы, использующие разные операторы цикла. 9. Напишите программу, в которой пользователем вводится два целых числа. Программа выводит все целые числа - начиная с наименьшего (из двух введенных чисел) и заканчивая наибольшим (из двух введен­ ных чисел). Предложите разные версии программы (с использованием 169 Глава З разных операторов цикла), а также механизм обработки исключений для этой программы. 1 0 . Напишите программу, в которой вычисляется сумма чисел, удовлет­ воряющих таким критериям: при делении числа на 5 в остатке получа­ ется 2 , или при делении на 3 в остатке получается 1 . Количество чисел в сумме вводится пользователем. Программа отображает числа, которые суммируются, и значение суммы. Используйте обработку исключений. Предложите версии программы, использующие разные операторы цикла. Гл ава 4 МАССИВЫ - Пойдем простым логическим ходом. - Пойдем вместе . из к/ф �Ирония судьбы, WlU С легким паром» В этой главе мы обсудим исключительно важную конструкцию - речь пойдет о массивах. Среди тем, которые мы рассмотрим, будут такие: • одномерные массивы - способы их объявления и использования ; • особенности работы с двумерными массивами ; • способы инициализации массивов ; • выполнение основных операций с массивами - в частности, речь бу­ дет идти о копировании и присваивании массивов ; • создание �рваных» массивов - то есть массивов со строками разной длины ; • особенности массива из объектных ссылок. Также мы познакомимся со способами обработки аргументов командной строки. Еще в главе есть различные примеры использования массивов. Начнем же с азов - с создания одномерных массивов. Одномерные масси вы Первый раз таких единоличников вижу. из к/ф �девчата» Массив - это набор элементов одного типа, которые объединены об­ щим именем. Переменные, входящие в массив, называются элемента­ ми массива. Поскольку одно и то же имя (имя массива) 4П рименяет­ ся» сразу к нескольким переменным, то эти переменные нужно как-то 171 Глава 4 идентифицировать. Идентификация выполняется с помощью индекса или индексов. Индекс - это целое число. Количество индексов, необ­ ходимых для однозначной идентификации переменной в массиве, опре­ деляет размерность массива. Под размером массива обычно подразуме­ вают общее количество элементов в массиве. Под размером массива для данной размерности имеют в виду количество значений, которые мо­ жет принимать индекс, соответствующий данной размерности. Самые простые - одномерные массивы, в которых для идентификации элемен­ та в массиве нужно указать всего один индекс. Знакомство с массива­ ми начнем именно с одномерных массивов. То есть далее в этом разделе речь идет об одномерных массивах. Мы уже выяснили, что массив - это набор переменных. Возникает есте­ ственный вопрос: как получить доступ к этому набору? Ответ состоит в том, что доступ к массиву получают с помощью специальной пере­ менной, которая называется переменной массива. Как и обычную пере­ менную, переменную массива перед использованием следует объявить. При объявлении переменной массива указывают идентификатор типа элементов массива, после него указываются пустые квадратные скобки и имя переменной массива. То есть шаблон объявления переменной мас­ сива такой: тип [ ] переменная Например, если мы хотим объявить переменную массива с названи­ ем nums и предполагается, что массив будет состоять из целочислен­ ных значений типа i n t , то переменная массива объявляется командой i n t [ ] num s . Если бы мы объявляли переменную массива с названием s ymЬs и массив предполагался состоящим из символьных значений, то ко­ манда объявления такой переменной выглядела бы как char [ ] s ymЬ s . Н о создание (объявление) переменной массива н е означает создания массива. Другими словами, даже если мы объявили переменную масси­ ва, сам массив от этого не появляется. Его еще нет. Массив нужно со­ здать. Для создания массива используется оператор n e w . После оператора new указывается идентификатор типа, соответствующий типу элемен­ тов создаваемого массива, а в квадратных скобках после идентифика­ тора типа указывается размер массива. Шаблон для команды создания массива имеет такой вид: new тип [ размер ] 172 Масс ивы Например, если мы хотим создать одномерный целочисленный мас­ сив из 1 2 элементов, то команда создания такого массива выглядит как new in t [ 1 2 ] . Чтобы создать массив из 1 О элементов символьного типа, используем команду new char [ 1 О ] . Остается открытым вопрос о том, как переменная массива связана с собственно массивом. Ответ очень про­ стой: переменная массива ссылается на массив. Можно сказать и иначе: значением переменной массива может быть адрес массива в памяти. G) Н А ЗАМ ЕТ КУ Что понимать под «адресом масси ва» и как этот адрес испол ьзуется , более детал ьно обсуждается в главе ( во второй части книги ) , посвя ­ щенной работе с указателя м и . Пока же нам достаточно самых общих представлений о том , что такое «адрес масси ва» . Также нужно учесть, что команда создания массива не только создает массив, но еще и имеет результат. Это адрес созданного массива. А адрес массива, как мы уже знаем, может быть значением переменной массива. Главное, чтобы тип переменной массива соответствовал типу элементов в массиве. Если обобщить все вышеизложенное, то общая схема созда­ ния массива реализуется следующими командами: тип [ ] переменная ; переменная=nеw тип [ размер ] ; То есть задача по созданию массива состоит из двух этапов: • • объявление переменной массива ; создание массива и присваивание ссылки на массив переменной мас­ сива. Эти два этапа можно объединить и воспользоваться такой командой: тип [ ] переменная=nеw тип [ размер ] ; Схема реализации одномерного массива проиллюстрирована на рис. 4. 1 . Например, командой i n t [ ] nums=new i n t [ 1 2 ] создается целочис­ ленный массив из 1 2 элементов, объявляется переменная массива nums и ссылка на созданный массив записывается в эту переменную. Ко­ мандой char [ ] s ymb s =new char [ 1 О ] создается массив из 1 О сим­ вольных элементов, объявляется переменная массива s ymb s и ссылка на массив записывается в переменную. 173 Глава 4 / , ... / .... - " ,- " - ,1 - , Пер емен н а я: масс и в а '. , , .... - - " <., - , ... ... ... 11, ':' Ссыл к а ' ... _ _ ", _ ' ... " _ _ .... .... '- ... тип [ ] , ,/ ... _ _ _ ... ' ' ' ,, ... ... ... --- " (. --- ... ... , , .,,. ... ... --- ... " ... " " Ма с с и в '--..,.. -..,.. �-�---��-� , / -" 1 \... " ' / , : '----'"-----'"--, _ ,, ' ' переме нная ... ... _ _ _ _ _ = new > ' ' ... - - - - - - _ , , ... " _ - _ ' t ' ' ' ' .... ..,, ' ... ... _ .... , , ' , " ' , , ... тип [ размер ] Рис . 4 . 1 . Схема реал и зации одномерного массива После того как массив создан, к его элементам можно обращаться для при­ сваивания значений и считывания значений. Делается это достаточно про­ сто: после имени массива (имя массива отождествляется с переменной массива, которая ссылается на этот массив) в квадратных скобках указы­ вается индекс элемента. Важно помнить, что индексация элементов всегда начинается с нуля. Поэтому у первого элемента индекс О , у второго элемен­ та индекс 1 и так далее. Индекс последнего элемента в массиве на единицу меньше размера массива. А размер массива можно определить с помощью свойства Length. Например, если мы имеем дело с одномерным массивом nums, то значением выражения nums . Length является количество элемен­ тов в массиве nums. Ссылка на первый элемент в массиве nums выглядит как nums [ О ] , ссылка на второй элемент - это nums [ 1 ] , а ссылка на послед­ ний элемента массива nums дается выражением nums [ nums . Length- 1 ] . Как пример создания и использования массива рассмотрим программу в листинге 4. 1 . Там создается массив целых чисел, а затем этот массив за­ полняется числами, которые при делении на 3 в остатке дают 1 (то есть речь о числах 1 , 4 , 7 , 1 0 , 1 3 , 1 6 и так далее). � Л истинг 4 . 1 . Создание одномерного числового массива us ing Sys tem; c l a s s I ntArrayDemo { static vo id Main ( ) { 1 1 Создание массива из 1 2 чисел : int [ ] nums=new int [ l 2 ] ; 1 1 Перебор элементов массива : for ( int k=O ; k<nums . Length; k++ ) { 174 Масс ивы 1 1 Присваивание значения элементу массива : nums [ k ] = 3 * k+ l ; 1 1 Отображение значения элемента массива : Console . Write ( " I " +nums [ k ] + " " ) ; Console . WriteLine ( " I " ) ; Результат выполнения программы такой, как показано ниже. [1i!J Результат выполнения программы (из листинга 4 . 1 ) 1 1 1 4 1 7 1 10 1 13 1 16 1 19 1 22 1 25 1 28 1 31 1 34 1 Программа очень проста. В теле главного метода командой i n t [ ] nums=new i n t [ 1 2 ] создается массив, состоящий из 1 2 целочисленных элементов. Ссылка на массив записывается в переменную массива num s . После создания массива его элементы существуют, н о и м необходимо присвоить значения. Для этого мы запускаем оператор цикла, в котором индексная переменная k принимает начальное значение О . Это значение индекса первого (начального) элемента в массиве. G) Н А ЗАМ ЕТ КУ И ндексная перемен ная k объявлена в первом блоке в операторе ци кла f o r . Поэтому перемен ная k доступна тол ько в операторе цик­ ла, но не за его п ределам и . Напом н и м общее правил о , оп ределяю­ щее область доступ ности перемен ной : оно гласит, что перемен ная доступ на в пределах того блока , в котором она объявлена. Для пере­ менной k таки м «блоком» является оператор цикла. За каждую итерацию переменная k увеличивает свое значение на едини­ цу (благодаря команде k + + в третьем блоке оператора цикла f o r ) . Опе­ ратор цикла продолжает работу, пока истинно условие k<nums . Length. Оно означает, что значение индексной переменной k не должно пре­ вышать значение выражения nums . L e n g t h . Значение выражения nums . Length это размер массива. Учитывая, что переменная k при­ нимает только целочисленные значения, легко догадаться, что последнее значение переменной k, при котором еще будут выполняться команды - 175 Глава 4 в теле оператора цикла, равно nums . L e n g t h - 1 . Это значение на едини­ цу меньше размера массива и совпадает со значением индекса последне­ го элемента в массиве nums (напомним, что индекс последнего элемента в одномерном массиве всегда на единицу меньше размера этого масси­ ва). Таким образом, переменная k последовательно пробегает значения всех элементов массива num s . За каждый цикл в теле оператора цикла выполняются такие команды. Сначала командой nums [ k ] = 3 * k+ 1 эле­ менту с индексом k присваивается значение 3 * k+ 1 . Здесь мы учли, что для чисел, которые при делении на 3 в остатке дают 1, может быть ис­ пользована формула Зk + 1, где целое число k может принимать значения О, 1 , 2 и так далее. Легко проверить, что, используя эту формулу, после­ довательно получаем числа 1 , 4 , 7 , 1 О и так далее. G) Н А ЗАМ ЕТ КУ Ч исла , которые при делении на ч исло А дают в остатке ч исло В , рас­ сч иты ваются по формуле Ak + В, где параметр k может принимать значения О , 1 , 2 и так далее. После того как значение очередному элементу массива присвоено, эле­ мент (его значение) отображается в консольном окне. Для этого исполь­ зована команда C on s o l e . W r i te ( 11 1 11 +nums [ k ] + 11 11 ) в теле опера­ тора цикла. В консольное окно выводится вертикальная черта, пробел, значение элемента и еще один пробел. Причем, поскольку использован метод W r i te ( ) , все сообщения выводятся в одну строку. В результате в консольном окне в одной строке появится последовательность зна­ чений элементов массива. Значения разделяются пробелами и верти­ кальными разделителями. Но после последнего элемента (и пробела) вертикальной черты не будет. Она добавляется командой C o n s o l e . W r i t e L i n e ( 11 1 11 ) уже после завершения оператора цикла. Еще один небольшой пример касается создания символьного массива (массива с элементами типа c h a r ). Размер массива определяется значе­ нием переменной. После создания массив заполняется случайными сим­ волами. Содержимое массива отображается в прямом и обратном поряд­ ке. Код программы представлен в листинге 4.2. [1i!J Листинг 4 . 2 . Символьны й массив us ing Sys tem; c l a s s CharArrayDemo { 176 Масс ивы static vo id Main ( ) { 1 1 Объект для генерирования случайных чисел : Random rnd=new Random ( ) ; 1 1 Размер массива и индексная переменная : int s i ze=l O , k ; 1 1 Создание символьного массива : char [ ] s ymЬ s=new char [ s i z e ] ; 1 1 Отображение сообщения : Console . WriteLine ( "Maccив случайных символов : " ) ; 1 1 Заполнение массива случайными символами : for ( k= O ; k< symЬs . Length ; k++ ) { 1 1 Значение элемента массива : s ymЬs [ k] = ( char ) ( ' А ' +rnd . Next ( 2 6 ) ) ; 1 1 Отображение значения элемента массива : Console . Write ( " I " + s ymЬs [ k ] + " " ) ; Console . WriteLine ( " I " ) ; 1 1 Отображение сообщения : Console . WriteLine ( "Maccив в обратном порядке : " ) ; for ( k=symЬs . Length - 1 ; k>=O ; k - - ) { 1 1 Отображение значения для элемента массива : Console . Write ( " I " + s ymЬs [ k ] + " " ) ; Console . WriteLine ( " I " ) ; G) Н А ЗАМ ЕТ КУ Вообще , сгенерировать случайное ч исло - задача очень нетриви­ ал ьная . Откровенно говоря , случайных чисел не существует вовсе. Есть иллюзия того , что числа случай ные. В реал ьности такие ч исла генери руются в соответствии с оп ределенным ал горитмом . И они конечно же не случай н ы . Поэтому более корректн ый термин - псев­ дослучай ные ч исла . 177 Глава 4 Как отмечалось выше, в программе массив заполняется случайными символами. Для генерирования случайных символов нам понадобятся случайные целые числа. Генерируются случайные числа с помощью специального объекта rnd, который создается на основе класса Random (из пространства имен S y s t em) командой Random r n d=new Ran dom ( ) . В результате выпол­ нения этой команды мы получаем объект r n d, у которого есть метод Next ( ) , возвращающий значением случайное целое число. � ПОДРОБН ОСТИ Классы и объекты , которые создаются на основе классов , обсужда­ ются позже . Сейчас отмети м л и ш ь основные, наиболее важн ые мо­ менты , связанные с созданием объектов. Объект создается на осно­ ве класса . Класс и грает рол ь шаблона, оп редел я ющего , какие пол я , свойства и методы будет иметь объект. Фактически класс п редстав­ ляет собой специфический «тип дан н ых» ( с поп равкой на то , что там не тол ько данные ) . Объекты реал изуются подобно масси вам : есть собственно объект, а есть переменная , которая на этот объект ссы ­ лается . Последня я назы вается объектной переменной . Например, м ы хотим создать объект на основе класса Random. Сам объект соз­ дается командой new Random ( ) . У вы ражения new Random ( ) есть значение - это ссыл ка на созданный объект. Ссылка записывается в объектную переменную rnd. Объектная перемен ная объя вл яется командой вида Random rnd, в которой в качестве типа переменной указы вается название класса Random. Есл и две команды ( объя вле­ ния объектной переменной и создания объекта) объеди н ить в одну, то получ им вы ражение Random rnd=new Random ( ) . Как перемен ная массива обычно отождествляется с массивом , так и объектная пе­ ремен ная , как правил о , отождествл яется с объектом . Символьный массив создается командой сhаr [ ] symЬs=new char [ s i z e ] . Размер массива определяется значением переменной s i z e . Поскольку предварительно при объявлении переменной s i z e ей было присвоено значение 1 0 , то создается массив из 1 0 элементов. G) Н А ЗАМ ЕТ КУ Размер массива определ яется значением переменной s i z e на мо­ мент создания массива. Есл и бы впоследствии значение перемен­ ной s i z e изменилось, размер массива остался бы неизмен н ы м . 178 Масс ивы Для заполнения массива случайными символами мы используем опе­ ратор цикла. Индексная переменная k в операторе цикла принима­ ет целочисленные значения от О и строго меньше s ymb s . L e n g t h (размер массива), увеличиваясь з а каждый цикл н а единицу. Значе­ ние элементу массива s ymb s с индексом k присваивается командой s ymb s [ k ] = ( c h a r ) ( ' А ' + r n d . N e x t ( 2 6 ) ) , в которой генерируется случайное число. Значением выражения rnd . Next ( 2 6 ) является слу­ чайное целое число в диапазоне от О до 2 5 включительно. � ПОДРОБН ОСТИ Метод Next ( ) вызы вается из объекта класса Random ( в нашем слу­ чае таким объектом я вляется rnd) . Есть нескол ько способов вызо­ ва метода Next ( ) . Есл и метод вызы вается с одн и м целочислен н ы м аргументо м , т о резул ьтатом я вляется неотри цател ьное случайное целое ч исло , не превы шающее то , которое указано аргументо м . На­ пример, есл и испол ьзована команда rnd . Next ( 2 6 ) , то резул ьтатом будет неотри цател ьное целое числ о , не бол ьшее 2 6 - это ч исло в диапазоне значений от О до 2 5 включ ител ьно. Следовательно, к символу ' А ' прибавляется целое число (речь о выра­ жении ' А ' + rn d . N e x t ( 2 6 ) ). Результат выражения вычисляется так: к коду символа ' А ' (значение 6 5, но в данном случае это не важно) при­ бавляется целочисленное слагаемое. В итоге получаем целое число. Пе­ ред всем выражением указана инструкция приведения к символьному типу ( cha r ) . Поэтому полученное числовое значение интерпретирует­ ся как код символа в кодовой таблице. Поскольку в английском алфави­ те 2 6 букв, а в кодовой таблице буквы расположены в соответствии с ал­ фавитом, то результатом выражения ( c h a r ) ( ' A ' + r n d . N e x t ( 2 6 ) ) является одна из больших (прописных) букв. Поскольку к коду символа ' А ' прибавляется случайное число, то и буква будет случайной. Диапа­ зон возможных значений - от ' А ' до ' z ' включительно. После того как значение элементу массива присвоено, командой Con s о 1 е . W r i t e ( 11 1 11 + s ymb s [ k ] + 11 11 ) оно отображается в консольном окне. Как и в предыдущем примере, мы используем вертикальные разделите­ ли и пробелы при отображении значений элементов массива. Последний разделитель отображается командой C on s o l e . W r i t e L i n e ( 11 1 11 ) после завершения выполнения оператора цикла. Для отображения содержимого массива в обратном порядке мы ис­ пользуем еще один оператор цикла. В нем индексная переменная k 179 Глава 4 (мы используем ту же переменную, что и в первом операторе цикла) за каждую итерацию уменьшает значение на единицу, начиная с началь­ ного значения s ymb s Length - 1 (значение индекса последнего элемен­ та в массиве s ymb s) до значения О включительно (использовано условие k>= O ) . За каждый цикл командой C o n s o l e . W r i te ( 11 1 11 + s ymb s [ k ] + 11 11 ) отображается значение элемента с индексом k. Поскольку элемен­ ты в массиве перебираются в обратном порядке, то в консольном окне массив тоже отображается в обратном порядке (начиная с последнего элемента и заканчивая первым). При этом сам массив не меняется. • � ПОДРОБН ОСТИ И ндексная перемен ная k объя влена вместе с переменной s i z e то есть она объя влена не в операторе цикла. Область доступности переменной k - главный метод програ м м ы . П оэтому мы можем испол ьзовать переменную в обоих операторах цикла. Есл и бы м ы объя вили переменную k в первом операторе цикла, т о в о втором е е испол ьзовать уже не смогл и б ы . Н о м ы могл и бы объя вить во вто­ ром операторе цикла другую и ндексную переменную или даже пе­ ремен ную с таким же названием k . Даже есл и так, то это были бы разные перемен ные. Область доступ ности каждой из н их огран ичи­ валась бы «свои м » оператором цикла. Результат выполнения программы может быть следующим. [1iJ Результат выполнения программ ы (из листинга 4 . 2) Массив случайных символов : 1 F 1 U 1 Q 1 Р 1 Р 1 А 1 С 1 Р 1 А 1 Н 1 Массив в обратном порядке : 1 Н 1 А 1 Р 1 С 1 А 1 Р 1 Р 1 Q 1 U 1 F 1 Поскольку в программе использован генератор случайных чисел, то от запуска к запуску значения у элементов массива могут (и будут) меняться. Неизменным остается тот факт, что во втором случае значе­ ния элементов отображаются в обратном порядке по сравнению с тем, как они отображались вначале. 180 Масс ивы И н и ц и ал иза ц ия масс и ва Не мешайте работать, инвентаризуемый. из к/ф -«Гостья из будущего » В рассмотренных примерах мы сначала создавали массив, а затем его заполняли. При заполнении массивов у нас был определенный алго­ ритм, в соответствии с которым элементам присваивались значения. Например, массив можно заполнять нечетными числами, или случай­ ными числами, или числами Фибоначчи. Но бывают ситуации, когда такой закономерности попросту нет. Элементам массива нужно присво­ ить значения, и эти значения нельзя (или нет смысла) записывать в виде формулы. Проще говоря, очень часто принципиально невозможно за­ полнить массив с использованием оператора цикла. Нужно каждому элементу присваивать значение �в индивидуальном порядке� . Это очень неудобно, особенно если массив большой. В таких случаях удобно вы­ полнять явную инициализацию массива. Делается все достаточно просто: при объявлении переменной массива ей присваивается список со зна­ чениями, которые формируют массив. Значения в списке заключают­ ся в фигурные скобки. Внутри скобок значения разделяются запятыми. Например, если мы воспользуемся командой i n t [ ] num s = { 1 , 3 , 5 } , то в результате будет создан целочисленный массив из трех элементов. Значение первого элемента равно 1 , значение второго элемента равно 3 , значение третьего элемента равно 5 . Ссьшка н а созданный массив запи­ сывается в переменную num s . � ПОДРОБН ОСТИ Кроме команды int [ ] nums= { 1 , 3 , 5 } , для создания и и н и циа­ л изаци и массива можно было бы воспол ьзоваться командами int [ ] nums =new i n t [ 3 ] { 1 , 3 , 5 } или i n t [ ] nums=new i n t [ ] { 1 , 3 , 5 } . Разница между последн ими двумя командами в том , что в одной размер массива указан явно, а в другой - нет. Есл и размер масси­ ва не указан , то он автоматически определяется по кол ичеству эле­ ментов в списке и н и циал изаци и . Есл и размер массива указан , то он должен соответствовать кол ичеству элементов в списке и н и циал и ­ заци и . В п роти вном случае возн и кает ош ибка. Небольшой пример с инициализацией различных массивов приведен в программе в листинге 4.3. 181 Глава 4 [1i!J Л истинг 4 .3. И ни циализация одномерного массива us ing Sys tem; c l a s s I n i tArrayDemo { static vo id Main ( ) { 1 1 Создание и инициализация массивов : int [ ] nums= { l , 3 , 5 , 7 , 6 , 5 , 4 } ; char [ ] s ymЬ s=new char [ ] { ' А ' , ' Z ' , ' В ' , ' У ' } ; s tring [ ] txt s=new string [ 3 ] { " один " , "два " , " три" } ; 1 1 Отображение содержимого массивов : Console . WriteLine ( "Maccив nums : " ) ; for ( int k=O ; k<nums . Length ; k++ ) { Console . Write ( nums [ k ] + " " ) ; Console . WriteLine ( " \nMaccив s ymЬs : " ) ; for ( int k=O ; k<s ymЬ s . Length ; k++ ) { Console . Write ( s ymЬs [ k ] + " " ) ; Console . WriteLine ( " \nMaccив txts : " ) ; for ( int k=O ; k<txt s . Length ; k++ ) { Consol e . Write ( txts [ k ] + " " ) ; Console . WriteLine ( ) ; Результат выполнения программы такой, как показано ниже. [1i!J Результат выполнения программы (из листинга 4 .3 ) Массив nums : 1 3 5 7 6 5 4 Массив s ymbs : А Z В У Массив txt s : один два три 1 82 Масс ивы В данном случае программный код очень прост. В нем последовательно создается и инициализируется три массива: целочисленный num s , сим­ вольный s ymb s и текстовый t x t s . Для каждого из этих массивов преду­ смотрен список со значениями, которыми инициализируются элементы массива. G) Н А ЗАМ ЕТ КУ М ы испол ьзуем три оператора цикла, в каждом из которых объя вля ­ ется и ндексная перемен ная с названием k . Это разные переменные, хотя формально они имеют оди наковые названия . Каждая такая пе­ ремен ная доступ на в пределах оператора цикла, в котором она объ­ явлена. Более того , такая перемен ная существует, пока вы полняет­ ся оператор цикла: при запуске оператора цикла на вы полнение под переменную выделяется место , а когда оператор цикла завершает работу - перемен ная удаляется ( в памяти освобождается место , выделенное под эту переменную ) . Операци и с масси вами Ведь это ж е настоящая тайна ! Ты нотом никог­ да себе не простишь ! из к/ф -«Гостья из будущего » Создание массива, заполнение массива и отображение значений его эле­ ментов - это далеко не все операции, которые можно выполнять с мас­ сивами. Область применения массивов намного шире. В этом разделе мы рассмотрим примеры, иллюстрирующие методы работы с массива­ ми. Для начала проанализируем программный код в листинге 4.4. [1iJ Л истинг 4 . 4 . Копирование и присваивание массивов u s ing Sys tem; c l a s s CopyArrayDemo { static vo id Main ( ) { 1 1 Целочисленный массив : int [ ] A= { l , 3 , 5 , 7 , 9 } ; 1 1 Переменные массива : 183 Глава 4 int [ ] В , С ; 1 1 Присваивание массивов : В=А ; 1 1 Создание нового массива : C=new int [ A . Length ] ; 1 1 Поэлементное копирование массива : for ( int k=O ; k<A . Length ; k++ ) { C [ k ] =A [ k ] ; 1 1 Изменение значения первого элемента в массиве А : А [ О ] =О ; 1 1 Изменение значения последнего элемента 11 в массиве В : B [ B . Length - 1 ] = 0 ; 1 1 Сообщение в консольном окне : Console . WriteLine ( "A : \ tB : \ tC : " ) ; 1 1 Отображение содержимого массивов : for ( int k=O ; k<A . Length ; k++ ) { 1 1 Отображение значений элементов массивов : Console . WriteLine ( " { O } \ t { l } \t { 2 } " , A [ k ] , B [ k ] , C [ k ] ) ; Результат выполнения программы представлен ниже. � Результат выполнения программы (из листинга 4 . 4) А: В: С: о о 1 3 3 3 5 5 5 7 7 7 о о 9 1 84 Масс ивы В программе командой i n t [ ] А= { 1 , 3 , 5 , 7 , 9 } создается и инициа­ лизируется целочисленный массив А из 5 элементов (значения - не­ четные числа от 1 до 9 включительно). А вот командой in t [ ] В , С объявляются две переменные массива (но сами массивы не создаются ! ) . Переменные В и С в силу того, как они объявлены (идентификатором типа указано выражение i n t [ ] ), могут ссылаться на одномерные цело­ численные массивы. Значение переменной В присваивается командой В=А. Но копирования массива А в данном случае не происходит. Чтобы понять, каковы последствия команды В=А, следует учесть, что в действи­ тельности значением переменной массива является ссылка или адрес массива. Поэтому значение переменной А - это адрес массива, на кото­ рый ссылается переменная. Этот адрес копируется в переменную в. Как следствие, переменная В будет ссьшаться на тот же массив, на который ссылается переменная А. Это важный момент: при выполнении команды В=А новый массив не создается. И переменная А, и переменная В будут ссылаться на физически один и тот же массив. Иначе мы поступаем с переменной С. Командой C=new in t [ А . Length ] создается новый пустой массив, и ссылка на этот массив записывается в переменную С. Размер созданного массива определяется выражени­ ем А . L e n g t h , и поэтому он такой же, как у массива А. Затем с помо­ щью оператора цикла выполняется поэлементное копирование (команда С [ k ] =А [ k ] в теле оператора цикла) содержимого массива А в массив С . В результате м ы получаем, что переменные А и С ссылаются н а одинако­ вые массивы, но это все же два отдельных массива (хотя и с совпадаю­ щими значениями элементов). При выполнении команды А [ О ] =О в массиве, на который ссьшается пере­ менная А (и переменная В), значение первого элемента меняется с 1 на О. При выполнении команды В [ В . Length- 1 ] = О значение последнего элемента в массиве, на который ссьшается переменная В (и переменная А), меняется с 9 на О . Но в массиве С все элементы остались с теми же значениями, кото­ рые у них бьши в самом начале после поэлементного копирования. В част­ ности, первый элемент остался со значением 1 , а последний элемент остал­ ся со значением 9 . Это подтверждает результат выполнения программы. � ПОДРОБН ОСТИ В команде C on s o l e . Wri teLine ( 11 А : \ tB : \ tC : 11 ) в текстовом л и ­ терале испол ьзована инструкция табул я ци и \ t . Инструкция об­ рабаты вается так. При отображен и и текста в консол и каждый 185 Глава 4 сим вол отображается в определенной позици и . Они идут подряд одна за друго й . Среди них есть особые « реперные» - как прави­ л о , через каждые восемь позици й . То есть «реперными» я вляет­ ся 1 -я , 9-я , 1 7-я , 25-я позиция и так далее. Есл и при отображен и и текста встречается инструкция \ t , т о следующий сим вол будет отображаться в ближайшей реперной позиции ( в п раво от пози­ ции п оследнего отображенного символа ) . На п рактике примене­ ние инструкци и табуляции \ t позволяет выполнять выравни вание по столбикам . В теле оператора цикла, в котором отображаются значения элемен­ тов масси вов А, В и С (точ нее, масси вов, на которые ссылаются ука­ зан ные перемен ные - п оскольку масси вов всего два) , испол ьзова­ на команда Con s o l e . W r i t e L i n e ( 11 { О } \ t { 1 } \ t { 2 } 11 , А [ k ] , В [ k ] , С [ k ] ) . П р и отображении текстовой строки , указанной первым аргу­ ментом метода Wr i teLine ( ) , вместо инструкции { О } подставляется значение элемента А [ k ] ( п ри заданном значен и и индекса k ) , вме­ сто инструкци и { 1 } подставляется значение элемента В [ k ] , а вме­ сто инструкци и { 2 } подставляется значение элемента С [ k] . После отображения первого и второго значения вы полняется табул я ция . В итоге значен ия элементов массива, на который ссылается пере­ менная А, отображаются в столбике под надп исью 11 А : 11 • Под над­ писью 11 В : 11 в столбик отображаются значения элементов массива, на который ссылается переменная В . А под надп исью 11 С : 11 в столбик отображаются значения элементов массива, на который ссылается перемен ная С . Таким образом, при копировании значений переменных массива ссылка из одной переменной копируется в другую. В результате обе переменные будут ссылаться на один и тот же массив. Как пример обработки содержимого массива рассмотрим задачу о по­ иске наибольшего элемента в числовом массиве. Соответствующая про­ грамма представлена в листинге 4.5. � Л истинг 4. 5 . Поиск наибол ь ш его значения в массиве us ing Sys tem; c l a s s MaxE lementDemo { static vo id Main ( ) { 11 Переменные для записи значения элемента и индекса : int value , index ; 1 1 Размер массива : 186 Масс ивы int s i z e= 1 5 ; / / Объект для генерирования случайных чисел : Random rnd=new Random ( ) ; / / Создание массива : int [ ] nums=new int [ s i ze ] ; / / Заполнение и отображение массива : for ( int k=O ; k<nums . Length ; k++ ) { / / Значение элемента массива : nums [ k ] =rnd . Next ( l , 1 0 1 ) ; / / Отображение значения элемента : Console . Write ( nums [ k ] + " " ) ; Console . WriteLine ( ) ; / / Поиск наибольшего элемента : index= O ; / / Начальное значение для индекса value=nums [ index ] ; / / Значение элемента с индексом / / Перебор элементов : for ( int k=l ; k<nums . Length ; k++ ) { / / Если значение проверяемого элемента больше / / текущего наибольшего значения : i f ( nums [ k ] >value ) { value=nums [ k ] ; / / Новое наибольшее значение index=k; / / Новое значение для индекса / / Отображение резуль тата : Console . WriteLine ( " Haибoльшee значение : " +value ) ; Console . WriteLine ( "Индeкc элемента : " + i ndex ) ; В программе создается целочисленный массив, заполняется случайны­ ми числами (в диапазоне значений от 1 до 1 0 0 включительно), а после 187 Глава 4 этого выполняется поиск элемента с наибольшим значением. Програм­ мой в консольное окно выводится сообщение о том, какое максимальное значение и какой индекс у элемента с максимальным значением. G) Н А ЗАМ ЕТ КУ Есл и в массиве нескол ько элементов с максимальным значением , то вы водится индекс первого из этих элементо в . Результат выполнения программы может быть таким (с поправкой на использование генератора случайных чисел). � Результат выполнения программы (из листинга 4 . 5) 43 4 40 30 4 1 18 77 70 91 15 40 8 6 9 55 3 8 Наибольшее значение : 9 1 Индекс элемента : 8 Для запоминания значения элемента и его индекса используются цело­ численные переменные val ue и i n dex. Размер массива, который созда­ ется и заполняется случайными числами, определяется значением цело­ численной переменной s i z e (значение переменной равно 1 5 то есть создается массив из 1 5 элементов). Для генерирования случайных чисел с помощью команды Random rnd=new Random ( ) создаем объект rnd класса Random. - G) Н А ЗАМ ЕТ КУ Объект создается инструкцией new Random ( ) , а ссыл ка на этот объ­ ект записы вается в объектную переменную rnd. После этого пере­ мен ную rnd можно отождествлять с данным объектом . У объекта есть метод Next ( ) , возвращающий результатом случайное целое ч исло . Диапазон возможных значений определяется аргументом (или аргументами) метода . Целочисленный массив создается командой int [ ] nums =new int [ s i z e ] . После этого для заполнения массива запускается оператор цикла, в ко­ тором локальная индексная переменная k последовательно перебирает значения индексов элементов массива. За каждый цикл выполняется ко­ манда nums [ k ] = r n d . N e x t ( 1 , 1 0 1 ) , которой элементу массива nums с индексом k значением присваивается случайное целое число в диапа­ зоне от 1 до 1 О О включительно. 1 88 Масс ивы � ПОДРОБН ОСТИ Есл и при вызове методу Next ( ) передается неотри цател ьное целое ч исло , то результатом я вляется случайное целое ч исло в диапазоне от О и строго меньше ч исла , переданного аргументом методу. Есл и аргументом методу Next ( ) переданы два неотри цател ьных цел ых ч исла , то результатом я вляется случайное ч исло , бол ьшее или рав­ ное первому аргументу и строго мен ьшее второго аргумента . На­ пример, есл и м ы вызы ваем метод Next ( ) с аргументами 1 и 1 0 1 ( команда rnd . Next ( 1 , 1 0 1 ) ) , то в резул ьтате будет сгенери ровано ч исло в диапазоне возможных значений от 1 до 1 О О включ ител ьно. После присваивания значения элементу массива это значение отобра­ жается в консольном окне командой C on s o l e . W r i te ( nums [ k ] + " " ) . После того как массив создан и заполнен, начинается поиск элемен­ та с наибольшим значением. Алгоритм используется следующий. Мы рассматриваем начальный элемент в массиве как такой, что имеет мак­ симальное значение. Это первый �претендент� на звание элемента с наибольшим значением. Затем последовательно перебираем все про­ чие элементы, и, как только находим элемент с большим значением, он занимает место �претендента� . В контексте этого алгоритма командой i n dex= O переменной i n dex значением присваивается начальное ну­ левое значение - это индекс первого элемента в массиве. Переменной v a l u e значение присваивается командой v a l u e = nums [ i n d e x ] . Это значение первого элемента в массиве. После присваивания началь­ ных значений переменным v a l u e и i n dex запускается оператор цик­ ла, в котором перебираются все элементы массива, кроме начального. Поэтому в этом операторе цикла локальная индексная переменная k принимает значения, начиная с 1 (а не с О , как было ранее). В теле опе­ ратора цикла размещен условный оператор. В условном операторе про­ веряется условие nums [ k ] >va l u e . Состоит оно в том, что проверяе­ мый элемент массива nums [ k ] имеет значение большее, чем текущее значение в переменной va l u e . Если условие истинно, то командой v a l u e = nums [ k ] переменной va l u e присваивается новое значение (значение проверяемого элемента массива). Также командой i n de x = k присваивается новое значение переменной i n dex (это индекс прове­ ряемого элемента). Таким образом, после завершения выполнения оператора цикла в пе­ ременную v a l u e записано значение наибольшего элемента в массиве, 189 Глава 4 а в переменную i n d e x записано значение индекса этого элемента (если таких элементов несколько, то индекс первого из них). Коман­ дами C o n s o l e . W r i t e L i n e ( " Наибол ь ш е е з н а ч е ние : " +va l u e ) и C o n s o l e . W r i t e L i n e ( " Индекс элемен т а : " + i ndex ) вычислен­ ные значения отображаются в консольном окне. G) Н А ЗАМ ЕТ КУ Есл и в условии в условном операторе оператор «больше » > заме­ н ить на «больше или равно» >= , то п рограм ма будет работать так же , за исключением одного момента . Есл и в массиве нескол ько элемен­ тов с наибольшим значением , то будет вычисляться и ндекс послед­ него из этих элементов . Следующий пример имеет отношение к сортировке массивов. Для это­ го используем метод пузырька. Метод пузырька может применяться к массиву из элементов, для которых имеют смысл операции сравне­ ния. Сортировка массива выполняется следующим образом. После­ довательно перебираются все элементы массива. Сравниваются пары соседних элементов. Если значение элемента слева больше значения элемента справа, то эти элементы обмениваются значениями. В про­ тивном случае ничего не происходит. После одного полного перебора элементов самое большое значение гарантированно будет у последнего элемента. Чтобы второе по величине значение было у предпоследнего элемента, необходимо еще раз перебрать элементы массива (и выпол­ нить для них попарное сравнение). При этом последний элемент уже можно не проверять. Чтобы переместить на «правильную» позицию третье по величине значение, еще раз перебираются элементы массива (теперь можно не проверять два последних элемента) . Процесс про­ должается до тех пор, пока значения элементов не будут отсортиро­ ваны в возрастающем порядке. И так, один полный перебор элементов гарантирует перестановку в «правильную» позицию одного значения. Количество полных переборов на единицу меньше количества эле­ ментов в массиве, поскольку, когда второй элемент имеет «правиль­ ное» значение, то автоматически у первого элемента оно тоже «пра­ вильное» . При первом переборе проверяются все элементы массива. За каждый следующий перебор проверяется на один элемент меньше, чем это было для предыдущего раза. Теперь рассмотрим программный код в листинге 4.6. Там методом пу­ зырька сортируется символьный массив. 1 90 Масс ивы [1i!J Л истинг 4 . 6 . Сортировка массива методом пузырька u s ing Sys tem; c l a s s S ortArrayDemo { static vo id Main ( ) { 1 1 Символьная переменная : char s ; 1 1 Исходный символьный массив : char [ ] s ymЬs= { ' Q ' , ' Ы ' , ' а ' , ' В ' , ' R ' , ' А ' , ' r ' , ' q ' , ' Ь ' } ; 1 1 Отображение содержимого массива : Console . WriteLine ( "Maccив до сортировки : " ) ; for ( int k=O ; k<s ymЬ s . Length ; k++ ) { Console . Write ( s ymЬs [ k ] + " " ) ; Console . WriteLine ( ) ; 1 1 Сортировка элементов в массиве : for ( int i=l ; i<symЬ s . Length ; i++ ) { 1 1 Перебор элементов : for ( int j = O ; j < s ymЬs . Length - i ; j ++ ) { 1 1 Если значение элемента слева больше 1 1 значения элемента справа : i f ( s ymЬs [ j ] > s ymЬs [ j + l ] ) { s=symЬs [ j + l ] ; s ymЬs [ j + l ] =s ymЬs [ j ] ; s ymЬs [ j ] =s ; 1 1 Отображение содержимого массива : Console . WriteLine ( "Maccив после сортировки : " ) ; for ( int k=O ; k<s ymЬ s . Length ; k++ ) { Console . Write ( s ymЬs [ k ] + " " ) ; 191 Глава 4 Console . WriteLine ( ) ; Как выглядит результат выполнения программы, показано ниже. [1i!J Результат выполнения программы ( из листинга 4 . 6) Массив до сортировки : Q Ы а В R А r q Ь Массив после сортировки : А В Q R а Ь q r Ы Массив, предназначенный для сортировки, создается в программе ко­ мандой c h a r [ ] s ymb s = { ' Q ' , ' Ы ' , ' а ' , ' В ' , ' R ' , ' А ' , ' r ' , ' q ' , ' Ь ' } . Это символьный массив. Он инициализирован списком, содер­ жащим строчные и прописные английские буквы и одну кирилличе­ скую (символ ' ы ' ). Массив перед сортировкой отображается в кон­ сольном окне. Для этой цели использован оператор цикла. После этого начинается сортировка элементов массива. Для сортировки использо­ ваны вложенные операторы цикла. Внешний оператор цикла с индекс­ ной переменной i «подсчитывает» общие переборы элементов в мас­ сиве. Значение индексной переменной i последовательно меняется с 1 до s ymb s . L e n g t h - 1 включительно (то есть общее количество пе­ реборов на единицу меньше количества элементов в массиве). При фиксированном значении индексной переменной i во внутреннем операторе цикла индексная переменная j пробегает значения от О до s ymЬ s . Length - i - 1 включительно. Эта индексная переменная «пе­ ребирает» индексы элементов в массиве. За первый цикл (при значе­ нии переменной i равном 1 ) переменная j пробегает значения от О до s ymb s . Lengt h - 2 . Здесь нужно учесть, что в теле оператора цикла обрабатываются два элемента - с индексом j и с индексом j + 1 . При первом переборе просматриваются все элементы. Последний допу­ стимый индекс в массиве определяется значением s ymb s . L e n gt h - 1 . Из условия, что больший индекс j + 1 принимает максимально возмож­ ное значение s ymb s . Length- 1 , получаем, что максимально возможное значение индекса j равно s ymЬ s . L e n gt h - 2 . За каждый перебор про­ сматривается на один элемент меньше, отсюда и получаем, что индекс j в общем случае должен быть меньше, чем s ymb s . L e n gt h - i . 1 92 Масс ивы В теле внутреннего оператора цикла размещен условный оператор. В условном операторе проверяется условие s ymb s [ j ] > s ymb s [ j + 1 ] . Оно истинное, если элемент слева больше элемента справа. Если так, то сначала командой s = s ymЬ s [ j + 1 ] значение «элемента справа» запи­ сывается в переменную s. Командой s уmЬ s [ j + 1 ] = s ymb s [ j ] значение «элемента слева» присваивается «элементу справа» . Наконец, командой s ymb s [ j ] =s «элементу слева» присваивается значение переменной s , которое есть исходное значение «элемента справа». Таким образом, со­ стоялся обмен значениями между элементами. Важный момент связан с процедурой сравнения значений элементов массива в условном операторе. Пикантность ситуации в том, что срав­ нивают символьные значения. В таком случае выполняется сравнение кодов символов. Поэтому сортировка символьного массива в поряд­ ке возрастания означает, что значения будут упорядочены от символов с наименьшим значением кода до символов с наибольшим значением кода. � ПОДРОБН ОСТИ В кодовой табл и це коды букв следуют оди н за другим в соответстви и с тем , как буквы размещены в алфавите . Код строчных ( мален ьких) букв бол ьше кодов прописных ( больш их) букв . Например, код ан­ гл ийской буквы ' А ' равен 6 5 , а код англ и йской буквы ' а ' равен 9 7 . Русская буква ' А ' в стандартной раскладке и меет код 1 04 0 , а рус­ ская буква ' а ' и меет код 1 0 7 2 . Поэтому п р и сорти ровке элементов сим вол ьного массива в порядке возрастания сначала следуют бол ь­ шие англ и йские буквы в алфавитном порядке , затем англ и йские ма­ лен ькие буквы в алфавитном п орядке , затем бол ьшие русские буквы в алфавитном порядке и после этого - мален ькие русские буквы в алфавитном порядке . После того как сортировка массива закончена, с помощью оператора цикла в консольном окне отображаются значения элементов уже отсор­ тированного массива. 1 93 Глава 4 Ци кл по массиву - Тов арищ старший лейтенант, только я в ас прошу все ценности принять но ониси. - Ну какой разговор. По усей форме : опись, протокол, сдал -принял, отпечатки пальцев. из к/ф «БрШ1Лuантовая рука» Кроме операторов цикла, которые мы рассматривали ранее, есть еще один оператор цикла f o r e a ch, который называется циклом по коJUtек­ ции. Он может использоваться для перебора элементов в массиве. Син­ таксис описания оператора цикла по коллекции такой (жирным шриф­ том выделены ключевые элементы шаблона): fоrеасh ( тип переменная in массив ) { 1 1 Команды В круглых скобках после ключевого слова foreach объявляется локаль­ ная переменная. Она доступна только в теле оператора цикла. При объяв­ лении переменной указывается ее тип и название. После имени переменной указьшается ключевое слово in и название массива, элементы которого пе­ ребираются в процессе вьшолнения оператора цикла. Команды, выполня­ емые за каждый цикл, указьшаются в фигурных скобках после fоrеасh­ инструкции. Оператор выполняется следующим образом. Объявленная в fоrеасh-инструкции переменная последовательно принимает значения элементов массива, указанного после ключевого слова in. Таким образом, в отличие от «классического� оператора цикла for, в котором элементы массива перебираются с помощью индексной переменной, в данном случае перебираются не индексы элементов, а сами элементы. � ПОДРОБН ОСТИ Важное огран ичение состоит в том , что перемен ная , прини мающая значения элементов массива, может быть испол ьзована тол ько для сч иты вания значений элементов массива. Через эту переменную нел ьзя получ ить доступ к элементу массива для присваи ван ия ему значения . Небольшой пример использования оператора цикла по коллекции при­ веден в листинге 4.7. 1 94 Масс ивы [1i!J Л истинг 4 . 7. И спользование цикла по коллекции u s ing Sys tem; c l a s s ForeachDemo { static vo id Main ( ) { 1 1 Целочисленный массив : int [ ] nums= { l , 3 , 4 , 8 , 9 } ; 1 1 Символьный массив : char [ ] s ymЬs= { ' а ' , ' Ь ' , ' А ' , ' В ' , ' Ы ' } ; 1 1 Текстовый массив : String [ ] txts= { " красный" , "желтый" , " синий" } ; Console . WriteLine ( " Цeлoчиcлeнный массив " ) ; 1 1 Циклы по целочисленному массиву : foreach ( int s in nums ) { Console . WriteLine ( "Чиcлo { 0 } - { 1 } " , s , s % 2==0 ? " четное " : " нечетное " ) ; Console . WriteLine ( " Cимвoльный массив " ) ; 1 1 Цикл по символьному массиву : foreach ( char s in s ymЬ s ) { Console . WriteLine ( " Koд символа { 0 } - { 1 } " , s , ( in t ) s ) ; Console . WriteLine ( " Teкcтoвый массив " ) ; 1 1 Цикл по текстовому массиву : foreach ( string s in txt s ) { Console . WriteLine ( " B слове \ " { 0 } \ " { 1 } букв " , s , s . Length ) ; Результат выполнения программы таков. [1i!J Результат выполнения программы (из листинга 4 . 7) Целочисленный массив Число 1 - нечетное 1 95 Глава 4 Число 3 - нечетное Число 4 - четное Число 8 - четное Число 9 - нечетное Символь ный массив Код символа а - 9 7 Код символа Ь - 9 8 Код символа А - 6 5 Код символа В - 6 6 Код символа Ы - 1 0 6 7 Текстовый массив В слове " красный" 7 букв В слове "желтый" 6 букв В слове " синий" 5 букв В программе создается три массива: целочисленный num s , символьный s ymЬ s и текстовый t x t s . Содержимое каждого из этих массивов ото­ бражается в консольном окне. Для перебора элементов в массивах ис­ пользуется оператор цикла f o r e a ch. В программе таких циклов три (по количеству массивов). В каждом из них использована локальная переменная для перебора элементов массива. Хотя во всех операторах цикла название у локальной переменной s , но это разные переменные и объявлены они с разными идентификаторами типов. Тип локальной переменной определяется типом элементов массива. Поэтому в первом операторе цикла переменная s объявлена с идентификатором типа i n t , в о втором операторе цикла локальная переменная объявлена с иденти­ фикатором типа c h a r , а в третьем операторе цикла переменная объяв­ лена с идентификатором типа s tr ing. Соответственно, в первом опера­ торе цикла переменная s обрабатывается как целочисленное значение, во втором операторе цикла переменная s обрабатывается как символь­ ное значение, а в третьем операторе цикла переменная s обрабатывается как текстовое значение. В первом операторе цикла переменная s после­ довательно принимает значения элементов из массива num s . За каж­ дый цикл командой C on s o l e . W r i t e L i n e ( " Число { О } - { 1 } " , s , s % 2 = = 0 ? " ч е т но е " : " не ч е т н о е " ) отображается сообщение, в котором 196 Масс ивы указано значение числа и отображен тот факт, четное число или нечет­ ное. В частности, при отображении текстовой строки " Число { О } { 1 } " вместо инструкции { О } подставляется значение переменной s (значение очередного элемента массива n ums ), а вместо инструкции { 1 } подставляется значение выражения s % 2 == 0 ? " че т н о е " : " не ч е т н о е " . В выражении использован тернарный оператор. Значением выражения является текст " че т н о е " , если истинно условие s % 2 = = 0 (остаток от де­ ления на 2 значения элемента массива равен нулю). Если условие s % 2 == О ложно, то значением выражения s % 2 == 0 ? " че т н о е " : " н е ч е т н о е " яв­ ляется текст " н ече т н о е " . Во втором операторе цикла переменная s последовательно принимает значения элементов из массива s ymb s . Для каждого такого значения отображается сам символ и его код. В теле оператора цикла использо­ вана команда C on s o l e . W r i t e L i n e ( " Код симв ола { О } - { 1 } " , s , ( i n t ) s ) . Вместо инструкции { О } подставляется значение элемента массива (оно же значение переменной s ), а вместо инструкции { 1 } под­ ставляется код этого символа. Код символа вычисляется инструкцией ( i n t ) s . Здесь имеет место явное приведение символьного значения переменной s к целочисленному формату. Результатом является код соот­ ветствующего символа в используемой кодовой таблице. В третьем операторе цикла переменная s принимает значения элемен­ тов из текстового массива t x t s . Для каждого элемента массива вычис­ ляется количество символов в тексте. Для этого использована инструк­ ция s . L e n g t h . Текстовое значение элемента и количество символов в тексте отображаются с помощью команды C o n s o l e . W r i t e L i n e ( " В сло в е \ " { 0 } \ " { 1 } букв " , s , s . Length ) в теле оператора цикла. � ПОДРОБН ОСТИ При отображении текстового сообщен ия командой C on s o l e . W r i t e L i n e ( " B сло в е \ " { 0 } \ " { 1 } букв " , s , s . Length ) мы за­ кл ючаем значение элемента в двойные кавычки . Другими словам и , в текстовый л итерал нам необходи мо включить двойные кавычки ( как символ ) . В таком случае (то есть для вставки в текстовый л и ­ терал двой н ых кавычек) испол ьзуется инструкция \ " , содержащая перед двойной кавычкой " обратную косую черту \ . 197 Глава 4 Двумерные массивы Ничего особенного. Обыкновенная контра­ банда. из к/ф «Бриллиантовая рука» В двумерном массиве для однозначной идентификации элемента в мас­ сиве необходимо указать два индекса. Двумерный массив удобно пред­ ставлять в виде таблицы. Первый индекс определяет строку, в которой находится элемент, а второй индекс определяет столбец. Создается дву­ мерный массив практически так же, как и одномерный: объявляется пе­ ременная массива, создается собственно массив, и ссылка на этот массив записывается в переменную массива. При объявлении переменной дву­ мерного массива после идентификатора типа указываются квадратные скобки с запятой внутри [ , ] . Например, если мы хотим объявить пере­ менную для двумерного целочисленного массива, то соответствующая инструкция могла бы выглядеть как i n t [ , ] num s . При создании двумерного массива используется инструкция n e w , а по­ сле нее указывается идентификатор типа для элементов массива и в ква­ дратных скобках разделенные через запятую два целых числа, опреде­ ляющих размер массива по каждому из индексов (количество строк и столбцов в двумерном массиве). Например, создать двумерный це­ лочисленный массив из 3 строк и 5 столбцов можно инструкцией new i n t [ 3 , 5 ] . Как и для одномерного массива, значением инструк­ ции, создающей двумерный массив, является ссылка на этот массив. Поэтому если переменная n ums предварительно объявлена командой i n t [ , ] num s , то законной была бы команда nums = ne w i n t [ 3 , 5 ] . Команды объявления переменной массива и создания массива можно объединять: i n t [ , ] nums =new i n t [ 3 , 5 ] . Ниже приведен шаблон объявления переменной для двумерного мас­ сива и создания двумерного массива: тип [ , ] переменная ; переменная=nеw тип [размер , размер ] ; Можно использовать и такой шаблон: тип [ , ] переменная=nеw тип [размер , размер ] ; 198 Масс ивы Для обращения к элементу массива указывают имя массива и в квад­ ратных скобках через запятую - индексы этого элемента. Индексация по каждому индексу начинается с нуля. То есть инструкция nums [ О , О ] означает обращение к элементу массива nums , который расположен в первой строке и первом столбце (в строке с индексом О и столбце с ин­ дексом О ) . Выражение nums [ 1 , 2 ] является обращением к элементу, на­ ходящемуся в строке с индексом 1 (вторая по порядку строка) и столбце с индексом 2 (третий по порядку столбец). Свойство L e n g t h для двумерного массива возвращает общее коли­ чество элементов в массиве. То есть для массива n ums из 3 строк и 5 столбцов значением выражения nums Length является число 1 5 (про­ изведение 3 на 5 ) . Чтобы узнать размер массива по какому-то индексу (то есть количество строк или количество столбцов в массиве), исполь­ зуют метод G e t L e n g t h ( ) . Метод вызывается из переменной массива, а аргументом методу передается целое число, определяющее индекс, для которого возвращается размер массива. • � ПОДРОБН ОСТИ Масси вы в С# по факту реал изуются как объекты . Как и у п рочих объектов , у массива есть методы ( и свойства) . Более детал ьно объ­ екты обсуждаются немного позже . Здесь для нас важно науч иться пол ьзоваться преимуществам и , которые имеются у масси вов бла­ годаря такому «объектному» способу их реал изаци и . Размерность массива оп ределяется кол ичеством индексов, кото­ рые нужно указать для идентификаци и элемента массива. Опреде­ л ить размерность массива можно с помощью свойства Ran k . Раз­ мерность двумерного массива равна 2 . Есл и мы вызы ваем метод GetLength ( ) с аргументом О , то значен и ­ ем возвращается размер массива по первому индексу. Есл и вызвать метод GetLength ( ) с аргументом 1 , то получ и м размер массива по второму индексу. Например, если массив n ums состоит из 3 строк и 5 столбцов, то значением выражения nums GetLength ( О ) является число 3 (размер по первому ин­ дексу - количество строк), а значением выражения nums . GetLength ( 1 ) является число 5 (размер по второму индексу - количество столбцов). • В листинге 4.8 приведен пример, в котором иллюстрируется способ соз­ дания двумерного массива: создается целочисленный массив и построч­ но заполняется натуральными числами. 199 Глава 4 [1i!J Л истинг 4. 8 . Двумерны й массив us ing Sys tem; c l a s s TwoDimArrayDemo { static vo id Main ( ) { 1 1 Количество строк и столбцов в массиве : int rows=З , cols=S ; 1 1 Создание двумерного массива : int [ , ] nums=new int [ rows , col s ] ; 1 1 Значение первого элемента в массиве : int value=l ; 1 1 Заполнение и отображение массива . 1 1 Перебор строк в массиве : for ( int i=O ; i<nums . GetLength ( O ) ; i + + ) { 1 1 Перебор столбцов в строке : for ( int j = O ; j <nums . GetLength ( l ) ; j ++ ) { 1 1 Присваивание значения элементу массива : nums [ i , j ] =value ; 1 1 Это будет значение следующего элемента : value + + ; 1 1 Отображение элемента в строке : Console . Write ( nums [ i , j ] + " \ t " ) ; 1 1 Переход к новой строке : Console . WriteLine ( ) ; Результат выполнения программы представлен ниже. [1i!J Результат выполнения п рограм мы (из листи нга 4 .8) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 200 Масс ивы В программе количество строк и столбцов в двумерном массиве задается через целочисленные переменные rows и c o l s (значения 3 и 5 соответ­ ственно). Двумерный массив создается командой i n t [ , ] num s =new i n t [ rows , c o l s ] . Для заполнения массива и отображения значений его элементов используются вложенные операторы цикла. Во внешнем операторе цикла индексная переменная i перебирает строки двумерно­ го массива. Начальное значение переменной равно О , а верхняя граница определяется значением выражения nums . GetLength ( О ) (количество строк в массиве nums - переменная i строго меньше этого значения). Во внутреннем операторе цикла переменная j перебирает столбцы дву­ мерного массива. Начальное значение переменной равно О , и за каждый цикл переменная увеличивается на единицу. Оператор цикла выполняет­ ся, пока значение переменной строго меньше значения выражения nums . GetLength ( 1 ) (количество столбцов в массиве nums ). При заданных значениях индексов i и j командой nums [ i , j ] =va l ue соответству­ ющему элементу массива присваивается значение. Начальное значение переменной value равно 1 , поэтому первый элемент в массиве (элемент с двумя нулевыми индексами - элемент в первой строке в первом столб­ це) получает единичное значение. После выполнения команды va l u e + + значение переменной va l u e увеличивается н а единицу. На следующем цикле это новое значение будет присвоено очередному элементу. После того как значение элементу присвоено, командой C o n s o l e . Wr i t e ( nums [ i , j ] + " \ t " ) оно отображается в консольном окне. Эле­ менты из одной строки массива отображаются в одной и той же строке консольного окна. Для размещения элементов в строке использована та­ буляция. Тело внешнего оператора цикла состоит из внутреннего оператора цик­ ла и команды C o n s o l e . W r i t e L i n e ( ) . Внутренний оператор цикла нужен для заполнения строк массива и отображения значений элемен­ тов в консольном окне. Когда внутренний оператор цикла завершает работу, благодаря команде C o n s o l e . W r i t e L i n e ( ) выполняется пере­ ход к новой строке. На этом завершается очередной цикл внешнего опе­ ратора цикла. На следующем цикле внешнего оператора цикла снова выполняется внутренний оператор цикла, и затем команда C on s o l e . W r i t е L i n e ( ) , и так далее, пока не будут перебраны все строки двумер­ ного массива. Как и одномерный массив, двумерный массив можно инициализиро­ вать при создании. В этом случае переменной массива присваивается 201 Глава 4 список со значениями, которыми инициализируется двумерный мас­ сив. Список значений заключается в фигурные скобки. Его элемента­ ми являются вложенные списки. Каждый вложенный список выде­ ляется парой фигурных скобок. Значения, указанные во вложенных списках, определяют строки двумерного массива. Например, коман­ дой i n t [ , ] num s = { { 1 , 2 , 3 } , { 4 , 5 , 6 } } создается целочисленный двумерный массив из двух строк и трех столбцов. Элементы в первой строке принимают значения 1 , 2 и 3 , а элементы во второй строке при­ нимают значения 4 , 5 и 6. А если воспользоваться командой i n t [ , ] nums = { { 1 , 2 } , { 3 , 4 } , { 5 , 6 } } , то получим массив из трех строк и двух столбцов (в первой строке - элементы со значениями 1 и 2 , во второй строке - элементы со значениями 3 и 4 , в третьей строке - элементы со значениями 5 и 6 ). � ПОДРОБН ОСТИ nums= { { l , 2 , 3 } , { 4 , 5 , 6 } } для и н и ­ Наряду с командой i n t [ , ] циал изаци и массива могут испол ьзоваться и команды int [ , ] nums =new int [ 2 , 3 ] { { 1 , 2 , 3 } , { 4 , 5 , 6 } } или i n t [ , ] nums=new int [ , ] { { 1 , 2 , 3 } , { 4 , 5 , 6 } } . Есл и размеры массива не указан ы , они оп ределя ются автоматически п о списку инициал изаци и . Есл и раз­ меры массива указаны явно, то они должны соответствовать струк­ туре сп иска и н и циал изаци и . Далее рассматривается пример, в котором, кроме прочего, использует­ ся инициализация двумерного массива. В символьный массив из двух строк и трех столбцов вставляется строка и столбец. Индексы добавля­ емых строки и столбца определяются с помощью генератора случайных чисел. Чтобы понять, как это делается, рассмотрим программный код в листинге 4.9. [1i!J Л истинг 4. 9 . Доб авление строки и столб ца в массив us ing Sys tem; c l a s s InitTwoDimArrayDemo { static vo id Main ( ) { 11 Создание и инициализация двумерного массива : char [ , ] s ymbs= { { ' А ' , ' В ' , ' С ' } , { ' D ' , ' Е ' , ' F ' } } ; Console . WriteLine ( "Иcxoдный массив : " ) ; 1 1 Отображение массива : 202 Масс ивы for ( int i=O ; i<symЬs . GetLength ( O ) ; i + + ) { for ( int j = O ; j < s ymЬs . GetLength ( l ) ; j ++ ) { 1 1 Отображение значения элемента : Console . Write ( s ymЬs [ i , j ] + " " ) ; 1 1 Переход к новой строке : Console . WriteLine ( ) ; 1 1 Объект для генерирования случайных чисел : Random rnd=new Random ( ) ; 1 1 Строка и столбец : int row=rnd . Next ( symЬs . GetLength ( O ) + l ) ; int col=rnd . Next ( symЬs . GetLength ( l ) + l ) ; Console . WriteLine ( " Дoбaвляeтcя { 0 } - я строка и { 1 } - й столбец" , row, col ) ; 1 1 Создание нового массива : char [ , ] tmp=new char [ s ymЬs . GetLength ( O ) + l , s ymЬs . GetLength ( l ) + l ] ; 1 1 Целочисленные переменные : int а , Ь ; 1 1 Символьная переменная : char s= ' a ' ; 1 1 Заполнение массива . Копирование значений 1 1 из исходного массива : for ( int i=O ; i<symЬs . GetLength ( O ) ; i + + ) { 1 1 Первый индекс для элемента нового массива : i f ( i<row) a=i ; e l s e a=i + l ; for ( int j = O ; j < s ymЬs . GetLength ( l ) ; j ++ ) { 1 1 Второй индекс для элемента нового массива : i f ( j <col ) b=j ; e l s e b=j + l ; 1 1 Присваивание значения элементу массива : tmp [ a , b ] =symbs [ i , j ] ; 203 Глава 4 11 Заполнение добавленной строки в новом массиве : for ( int j = O ; j <tmp . GetLength ( l ) ; j ++ ) { 1 1 Значение элемента в строке : tmp [ row, j ] =s ; 1 1 Новое значение для следующего элемента : s++; for ( int i=O ; i<tmp . GetLength ( O ) ; i + + ) { 1 1 Если элемент не в добавленной строке : i f ( i ! =row) { 1 1 Значение элемента в столбце : tmp [ i , col ] =s ; 1 1 Новое значение для следующего элемента : s++ ; 1 1 Присваивание массивов : s ymbs=tmp ; Console . WriteLine ( " Hoвый массив : " ) ; 1 1 Отображение массива : for ( int i=O ; i<symЬ s . GetLength ( O ) ; i++ ) { for ( int j = O ; j < s ymЬs . GetLength ( l ) ; j ++ ) { 1 1 Отображение значения элемента : Console . Write ( s ymЬs [ i , j ] + " " ) ; 1 1 Переход к новой строке : Console . WriteLine ( ) ; 204 Масс ивы С поправкой на использование генератора случайных чисел результат выполнения программы может быть таким. � Результат выполнения программы ( из листинга 4 . 9) Исходный массив : А В С D Е F Добавляется 1 - я строка и 2 - й столбец Новый массив : А В д С а б в г D Е е F Или таким. � Результат выполнения программы (из листинга 4 . 9) Исходный массив : А В С D Е F Добавляется 2 - я строка и 3 - й столбец Новый массив : А В С д D Е F е а б в г Или, например, таким. � Результат выполнения программы (из листинга 4 . 9) Исходный массив : А В С D Е F Добавляется 0 - я строка и 0 - й столбец Новый массив : а б в г 205 Глава 4 д А В С е D Е F Есть и другие варианты. Мы же проанализируем программный код. Исходный символьный массив s ymb s создается и инициализирует­ ся командой c h a r [ , ] s ymb s = { { ' А ' , ' в ' , ' с ' } , { ' о ' , ' Е ' , ' F ' } } . Это массив из двух строк и трех столбцов, заполненный английскими буквами в алфавитном порядке (хотя в данном случае это не принци­ пиально). С помощью вложенных операторов цикла содержимое этого массива отображается в консольном окне. Поскольку нам понадобится генерировать случайные числа, командой Ran dom rnd=new Random ( ) создается объект r n d класса Ran dom. Объект используется для опре­ деления индекса добавляемой строки и индекса добавляемого столбца. Индекс строки записывается в переменную row, а значение вычисляется как rnd . Next ( s ymЬ s . G e t L e n g t h ( О ) + 1 ) . Это случайное число в ди­ апазоне от О до s ymb s . G e t L e n g t h ( О ) (верхняя допустимая граница определяется количеством строк в исходном массиве). Индекс добавля­ емого столбца записывается в переменную со 1 и вычисляется командой r n d . Next ( s ymb s . G e t L e n g t h ( 1 ) + 1 ) . Это случайное число в диапа­ зоне от О до s ymb s . G e t Length ( 1 ) (верхняя допустимая граница опре­ деляется количеством столбцов в исходном массиве). После того как ин­ дексы добавляемой строки и столбца определены, командой C o n s o l e . W r i t е L i n е ( " Добавля е т с я { 0 } - я строка и { 1 } -й с т олбец " , r o w , c o l ) отображается сообщение об индексе добавляемой строки и индексе добавляемого столбца. Командой c h a r [ , ] tmp = n e w c h a r [ s ymb s . G e t L e n g t h ( O ) + l , s ymb s . G e t L e n g t h ( 1 ) + 1 ] создается новый символьный массив. В нем на одну строку и на один столбец больше, чем в исходном масси­ ве s ymb s . После создания массив tmp заполняется. Делается это в не­ сколько этапов. Сначала в массив tmp копируются значения из исход­ ного массива s ymb s . Но специфика этого процесса состоит в том, что если первый индекс элемента в массиве s ymb s больше или равен ин­ дексу row добавляемой строки, то при копировании нужно выполнить сдвиг на одну позицию вниз. Аналогично, если второй индекс элемента в массиве s ymb s больше или равен индексу c o l добавляемого столб­ ца, то при копировании необходимо выполнить сдвиг на одну пози­ цию вправо. Другими словами, индекс копируемого элемента в масси­ ве s ymb s и индекс элемента в массиве tmp, которому присваивается 206 Масс ивы значение, могут отличаться. С помощью вложенных операторов цикла перебираются элементы в массиве s ymЬ s . Индексы элемента из масси­ ва tmp, которому присваивается значение, записываются в переменные а и Ь. Во внешнем операторе цикла (индексная переменная i переби­ рает строки в массиве s ymb s ) использован условный оператор, кото­ рым переменой а присваивается значение i при условии i < row и i + 1 в противном случае. Во внутреннем операторе цикла (индексная пере­ менная j перебирает столбцы в массиве s ymb s ) с помощью условного оператора переменной Ь присваивается значение j при условии j < c o l и значение j + 1 в противном случае. Командой tmp [ а , Ь ] = s уmЬ s [ i , j ] присваивается значение элементу в массиве tmp. После выполнения вложенных операторов цикла в массиве tmp при­ своены значения всем элементам, за исключением тех, что находятся в добавляемой строке и добавляемом столбце. Эти строка и столбец за­ полняются отдельно. Сначала заполняется добавляемая строка: запу­ скается оператор цикла, в котором индексная переменная j перебирает элементы в строке с индексом row. Значение элементу в строке присва­ ивается командой tmp [ row , j ] = s . Начальное значение переменной s равно ' а ' (маленькая буква русского алфавита). Такое значение будет у первого элемента в добавленной строке. Для следующего элемента зна­ чением будет следующая буква в алфавите, поскольку мы использовали команду s + + , которой значение переменной s �увеличивается на едини­ цу� : значением переменной становится следующий символ в алфавите. После заполнения добавленной строки заполняется добавленный стол­ бец. В соответствующем операторе цикла индексная переменная i пе­ ребирает элементы в столбце с индексом c o l . Значение элементам при­ сваивается командой tmp [ i , c o l ] = s . Единственное, это происходит лишь в случае, если элемент не находится в добавленной строке ( значе­ ние этого элемента уже определено при заполнении строки). Поэтому мы использовали условный оператор с условием i ! = r ow (первый ин­ декс i элемента не совпадает с индексом row добавленной строки). На этом заполнение массива tmp завершается. Но мы идем дальше и вы­ полняем присваивание массивов командой s ymb s = tmp. В результате и переменная s уmЬ s , и переменная tmp ссылаются на один и тот же мас­ сив - это новый массив, созданный путем добавления к исходному мас­ сиву строки и столбца. Для проверки содержимого созданного массива использованы вложенные операторы цикла. 207 Глава 4 G) Н А ЗАМ ЕТ КУ М ы создал и новый массив и ссылку в переменной массива s ymЬ s «перебросил и » на этот новый масси в . В итоге создается илл юзия , что изменен исходн ы й масси в . Так, вложенные операторы цикла в самом начале п рограм м ы и в самом кон це п рограм м ы фактиче­ ски одинаковы. Эти блоки команд испол ьзуются для отображения содержи мого масси ва, на который ссылается перемен ная s ymb s . Но массивы отображаются разные. Причина в том , что в начале вы­ полнения п рограм м ы и в кон це вы полнения п рограм м ы перемен ная s ymЬ s ссылается на разные массивы. П ричем у н их не п росто раз­ ные значения элементов , а разное кол ичество строк и столбцов. Что касается исходного масси ва, на который ранее ссылалась пе­ ремен ная s ymb s , то , п оскол ьку на этот массив бол ьше не ссылает­ ся ни одна переменная , массив из памяти будет удален системой сборки мусора. М ногомерные массивы Три м агнитофона , три кинокамеры загранич­ ных, три портсигара отечественных, куртка замшевая . . . Три куртки. из к/ф �иван Ва сильевич меняет професси ю» Размерность массива может быть большей двух. Правда, на практике массивы размерности большей, чем два, используются достаточно ред­ ко. Но это вопрос другой. G) Н А ЗАМ ЕТ КУ Напом н и м , что размерность массива оп ределяется кол ичеством индексов , которые следует указать для однозначной идентифика­ ции элемента масси ва. Итак, многомерные массивы реализуются таким же образом, как одномер­ ные и двумерные массивы: нам нужна переменная массива и, собственно, сам массив. Ссьтка на массив записывается в переменную массива. Поэ­ тому, как и во всех предыдущих случаях (с одномерными и двумерными массивами), нам предстоит объявлять переменную массива, создавать сам массив и записывать в переменную массива ссьтку на массив. 208 Масс ивы Переменная для многомерного массива объявляется следующим об­ разом: указывается идентификатор типа для элементов массива, после которого следуют квадратные скобки с запятыми внутри. Количество запятых на единицу меньше размерности массива. Скажем, если мы объявляем переменную для массива размерности 5, то внутри квадрат­ ных скобок должны быть 4 запятые. После квадратных скобок с запяты­ ми указывается имя переменной массива. Значением этой переменной можно присвоить ссьтку на массив. Он должен быть той же размерно­ сти, которая указана при создании переменной массива, а тип элемен­ тов массива должен совпадать с типом, указанным при объявлении пе­ ременной массива. Например, командой i n t [ , , ] nums объявляется переменная nums для трехмерного целочисленного массива, а командой c h a r [ , , , , ] s ymb s объявляется переменная s ymb s для символьного массива размерности 5 . Многомерный массив создается с помощью оператора new. После опера­ тора new указывается идентификатор типа элементов массива, и в квад­ ратных скобках перечисляются размеры массива по каждому из ин­ дексов. Значением инструкции по созданию массива является ссылка на массив. Эта ссьтка может быть записана в переменную массива. На­ пример, командой i n t [ , , ] nums=new i n t [ 3 , 4 , 5 ] создается трех­ мерный целочисленный массив размерами 3 на 4 на 5 . G) Н А ЗАМ ЕТ КУ Одномерный массив можно представлять в виде цепочки элементов. Двумерный массив удобно представлять в виде табл ицы . Трехмер­ ный массив можно представить как сложенный из элементов прямо­ угольный параллелепипед. Первый индекс определяет «длину •> , вто­ рой оп ределяет «ширину•> , и третий оп ределяет «высоту•> , на которой элемент находится в « параллелепи педе». С массивами больших раз­ мерностей геометрическая и нтерпретация не так очевидна. При создании многомерных массивов можно использовать инициализа­ цию аналогично тому, как мы это делали для одномерных и двумерных массивов. Значением переменной массива присваивается список, эле­ ментами которого являются списки, элементами которых также явля­ ются списки, и так далее - количество вложений определяется размер­ ностью массива. Например, командой i n t [ , , ] num s = { { { О , О , О , О } , {0, О, 0, 1 } , {0, О, 1, 0 } } , { {0, О, 1, 1 } , {0, 1, 0, О} , {0, 1, 0, 1 } } } создается трехмерный целочисленный массив, который: 209 Глава 4 • по первому индексу имеет размер 2 (поскольку внешний список со­ держит два внутренних списка) ; • по второму индексу массив имеет размер 3 (каждый внутренний список содержит по 3 вложенных списка-элемента) ; • по третьему индексу размер массива равен 4 (поскольку каждый вложенный список-элемент содержит по 4 значения). Мы также могли бы использовать команды i n t [ , , ] n um s = n e w int [ 2 , 3 , 4 ] { { { О , О , О , О } , { О , О , О , 1 } , { О , О , 1 , О } } , { { О , О , 1 , 1 } , { О , 1 , О , О } , { О , 1 , О , 1 } } } или i n t [ , , ] nums=new i n t [ , , ] { { { О , О , 0, 0} , {0, 0, 0, 1 } , {0, 0, 1, 0 } } , { {0, 0, 1, 1 } , { 0, 1, 0, 0 } , {0, 1, 0, 1} } }. Скромный пример, в котором использован многомерный (трехмерный, если точнее) символьный массив, представлен в листинге 4. 10. [1iiJ Л истинг 4. 1 О. Знакомство с многомерными массивами us ing Sys tem; c l a s s Mul t i DimArrayDemo { static vo id Main ( ) { 1 1 Трехмерный символьный массив : char [ , , ] s ymbs=new char [ 4 , 3 , 5 ] ; 1 1 Одномерный символьный массив : char [ ] s= { ' б ' , ' Б ' , ' Ь ' , ' В ' } ; 1 1 Заполнение трехмерного массива и отображение 11 значений его элементов . 1 1 Цикл по первому индексу : for ( int i=O ; i<symЬ s . GetLength ( O ) ; i++ ) { 1 1 Отображение сообщения : Console . WriteLine ( " Слой № { 0 } : " , i+ l ) ; 1 1 Цикл по второму индексу : for ( int j = O ; j < s ymЬs . GetLength ( l ) ; j ++ ) { 1 1 Цикл по третьему индексу : for ( int k=O ; k<s ymЬs . GetLength ( 2 ) ; k++ ) { 1 1 Присваивание значения элементу массива : 210 Масс ивы s ymЬs [ i , j , k ] =s [ i ] ; 1 1 Изменение элемента в одномерном массиве : s [ i ] ++ ; 1 1 Отображение значения элемента массива : Console . Write ( s ymbs [ i , j , k ] + " " ) ; 1 1 Переход к новой строке : Console . WriteLine ( ) ; 1 1 Отображение "линии" : Console . WriteLine ( " - - - - - - - - - - 11 ) ; Результат выполнения программы такой. [1iJ Результат выполнения программы (из листинга 4 . 1 О ) Слой № 1 : б в г д е ж з и й к л м н о п Слой № 2 : Б В Г Д Е ж з и й к л м н о п Слой № 3 : Ь с d е f g h i j k 1 m n о р 211 Глава 4 Слой № 4 : В С D Е F G Н I J К 1 М N О Р Командой c h a r [ , , ] s ymb s =new cha r [ 4 , 3 , 5 ] создается трехмер­ ный символьный массив. Размер массива по первому индексу равен 4 , размер массива по второму индексу равен 3 , и размер массива по треть­ ему индексу равен 5 . Этот массив можно представить себе как «слой­ ку» из четырех таблиц, каждая из трех строк и пяти столбцов. Мы эти таблицы заполняем символами - буквами алфавита. Мы используем русский и английский алфавит, маленькие и большие буквы - то есть всего четыре варианта, по количеству таблиц в «слойке» . Для заполне­ ния трехмерного массива используется конструкция из трех вложенных операторов цикла. Первый внешний цикл с индексной переменной i вы­ полняется по первому индексу элементов массива. Для перебора второго индекса использован вложенный оператор цикла с индексной перемен­ ной j , а третий индекс перебирается с помощью оператора цикла с ин­ дексной переменной k. Для определения размера массива по первому индексу использована инструкция s ymb s . GetLe ngth ( О ) , инструкци­ ей s ymb s . G e t L e n g t h ( 1 ) вычисляется размер массива по второму ин­ дексу, а размер по третьему индексу определяется выражением s ymb s . G e t L e n g t h ( 2 ) . Значение элементу массива присваивается командой s ymЬ s [ i , j , k ] = s [ i ] . Здесь s [ i ] - это элемент одномерного сим­ вольного массива из четырех элементов (в соответствии с размером трехмерного массива s ymb s по первому индексу). Массив s содержит буквы, с которых начинается «послойное» заполнение трехмерного мас­ сива. После присваивания значения элементу s [ i , j , k ] значение в одномерном символьном массиве изменяется командой s [ i ] + + , так что у следующего элемента значение будет другим. После присваива­ ния значения элементу трехмерного массива оно выводится в консоль­ ное окно. 212 Масс ивы М асс и в со строка м и разной дл и н ы Ох, красота-то какая ! Л епота ! из к/ф �иван Ва силье вич меняет професси ю» Напомним, как создается одномерный массив: объявляется перемен­ ная массива, и значением ей присваивается ссылка на массив. Впослед­ ствии переменная массива отождествляется с массивом, на который она ссылается. Важно здесь то, что такую переменную можно индексиро­ вать - после имени переменной в квадратных скобках указывается ин­ декс, в результате чего мы получаем доступ к элементу массива. А теперь представим, что мы создали одномерный массив, элементами которого являются переменные массива. Общая схема будет следующей: есть пе­ ременная массива, которая ссылается на массив, и этот массив состоит из переменных массива, ссылающихся на массивы. Именно такой под­ ход реализован в примере, представленном в листинге 4. 1 1 . [1i!J Л истинг 4 . 1 1 . М ассив из переменных массива u s ing Sys tem; c l a s s AnotherTwoDimArrayDemo { static vo id Main ( ) { 1 1 Символьный массив из переменных массива : char [ ] [ ] s ymЬs=new char [ S ] [ ] ; 1 1 Целочисленный массив из переменных массива : int [ ] [ ] nums= { new int [ ] { 1 , 2 , 3 ) , new int [ ] { 4 , 5 ) , new int [ ] { 6 , 7 , 8 , 9 ) } ; 1 1 Символьная переменная : char s = ' A ' ; 1 1 Заполнение символьного массива . 1 1 Перебор элементов во внешнем массиве : for ( int i=O ; i<symЬ s . Length ; i++ ) { 1 1 Создание внутреннего массива : s ymЬs [ i ] =new char [ i+ З ] ; 1 1 Перебор элементов во внутреннем массиве : for ( int j = O ; j < s ymЬs [ i ] . Length ; j ++ ) { 1 1 Значение элемента внутреннего массива : 213 Глава 4 s ymЬs [ i ] [ j ] =s ; 1 1 Значение для следующего элемента : s++ ; Console . WriteLine ( " Цeлoчиcлeнный массив : " ) ; 1 1 Отображение целочисленного массива : for ( int i=O ; i<nums . Length ; i + + ) { for ( int j = O ; j <nums [ i ] . Length ; j ++ ) { 1 1 Отображение элемента массива : Console . Write ( " { 0 , 3 ) " , nums [ i ] [ j ] ) ; Console . WriteLine ( ) ; Console . WriteLine ( " Cимвoльный массив : " ) ; 1 1 Отображение символьного массива . 1 1 Перебор элементов внешнего массива : foreach ( char [ ] q in s ymЬ s ) { 1 1 Перебор элементов во внутреннем массиве : foreach ( char р in q) { 1 1 Отображение элемента массива : Console . Write ( " { 0 , 2 ) " , р ) ; Console . WriteLine ( ) ; Результат выполнения программы представлен ниже. � Результат выполнения программы (из листинга 4 . 1 1 ) Целочисленный массив : 1 214 2 3 Масс ивы 4 5 6 7 8 9 Символьный массив : А Б В Г Д Е Ж з и й к л м н о п р с т у ф х ц ч ш В программе есть несколько моментов, достойных внимания. Итак, ко­ мандой c h a r [ ] [ ] s ymb s =new c h a r [ 5 ] [ ] создается массив s ymb s . Н о это н е очень обычный массив. Это массив одномерный, н е двумер­ ный. Массив из пяти элементов. Элементами массива являются пере­ менные, которые могут ссылаться на символьные одномерные масси­ вы. При объявлении переменной массива s ymb s после идентификатора типа c h a r использованы двойные прямоугольные скобки [ ] [ ] . Одна пара квадратных скобок означает, что создается массив. Вторая отно­ сится непосредственно к идентификатору типа. Фактически получается, что мы объявляем переменную для одномерного массива, который со­ стоит из элементов типа c h a r [ ] . А такое выражение используется при объявлении переменной для символьного массива. Далее, инструкция n e w c h a r [ 5 ] [ ] означает, что создается одномерный массив из пяти элементов (значение 5 в первых квадратных скобках). Вторые пустые квадратные скобки относятся к идентификатору типа c h a r . Получает­ ся, что мы создаем массив из элементов типа c h a r [ ] и размер масси­ ва равен 5 . Итог такой: переменная s ymb s ссылается на массив из пяти элементов. Каждый элемент массива является переменной массива. Ка­ ждая такая переменная может ссылаться на одномерный символьный массив (но пока не ссылается, поскольку самих массивов еще нет - их нужно создать). Массивы создаются и заполняются с помощью операто­ ров цикла. С помощью оператора цикла с индексной переменной i пе­ ребираются элементы в массиве s ymb s (будем называть его внешним массивом). Индексная переменная i принимает значения в диапазо­ не, ограниченном сверху величиной s ymb s . L e n g t h (условие «строго меньше»). Здесь еще раз имеет смысл подчеркнуть, что массив s ymb s одномерный. В теле этого оператора цикла командой s ymb s [ i ] =new c h a r [ i + 3 ] создается символьный массив, и ссылка на него записыва­ ется в элемент s ymb s [ i ] . Размер массива определяется значением вы­ ражения i + З . То есть массивы, создаваемые в рамках разных циклов, 215 Глава 4 имеют разные размеры. И эти массивы нужно заполнять. Поэтому ис­ пользуется внутренний оператор цикла с индексной переменной j . Верхняя граница диапазона изменения индексной переменной j опре­ деляется выражением s ymb s [ i ] . Length (условие «строго меньше» ) . Здесь мы должны учесть, что s ymb s [ i ] - это переменная, которая ссы­ лается на одномерный массив, и фактически переменная обрабатывает­ ся как одномерный массив. Значением выражения s ymb s [ i ] . Length является количество элементов в массиве, на который ссылается пере­ менная s ymb s [ i ] . В теле внутреннего оператора цикла командой s ymb s [ i ] [ j ] = s присва­ ивается значение элементу во внутреннем массиве (массив, на который ссылается переменная s ymb s [ i ] ). Начальное значение переменной s равно ' А ' (большая буква русского алфавита). За каждый цикл значе­ ние переменной s «увеличивается на единицу», в результате чего эле­ менты массива заполняются большими буквами русского алфавита. Для отображения элементов созданного «массива из массивов» исполь­ зован оператор цикла по коллекции. Точнее, мы используем вложенные операторы цикла f o r e a ch. Во внешнем операторе f o r e a ch перебираются элементы массива s ymЬ s , которые, как отмечалось выше, сами являются массивами (переменными, ссылающимися на массивы). Локальная переменная q во внешнем опе­ раторе цикла указана с идентификатором типа char [ ] и как такая, что принимает значения элементов массива s уmЬ s. Значение переменной q это ссылка на массив. Поэтому переменная q обрабатывается как массив. Как массив она использована во внутреннем операторе цикла f o r e a ch. Там объявлена локальная переменная р типа char, и эта переменная по­ следовательно принимает значения элементов массива q. Таким образом, если q является элементом массива s ymb s , то р - это элемент в массиве, на который ссылается элемент массива s ymЬ s . Значение этого элемента отображается командой Соn s о l е . W r i t e ( 11 { О , 2 } 11 , р ) . Первый О в ин­ струкции { О , 2 } означает, что вместо нее подставляется значение первого аргумента после текстовой строки 11 { О , 2 } 11 (то есть значение перемен­ ной р ), а число 2 в инструкции { О , 2 } означает, что для отображения зна­ чения выделяется не меньше двух позиций. Программа содержит пример создания еще одного массива, состояще­ го из переменных массива. На этот раз создается массив, состоящий из ссылок на целочисленные массивы. Особенность в том, что при соз­ дании массив инициализируется. Мы использовали команду i n t [ ] [ ] 216 Масс ивы n um s = { n e w i n t [ ] { 1 , 2 , 3 } , n e w i n t [ ] { 4 , 5 } , n e w i n t [ ] { 6 , 7 , 8 , 9 } } . Переменная массива nums объявлена с идентификатором типа i n t [ ] [ ] . Это означает, что n ums может ссылаться на одномерный массив, элементы которого имеют тип i n t [ ] - то есть являются ссыл­ ками на целочисленные массивы. Значением переменной nums присва­ ивается список. Список определяет массив, на который будет ссылаться переменная nums . В списке всего три элемента: new i n t [ ] { 1 , 2 , 3 } , new i n t [ ] { 4 , 5 } и new i n t [ ] { 6 , 7 , 8 , 9 } . В итоге получается, что переменная nums ссылается на массив, состоящий из трех элементов. Первый элемент nums [ О ] ссылается на целочисленный массив, состоя­ щий из элементов со значениями 1, 2 и 3. Второй элемент nums [ 1 ] ссы­ лается на целочисленный массив, состоящий из элементов со значения­ ми 4 и 5 . Третий элемент nums [ 2 ] ссьшается на целочисленный массив, состоящий из элементов со значениями 6, 7 , 8 и 9. Как так получилось? Нужно вспомнить, что элементы массива nums - ссылки на одномер­ ные массивы. Но кроме самих ссылок, нужны еще и сами одномерные массивы. Эта �двойная» задача решается с помощью инструкций new i n t [ ] { l , 2 , 3 } , new i nt [ ] { 4 , 5 } и new i n t [ ] { б , 7 , 8 , 9 } . Напри­ мер, что такое инструкция new i n t [ ] { 1 , 2 , 3 } ? Это команда создания целочисленного массива с элементами 1 , 2 и 3. То есть одна задача ре­ шается - создается одномерный целочисленный массив. У инструкции new in t [ ] { 1 , 2 , 3 } есть значение - это ссылка на созданный массив. Аналогично с двумя другими инструкциями. Следовательно, получа­ ется, что в списке, присваиваемом переменной nums , три элемента, ко­ торые являются ссылками на одномерные массивы. Эти три значения определяют содержимое массива, на который будет ссьшаться перемен­ ная num s . � ПОДРОБН ОСТИ При форми рован ии списка и н и циал изации для переменной масси­ ва nums м ы не можем испол ьзовать сп исок из списков , поскол ьку та­ кой способ испол ьзуется при создании двумерного массива, когда кажды й внутрен н и й сп исок определя ет строку в двумерном масси­ ве. И менно поэтому перед спискам и , оп ределяющими одномерные целочисленные масси в ы , испол ьзована инструкция new i n t [ ] . Для отображения содержимого массива nums мы используем обычный оператор цикла f o r (вложенные операторы цикла). Во внешнем опе­ раторе цикла индексная переменная i (с начальным нулевым значени­ ем) перебирает индексы элементов в массиве num s . Значение индексной 217 Глава 4 переменной каждый раз увеличивается на единицу, а цикл выполняется, пока значение переменной меньше значения nums . Length (количество элементов в одномерном массиве nums ) . Во внутреннем операторе цик­ ла индексная переменная j перебирает элементы в массиве, на который ссылается элемент nums [ i ] массива nums . Количество элементов в мас­ сиве nums [ i ] дается выражением nums [ i ] . Length. За каждый цикл выполняется команда C on s o l e . W r i te ( " { О , 3 } " , nums [ i ] [ j ] ) , ко­ торой в консольном окне отображается значение элемента nums [ i ] [ j ] , причем под отображаемое число выделяется не менее трех позиций (число 3 после запятой в инструкции { О , 3 } ) . М асси в об ъ ектн ых ссылок Я артист больших и малых академических те­ атров. из к/ф «Иван Васильевич меняет професси ю» Ранее мы всегда явно или неявно имели в виду, что элементы массива от­ носятся к одному типу. В общем-то, это так. Но бывают и неординарные ситуации. Как мы узнаем немного позже, в языке С# в пространстве имен S ys tem есть один особый класс, который называется Obj е c t . Его «осо­ бенность� связана с тем, что переменная типа Ob j e c t может ссылать­ ся на значение любого типа. Проще говоря, если объявить переменную и идентификатором типа для нее указать имя класса Obj e c t , то значени­ ем такой переменной можно присвоить практически все, что угодно. � ПОДРОБН ОСТИ В языке С# существует механизм наследования , который позволяет создавать один класс ( п роизводный класс) на основе другого класса (базовый класс) . В результате получается иерархия классов , связан ­ ных наследованием , когда класс создается на основе класса, и на его основе создается еще один класс, и так далее . В наследовании есть важный момент: переменная базового класса может ссылаться на объ­ екты , созданные на основе п роизводных классов. Специфика класса Obj ect состоит в том , что он находится в вершине иерархии наследо­ вания и любой класс является его прямым или косвенным потомком . Поэтому переменная типа Obj ect может ссылаться на любой объект. Более подробно методы работы с классами и объектами, в том числе и механизм наследован ия, описы ваются в последующих главах кн иги . 218 Масс ивы Если создать массив из элементов типа Ob j e c t , то элементам такого массива можно присваивать значения разных типов. В некоторых слу­ чаях это бывает удобно и полезно. G) Н А ЗАМ ЕТ КУ Н а п рактике вместо названия класса Ob j e c t обычно испол ьзуют идентификатор obj e c t , который является псевдонимом для вы ра­ жения S y s t em . Obj e c t . Небольшой пример, в котором создается и используется массив с эле­ ментами типа Obj e c t , приведен в листинге 4 . 1 2 . � Листинг 4 . 1 2 . М ассив и з переменных типа Obj ect u s ing Sys tem; c l a s s Obj ectArrayDemo { static vo id Main ( ) { / / Массив из трех переменных типа Obj ect : Obj ect [ ] obj s =new Obj ect [ З ] ; / / Элементам массива присваиваются значения / / разных типов : obj s [ 0 ] =1 2 3 ; / / Целое число obj s [ l ] = ' A ' ; / / Символ оЬj s [ 2 ] = " Третий элемент " ; / / Текст Console . WriteLine ( " Coздaн такой массив : " ) ; / / Проверка содержимого массива : for ( int k=O ; k<obj s . Length ; k++ ) { Console . WriteLine ( k+ " : " +obj s [ k ] ) ; / / Новые значения элементов : obj s [ O ] = ( in t ) obj s [ O ] + l l l ; / / Целое число obj s [ l ] = " Bтopoй элемент " ; / / Текст obj s [ 2 ] =3 . 1 4 1 5 92 ; / / Действительное число Console . WriteLine ( " Пocлe присваивания значений : " ) ; / / Проверка содержимого массива : 219 Глава 4 for ( int k=O ; k<obj s . Length ; k++ ) { Console . WriteLine ( k+ " : " +obj s [ k ] ) ; 1 1 Целочисленный массив : int [ ] nums= { l 0 , 2 0 , 3 0 ) ; 1 1 Переменная массива присваивается как значение 11 элементу массива : obj s [ 2 ] =nums ; Console . WriteLine ( " Цeлoчиcлeнный массив : " ) ; 1 1 Отображение элементов целочисленного массива : for ( int i=O ; i< ( ( int [ ] ) obj s [ 2 ] ) . Length ; i + + ) { Console . Write ( " { О , 3 ) " , ( ( int [ ] ) obj s [ 2 ] ) [ i ] ) ; Console . WriteLine ( ) ; 1 1 Новое значение элемента в числовом массиве : ( ( int [ ] ) obj s [ 2 ] ) [ 1 ] =0 ; Console . WriteLine ( " Eщe раз тот же массив : " ) ; 1 1 Отображение элементов целочисленного массива : for ( int i=O ; i<nums . Length ; i + + ) { Console . Write ( " { 0 , 3 } " , nums [ i ] ) ; Console . WriteLine ( ) ; Результат выполнения программы будет таким, как показано ниже. [1i!J Результат выполнения программы ( из листинга 4 . 1 2) Создан такой массив : О : 123 1: А 2 : Третий элемент После присваивания значений : О : 234 220 Масс ивы 1 : Второй элемент 2 : 3 , 1 4 1 5 92 Целочисленный массив : 10 20 30 Еще раз т о т же массив : 10 о 30 Массив из трех переменных типа Obj e c t создается с помощью коман­ ды Obj e c t [ ] o b j s = n e w Ob j e c t [ 3 ] . После создания массива его элементам присваиваются значения. Делается это в явном виде, но пи­ кантность ситуации в том, что присваиваемые значения относятся к раз­ ным типам. Так, командой оЬ j s [ О ] = 1 2 3 первому элементу массива оЬ j s значением присваивается целочисленный литерал 1 2 3. Командой obj s [ 1 ] = ' А ' второму элементу массива obj s присваивается символь­ ное значение ' А ' , а командой obj s [ 2 ] = " Тре тий элеме н т " третье­ му элементу массива obj s присваивается текстовое значение " Тре тий элеме н т " . С помощью оператора цикла убеждаемся, что значения эле­ ментов именно такие. На следующем этапе элементам массива присваиваются новые значе­ ния. Причем тип присваиваемых значений может не совпадать с типом текущего значения элемента. Здесь все просто. Некоторых пояснений требует лишь команда obj s [ О ] = ( i n t ) obj s [ О ] + 1 1 1 . Все дело в том, что на момент выполнения команды фактическим значением элемента оЬ j s [ О ] является целое число. Что мы пытаемся сделать - так это к те­ кущему значению элемента прибавить число 1 1 1 . И вот здесь сталки­ ваемся с ситуацией, которая является платой за возможность присваи­ вать переменным типа Obj e c t значения разных типов: если мы хотим, чтобы с элементом o b j s [ О ] выполнялась арифметическая операция и сам элемент обрабатывался как целочисленное значение, то необходи­ мо этот факт в явном виде отобразить. Именно поэтому в правой части выражения перед ссьшкой на элемент obj s [ О ] использована инструк­ ция явного приведения типа ( i n t ) . Фактически, используя выражение вида ( i n t ) obj s [ О ] , мы даем команду обрабатывать значение элемента obj s [ О ] как целое число типа i n t . После изменения значений элементов массива obj s м ы опять проверя­ ем содержимое массива (с помощью оператора цикла) и убеждаемся, что изменения вступили в силу. 22 1 Глава 4 На следующем этапе командой i n t [ ] n ums = { 1 О , 2 О , 3 О } создается целочисленный массив из трех элементов со значениями 1 О , 2 О и 3 О . Это обычный массив. Не очень обычен способ его использования: ко­ мандой o b j s [ 2 ] =nums переменную массива nums мы присваиваем значением элементу obj s [ 2 ] . Значение переменной nums - это ссыл­ ка на массив из трех чисел. После выполнения команды obj s [ 2 ] =nums значением элемента obj s [ 2 ] является ссылка на точно тот же массив. Поэтому мы вполне можем использовать для доступа к массиву эле­ мент obj s [ 2 ] . Но если так, то мы должны в явном виде указать, как следует обрабатывать значение элемента obj s [ 2 ] - используется ин­ струкция приведения типа ( i n t [ ] ) , означающая, что значение эле­ мента является ссылкой на целочисленный массив. Так мы поступаем в операторе цикла, в котором индексная переменная i принимает зна­ чения от О и до некоторого значения: переменная i должна быть стро­ го меньше значения ( ( in t [ ] ) o b j s [ 2 ] ) . L e n g t h , которое является размером массива, на который ссылается элемент obj s [ 2 ] . Выражение ( ( i n t [ ] ) obj s [ 2 ] ) . Length - это значение свойства Length для элемента obj s [ 2 ] , если значение элемента интерпретировать как ссылку на одномерный целочисленный массив (значение типа i n t [ ] ). В теле оператора цикла ссылка на элементы массива, на который ссылается элемент o b j s [ 2 ] , выполняется в формате ( ( i n t [ ] ) o b j s [ 2 ] ) [ i ] : сначала значение элемента obj s [ 2 ] приводится к типу i n t [ ] , а затем то, что получилось, индексируется (в квадратных скобках указывается индекс элемента). � ПОДРОБН ОСТИ Ч исло 3 в инструкци и { О , 3 } в команде Con s o l e . W r i t e ( 11 { О , 3 } 11 , ( ( i n t [ ] ) ob j s [ 2 ] ) [ i ] ) означае� что при отображен и и значения элемента числового массива выделяется не менее трех пози ци й . Убедиться в том, что переменная n u m s и элемент obj s [ 2 ] ссылают­ ся на один и тот же массив, достаточно легко. Командой ( ( i n t [ ] ) obj s [ 2 ] ) [ 1 ] = О меняем значение второго (по порядку) элемента в мас­ сиве, на который ссылается элемент obj s [ 2 ] , а затем с помощью опе­ ратора цикла проверяем содержимое массива num s . Несложно заметить, что значение элемента nums [ 1 ] действительно изменилось. 222 Масс ивы П араметры командной строки Фигуры, может, и нет, а характер - налицо. из к/ф «девчата» При запуске программы на выполнение ей могут передаваться параме­ тры, которые обычно называют параметрами командной строки. Если программа запускается из командной строки, то параметры указываются после имени файла программы через пробел. При работе со средой разра­ ботки параметры командной строки задаются на вкладке свойств проекта. Окно свойств проекта можно открывать по-разному. Например, в меню Project можно выбрать команду Properties, как показано на рис. 4.2. o<J F.at: ""'"""'..1.1 - - v..... - ь,.... 20u 1" w_ o...,.. ro11 Pro,cct v.r. Ddiuv • Add W� lcмm... •• Add fokw O.U Souк� 8'!1r--- 0 Vrofrtrn.a • �;�, s....w lum rооЬ т� Add U... C...,.,_ cla• ·� A.cfd №w- •tmb Add Emmg •em• liJ W1ndow р Нdр . ..... . с: с и а оw : � &di.ldc froin Pro,IO sь-.u r... �pawe'I'poa : ) n•paм&!t"po• " , а rgs . Len9·ch) ; •Ню.�х -:роа : 1 ЗJr,C&Tpa : рам.пр: -О Stt n SUrtUp Proj«t 1) ' с, RdrtshPl'O)Кt loolЬcw •tmS )' Lii.tin&tJl_l). euUd • • • • •••••• U.U.,..J I ,.,...,...._ -> 1 01 \8oo1t.s.\8 S«<lfcON, � < • Llltir9)4.U • ); Propctt)CS • • • Rdttft'ICts · -" • О> Prt1gtttn.C.i ( 1 ) " , ( k+ l ) , a.rq• ( k ] ) ; j t'A """"' """" '­ • х - " :!! �i ----- "." !§ :-.> ;w6ofe\C:sti;r-p\cxнptes\ch64\L i -1 t in�_J3\Li s t J n... _!1\Ы11\0ctl\la'\ L i s:t in1 • • up• to·d•t•, 8 stcJpPl'd " • • • • • • • • • • fal lce1, Рис. 4 . 2 . Переход к окну свойств проекта ( команда Properties в меню Project) для оп ределения параметров командной строки В результате откроется вкладка свойств проекта. В этой вкладке следует выбрать раздел Debug, как показано на рис. 4.3. В данном разделе есть поле Command line arguments. Туда через пробел вводятся параметры командной строки. В данном примере мы вводим туда такие параметры: первый 1 2 3 А 3 , 1 4 1 5 9 2 последний 223 Глава 4 DdNv Tum ...... UшnglМ.JJ" • )( ' . ' • f� Any CPU Tm Yiondow • • ,... . -- ..... ....... ,... .,,...... Cotnr'l"llnd lin• wgutntl'ltJ! х .... ...... . 111 Нrtp ·1 nc-pad Ш A ЗJ•U92: � * "<!>- � G • lit ь- . t1I• Р· 1$) ...._ .....,.,.P .. ' D �<.,.al .......,.. 13 • . )' � •• Rdtrcntl:S n [NЫс � code ddм.lgglng .1 1> (IWWe U1ot Vsu.I Stucf.o hoI0n9 proc.es � :".2 Utt1n,.-_1J -> O t \looк;\8 �Ooa\�u;rp\�l=s\c'*\Lfsttnifм_1J\Ustt._..... B\&in�tк11\ Li1 t i"1 "' SUild: 1 $U(.CC8de0, • f•J teo, • UP• tO•dltc" • sldppeci " . " " • • • " •• • • • • • • • "• • Рис. 4 . 3 . Параметры командной строки оп ределя ются на вкладке свойств проекта в поле C ommand line arguments в разделе Debug Это параметры, которые мы собираемся передавать программе при за­ пуске. Но остается открытым вопрос о том, как эти параметры в про­ грамме можно использовать. Ответ простой: главный метод программы необходимо описать с аргументом, который является одномерным тек­ стовым массивом. Параметры, которые передаются программе при запу­ ске, отождествляются с элементами этого массива. Два важных момента: Параметры командной строки, какими бы они ни были по факту, пе­ редаются в текстовом формате (как элементы текстового массива). Если необходимо получить параметр в его «родном� формате, то вы­ полняется явное преобразование из текстового формата к нужному типу. • • Текстовый массив, через который передаются параметры командной строки, создавать не нужно. В круглых скобках после названия глав­ ного метода программы объявляется переменная текстового масси­ ва. Эта переменная обрабатывается в программе как массив, и она отождествляется с массивом, сформированным параметрами ко­ мандной строки. Пример, в котором программе при запуске передаются параметры ко­ мандной строки и эти параметры в программе обрабатываются, приве­ ден в листинге 4. 1 3. 224 Масс ивы [1i!J Листинг 4 . 1 З. Параметры командной строки u s ing Sys tem; c l a s s ProgArgs Demo { 1 1 Главный метод с аргументом - массивом : static vo id Main ( string [ ] args ) { 1 1 Определение количества переданных параметров : Console . WriteLine ( " Пepeдaнo { 0 ) параметров " , args . Length ) ; 1 1 Отображение значений параметров : for ( int k=O ; k<args . Length ; k++ ) { 1 1 Отображение значения параметра : Console . WriteLine ( " { О ) - й параметр : { 1 ) " , ( k+ l ) , args [ k] ) ; Console . WriteLine ( " Bыпoлнeниe программы завершено " ) ; Если программе передаются те параметры, что были указаны ранее, то в результате выполнения программы в консольном окне появляются такие сообщения. [1i!J Результат выполнения программы (из листинга 4 . 1 3) Передано 5 параметров 1 - й параметр : первый 2 - й параметр : 1 2 3 3 - й параметр : А 4 - й параметр : 3 , 1 4 1 5 92 5 - й параметр : последний Выполнение программы завершено Первая особенность программы состоит в том, что метод Ma i n ( ) опи­ сан с аргументом s t r i n g [ ] a r g s . Поскольку массив формируется автоматически на основе переданных программе параметров, то коли­ чество элементов в массиве совпадает с количеством параметров. Сле­ довательно, количество переданных параметров можно определить с по­ мощью инструкции a r g s . Length (размер массива a r g s ) . Получение 225 Глава 4 текстового представления параметров - обращение к элементам масси­ ва a r g s в формате a r g s [ k ] . Сама программа очень простая: она выво­ дит сообщение о количестве переданных программе параметров, а затем последовательно в консольном окне отображаются значения этих пара­ метров. Р ез ю ме Как-то даже вот тянет устроить скандал. из к/ф �иван Ва сильевич меняет професси ю» • Массив представляет собой набор переменных, объединенных об­ щим именем. Переменные, входящие в массив, называются элемен­ тами массива. Для идентификации элементов используется имя мас­ сива и индекс (или индексы). Количество индексов, необходимых для однозначной идентификации элемента массива, определяет раз­ мерность массива. Индексация всегда начинается с нуля. Размер­ ность массива можно определить с помощью свойства Ran k. Свой­ ство Length позволяет определить количество элементов в массиве. Для определения размера массива по отдельным индексам использу­ ют метод G e t Length ( ) (аргумент метода определяет индекс, для которого вычисляется размер массива). • Для реализации массива нужна переменная массива и собственно сам массив. Переменная массива содержит ссьшку на массив. Для объявления переменной массива указываются идентификатор типа элементов массива, затем квадратные скобки и имя переменной. Для одномерного массива квадратные скобки пустые. Для многомерных массивов внутри квадратных скобок указывают запятые - количе­ ство запятых на единицу меньше размерности массива. • Массив создается с помощью оператора new, после которого сле­ дуют идентификатор типа, определяющий тип элементов массива, и квадратные скобки. В квадратных скобках через запятую указыва­ ется размер массива по каЖдому из индексов. В результате выполне­ ния инструкции на основе оператора new создается массив, а ссьшка на этот массив является значением инструкции. Ссылку на массив можно записать в переменную массива (при условии, что тип и раз­ мерность массива совпадают с типом и размерностью, указанными при объявлении переменной массива). 226 Масс ивы • Массив при создании можно инициализировать. Для этого перемен­ ной массива значением присваивается список, содержащий значе­ ния, которыми инициализируется массив. Для одномерного массива список инициализации представляет собой последовательность раз­ деленных запятыми значений, заключенных в фигурные скобки. Для двумерного массива элементами списка инициализации являются вложенные списки, которые содержат значения элементов в стро­ ках. Для трехмерного массива список инициализации содержит спи­ ски, элементами которых являются списки со значениями элементов трехмерного массива, и так далее. • Для работы с массивами может использоваться оператор цикла f o r e a ch. В нем объявляется локальная переменная (того же типа, что и тип элементов массива). Эта локальная переменная при выпол­ нении оператора цикла последовательно принимает значения элемен­ тов массива. Через локальную переменную можно прочитать значение элемента массива, но нельзя присвоить элементу новое значение. • Можно создать массив, элементами которого являются переменные массива. В таком случае можно создать иллюзию двумерного масси­ ва со строками разной длины. Если создать массив объектных пере­ менных класса Ob j e c t , то элементам такого массива можно присва­ ивать практически любые значения. • При запуске программе можно передавать параметры (параметры командной строки). В программе такие параметры обрабатываются с помощью текстового массива, который объявляется как аргумент главного метода программы. Задания для самостоятел ьной работы - Я н а следующий Новый год обязательно пойду в баню. - З ачем же ждать целый год ? из к/ф �Ирония судьбы, WlU С легким паром» 1 . Напишите программу, в которой создается одномерный числовой мас­ сив и заполняется числами, которые при делении на 5 дают в остатке 2 (числа 2 , 7 , 1 2 , 1 7 и так далее). Размер массива вводится пользователем. Предусмотреть обработку ошибки, связанной с вводом некорректного значения. 227 Глава 4 2 . Напишите программу, в которой создается массив из 1 1 целочис­ ленных элементов. Массив заполняется степенями двойки - числами о 1 2 3 10 2 = 1 , 2 = 2, 2 = 4, 2 = 8 и так далее до 2 = 1 024. При заполнении массива учесть, что начальный элемент равен 1, а каждый следующий больше предыдущего в 2 раза. Отобразить массив в консольном окне в прямом и обратном порядке. Размер массива задается переменной. 3. Напишите программу, в которой создается одномерный символьный массив из 10 элементов. Массив заполняется буквами �через одну�. на­ чиная с буквы ' а ' : то есть массив заполняется буквами ' а ' , ' с ' , ' е ' , ' g ' и так далее. Отобразите массив в консольном окне в прямом и об­ ратном порядке. Размер массива задается переменной. 4. Напишите программу, в которой создается символьный массив из 10 элементов. Массив заполнить большими (прописными) буква­ ми английского алфавита. Буквы берутся подряд, но только согласные (то есть гласные буквы ' А ' , ' Е ' и ' I ' при присваивании значений эле­ ментам массива нужно пропустить). Отобразите содержимое созданного массива в консольном окне. 5. Напишите программу, в которой создается массив и заполняется слу­ чайными числами. Массив отображается в консольном окне. В этом массиве необходимо определить элемент с минимальным значением. В частности, программа должна вывести значение элемента с минималь­ ным значением и индекс этого элемента. Если элементов с минималь­ ным значением несколько, должны быть выведены индексы всех этих элементов. 6. Напишите программу, в которой создается целочисленный массив, заполняется случайными числами и после этого значения элементов в массиве сортируются в порядке убывания значений. 7. Напишите программу, в которой создается символьный массив, а за­ тем порядок элементов в массиве меняется на обратный. 8. Напишите программу, в которой создается двумерный целочислен­ ный массив. Он заполняется случайными числами. Затем в этом массиве строки и столбцы меняются местами: первая строка становится первым столбцом, вторая строка становится вторым столбцом и так далее. На­ пример, если исходный массив состоял из 3 строк и 5 столбцов, то в ито­ ге получаем массив из 5 строк и 3 столбцов. 228 Масс ивы 9. Напишите программу, в которой создается и инициализируется дву­ мерный числовой массив. Затем из этого массива удаляется строка и столбец (создается новый массив, в котором по сравнению с исход­ ным удалена одна строка и один столбец). Индекс удаляемой строки и индекс удаляемого столбца определяется с помощью генератора слу­ чайных чисел. 1 0 . Напишите программу, в которой создается двумерный числовой мас­ сив и этот массив заполняется �змейкой�: сначала первая строка (сле­ ва направо), затем последний столбец (сверху вниз), последняя строка (справа налево), первый столбец (снизу вверх), вторая строка (слева на­ право) и так далее. Гл ава 5 СТАТ И Ч ЕСКИ Е МЕТОДЫ Не надо меня щадить : пусть самое страшное, но нравда. из к/ф �БрW1Лu антовая рука» Пришло время обсудить статические методы. В принципе, это тема, не­ посредственно относящаяся к классам и объектам. Поэтому мы, присту­ пая к ее рассмотрению, немного забегаем вперед. Вместе с тем, такой подход представляется оправданным по нескольким причинам. Во-пер­ вых, впоследствии легче будет понять, как функционируют методы (не только статические, но и обычные) при их использовании с класса­ ми и объектами. Во-вторых, есть некоторые технологии, которые нам по­ надобятся в дальнейшем при анализе программных кодов. Знакомство, хоть и �преждевременное�, со статическими методами облегчит нам эту задачу. В-третьих, мы обсудим статические методы лишь в той части, ко­ торая не подразумевает детального знакомства с принципами описания классов и создания объектов. Среди вопросов, поднимаемых в этой главе, можно выделить следующие: • особенности статических методов ; • принципы описания статических методов; • перегрузка статических методов ; • передача массива аргументом статическому методу ; • методы, возвращающие результатом массив ; • механизмы передачи аргументов методам ; • использование рекурсии ; • методы с произвольным количеством аргументов. Многие приемы и подходы, рассмотренные в этой главе, затем будут пе­ ренесены и на более общие случаи. 230 Статические методы Зн акомство со статически м и метода м и В ы убедили меня. Я понял, что вам еще нужен. из к/ф � старики -разбой ники» Метод представляет собой именованный блок команд, которые можно выполнять, вызывая метод. В этой главе будут обсуждаться статические методы, описанные в том же классе, в котором описан главный метод программы. G) Н А ЗАМ ЕТ КУ Метод в любом случае описывается в классе . Бы вают методы стати­ ческие и нестатические. Нестатический метод вызывается из объек­ та. То есть для вызова нестатического метода нужен объект: указыва­ ется имя объекта и через точ ку имя метода , который вызывается . Если метод статически й , то для его вызова объект не нужен. При вызове статического метода вместо имени объекта указывается имя класса . Но есл и статически й метод вызы вается в том же классе , в котором он описан , то имя класса можно не указывать. Мы план ируем вызывать статические методы в главном методе п рограмм ы . Поэтому, если такие статические методы описаны в том же классе , в котором опи­ сан главный метод програм м ы , то нет необходимости указывать имя класса . Именно такие ситуации рассматриваются в этой главе. Для работы со статическими методами нам необходимо выяснить, как метод описывается и как метод вызывается. Начнем с описания метода. Мы исходим из того, что метод описывается в том же классе, в котором содержится метод Ma i n ( ) , и вызывается описанный статический метод тоже в методе Ma i n ( ) (или в другом статическом методе, описанном в том же самом классе). Описание статического метода можно разме­ щать как пред описанием метода Ма i n ( ) , так и после него. Ниже приве­ ден шаблон описания статического метода: static тип имя ( аргументы) { 1 1 Команды Начинается описание статического метода с ключевого слова s t a t i c . Ключевое слово �сигнализирует» о том, что метод статический. Затем в описании метода указывается идентификатор, определяющий тип 23 1 Глава 5 результата метода. Например, если метод результатом возвращает цело­ численное значение, то в качестве идентификатора типа указываем клю­ чевое слово i n t . Если метод результатом возвращает символ, то иден­ тификатором типа результата метода указываем ключевое слово c h a r . Может так быть, что метод н е возвращает результат. В этом случае иден­ тификатором типа результата указывается ключевое слово vo i d. � ПОДРОБН ОСТИ Метод - это набор команд. Команды выпол н я ются кажды й раз , ког­ да вызы вается метод. Но кроме выполнения команд, метод может еще и возвращать значение. Возвращаемое методом значение это значение инструкци и , которой вызы вается метод. Есл и метод возвращает значение, то команду вызова метода можно отождест­ влять с возвращаемым значением . После ключевого слова, определяющего тип результата, следует имя ме­ тода - идентификатор, который выбирается пользователем. Он по воз­ можности должен быть информативным и не может совпадать с заре­ зервированными ключевыми словами языка С#. После имени метода в круглых скобках описываются его аргументы. Дело в том, что при вы­ зове методу могут передаваться некоторые значения, используемые ме­ тодом в процессе выполнения команд и вычисления результата. Аргу­ менты описываются так же, как объявляются переменные: указывается тип аргумента и его название. Аргументы метода разделяются запятыми. Тип указывается для каждого метода индивидуально - даже если они относятся к одному типу. Наконец, в фигурных скобках размещаются команды, выполняемые при вызове метода. В этих командах могут ис­ пользоваться аргументы метода. Там также могут объявляться перемен­ ные, которые будем называть локальными. G) Н А ЗАМ ЕТ КУ Есл и метод описан в классе , то это совсем не означает, что команды в теле метода будут выполнен ы . Для выполнения команд в теле ме­ тода этот метод необходи мо вызвать. Команды выпол н я ются тол ько тогда , когда вызывается метод. Есл и метод не вызы вается , то и ко­ манды не выполняются . Перемен ные, которые объя влены в методе , доступны тол ько в теле метода . Здесь уместно вспомн ить главное п равил о , связанное с областью доступности переменных: пере­ мен ная доступ на в том блоке , в котором она объя влена. Есл и пере­ мен ная объя влена в теле метода , то в теле метода она и доступ на. 232 Статические методы Поэтому такая перемен ная назы вается локальной. Место в памяти для локал ьной переменной выделяется при выполнении метода . А п осле того как метод завершает выполнение, перемен ная из па­ мяти удаляется . При новом вызове метода все повторится : п од пе­ ременную будет выделено место в памяти , а затем при завершении работы метода выделен ное под переменную место освобождается . Для того чтобы команды, описанные в методе, вьшолнились, метод необ­ ходимо вызвать. Вызывается метод просто: в соответствующем месте про­ граммного кода указываются имя метода и, если необходимо, аргументы, которые передаются методу при вызове. Аргументы указываются в кру­ глых скобках после имени метода. Если у метода аргументов нет, то при вызове метода после имени метода указываются пустые круглые скобки. G) Н А ЗАМ ЕТ КУ При вызове метода аргументы ему передаются строго в том поряд­ ке , как они объя влены в описании метода . Если метод вызывается с аргументами, то в результате вызова метода выполняются команды, описанные в теле метода, а вместо переменных, объявленных в описании метода в качестве аргументов, подставляются значения, фактически переданные методу при вызове. Если метод не возвращает результат, то вызов метода сводится к выпол­ нению команд из тела метода. Если метод возвращает результат, то в вы­ ражение, содержащее инструкцию вызова метода, вместо этой инструк­ ции подставляется значение, возвращаемое методом. При работе с методами часто используется инструкция r e t u r n . Вы­ полнение этой инструкции в теле метода завершает выполнение метода. Если после инструкции r e t u r n указано некоторое выражение, то вы­ полнение метода завершается, а значение выражения возвращается ре­ зультатом метода. При этом тип значения выражения, указанного после r е t u rn-инструкции, должен совпадать с идентификатором типа, ука­ занным в описании метода как тип возвращаемого методом результата. G) Н А ЗАМ ЕТ КУ Есл и метод возвращает результат, то в теле метода испол ьзует­ ся rеturn-и нструкция (с указанием возвращаемого значен ия ) . Есл и метод не возвращает резул ьтат, то инструкцию return можно 233 Глава 5 не испол ьзовать ( н о есл и она испол ьзуется , то после кл ючевого сло­ ва return ничего не указы вается ) . В листинге 5. 1 представлена программа, в которой описываются и ис­ пользуются достаточно несложные статические методы. � Л истинг 5 . 1 . Знакомство со статически ми методами using Sys tem; c l a s s StatMethDemo { 1 1 Статический метод для отображения текс т а , 1 1 переданного аргументом методу : static void show ( string txt ) { Console . WriteLine ( txt ) ; 1 1 Статический метод для вычисления факториала числа , 1 1 переданного аргументом методу : static int factorial ( int n ) { 1 1 Локаль ная переменная : int s=l ; 1 1 Вычисление произведения : for ( int k=l ; k<=n ; k++ ) { 1 1 Умножение произведения на число : s *=k; 11 Резуль тат метода : return s ; 1 1 Статический метод для возведения числа в степень . 1 1 Число и степень передаются аргументами методу : static douЫ e power ( douЫe х , int n ) { 11 Локаль ная переменная : douЫe s=l ; 1 1 Вычисление результата ( число в степени) : 234 Статические методы for ( int k=l ; k<=n ; k++ ) { / / Текущее значение умножается на число : s *=x ; / / Результат метода : return s ; } / / Главный метод программы : static void Main ( ) { / / Вызываем статический метод для отображения / / сообщения в консольном окне : shоw ( " Начинаем вычисления : " ) ; int m=5 ; / / Целочисленные переменные douЫe z=З , num; / / Действитель ные переменные / / Вычисление факториала числа : show (m+ " ! =" + factorial (m) ) ; / / Число в степени : num=power ( z , m) ; / / Отображение сообщения вызовом статического метода : show ( z + " в степени " +m+ " : " +num) ; Результат выполнения программы такой. [1iJ Результат выполнения программы (из листинга 5. 1 ) Начинаем вычисления : 5 ! =1 2 0 3 в степени 5 : 2 4 3 В этой программе, кроме главного метода, описывается еще и три ста­ тических метода. Эти статические методы затем вызываются в методе Ma i n ( ) . Проанализируем их программные коды. 235 Глава 5 Статический метод s how ( ) не возвращает результат (ключевое слово vo i d перед названием метода в его описании), и у него один текстовый аргумент (описан как s t r i n g t x t ) . В теле метода всего одна коман­ да C on s o l e . W r i t e L i n e ( tx t ) , которой значение текстового аргумен­ та метода отображается в консольном окне. Поэтому при вызове мето­ да в консольном окне будет отображаться значение (текст), переданное методу аргументом. Статический метод f a c t o r i a l ( ) предназначен для вычисления факто­ риала числа, переданного аргументом методу. Поэтому аргумент n мето­ да f a c t o r i a l ( ) объявлен как целочисленный (тип i n t ) . Вычисленное значение возвращается результатом метода. Тип результата определяет­ ся как целочисленный (тип i n t ) . G) Н А ЗАМ ЕТ КУ Факториал ( обозначается как п ! ) числа п - это произведение чисел от 1 до данного числа включ ител ьно. Други м и словам и , по оп реде­ лению п ! = 1 х 2 х З х . . . х п. Например, 5 ! = 1 х 2 х З х 4 х 5 = 1 20 . В теле метода командой объявляется целочисленная локальная пе­ ременная s с начальным значением 1. После этого запускается опера­ тор цикла, в котором индексная переменная k пробегает значения от 1 до n включительно (напомним, что n - это аргумент метода). За каж­ дый цикл командой s * = k текущее значение переменной s умножается на значение индексной переменной k. В результате после завершения оператора цикла значение переменной s равно произведению натураль­ ных чисел от 1 до n. Это именно то значение, которое нам нужно вычис­ лить. Командой r e t u rn s оно возвращается как результат метода. � ПОДРОБН ОСТИ В теле метода fact o r i a l ( ) объя влена перемен ная s . Также в опе­ раторе цикла ( в теле метода) объя влена перемен ная k . У этих пере­ менных разная область доступности и разное время существования . Под переменную s место в памяти выделя ется при вызове метода . Перемен ная ( п осле объя вления) доступна везде в теле метода . Су­ ществует перемен ная , пока выполняется метод. Перемен ная k доступна тол ько в операторе ци кла. Под нее память выделяется при запуске на выполнение оператора цикла. После того как оператор цикла завершает работу, переменная k удаляется 236 Статические методы из памяти ( освобождается место в памяти , выделенное под эту пе­ ременную ) . Еще один статический метод, описанный в программе, предназначен для вычисления результата возведения числа в целочисленную сте­ пень. Метод называется powe r ( ) , у него два аргумента (первый типа douЫ e и второй типа i n t ) . Результатом метод возвращает значение типа dоuЫ е . G) Н А ЗАМ ЕТ КУ Реч ь идет о выч ислении значения вы ражения вида Х' . П о оп ределе­ нию Х' = х х х х . . . х х ( всего п множителей ) . Значения х и п переда­ ются аргументом методу power ( ) , а результатом возвращается зна­ чение Х'. Код метода powe r ( ) немного напоминает код метода f a c t o r i a l ( ) . В теле метода powe r ( ) объявляется переменная s типа douЫ e с на­ чальным значением 1. Затем с помощью оператора цикла значение пере­ менной s последовательно умножается n раз (второй аргумент метода) на значение х (первый аргумент метода). Значение переменной s воз­ вращается результатом метода. В главном методе программы сначала командой s h o w ( " Н ачин а ­ е м вычисле ния : " ) вызывается статический метод s how ( ) для ото­ бражения текстового сообщения. Впоследствии этот же метод вы­ зывается командой s h o w ( m+ " ! = " + f a c t o r i a l ( m ) ) . В аргументе, переданном методу s h o w ( ) , использована инструкция fa c t o r i a l ( m ) , которой вызывается статический метод f a c t o r i a l ( ) . Команда s h ow ( m+ " ! = " + f a c t o r i a l ( m ) ) обрабатывается в несколько этапов. Сначала вычисляется текстовое значение m+ " ! = " + fa c t o r i a l ( m ) , ко­ торое передается аргументом методу s how ( ) и, как следствие, отобража­ ется в консольном окне. Значение выражения m+ " ! = " + fa c t o r i a l ( m ) вычисляется путем преобразования значения переменной m к текстово­ му формату, после чего к полученному тексту дописывается текст " ! = " . Затем вычисляется значение выражения f a c t o r i a l ( m ) , и оно допи­ сывается к полученному ранее текстовому значению. Значение выраже­ ния f a c t o r i a l ( m ) - это факториал числа, определяемого значением переменной m. 237 Глава 5 � ПОДРОБН ОСТИ Выч исление вы ражения fact o r i a l ( m ) выполняется так. П оскол ьку метод facto r i a l ( ) возвращает целочисленный резул ьтат, то при вызове метода для запом инания этого резул ьтата автоматически выделяется место в памяти . В п роцессе выполнения кода метода facto r i a l ( ) форми руется значение переменной s . При вы полне­ н и и команды return s в теле метода значение переменной s коп и ­ руется в ячейку памяти , выделенную для хранения результата мето­ да . Все локал ьные перемен ные, созданные при выполнении метода , удаля ются , а значение из ячейки , выделенной под резул ьтат метода , испол ьзуется в выч ислениях - в данном случае п одставляется вме­ сто вы ражения fac t o r i a l (m) . Командой n um=powe r ( z , m ) переменной n um в качестве значения при­ сваивается результат вызова метода powe r ( ) с аргументами z и m. � ПОДРОБН ОСТИ При вызове метода power ( ) с аргументами z и m значение z возво­ дится в степень m, а резул ьтат записы вается в локал ьную перемен­ ную s . П р и выполнении команды return s значение переменной s коп и руется в ячейку, автоматически выделен ную для резул ьтата ме­ тода . П р и выполнении команды num=powe r ( z , m ) значение из ячей­ ки с резул ьтатом метода коп и руется в перемен ную num. После этого ячейка с результатом метода удаляется из памяти . Вычисленное значение переменной n um используется в команде show ( z + " в с т е пени " +m+ " : " +num) . П ерегруз ка статических методов Я вся такая внезапная, такая противоречивая вся . . . из к/ф �покровские ворота» В языке С# есть очень важный и полезный механизм, который называ­ ется перегрузкой методов. В частности, перегрузка может применяться и к статическим методам. Идея перегрузки состоит в том, что мы можем описать несколько методов с одним и тем же названием. В таком случае 238 Статические методы обычно говорят про разные версии одного и того же метода, хотя в дей­ ствительности методы разные. И они должны отличаться. Поскольку именем они не отличаются, то отличаться они должны списком аргумен­ тов (количеством и/или типом аргументов). При вызове перегружен­ ного метода (то есть метода, у которого есть несколько версий) версия, которая будет вызвана, определяется по количеству и типу аргументов, фактически переданных методу. Поэтому при перегрузке метода его раз­ ные версии должны быть описаны так, чтобы отсутствовала любая неод­ нозначность при вызове метода. В противном случае программа просто не скомпилируется. Пример, в котором используется перегрузка статических методов, пред­ ставлен в листинге 5.2. � Л истинг 5 . 2 . Перегрузка статических методов using Sys tem; c l a s s OverloadMethDemo { 1 1 Версия статического метода для отображения текста 11 (с одним текстовым аргументом ) : static void show ( string txt ) { Console . WriteLine ( " Текст : " +txt ) ; 1 1 Версия статического метода для отображения 11 целого числа ( аргумент метода ) : static void show ( int num) { Console . WriteLine ( " Целое число : " +num) ; 1 1 Версия статического метода для отображения 11 действительного числа ( аргумент метода ) : static void show ( douЫe num) { Console . WriteLine ( " Дeйcтвитeльнoe число : " +num) ; 1 1 Версия статического метода для отображения символа 11 ( аргумент метода ) : static void show ( char s ) { 239 Глава 5 Console . WriteLine ( " Cимвoл : " +s ) ; 1 1 Версия статического метода для отображения числа 11 ( первый аргумент ) и символа ( второй аргумент ) : static void show ( int num, char s ) { Console . WriteLine ( "Apгyмeнты { 0 } и { 1 } " , num, s ) ; 1 1 Главный метод программы : static void Mai n ( ) { 1 1 Целочисленная переменная : int num=S ; 1 1 Действительная числовая переменная : douЫe z = 1 2 . 5 ; 1 1 Символьная переменная : char s ymЬ= ' W ' ; 1 1 Вызываем метод с символьным аргументом : show ( s ymЬ ) ; 1 1 Вызываем метод с текстовым аргументом : shоw ( " Знакомимся с перегрузкой методов " ) ; 1 1 Вызываем метод с целочисленным аргументом : show ( num) ; 1 1 Вызываем метод с действительным аргументом : show ( z ) ; 1 1 Вызываем метод с двумя аргументами : show ( num, ' Q ' ) ; Результат выполнения программы представлен ниже. [1i!J Результат выполнения программ ы (из листинга 5 . 2) Символ : W Текст : Знакомимся с перегрузкой методов 240 Статические методы Целое число : 5 Действительное число : 1 2 , 5 Аргументы 5 и Q В представленной программе описано пять версий статического метода s h ow ( ) : • в программе есть версия метода с текстовым аргументом ; • описана версия метода с целочисленным аргументом; • есть версия метода с действительным числовым аргументом; • описана версия метода с символьным аргументом ; • а также версия метода с двумя аргументами (целочисленным и сим­ вольным). Во всех случаях метод не возвращает результат. Каждая из версий мето­ да отображает в консольном окне значения переданного аргумента (или аргументов). Вопрос только в том, какой вспомогательный текст (поми­ мо значения аргументов) появляется в консоли. В главном методе программы есть несколько команд, которыми вызы­ вается метод s h ow ( ) . Но аргументы методу передаются разные. При выполнении соответствующей команды на основании тех аргументов, которые переданы методу, выбирается версия метода для вызова. На­ пример, при выполнении команды s how ( s ymb ) вызывается версия ме­ тода s how ( ) с одним символьным аргументом, поскольку переменная s ymb, переданная аргументом методу при вызове, объявлена как сим­ вольная. А вот при выполнении команды s how ( num , ' Q ' ) вызывается версия метода s how ( ) с первым целочисленным аргументом и вторым символьным аргументом. � ПОДРОБН ОСТИ Для выбора версии метода должно совпадать не тол ько кол ичество аргументо в , но еще и тип каждого аргумента . Допусти м , в п рограм ­ ме вместо версии метода show ( ) с целочисленным и символ ьным аргументами оп исана версия метода show ( ) с двумя символьными аргумента м и . Тогда команда show ( num , ' Q ' ) была бы некоррект­ ной , п оскол ьку в п рограмме не оказалось бы п одходя щей версии вызы ваемого метода . Такие программные коды не компилируются . С другой сторон ы , есл и мы опишем вместо н ы нешней верси и ме­ тода show ( ) с целоч ислен н ы м и символ ьным аргументами верси ю 241 Глава 5 метода show ( ) с двумя целочисленными аргумента м и , то п рограм­ ма скомпилируется и команда show ( num , ' Q ' ) выпол н ится так: вы­ зы вается метод show ( ) с двумя целочисленными аргументами, и вме­ сто символа ' Q ' испол ьзуется его код 81 в кодовой табл ице . То есть здесь выполняется автоматическое приведение значения типа char в значение типа int . А вот обратное преобразование из типа int в тип char автоматически не выполняется , поэтому вариант метода show ( ) с двумя символьными аргументами не сработает. Аналогично, есл и мы уберем из п рограм м ы описан ие метода show ( ) с целочисленным аргументо м , то при выпол нении команды show ( num ) будет вызвана версия метода с dоuЫ е -аргументом и це­ лочислен ное значение переменной num автоматически п реобразу­ ется в значение типа douЫ e . М асс и в как аргумент метода У папы там совсем не то, что он всем говорит. из к/ф «Бриллиантовая рука» Аргументами методам можно передавать практически все, что угодно. Среди этого �всего� есть и массивы. Другими словами, аргументом ме­ тоду можно передать массив (одномерный, двумерный, многомерный). Чтобы понять, как это делается, следует вспомнить, как мы получаем до­ ступ к массиву. Используется переменная массива - то есть переменная, которая ссылается на массив и которую в большинстве случаев отождест­ вляют с массивом. Поэтому, если у нас имеется массив, который следует передать аргументом методу, то сделать это можно, передав методу ссьтку на массив. Этого будет вполне достаточно для того, чтобы метод получил доступ к массиву. Именно такой подход используется в С#. С технической точки зрения, при передаче массива методу соответству­ ющий аргумент описывается точно так же, как объявляется перемен­ ная массива соответствующего типа. Как данный подход используется на практике, проиллюстрировано в программе в листинге 5.3. [1iiJ Л истинг 5 .3. Передача массива аргументом методу us ing Sys tem; c l a s s ArrayToMethDemo { 242 Статические методы 1 1 Метод для заполнения массива случайными числами : s tatic vo id f i l l Rand ( int [ ] nums ) { 1 1 Объект для генерирования случайных чисел : Random rnd=new Random ( ) ; 1 1 Заполнение массива случайными числами : for ( int k=O ; k<nums . Length ; k++ ) { nums [ k ] =rnd . Next ( l , 1 0 1 ) ; 1 1 Метод для отображения одномерного 11 целочисленного массива : s tatic vo id showArray ( int [ ] nums ) { 1 1 Перебор элементов массива : for ( int k=O ; k<nums . Length ; k++ ) { 1 1 Отображение значения элемента : Console . Write ( " I { 0 } " , nums [ k ] ) ; Console . WriteLine ( " I " ) ; 1 1 Метод для отображения двумерного 11 целочисленного массива : s tatic vo id showArray ( int [ , ] nums ) { 1 1 Перебор строк в массиве : for ( int i=O ; i<nums . GetLength ( O ) ; i + + ) { 1 1 Перебор элементов в строке : for ( int j = O ; j <nums . GetLength ( l ) ; j ++ ) { 1 1 Отображение значения элемента массива : Console . Write ( " { 0 , 3 } " , nums [ i , j ] ) ; 1 1 Переход к новой строке : Console . WriteLine ( ) ; 243 Глава 5 1 1 Метод для вычисления наименьшего элемента в массиве : s tatic int findМin ( int [ ] nums ) { 1 1 Локаль ная переменная : int s=nums [ O ] ; 1 1 Поиск наименьшего значения : for ( int k=l ; k<nums . Length ; k++ ) { 1 1 Если проверяемый элемент имеет значение , 1 1 меньшее текущего значения переменной s : i f ( nums [ k ] < s ) s=nums [ k ] ; 1 1 Резуль тат метода : return s ; 1 1 Главный метод программы : s tatic vo id Main ( ) { 1 1 Одномерные массивы : int [ ] A= { l , 3 , 5 , 7 , 9 , 1 1 , 1 3 , 1 5 } ; int [ ] B=new int [ 5 ] ; 1 1 Двумерный массив : int [ , ] C= { { l , 2 , 3 , 4 } , { 5 , 6 , 7 , 8 } , ( 9 , 1 0 , 1 1 , 1 2 } } ; 1 1 Массив В заполняется случайными числами : f i l lRand ( В ) ; Console . WriteLine ( " Oднoмepный массив А : " ) ; 1 1 Отображается массив А : showArray (А) ; Console . WriteLine ( " Oднoмepный массив В : " ) ; 1 1 Отображается массив В : showArray ( В ) ; 1 1 Поиск наименьшего элемента : int m=findМin ( B ) ; Console . Wri teLine ( " Наименьшее значение : { О } " , m) ; 244 Статические методы Console . WriteLine ( " Двyмepный массив С : " ) ; 1 1 Отображается массив С : showArray ( С ) ; Результат выполнения программы такой (учтите, что используется гене­ ратор случайных чисел). � Результат выполнения программы (из листинга 5 .3 ) Одномерный массив А : 1 1 1 3 1 5 1 7 1 9 1 11 1 13 1 15 1 Одномерный массив В : 1 4 3 1 7 1 90 1 23 1 5 2 1 Наименьшее значение : 7 Двумерный массив С : 1 2 3 4 5 6 7 8 9 10 11 12 В программе описан метод f i l l Rand ( ) , предназначенный для запол­ нения одномерного целочисленного массива случайными числами. Ме­ тод не возвращает результат, а аргумент метода объявлен инструкцией i n t [ ] num s , которая формально совпадает с объявлением переменной массива. В теле метода командой Ran dom rnd=new Random ( ) соз­ дается объект rnd класса Random, который используется в теле метода для генерирования случайных чисел. Это происходит в операторе цик­ ла, в котором индексная переменная k перебирает индексы элементов массива, объявленного аргументом метода. Значение элементу присваи­ вается командой nums [ k ] = r n d . Next ( 1 , 1 О 1 ) (случайное целое число в диапазоне значений от 1 до 1 0 0 включительно). Для отображения содержимого массивов описаны две версии метода s h owAr r a y ( ) . Одна версия предназначена для отображения содер­ жимого одномерного массива, а другая версия метода предназначена для отображения содержимого двумерного целочисленного массива. Если аргументом методу s howAr r a y ( ) передается одномерный массив 245 Глава 5 (объявлен как i n t [ ] nums ) , то элементы массива отображаются в од­ ной строке с использованием вертикальной черты в качестве разделителя. Если аргументом методу s howA r r a y ( ) передается двумерный массив (аргумент объявлен как in t [ , ] n ums ), то элементы массива отобража­ ются в консольном окне построчно. Еще один метод, который называется f i ndM i n ( ) , получает аргументом одномерный целочисленный массив, а результатом возвращает целочис­ ленное значение, которое совпадает со значением наименьшего элемента в массиве. В теле метода объявляется локальная переменная s с началь­ ным значением nums [ О ] (значение первого элемента массива, пере­ данного аргументом методу). После этого запускается оператор цикла, в котором перебираются все элемента массива, кроме начального, и если значение проверяемого элемента n ums [ k ] меньше текущего значения переменной s (условие nums [ k ] < s в условном операторе в теле опера­ тора цикла), то командой s =nums [ k ] переменной s присваивается но­ вое значение. В итоге после завершения оператора цикла в переменную s будет записано значение наименьшего элемента в массиве. Командой r e t u r n s значение переменной s возвращается результатом метода. В главном методе программы командой int [ ] А= { 1 , 3 , 5 , 7 , 9 , 1 1 , 1 3 , 1 5 } создается и инициализируется одномерный массив А. Командой i n t [ ] B=new i n t [ 5 ] просто создается одномерный массив В (но не заполня­ ется). Для заполнения массива случайными числами использована ко­ манда f i l lRand ( В ) . � ПОДРОБН ОСТИ При создании целочисленных массивов они автоматически запол ­ ня ются нул я м и . Вместе с тем , хороший стиль п рограм м и рования состоит в том , чтобы запол нять масси вы явным образом (даже есл и их нужно запол н ить нулями ) . Командой i n t [ , ] С = { { 1 , 2 , 3 , 4 } , { 5 , 6 , 7 , 8 } , { 9 , 1 О , 1 1 , 1 2 } } создается и инициализируется двумерный целочисленный мас­ сив из трех строк и четырех столбцов. Для отображения содержимо­ го массивов используются команды s howAr r a y ( А ) , s h o wA r r a y ( В ) и s howAr r a y ( С ) . Также для массива В вычисляется наименьшее зна­ чение элемента. Для этой цели использован метод f i ndM i n ( ) : коман­ дой in t m= f i n dMin ( В ) объявляется переменная m, значением которой присваивается результат вызова метода f i ndM i n ( ) с аргументом В. 246 Статические методы М асс и в как ре зул ьтат метода Ну а это довесок к кошмару. из к/ф � старики -разбой ники» Метод может возвращать результатом массив. Точнее, метод может воз­ вращать ссылку на массив. Обычно схема такая: при вызове метода соз­ дается массив, а результатом метод возвращает ссылку на него. Как уже отмечалось, переменные, которые создаются при вызове метода, суще­ ствуют, пока метод выполняется, а после завершения метода они удаля­ ются из памяти. В принципе, массив, который создан при вызове метода, также является �кандидатом на удаление�. Но если ссылка на массив возвращается методом как результат и присваивается в качестве значе­ ния, например, какой-то переменной, то эта переменная (объявленная до вызова метода) будет ссылаться на массив. Такой массив не будет уда­ лен из памяти даже после завершения выполнения метода. � ПОДРОБН ОСТИ В языке С# существует система автоматического сбора мусора . Есл и в памяти есть объект или масси в , на который не ссылается ни одна переменная в п рограм ме, такой объект/массив будет из па­ мяти удален системой сборки мусора. Так, есл и при вызове метода создается массив и ссылка на него записы вается в локал ьную пере­ менную , но как резул ьтат этот массив (ссыл ка на него) не возвраща­ ется , то после вы пол нения метода локал ьная перемен ная из памяти удаляется . Следовател ьно , на созданный массив не будет ссылок, и он удаляется системой сборки мусора. Но есл и ссылка на массив возвращается резул ьтатом метода и записы вается во внешнюю (для метода) перемен ную , то после завершения работы метода на созданный массив в программе будет ссыл ка . Поэтому массив не удаляется из памяти . Если метод возвращает результатом ссьтку на массив, то тип результа­ та метода указывается точно так же, как он указывается для переменной массива того же типа. Например, если метод возвращает результатом ссылку на одномерный целочисленный массив, то идентификатор типа для такого метода выглядит как i n t [ ] . Если метод возвращает резуль­ татом ссьтку на двумерный символьный массив, то идентификатор типа результата метода имеет вид c h a r [ , ] . 247 Глава 5 В листинге 5.4 представлена программа, в которой описано несколько статических методов, возвращающих результатом массив. [1iJ Л истинг 5 . 4 . М ассив как результат метода us ing Sys tem; c l a s s ArrayFromМethDemo { 1 1 Метод для создания массива с числами Фибоначчи : s tatic int [ ] fibs ( int n ) { 1 1 Создается массив : int [ ] nums=new int [ n ] ; 1 1 Первый элемент массива : nums [ O ] =l ; 1 1 Е сли массив из одного элемента : i f ( nums . Length==l ) return nums ; 1 1 Второй элемент массива : nums [ l ] =l ; 1 1 Заполнение элементов массива : for ( int k=2 ; k<nums . Length ; k++ ) { 1 1 Значение элемента массива равно сумме значений 1 1 двух предыдущих элементов : nums [ k ] =nums [ k - l ] +nums [ k - 2 ] ; 1 1 Резуль тат метода - ссылка на массив : return nums ; 1 1 Метод для создания массива со случайными символами : s tatic char [ ] rands ( int n ) { 1 1 Объект для генерирования случайных чисел : Random rnd=new Random ( ) ; 1 1 Создание массива : char [ ] s ymЬs=new char [ n ] ; 1 1 Заполнение массива : 248 Статические методы for ( int k=O ; k<s ymЬs . Length ; k++ ) { 1 1 Значение элемента - случайный символ : s ymЬs [ k] = ( char) ( ' А ' +rnd . Next ( 2 6 ) ) ; 1 1 Результат метода - ссылка на массив : return s ymЬ s ; 1 1 Метод для создания двумерного массива с нечетными 1 1 числами : s tatic int [ , ] odds ( int m, int n ) { 1 1 Создание двумерного массива : int [ , ] nums=new int [m, n ] ; 1 1 Локальная переменная : int val= l ; 1 1 Перебор строк массива : for ( int i=O ; i<nums . GetLength ( O ) ; i + + ) { 1 1 Перебор элементов в строке : for ( int j = O ; j <nums . GetLength ( l ) ; j ++ ) { 1 1 Значение элемента : nums [ i , j ] =val ; 1 1 Значение для следующего элемента : val +=2 ; 1 1 Результат метода - ссылка на массив : return nums ; 1 1 Главный метод программы : s tatic vo id Main ( ) { 1 1 Переменная для целочисленного массива : int [ ] А; 11 Переменная для символьного массива : 249 Глава 5 char [ ] В ; 1 1 Переменная для двумерного массива : int [ , ] С ; 1 1 Создается массив с числами Фибоначчи : A=fibs ( l O ) ; Console . WriteLine ( "Чиcлa Фибоначчи : " ) ; 1 1 Отображение содержимого массива : foreach ( int s in А) { Console . Write ( " I { 0 } " , s ) ; Console . WriteLine ( " I " ) ; 1 1 Создается массив со случайными символами : B=rands ( 8 ) ; Console . WriteLine ( " Cлyчaйныe символы : " ) ; 1 1 Отображение содержимого массива : foreach ( char s in В ) { Console . Write ( " I { 0 } " , s ) ; Console . WriteLine ( " I " ) ; 1 1 Создается двумерный массив с нечетными числами : C=odds ( 4 , 6 ) ; Console . WriteLine ( " Двyмepный массив : " ) ; 1 1 Отображение содержимого двумерного массива : for ( int i=O ; i<C . GetLength ( O ) ; i++ ) { for ( int j = O ; j <C . GetLength ( l ) ; j ++ ) { Console . Write ( " { 0 , 4 ) " , C [ i , j ] ) ; Console . WriteLine ( ) ; 250 Статические методы В результате выполнения программы в консольном окне могут появить­ ся такие сообщения (нужно учесть, что в программе использован гене­ ратор случайных чисел). � Результат выполнения программы ( из листинга 5 . 4) Числа Фибоначчи : 1 1 1 1 1 2 1 3 1 5 1 8 1 13 1 2 1 1 34 1 55 1 Случайные символы : 1 R 1 Р 1 Е 1 Р 1 С 1 Т 1 К 1 U 1 Двумерный массив : 1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 В программе описывается три статических метода. Каждый из них воз­ вращает результатом ссылку на массив. Метод f i b s ( ) создает одномер­ ный целочисленный массив, заполняет его числами Фибоначчи, а ре­ зультатом возвращает ссьшку на этот массив. G) Н А ЗАМ ЕТ КУ Последовател ьность Фибоначчи форми руется так: первые два ч ис­ ла в последовател ьности равны 1 , а каждое следующее число - это сум ма двух предыдущих. То есть речь идет о последовател ьности чисел 1 , 1 , 2 , 3 , 5 , 8 , 1 3 , 2 1 и так далее. Аргументом методу передается целое число, определяющее размер мас­ сива. Идентификатором типа результата указана инструкция i n t [ ] . В теле метода командой i n t [ ] num s = n e w i n t [ n ] создается одно­ мерный целочисленный массив. Размер массива определяется аргумен­ том n, переданным методу. Командой nums [ О ] =1 первому (начальному) элементу массива присваивается единичное значение. Затем выполняет­ ся условный оператор. В нем проверяется условие nums . L e n g t h== l , истинное в случае, если массив состоит всего из одного элемента. Если так, то получается, что массив уже заполнен (на предыдущем этапе един­ ственному элементу массива было присвоено единичное значение) . Поэтому командой r e t u r n n u m s завершается выполнение метода, 251 Глава 5 а результатом возвращается ссьшка на созданный массив (напомним, что выполнение инструкции r e t u rn в теле метода приводит к заверше­ нию выполнения метода). Если же условие в условном операторе ложно, то командой nums [ 1 ] =1 единичное значение присваивается и второму (по порядку) элементу массива. G) Н А ЗАМ ЕТ КУ Получается так, что нам нужно явно присвоить первым двум эле­ ментам массива единичные значен и я . Н о у нас нет гаранти и , что второй элемент в массиве существует. Поэтому после п р исваива­ ния значения первому элементу вы полняется п роверка на п редмет того , равен ли размер массива еди н и це . Есл и это так, то с помощью rеturn-инструкци и вы полнение метода завершается . Есл и вы пол ­ нение метода не завершилось ( размер массива не равен еди н и це ) , т о п рисваи вается значение второму элементу. После того как первым двум элементам присвоены значения, с помощью оператора цикла перебираются прочие элементы массива, и при задан­ ном индексе k командой nums [ k ] =nums [ k - 1 ] +nums [ k - 2 ] значение элемента массива вычисляется как сумма значений двух предыдущих элементов. По завершении оператора цикла, когда массив заполнен, командой r e t u rn nums ссьшка на массив возвращается результатом метода. � ПОДРОБН ОСТИ Есл и окажется , что массив состоит всего из двух элементов, то при первой п роверке условия k<nums . Length в операторе цикла с уче­ том того , что начал ьное значение переменной k равно 2, условие окажется л ожн ы м . Поэтому команды в теле оператора ци кла выпол ­ няться н е будут. Метод r a n d s ( ) предназначен для создания одномерного массива, за­ полненного случайными символами. Результат массива имеет тип c h a r [ ] (ссьшка на символьный массив), а аргументом методу переда­ ется целое число, определяющее размер массива. В теле метода создает­ ся объект rnd класса Ran dom для генерирования случайных чисел (ко­ манда Random rnd=new Random ( ) ). Командой ch a r [ ] s ymb s =new c h a r [ n ] создается символьный массив. После этого запускается опе­ ратор цикла, в котором индексная переменная k перебирает значения 252 Статические методы индексов элементов созданного массива. За каждый цикл командой s ymb s [ k ] = ( ch a r ) ( ' А ' + r n d . N e x t ( 2 6 ) ) элементу массива присва­ ивается случайное символьное значение. После заполнения массива ко­ мандой return s ymЬ s ссьшка на него возвращается результатом метода. � ПОДРОБН ОСТИ Значен ием вы ражения rnd . Next ( 2 6 ) я вляется случайное целое ч исло в диапазоне от О до 2 5 вкл юч ител ьно. Вы ражение ' А ' +rnd . Next ( 2 6 ) вычисляется так: к коду 6 5 сим вола ' А ' прибавляется зна­ чение вы ражения rnd . Next ( 2 6 ) . Получается какое-то (случайное) целое число в диапазоне значений от 6 5 до 9 О. После явного п риве­ дения к сим вол ьному типу (инструкция ( ch a r ) перед вы ражением ' А ' + rnd . Next ( 2 6 ) ) получаем сим вол с соответствующим кодом . Метод o d d s ( ) создает двумерный целочисленный массив, заполняет его нечетными числами и возвращает ссьшку на этот массив. У метода два целочисленных аргумента, которые определяют количество строк и столбцов в двумерном массиве. Идентификатор типа результата име­ ет вид i n t [ , ] (ссылка на двумерный целочисленный массив). В теле метода двумерный массив создается командой i n t [ , ] nums=new int [ m , n ] . Для заполнения массива использованы вложенные опера­ торы цикла. При заданных индексах i и j значение элемента масси­ ва вычисляется командой nums [ i , j ] =va l . После этого командой va l += 2 текущее значение переменной val увеличивается на 2 . Поэто­ му значение следующего элемента будет на 2 больше. Начальное значе­ ние переменной va l равно 1 . В итоге элементы массива заполняются числами, начиная со значения 1 , и каждое следующее значение больше предыдущего на 2 . А это последовательность нечетных чисел. После за­ полнения массива ссылка на него возвращается результатом метода. G) Н А ЗАМ ЕТ КУ Желающие могут подумать, как следует изменить п рограм м н ы й код, чтобы массив заполнялся четн ы м и числами ил и , нап ример, ч исла­ м и , которые кратны 5 (то есть 5 , 1 0 , 1 5 , 20 и так дал ее ) . В главном методе программы объявляется несколько переменных масси­ ва: переменная А для одномерного целочисленного массива, переменная В для одномерного символьного массива и переменная С для двумерного 253 Глава 5 целочисленного массива. Сами массивы не создаются. Точнее, они соз­ даются при вызове статических методов, описанных в программе. При выполнении команды A= f i b s ( 1 0 ) создается массив из 1 0 (аргумент метода f i b s ( ) ) чисел Фибоначчи, и ссьшка на массив записывается в переменную А. Командой B = r a n d s ( 8 ) создается массив из 8 (аргу­ мент метода r a n d s ( ) ) случайных символов, и ссылка на этот массив записывается в переменную В. Наконец, командой C = o dd s ( 4 , 6 ) соз­ дается двумерный массив из 4 строк и 6 столбцов (аргументы метода odds ( ) ), и ссылка на массив записывается в переменную С. Массив за­ полнен нечетными числами. Также в главном методе есть команды ( опе­ раторы цикла), предназначенные для отображения содержимого соз­ данных массивов. Читателю они уже должны быть понятны, поэтому особых комментариев не требуют. G) Н А ЗАМ ЕТ КУ Допусти м , и меется два метода . Оди н метод, аргументом которому передается ссыл ка на целочисленный масси в , при вызове запол ­ няет этот массив случай н ы м и числам и . Другой метод, аргументом которому передается целое число ( размер создаваемого масси ва) , возвращает ссыл ку на целочисленный масси в , заполненный случай­ н ы м и числа м и . В чем между ними принци п иал ьная разн и ца? В пер­ вом случае м ы сам и создаем массив и ссылку на него передаем ме­ тоду. Во втором случае массив создается при вызове метода . М еха н и з м ы передач и аргументов методу Начинаю действовать без шума и ныли по вновь утвержденному плану. из к/ф «Бриллиантовая рука» Есть два механизма (или способа) передачи аргументов в метод - по зна­ чению и по ссьtлке. По умолчанию в С# аргументы в метод передаются по значению. Это подразумевает, что в действительности в метод пере­ дается не та переменная, которую мы непосредственно указали аргумен­ том метода при вызове, а ее техническая копия. Каждый раз, когда мы вызываем некоторый метод и передаем ему аргументы, автоматически для каждого из аргументов создается копия. Все операции при выполне­ нии команд метода выполняются с этой копией. В большинстве случаев 254 Статические методы не играет особой роли, как именно аргументы передаются в метод. Важ­ ным это становится, когда мы хотим при вызове метода изменить значе­ ния аргументов, переданных ему. Для большей конкретики рассмотрим пример, представленный в листинге 5.5. [1iiJ Л истинг 5 . 5 . М еханизм передачи аргументов методу u s ing Sys tem; c l a s s Args Demo { 1 1 Первый метод . Аргумент - целое число : s tatic vo id alpha ( int n ) { 1 1 Проверка значения аргумента : Console . WriteLine ( " В методе alpha ( ) . На входе : " + n ) ; 1 1 Попытка изменить значение аргумента : n++ ; 1 1 Проверка значения аргумента : Console . WriteLine ( " В методе alpha ( ) . На выходе : " + n ) ; 1 1 Второй метод . Аргумент - ссылка на массив : s tatic vo id bravo ( int [ ] n ) { 1 1 Проверка содержимого массива : Console . WriteLine ( " В методе bravo ( ) . На входе : " +ArrayToText ( n ) ) ; 1 1 Перебор элементов массива : for ( int k=O ; k<n . Length ; k++ ) { 1 1 Попытка изменить значение элемента массива : n [ k ] ++ ; 1 1 Проверка содержимого массива : Console . Wri teLine ( " В методе bravo ( ) . На выходе : " +ArrayToText ( n ) ) ; 1 1 Третий метод . Аргумент - ссылка на массив : s tatic vo id charlie ( int [ ] n ) { 1 1 Проверка содержимого массива : Console . Wri teLine ( " В методе charlie ( ) . На входе : " +ArrayToText ( n ) ) ; 255 Глава 5 1 1 Создается новый массив : int [ ] m=new int [ n . Length ] ; 1 1 Перебор элементов в массиве : for ( int k=O ; k<n . Length; k++ ) { 1 1 Значение элемента массива : m [ k ] =n [ k ] + l ; 1 1 Попытка присвоить новое значение аргументу : n=m; 1 1 Проверка содержимого массива : Console . WriteLine ( " В методе charlie ( ) . На выходе : " +ArrayToText ( n ) ) ; 1 1 Метод для преобразования массива в текст : static st ring ArrayToText ( int [ ] n ) { 1 1 Текстовая переменная : s tring res=" [ " +n [ O ] ; 1 1 Перебор элементов массива ( кроме началь ного ) : for ( int k=l ; k<n . Length; k++ ) { 1 1 Дописывание текста в текстовую переменную : res+=" , " + n [ k ] ; 1 1 Дописывание текста в текстовую переменную : res+=" ] " ; 1 1 Резуль тат метода - текстовая строка : return re s ; 1 1 Главный метод программы : static vo id Main ( ) { 11 Переменная для передачи аргументом методу : int A=l O O ; 1 1 Проверка значения переменной : Console . WriteLine ( " До вызова метода alpha ( ) : А= " +А ) ; 256 Статические методы 1 1 Вызов метода : alpha (A) ; 1 1 Проверка значения переменной : Console . WriteLine ( " Пocлe вызова метода alpha : А= " +А) ; 1 1 Массив для передачи аргументом методу : int [ ] B= { l , 3 , 5 } ; 1 1 Проверка содержимого массива : Console . WriteLine ( " До вызова метода bravo ( ) : B=" +ArrayToText ( В ) ) ; 1 1 Вызов метода : bravo ( B ) ; 1 1 Проверка содержимого массива : Console . WriteLine ( " После вызова метода bravo ( ) : B=" +ArrayToText ( В ) ) ; 1 1 Массив для передачи аргументом методу : int [ ] С= { 2 , 4 , 6 } ; 1 1 Проверка содержимого массива : Console . WriteLine ( " До вызова метода charlie ( ) : C=" +ArrayToText ( С ) ) ; 1 1 Вызов метода : charlie ( C ) ; 1 1 Проверка содержимого массива : Console . WriteLine ( " После вызова метода charlie ( ) : C=" +ArrayToText ( С ) ) ; В программе описаны три метода: a lpha ( ) , b r avo ( ) и ch a r l i e ( ) . В каждом из этих методов выполняется попытка изменить аргумент (или создается иллюзия, что аргумент изменяется). Но аргументы у ме­ тодов разные. У метода a lpha ( ) аргумент - целое число. У методов b r avo ( ) и ch a r l i e ( ) аргумент - ссылка на одномерный целочис­ ленный массив. Проанализируем коды этих методов. Начнем с метода alpha ( ) . При вызове метода появляется сообщение: в консольное окно выводится значение аргумента (обозначен как n ), переданного методу. После это­ го командой n + + выполняется попытка увеличить на единицу значение 257 Глава 5 аргумента. Затем снова в консольное окно выводится сообщение с уже �новым» значением аргумента. При выполнении метода b r avo ( ) в консольном окне отображается со­ держимое массива (обозначен как n), переданного аргументом методу. Затем с помощью оператора цикла перебираются все элементы масси­ ва, и значение каждого элемента увеличивается на единицу (команда n [ k ] + + в теле оператора цикла). После этого снова отображается со­ держимое массива. � ПОДРОБН ОСТИ При отображении содержи мого массивов в программе испол ьзован вспомогател ьный метод ArrayToText ( ) . Аргументом методу пере­ дается ссыл ка на целочисленный одномерный масси в . Резул ьтатом метод возвращает текстовую строку, которая содержит значения элементов массива. Значения закл ючены в квадратн ые скобки и раз­ делены запяты м и . Поэтому для отображения содержи мого массива достаточно передать массив аргументом методу ArrayToText ( ) и вывести на экран резул ьтат, возвращаемый методо м . Методу cha r l i e ( ) так же, как и методу b r avo ( ) , передается ссьшка на целочисленный массив. Но операции, выполняемые с аргументом и массивом, в данном случае несколько сложнее. В частности, при вы­ полнении метода cha r l i e ( ) создается новый массив m (команда i n t [ ] m=n e w i n t [ n . L e n g t h ] ). Размер этого массива точно такой же, как и размер массива n, переданного аргументом методу. Новый массив за­ полняется поэлементно. Для этого использован оператор цикла, в теле которого командой m [ k ] =n [ k ] + 1 значение элемента созданного мас­ сива вычисляется как соответствующее значение переданного аргумен­ том массива, увеличенное на единицу. После завершения оператора цик­ ла командой n=m выполняется попытка изменить значение аргумента. Если подойти к этой команде чисто формально, то мы пытаемся пере­ бросить ссылку из аргумента n с исходного массива (переданного че­ рез ссылку аргументом методу) на массив, который был создан в мето­ де (хотя в реальности все не так просто). После выполнения команды проверяется содержимое массива, на который ссылается переменная n . В главном методе программы объявляется переменная А с о значением 1 О О и объявляются и инициализируются два целочисленных массива В и С. Мы проверяем работу методов a lpha ( ) , b r avo ( ) и cha r l i e ( ) : 258 Статические методы • • • Проверяется значение переменной А, потом эта переменная переда­ ется аргументом методу a lpha ( ) , и после вызова метода снова про­ веряется значение переменной А. Проверяется содержимое массива В, массив передается аргументом методу b r avo ( ) , и снова проверяется содержимое массива В. Проверяется содержимое массива С, массив передается аргументом методу cha r l i e ( ) , после чего снова проверяется содержимое мас­ сива С. Результат выполнения программы такой (места, на которые следует об­ ратить внимание, выделены жирным шрифтом). � Результат выполнения программы ( из листинга 5 . 5) До вызова метода alpha ( ) : A=l O O В методе alpha ( ) . Н а входе : 1 0 0 В методе alpha ( ) . Н а выходе : 1 0 1 После вызова метода alpha : A=lOO До вызова метода bravo ( ) : B= [ l , 3 , 5 ] В методе bravo ( ) . На входе : [ 1 , 3 , 5 ] В методе bravo ( ) . На выходе : [ 2 , 4 , 6 ] После вызова метода bravo () : В= [2 , 4 , б ] До вызова метода cha rlie ( ) : С= [ 2 , 4 , 6 ] В методе charlie ( ) . Н а входе : [ 2 , 4 , 6 ] В методе charlie ( ) . Н а выходе : [ 3 , 5 , 7 ] После вызова метода charlie () : С= [2 , 4 , б] Что мы видим? Значение 1 О О переменной А после вызова метода a lpha ( ) не изменилось, хотя при второй проверке аргумента в методе a lpha ( ) значение было 1 0 1 . Чтобы понять этот «парадокс�, следует учесть, что аргументы по умолчанию передаются по значению. Поэто­ му при выполнении команды a lpha ( А ) вместо переменной А в метод a lpha ( ) передается техническая копия этой переменной. Ее значе­ ние - такое же, как у переменной А. Поэтому при проверке значения аргумента появляется значение 1 0 0 . При попытке увеличить значение аргумента метода на 1 в действительности увеличивается значение тех­ нической переменной. Ее же значение проверяется в методе, и полу­ чаем число 1 0 1 . Когда метод a lpha ( ) завершает работу, техническая 259 Глава 5 переменная удаляется из памяти. А переменная А остается со своим ис­ ходным значением 1 О О . Что касается массива В , то он после выполнения команды b r avo ( В ) изменился. Причина в том, что здесь на самом деле мы не пытаемся из­ менить аргумент метода. Им является ссьтка на массив, а изменяем мы массив. Более детально все происходит следующим образом. При пере­ даче переменной В аргументом методу b r avo ( ) для нее создается тех­ ническая копия. Значение у копии переменной В такое же, как и у самой переменной В. И это адрес массива. Поэтому и переменная В, и ее ко­ пия ссьтаются на один и тот же массив. Когда при выполнении метода b r avo ( ) изменяются значения элементов массива, то, хотя они изменя­ ются через копию переменной В, речь идет о том же массиве, на который ссылается переменная В. Отсюда и результат. Иная ситуация при вызове метода cha l r i e ( ) с аргументом С. Массив, на который ссьтается переменная с, после вызова метода не изменяется. Причина в том, что в теле метода char 1 i e ( ) мы пытаемся записать в пере­ менную, переданную аргументом, новую ссьтку. Но поскольку, как и во всех предыдущих случаях, аргументом передается не сама переменная, а ее ко­ пия, то новое значение присваивается копии переменной С. Копия ссьта­ ется на новый созданный массив. Когда мы проверяем содержимое массива (после присваивания n=m) в теле метода, то проверяется новый созданный массив. Массив, на который ссьтается переменная С, не меняется. Кроме используемого по умолчанию режима передачи аргументов по зна­ чению, аргументы еще можно передавать и по ссьutке. В таком случае в ме­ тод передается именно та переменная, которая указана аргументом (а не ее копия). Если мы хотим, чтобы аргументы в метод передавались по ссьтке, то в описании метода перед соответствующим аргументом указывается инструкция re f. Такая же инструкция указывается перед аргументом при вызове метода. Как иллюстрацию рассмотрим программу в листинге 5.6. Это модификация предыдущего примера (см. листинг 5.5), но только ар­ гументы методам a lpha ( ) , bravo ( ) и cha r l i e ( ) передаются по ссьт­ ке. Для удобства и сокращения объема кода все комментарии удалены, а места появления инструкции re f выделены жирным шрифтом. [1i!] л истинг 5 . 6 . Передача аргументов по ссылке us ing Sys tem; c l a s s RefArgs Demo { static vo id alpha ( ref int n ) { 260 Статические методы Console . WriteLine ( " В методе alpha ( ) . На входе : " + n ) ; n++ ; Console . WriteLine ( " В методе alpha ( ) . На выходе : " + n ) ; static vo id bravo ( ref int [ ] n ) { Console . WriteLine ( " В методе bravo ( ) . На входе : " +ArrayToText ( n ) ) ; for ( int k=O ; k<n . Length; k++ ) { n [ k ] ++ ; Console . Wri teLine ( " В методе bravo ( ) . Н а выходе : " +ArrayToText ( n ) ) ; static vo id charlie ( ref int [ ] n ) { Console . Wri teLine ( " В методе charlie ( ) . На входе : " +ArrayToText ( n ) ) ; int [ ] m=new int [ n . Length ] ; for ( int k=O ; k<n . Length; k++ ) { m [ k ] =n [ k ] + l ; n=m; Console . Wri teLine ( " В методе charlie ( ) . На выходе : " +ArrayToText ( n ) ) ; static string ArrayToText ( int [ ] n ) { s tring res=" [ " +n [ O ] ; for ( int k=l ; k<n . Length; k++ ) { res+= " , " +n [ k ] ; res+=" ] " ; return re s ; static vo id Main ( ) { int A=l O O ; Console . Wri teLine ( " До вызова метода alpha ( ) : А= " +А ) ; alpha ( ref А) ; Console . WriteLine ( " После вызова метода alpha ( ) : А= " +А) ; 261 Глава 5 int [ ] B= { l , 3 , 5 } ; Console . WriteLine ( " До вызова метода bravo ( ) : B=" +ArrayToText ( В ) ) ; bravo ( ref В ) ; Console . WriteLine ( " После вызова метода bravo ( ) : B=" +ArrayToText ( В ) ) ; int [ ] С= { 2 , 4 , 6 } ; Console . WriteLine ( " До вызова метода charlie ( ) : C=" +ArrayToText ( С ) ) ; charlie ( ref С ) ; Console . Wri teLine ( " После вызова метода char l i e ( ) : C=" +ArrayToText ( С ) ) ; В этом случае результат выполнения программы такой (жирным шриф­ том выделены места, на которые стоит обратить внимание). � Результат выполнения программы (из листинга 5 . 6 ) До вызова метода alpha ( ) : A=l O O В методе alpha ( ) . Н а входе : 1 0 0 В методе alpha ( ) . Н а выходе : 1 0 1 После вызова метода alpha : A=lOl До вызова метода bravo ( ) : B= [ l , 3 , 5 ] В методе bravo ( ) . На входе : [ 1 , 3 , 5 ] В методе bravo ( ) . На выходе : [ 2 , 4 , 6 ] После вызова метода bravo () : В= [2 , 4 , б] До вызова метода charlie ( ) : С= [ 2 , 4 , 6 ] В методе charlie ( ) . На входе : [ 2 , 4 , 6 ] В методе charlie ( ) . На выходе : [ 3 , 5 , 7 ] После вызова метода charlie () : С= [ З , 5 , 7 ] Видим, что теперь изменяется не только массив, на который ссылается переменная В, но и массив, на который ссылается переменная С, а также изменяется переменная А. Объяснение простое - аргументы передаются по ссылке, поэтому изменения, которые мы вносим в методе в аргумен­ ты, применяются к аргументам, а не к их копиям. С передачей аргументов связан еще один механизм, позволяющий пе­ редавать в метод переменные, которым не присвоено значение - то есть 262 Статические методы неинициализированные аргументы. Необходимость в использовании та­ кого подхода может возникнуть, когда при выполнении метода нужно вернуть не одно значение, а сразу несколько. Можно, конечно, эти значе­ ния реализовать в виде массива, но этот прием не всегда приемлем. Мож­ но передать аргумент по ссьшке. Но в таком случае есть одна проблема: данному аргументу необходимо предварительно присвоить значение. А это тоже не всегда приемлемо. В таких случаях можно использовать передачу неинициализированного аргумента. То есть мы можем пере­ дать аргументом переменную, которая объявлена в программе, но зна­ чение ей не присваивалось. Просто так это сделать не получится - при компиляции возникнет ошибка. Придется использовать специальный режим. Если мы предполагаем, что некоторый аргумент метода будет пе­ редаваться через неинициализированную переменную, то соответствую­ щий аргумент в описании метода указывается с идентификатором o u t . При вызове метода такой аргумент также передается с использованием идентификатора o u t . Есть еще одно важное условие: аргументу, кото­ рый передан с о u t -инструкцией, при выполнении метода должно при­ сваиваться значение. Как иллюстрацию к использованию механизма передачи неинициализи­ рованных аргументов рассмотрим программу в листинге 5. 7. Там описы­ вается метод, который для массива, переданного аргументом, возвращает значение наименьшего элемента в массиве. Массив передается первым аргументом методу. А вторым аргументом методу передается перемен­ ная (неинициализированная), в которую записывается значение индек­ са элемента с наименьшим значением. Проанализируем код программы (жирным шрифтом выделены места, где используется инструкция аи t ). [1iJ Листинг 5 . 7. Неини циализированный аргумент u s ing Sys tem; c l a s s U s ingOutDemo { 1 1 Метод для вычисления значения наименьшего элемента 11 в массиве и его индекса : static int getMin ( int [ ] nums , out int index) { 1 1 Начальное значение для индекса : index= O ; 1 1 Перебор элементов массива : for ( int k=l ; k<nums . Length; k++ ) { 263 Глава 5 1 1 Если значение элемента массива меньше текущего 11 минимального значения : i f ( nums [ k ] <nums [ index ] ) { 1 1 Новое значение для индекса : index=k; 11 Резуль тат метода : return nums [ index ] ; 1 1 Главный метод программы : static vo id Main ( ) { 1 1 Целочисленный массив : int [ ] А= { 1 2 , 7 , 8 , 3 , 8 , 4 , 1 , З , 4 , 1 , 7 , 1 5 } ; 1 1 Отображение содержимого массива : foreach ( int v in А) { Console . Write ( " I { 0 } " , v ) ; Console . WriteLine ( " I " ) ; 1 1 Объявление переменных : int val , k ; 1 1 Вычисление элемента с наименьшим значением : val=getMin ( A , out k ) ; 1 1 Отображение резуль татов : Console . WriteLine ( " Haимeньшee значение : " +val ) ; Console . WriteLine ( "Индекс элемента : " + k ) ; Console . WriteLine ( " Проверка : А [ { О } ] = { 1 } " , k , A [ k ] ) ; Результат выполнения программы такой. � Результат выполнения программы (из листинга 5 . 7) 1 12 1 7 1 8 1 3 1 8 1 4 1 1 1 3 1 4 1 1 1 7 1 15 1 264 Статические методы Наименьшее значение : Индекс элемента : 6 Проверка : А [ б ] =l Метод для вычисления значения наименьшего элемента описан доста­ точно просто. Он возвращает i n t -значение, и у него два аргумента: це­ лочисленный массив nums и целочисленный аргумент i n dex, описан­ ный с о u t -инструкцией. В теле метода командой i n dex= O аргументу i ndex присваивается начальное нулевое значение (индекс первого эле­ мента в массиве). Затем перебираются прочие элементы массива (на­ чиная со второго). Каждый очередной элемент сравнивается со зна­ чением элемента, чей индекс записан в аргумент i n de x (условие nums [ k ] <nums [ i nde x ] в условном операторе). Если значение прове­ ряемого элемента с индексом k меньше значения элемента с индексом i ndex, то командой i ndex=k аргумент i ndex получает новое значение. После завершения выполнения оператора цикла в аргумент i ndex запи­ сано значение элемента массива с наименьшим значением. Если таких элементов несколько, то запоминается индекс первого из них. Результат метода возвращается командой r e t u r n nums [ i ndex ] . В данном слу­ чае мы по индексу элемента определяем значение элемента, и это значе­ ние является результатом метода. В главном методе программы командой i n t [ ] А= { 1 2 , 7 , 8 , 3 , 8 , 4 , 1 , 3 , 4 , 1 , 7 , 1 5 } создается и инициализируется целочисленный мас­ сив. Содержимое массива отображается в консольном окне. Также в про­ грамме объявляются две целочисленные переменные v a l и k. Несмотря на то что переменной k значение не присваивалось, выполняется коман­ да v a l =getMi n ( А , out k ) . Второй аргумент методу getMin ( ) переда­ ется с о u t -инструкцией. После выполнения команды в переменную val записывается значение элемента с наименьшим значением, а в перемен­ ную k записывается значение индекса этого элемента (первого из них). Значение переменных val и k отображается в консольном окне. Для проверки мы также отображаем значение элемента из массива А с ин­ дексом k (элемент А [ k ] ). Понятно, что значения переменной v a l и эле­ мента А [ k ] должны совпадать. G) Н А ЗАМ ЕТ КУ Описанные выше механизмы передач и аргументов применяются ко всем методам , не тол ько статически м . 265 Глава 5 Р е курсия Именно так выражается ее потребность в ми­ ровой гармонии. из к/ф «Покровские ворота» При описании методов можно использовать рекурсию. При рекурсии в теле метода вызьшается этот же метод (но обычно с другим значением аргумента или аргументов). Такой подход позволяет создавать эффектные коды. Прав­ да, «эффектный» не всегда означает «эффективный». Классическим приме­ ром использования рекурсии является программа для вычисления факто­ риала числа (с использованием рекурсии). Чтобы понять, как организуется программный код, имеет смысл учесть, что факториал числа п! = 1 х 2 х 3 х ... х (п - 1 ) х п может бьпь представлен в виде п! = (п - 1 ) ! х п то есть фак­ ториал для числа п может быть представлен как факториал для числа п - 1 , умноженный на п . Программный код статического метода, вычисляющего факториал числа (переданного аргументом методу), может быть таким: - static int factorial ( int n ) { i f ( n==l ) return 1 ; e l s e return n* factorial ( n - 1 ) ; Основу тела метода f а с t о r i а 1 ( ) составляет условный оператор, в ко­ тором проверяется равенство единице аргумента метода n (условие n== l ). Если это так, то результатом метода возвращается значение 1 (по определению 1 ! = 1 ). Если условие ложно, то результатом метода воз­ вращается значение выражения n * f а с t о r i а 1 ( n - 1 ) . В этом выраже­ нии использован вызов метода f а с t о r i а 1 ( ) , но с аргументом n - 1 . Как это работает? Достаточно просто. Допустим, метод f a c t o r i a l ( ) вызы­ вается с аргументом 1 . Тогда при проверке условия в условном операто­ ре оно окажется истинным и результатом метода возвращается значение 1 . Теперь предположим, что метод f а с t о r i а 1 ( ) вызывается с аргумен­ том, отличным от 1 например, с аргументом 3. При проверке условия оно окажется ложным, и результатом метода возвращается значение вы­ ражения З * f a ct o r i a l ( 2 ) . Но перед тем как вернуть значение, его необ­ ходимо вычислить. А для этого снова вызывается метод f a c t o r i a l ( ) , но с аргументом 2 . Проверяется условие, оно ложное, и значение выра­ жения f a c t o r i a l ( 2 ) вычисляется как 2 * fa c t o r i a l ( 1 ) . Снова вы­ зывается метод f a c t o r i a l ( ) , но с аргументом 1 . А мы уже знаем, что - 266 Статические методы значением выражения f a ct o r i a l ( 1 ) является 1 . Тогда значение выра­ жения f a c t o r i a l ( 2 ) равно 2, а значение выражения f a c t o r i a l ( 3 ) равно 6 . Понятно, что, чем больше значение аргумента метода, тем длин­ нее цепочка вызовов метода f a c t o r i a l ( ) . Аналогичным образом с использованием рекурсии описываются и дру­ гие методы. Например, так может выглядеть программный код для ста­ тического метода, вычисляющего число в последовательности Фибонач­ чи по его номеру (аргумент метода): static int fibs ( int n ) { i f ( n==l l l n==2 ) return 1 ; e l s e return fibs ( n - l ) + fibs ( n - 2 ) ; Здесь мы учли тот факт, что при значениях аргумента 1 или 2 (первое или второе число в последовательности) метод должен возвращать значение 1 . Во всех остальных случаях число с номером n вычисляется как сумма двух предыдущих чисел. Поэтому программный код метода f i b s ( ) ор­ ганизован в виде условного оператора, в котором проверяется условие n== l 1 1 n==2 (аргумент n равен 1 или 2 ). Если так, то результатом мето­ да возвращается значение 1 . Если условие ложно, то результатом метода возвращается значение выражения f ib s ( n - 1 ) + f ib s ( n - 2 ) , в котором вызывается метод f i b s ( ) (причем дважды). Рекурсию можно использовать даже там, где ее, на первый взгляд, не мо­ жет быть вообще. Например, задача на вычисление суммы натуральных чисел. Допустим, нам нужно вычислить сумму S(n) = 1 + 2 + 3 + ". + ( п - 1 ) + п. Но ее можно представить в виде S(n) = п + S(n - 1 ) - то есть сумма п натуральных чисел есть сумма п - 1 натуральных чисел плюс число п. Значит, статический метод для вычисления суммы натуральных чисел с использованием рекурсии можно описать так: static int sum ( int n ) { i f ( n==O ) return О ; e l s e return n+ sum ( n - 1 ) ; То есть если аргумент метода равен О , то результатом является значе­ ние О . В противном случае значение выражения s um ( n ) вычисляется как n + s um ( n - 1 ) . В листинге 5.8 представлена программа со статиче­ скими методами, в описании которых использована рекурсия. 267 Глава 5 [1i!J Листинг 5 . 8 . Использование рекурсии us ing Sys tem; c l a s s Recurs ionDemo { 1 1 Метод для вычисления факториала числа : static int factorial ( int n ) { i f ( n==l ) return 1 ; e l s e return n * factorial ( n - 1 ) ; 1 1 Метод для вычисления чисел Фибоначчи : static int fibs ( int n ) { i f ( n==l 1 1 n==2 ) return 1 ; e l s e return fibs ( n - l ) + fibs ( n - 2 ) ; 1 1 Метод для вычисления суммы чисел : static int sum ( int n ) { i f ( n==O ) return О ; e l s e return n+sum ( n - 1 ) ; 1 1 Метод для отображения содержимого массива : static vo id show ( int [ ] а , int k) { 11 Отображение значения элемента массива : Console . Write ( a [ k ] + " " ) ; 1 1 Е сли элемент в массиве последний : i f ( k==a . Length - 1 ) { Console . WriteLine ( ) ; 1 1 Е сли элемент в массиве не последний : else { 1 1 Рекурсивный вызов метода : show ( а , k+ l ) ; 268 Статические методы 1 1 Перегрузка метода для отображения 11 содержимого массива : static vo id show ( int [ ] а ) { 1 1 Вызов версии метода с двумя аргументами : show ( a , 0 ) ; 1 1 Главный метод программы : static vo id Main ( ) { Console . WriteLine ( " Фaктopиaл числа : " ) ; for ( int k=l ; k<=l O ; k++ ) { 1 1 Вычисление факториала числа : Console . WriteLine ( k+ " ! = " + factorial ( k ) ) ; Console . WriteLine ( "Чиcлa Фибоначчи : " ) ; for ( int k=l ; k<=l O ; k++ ) { 1 1 Вычисление чисел Фибоначчи : Console . Write ( fibs ( k) + " " ) ; Console . WriteLine ( ) ; Console . Write ( " Cyммa чисел от 1 до 1 0 0 : " ) ; Console . WriteLine ( sum ( l O O ) ) ; 1 1 Числовой массив : int [ ] А= { 1 , 3 , 5 , 7 , 9 , 1 1 , 1 3 , 1 5 , 1 7 , 1 9 , 2 1 } ; Console . WriteLine ( "Чиcлoвoй массив : " ) ; 1 1 Отображение всех элементов массива : show (A) ; Console . WriteLine ( " Элeмeнты, начиная с третьего : " ) ; 1 1 Отображение элементов , начиная с третьего : show (A, 2 ) ; Результат выполнения программы представлен ниже. 269 Глава 5 [1i!J Результат выполнения программы ( из листинга 5 . 8) Факториал числа : 1 ! =1 2 ! =2 3 ! =6 4 ! =2 4 5 ! =1 2 0 6 ! =7 2 0 7 ! =5 0 4 0 8 ! =4 0 3 2 0 9 ! =3 6 2 8 8 0 1 0 ! =3 6 2 8 8 0 0 Числа Фибоначчи : 1 1 2 3 5 8 13 2 1 34 55 Сумма чисел от 1 д о 1 0 0 : 5 0 5 0 Числовой массив : 1 3 5 7 9 11 13 15 17 19 21 Элементы, начиная с третьего : 5 7 9 11 13 15 17 19 21 В программе, кроме уже описанных методов, использовался метод s h o w ( ) для отображения массива. В нем также задействована рекур­ сия. Аргументов у метода два: отображаемый массив (объявлен как i n t [ ] а) и индекс элемента, начиная с которого выполняется отобра­ жение элементов массива (соответствующий аргумент объявлен как i n t k). В теле метода командой C o n s o l e . W r i te ( а [ k ] + " " ) ото­ бражается значение элемента массива с индексом, переданным вторым аргументом методу. Затем в условном операторе проверяется условие k==a . Lengt h - 1 . Если оно истинно, то это означает, что мы имеем дело с последним элементом в массиве. В таком случае командой C o n s o l e . W r i t e L i n e ( ) выполняется переход к новой строке. Но если условие оказалось ложным, то командой s h ow ( а , k+ l ) выполняется рекур­ сивный вызов метода s h o w ( ) . Первый аргумент, переданный методу, все тот же массив. А второй аргумент - на единицу больше. Получает­ ся, что когда вызывается метод show ( ) , то аргументом ему передается массив и индекс элемента. Если это индекс последнего элемента, то его 270 Статические методы значение отображается в консольном окне и, в общем-то, все (за исклю­ чением перехода к новой строке). Если элемент не последний, то после отображения его значения метод s h ow ( ) вызывается для следующего элемента, и так далее по цепочке, пока метод не будет вызван с индексом последнего элемента. Если мы хотим отобразить значения всех элементов массива, то нам не­ обходимо вызвать метод s h ow ( ) со вторым нулевым аргументом. Мы для удобства переопределяем метод s h o w ( ) , описав версию метода без второго аргумента (первый аргумент - отображаемый массив). В описа­ нии версии метода s h o w ( ) с одним аргументом есть всего одна команда s how ( а , 0 ) , которой вызывается версия метода s how ( ) с двумя аргу­ ментами, причем второй аргумент нулевой. � ПОДРОБН ОСТИ При перегрузке метода show ( ) о рекурси и речь не идет. Дело в том , что когда в теле версии метода show ( ) с одн им аргументом вызыва­ ется версия метода show ( ) с двумя аргументами , то фактически вы­ зывается другой метод ( просто название у него такое же) . Это не ре­ курсивный вызов. При рекурсивном вызове метод вызывает сам себя . В главном методе программы проиллюстрирована работа статических методов. В частности, там для чисел от 1 до 10 вычисляется фактори­ ал, вычисляется последовательность чисел Фибоначчи, а также отобра­ жается содержимое одномерного целочисленного массива. Во всех этих случаях используются методы, реализованные на основе рекурсии. М етоды с п рои з вол ьн ы м кол ичеством аргументов История, леденящ ая кровь. Под маской овцы скрывался лев ! из к/ф «Покровские ворота» Допустим, мы хотим описать метод, который вычисляет сумму переданных ему аргументов. В принципе, путем перегрузки метода мы можем описать версию с одним аргументом, двумя аргументами, тремя аргументами и так далее. Проблема в том, что на каждое количество аргументов нужна «персо­ нальная� версия метода. И сколько бы версий метода мы ни описали, всегда 271 Глава 5 можно передать на один аргумент больше. Здесь мы сталкиваемся с пробле­ мой, которая состоит в том, что наперед не известно, сколько аргументов будет передано методу при вызове. Причем мы хотим, чтобы можно бьmо передавать любое количество. Именно для подобных случаев в С# есть ме­ ханизм, позволяющий описывать методы с произвольным количеством ар­ гументов. Набор аргументов, количество которых неизвестно, формально описывается как массив, но только перед описанием этого массива в списке аргументов указывается ключевое слово params . В теле метода обрабаты­ ваются такие аргументы, как элементы массива. Но при вызове метода ар­ гументы просто перечисляются через запятую. Например, если некоторый метод описан в формате ме тод (pa rams int [ ] args ) , то это означает, что при вызове методу может быть передано произвольное количество це­ лочисленных аргументов. В теле метода к этим аргументам обращаемся так, как если бы они бьmи элементами массива a r g s : то есть args [ О ] (первый аргумент), args [ 1 ] (второй аргумент) и так далее. Количество передан­ ных аргументов можно определить с помощью свойства Length (выраже­ ние вида args . Length). (D Н А ЗАМ ЕТ КУ В принципе, вместо аргумента, описанного как массив с кл ючевым словом params , можно передавать обычный массив соответствую­ щего типа. Метод можно описывать так, что в нем будут как «обычные» (явно описанные) аргументы, так и аргументы, количество которых наперед не задано. В таком случае «обычные» аргументы описываются сначала. Допустим, мы хотим описать метод, которому при вызове обязательно передаются один символьный аргумент и произвольное количество це­ лочисленных аргументов. Такой метод описывается в формате ( c h a r s ymb , pa rams i n t [ ] a r g s ) . Нельзя описать метод, у которого не­ сколько наборов аргументов, количество которых неизвестно. Напри­ мер, не получится описать метод, которому передается произвольное ко­ личество символьных и целочисленных аргументов. � ПОДРОБН ОСТИ При вызове метода фактически переданные ему аргументы заносят­ ся в память. Есл и известен объем памяти , занимаем ый аргумента­ м и , и тип значен и й , то можно выч исл ить кол ичество этих аргументов. Есл и , например, известно, что методу передается один сим вол ьный 272 Статические методы аргумент и п роизвольное количество целочисленных аргументов, то задача о «расп ределении памяти» решается однознач но - можно определ ить, какая память выделена под символьный аргумент, а то , что осталось, выделено под целочисленные аргументы . Зная , какой объем памяти выделяется для значения целочисленного типа, можно определ ить кол ичество целочисленных аргументов и область памяти , выделенную для каждого из них. Но есл и бы было указано, что некото­ рая область памяти выделена для неизвестного кол ичества сим вол ь­ ных и целоч исленных значен и й , то здесь могут быть разные варианты распределения памяти . П роще говоря , задача не имеет однознач но­ го решен ия . Поэтому в методе может быть тол ько один набор аргу­ ментов, кол ичество которых не задано. Небольшая программа, иллюстрирующая способы описания и исполь­ зования методов с произвольным количеством аргументов, представле­ на в листинге 5.9. [1i!J Л истинг 5 . 9 . Методы с п роизвольным кол ичеством аргументов u s ing Sys tem; c l a s s Params Demo { 1 1 Метод для вычисления суммы чисел : static int sum (params int [ ] а ) { 1 1 Локальная переменная ( значение суммы) : int res=O ; 1 1 Перебор аргументов метода : for ( int k=O ; k<a . Length ; k++ ) { 1 1 Прибавление слагаемого к сумме : res +=a [ k ] ; 1 1 Результат метода : return re s ; 1 1 Метод для извлечения символов из текста : static string getText ( string t , params int [ ] а ) { 1 1 Начальное значение формируемой текстовой строки : s tring res=" " ; 273 Глава 5 1 1 Перебор аргументов метода : for ( int k=O ; k<a . Length ; k++ ) { 1 1 Добавление символа в текст : res+=t [ a [ k ] ] ; 1 1 Резуль тат метода : return re s ; 1 1 Метод отображает значения аргументов : static vo id show ( int [ ] а , params char [ ] Ь ) { 1 1 Количество элементов в числовом массиве : Console . Write ( "Чиceл " +a . Length+ " : " ) ; 1 1 Значения элементов в числовом массиве : for ( int k=O ; k<a . Length - 1 ; k++ ) { Console . Write ( a [ k ] + " " ) ; Console . WriteLine ( "и " +a [ a . Length - 1 ] ) ; 1 1 Количество символьных аргументов : Console . Write ( " Cимвoлoв " +b . Length+ " : " ) ; 1 1 Значения символьных аргументов : for ( int k=O ; k<b . Length - 1 ; k++ ) { Console . Write (b [ k ] + " " ) ; Console . WriteLine ( "и " +b [ b . Length - 1 ] ) ; 1 1 Главный метод программы : static vo id Main ( ) { 1 1 Примеры вызова методов . 1 1 Вычисление суммы чисел : Console . WriteLine ( " Cyммa чисел : " + s um ( l , 6 , 9 , 2 , 4 ) ) ; Console . WriteLine ( " Cyммa чисел : " + s um ( S , 1 , 2 ) ) ; 1 1 Формируется текст : 274 Статические методы Console . WriteLine ( getText ( " Paз два три" , 0 , 1 0 , 8 , 1 ) ) ; Console . WriteLine ( getText ( " Бpeвнo" , 3 , 5 , 1 , 5 , 4 ) ) ; 1 1 Отображаются аргументы : show ( new int [ ] { 1 , 3 , 5 } , ' А ' , ' В ' , ' С ' , ' D ' , ' Е ' ) ; show ( new int [ ] { 1 , 3 , 5 , 7 , 9 ) , ' А ' , ' В ' , ' С ' , ' D ' ) ; Как будет выглядеть результат выполнения этой программы, показано ниже. � Результат выполнения программы (из листинга 5 . 9) Сумма чисел : 2 2 Сумма чисел : 8 Рита ворон Чисел 3 : 1 3 и 5 Символов 5 : А В С D и Е Чисел 5 : 1 3 5 7 и 9 Символов 4 : А В С и D В программе описан статический метод s um ( ) , предназначенный для вычисления суммы чисел, переданных аргументами методу. Аргумент описан как p a r ams i n t [ ] а. В теле метода объявлена локальная пе­ ременная re s с начальным нулевым значением. В операторе цикла перебираются элементы массива а (но в реальности это перебор аргу­ ментов, переданных методу), и за каждый цикл командой r e s += a [ k ] к текущему значению переменной r e s прибавляется значение очеред­ ного аргумента. После того как сумма аргументов вычислена и записа­ на в переменную r e s , значение переменной возвращается результатом метода. Так, если вызвать метод командой s um ( 1 , 6 , 9 , 2 , 4 ) , то значе­ нием будет число 2 2 (сумма чисел 1 , 6, 9, 2 и 4 ). Значением выражения s um ( 5 , 1 , 2 ) является число 8 (сумма чисел 5 , 1 и 2 ). Метод g e t T e x t ( ) предназначен для формирования одной текстовой строки на основе другой текстовой строки. Аргументом методу передает­ ся текст (первый аргумент) и произвольное количество целочисленных 275 Глава 5 аргументов. Эти целочисленные аргументы определяют индексы сим­ волов в текстовой строке (первый аргумент), из которых следует сфор­ мировать строку-результат. Например, если метод вызывается командой g e t T e x t ( " Ра з д в а три " , О , 1 О , 8 , 1 ) , то значение вычисляется так: из строки " Ра з д в а три " берем символы с индексами О , 1 0 , 8 и 1 и объединяем их в текстовую строку. Это слово " Рит а " . Если метод вы­ зывается командой ge t T e x t ( " Бр е в н о " , 3 , 5 , 1 , 5 , 4 ) , то результатом является текст " в ор о н " : из текста " Бр е в н о " следует взять символы с индексами 3 , 5 , 1 , снова 5 и 4 . Код метода g e t T e x t ( ) реализован несложно. Объявляется текстовая переменная r e s , начальное значение которой - пустая текстовая стро­ ка. Затем запускается оператор цикла, в котором перебираются целочис­ ленные аргументы метода (отождествляются с элементами массива а ) . Командой r e s +=t [ а [ k ] ] к текущему значению текстовой переменной r e s дописывается символ из текста t (первый аргумент метода) с ин­ дексом а [ k ] . Строка r e s возвращается результатом метода. Метод s how ( ) выполняет незатейливую работу - он отображает значе­ ния переданных ему аргументов. Первым аргументом методу передается целочисленный массив а (обычный). После этого обязательного аргу­ мента методу может передаваться произвольное количество символь­ ных аргументов. Поэтому формальный второй аргумент Ь метода описан как массив с идентификатором p a r am s . Методом при вызове отобража­ ется такая информация: • количество элементов в числовом массиве (определяется выражени­ ем а . Length ) ; • значения элементов в числовом массиве, причем перед последним значением отображается текст " и " ; • количество символьных аргументов (определяется выражением b . Length ) ; • значения символьных аргументов (перед последним значением ото­ бражается текст "и " ). Для проверки работы метода использованы команды s h o w ( n e w i n t [ ] { l , 3 , 5 } , ' A ' , ' B ' , ' C ' , ' D ' , ' E ' ) и s how ( new i nt [ ] { 1 , 3 , 5 , 7 , 9 } , ' А ' , ' в ' , ' с ' , ' D ' ) . В обоих случаях первым аргумен­ том методу передаются анонимные массивы, которые создаются коман­ дой вида new i n t [ ] { значения } . 276 Статические методы G) Н А ЗАМ ЕТ КУ Например, командой new i n t [ ] { 1 , 3 , 5 } создается массив из трех элементов со значениями 1 , 3 и 5 . Значение вы ражения new i n t [ ] { 1 , 3 , 5 } - это ссыл ка на созданный масси в . Эта ссыл ка передается первым аргументом методу show ( ) . Главны й метод п рограм м ы Известный чародей и магистр тайных сил. Нынче в Петербурге шуму много наделал . Га­ зеты пишут - камни драгоценные растил, бу­ дущность предсказывал. из к/ф �Формула лю бви» Главный метод программы может возвращать результат. Возвращаемый главным методом результат является индикатором того, что работа про­ граммы завершилась в нормальном режиме (без ошибок). Поскольку результат предназначен для исполнительной системы, то в прикладном плане все сводится к возможности описывать главный метод программы способом, отличным от того, который мы использовали ранее и будем использовать впредь. И так, вместо того чтобы описывать метод Ма i n ( ) как не возвращающий результат (с идентификатором vo i d), метод Ma i n ( ) можно описать как возвращающий целочисленное значение (тип результат i n t ). При этом в теле метода должна быть r е t u r n-инструкция, которая, собственно, результат и возвращает. Возвращающим значением обычно является О , означающий, что выполнение программы завершилось штатно. Как правило, если метод Ma i n ( ) описан с идентификатором i n t для типа результата, то последней командой в теле метода является инструкция r e t u r n О . Как выглядит программа в таком случае, показано в листин­ ге 5. 1 0 (жирным шрифтом выделены места кода, на которые стоит обра­ тить внимание). � Листинг 5 . 1 О. Главный метод возвращает результат u s ing Sys tem; c l a s s MainMethDemo { 277 Глава 5 1 1 Главный метод возвращает резуль тат : static int Main ( ) { Console . WriteLine ( " Глaвный метод возвращает результат ! " ) ; 1 1 Резуль тат главного метода : retum О ; Результат выполнения программы представлен ниже. � Результат выполнения программ ы ( из листинга 5 . 1 О ) Главный метод возвращает резуль тат ! Еще раз подчеркнем, что особенность данной программы в том, что метод Ma i n ( ) описан с идентификатором i n t и содержит r е t u r n ­ инструкцию. При желании можно описывать главный метод в представ­ ленном выше формате. G) Н А ЗАМ ЕТ КУ М ы уже знаем , что главн ы й метод п рограм м ы может описы ваться с аргументом - текстовым масси вом , через который в п рограмму передаются параметры командной строки . Также нередко главный метод программы описывают с кл ючевым словом puЫ i c . Все эти варианты оп исания метода Ma i n ( ) , вкл ючая реал изаци ю мето­ да , возвращающего целочисленный резул ьтат, можно « комби н и ­ ровать» . Например, м ы можем описать главный метод п рограммы с кл ючевым словом puЫ i c как тако й , что возвращает i n t -значение, и с аргументом - текстовым масси вом . Р ез ю ме М астера н е мудрствуют. из к/ф «Покровские ворота» • Метод - именованный блок кода, который может выполняться мно­ гократно (через вызов метода). Методы бывают статические и неста­ тические. Для вызова нестатического метода нужен объект. Для вы­ зова статического метода объект не нужен. 278 Статические методы • Статический метод описывается с ключевым словом s t a t i c . По­ сле него указывается идентификатор типа результата, возвраща­ емого методом (если метод не возвращает результат - указывают идентификатор vo i d ) . Далее следует имя метода, в круглых скобках описываются аргументы метода, а код метода описывается в блоке из фигурных скобок. Значение, возвращаемое методом, в теле метода указывают после инструкции r e t u rn. • Методы можно перегружать: в таком случае описывается несколь­ ко версий метода с одним и тем же именем. Перегруженные версии метода должны отличаться количеством и/или типом аргументов. Решение о том, какую версию следует вызывать, принимается на ос­ нове аргументов, которые фактически переданы методу. • При передаче аргументом методу массива в действительности в ме­ тод передается ссылка на массив. Если метод возвращает результа­ том массив, то обычно при вызове метода создается массив, а ссылка на него возвращается результатом метода. • Аргументы в метод могут передаваться по значению и по ссылке. При передаче аргументов по значению (такой режим используется по умолчанию) для аргументов создаются технические копии и все операции выполняются с ними. При передаче аргументов по ссылке в метод передаются те переменные, которые указаны аргументами. Чтобы аргумент передавался по ссылке, в описании метода аргумент должен быть описан с инструкцией re f. Такая же инструкция ука­ зывается вместе с аргументом при вызове метода. • В метод можно передавать неинициализированный аргумент - пе­ ременную, которая объявлена, но не инициализирована. Такой аргу­ мент должен получить значение в процессе выполнения метода. Для возможности передачи неинициализированного аргумента в опи­ сании метода такой аргумент описывается с инструкцией o u t . Ин­ струкция o u t также указывается вместе с неинициализированным аргументом при вызове метода. • При описании методов можно использовать рекурсию. При рекур­ сии в процессе выполнения метода он вызывает сам себя. • Можно описывать методы с аргументами, количество которых зара­ нее не известно. Такие параметры формально описываются как мас­ сив с инструкцией p a r am s . В теле метода аргументы обрабатывают­ ся как элементы массива, а при вызове метода аргументы передаются 279 Глава 5 как обычно - через запятую. Если у метода, кроме набора аргумен­ тов неизвестного количества, есть и «обычные» аргументы, то они в описании метода указываются в начале. Аргумент с инструкцией p a r am s должен быть последним в списке аргументов метода. • Главный метод программы Ma i n ( ) может описываться как такой, что возвращает i n t -значение. В таком случае в теле метода обычно последней является инструкция r e t u r n О . Задания для самостоятел ьной работы Я пришел вам сделать предложение впрок. из к/ф « Старики -разбой ники» 1 . Напишите программу, в которой описан статический метод для вы­ числения двойного факториала числа, переданного аргументом методу. По определению, двойной факториал числа п (обозначается как п! ! ) это произведение через одно всех чисел, не больших числа п. То есть п! ! = п х (п - 2) х (п - 4) х ... (последний множитель равен 1 для нечетного п и равен 2 для четного п). Например, 6! ! = 6 х 4 х 2 = 48 и 5 ! ! = 5 х 3 х 1 = 1 5. Предложите версию метода без рекурсии и с рекурсией. 2. Напишите программу со статическим методом, которым вычисляется 2 + 2 + 32 + + 2 ... 2 сумма квадратов натуральных чисел 1 п . Число п передается аргументом методу. Предложите версию метода с рекурсией и без ре2+ 2+ курени. Для проверки результата можно использовать формулу 1 2 2 + + 2 n(n + 1 ) (2n + 1 ) 3 ... п = 6 • 3. Напишите программу со статическим методом, которому аргументом передается целочисленный массив и целое число. Результатом метод возвращает ссылку на новый массив, который получается из исходного массива (переданного первым аргументом методу), если в нем взять не­ сколько начальных элементов. Количество элементов, которые нужно взять из исходного массива, определяются вторым аргументом метода. Если второй аргумент метода больше длины массива, переданного пер­ вым аргументом, то методом создается копия исходного массива и воз­ вращается ссылка на эту копию. 4. Напишите программу со статическим методом, аргументом которо­ му передается символьный массив, а результатом возвращается ссылка 280 Статические методы на целочисленный массив, состоящий из кодов символов из массива­ аргумента. 5. Напишите программу со статическим методом, аргументом которому передается целочисленный массив, а результатом возвращается среднее значение для элементов массива (сумма значений элементов, деленная на количество элементов в массиве). 6. Напишите программу со статическим методом, аргументом которому передается двумерный целочисленный массив. У метода, кроме аргумен­ та-массива, есть два неинициализированных аргумента. Результатом метод возвращает значение наибольшего элемента в массиве. Неинициализиро­ ванным аргументам присваиваются значения индексов этого элемента. 7. Напишите программу со статическим методом, аргументом которому передается одномерный символьный массив. В результате вызова мето­ да элементы массива попарно меняются местами: первый - с послед­ ним, второй - с предпоследним и так далее. 8. Напишите программу с перегруженным статическим методом. Если аргументом методу передается два целых числа, то результатом возвра­ щается ссылка на целочисленный массив, состоящий из натуральных чисел, а первое и последнее число в массиве определяется аргументами метода. Например, если передать аргументами числа 2 и 4 , то результа­ том будет массив из чисел 2 , 3 и 4 . Если аргументами методу передаются два символьных значения, то результатом возвращается ссылка на мас­ сив, состоящий из последовательности символов, а первый и последний символы определяются аргументами метода. Например, если передать аргументами методу символы ' В ' и ' D ' , то в результате получим мас­ сив из символов ' В ' , ' С ' и ' D ' . 9. Напишите программу со статическим методом, аргументом которому передается произвольное количество целочисленных аргументов. Резуль­ татом метод возвращает массив из двух элементов: это значения наиболь­ шего и наименьшего значений среди аргументов, переданных методу. 1 0 . Напишите программу со статическим методом, которому передается текст и произвольное количество символьных аргументов. Результатом возвращается текст, который получается добавлением в конец исходно­ го текста (первый аргумент метода) символьных значений, переданных аргументами методу. Гл ава 6 ЗНАКОМСТВО С КЛАССАМИ И О БЪЕКТАМИ Вообще-то я не специалист но этим гравицапам. из к/ф -«Кин-дза-дза» В этой главе состоится наше первое осознанное знакомство с классами и объектами. Мы будем обсуждать следующие вопросы: • познакомимся со способами описания классов ; • узнаем, что такое объекты и как они создаются ; • научимся описывать и использовать поля и методы ; • узнаем, какие операции и как выполняются с объектами ; • расширим наши представления о перегрузке методов ; • познакомимся с конструкторами и деструкторами. Но прежде всего познакомимся с базовыми принципами объектно ори­ ентированного программирования. Ба з овые п р и н ц и п ы ОО П Если у меня немножко К Ц есть, я имею пра­ во носить желтые штаны и нередо мной нацак должен не один, а два раза нриседать. из к/ф -«Кин-дза-дза» Как мы уже знаем, язык программирования С# полностью объектно ориентированный. Это означает, что в нем реализована парадигма ООП. В чем же особенность ООП? Важно понимать, что речь идет о спосо­ бе организации программы. На сегодня существует два основных спо­ соба организации программного кода. Это объектно ориентированное - 282 З нако мст во с класса м и и о бъекта м и и процедурное (структурное) программирование. Попробуем разобрать­ ся, чем отличаются эти концепции программирования. При процедурном программировании программа реализуется как набор подпрограмм (процедуры и функции), используемых для обработки данных. То есть данные, используемые в программе, и программный код, применяемый для обработки этих данных, существуют отдельно друг от друга. В процессе выполнения программы выбираются нужные дан­ ные и обрабатываются с помощью специальных процедур и функций. Если провести аналогию, то можно такой способ программирования и организации программы сравнить с кухней. На этой кухне стоят холо­ дильники и специальные боксы с продуктами. Это данные. Еще на кухне есть повара, которые из продуктов готовят блюда. Повара - это аналог процедур и функций. Есть шеф-повар (которого можно отождествить с программой или, как в нашем случае, с главным методом). Шеф-по­ вар определяет блюда, которые должны готовить его подчиненные, и ка­ кие продукты (из какого холодильника или бокса) должны использо­ ваться. Что не так в этой схеме? Да, в общем-то, все нормально. Если шеф-повар профессиональный, а команда поваров подобрана грамотно, то обязанности будут распределены правильно и кухня будет работать нормально. Так все происходит, если кухня не очень большая. Но если у вас огромный цех, в котором много поваров и холодильников с продук­ тами, то могут возникнуть проблемы организационного характера: кто­ то не в своем холодильнике продукты взял, кто-то заказ неправильно понял. При этом повара-исполнители могут быть высочайшими профес­ сионалами и продукты могут быть отменного качества. Но этого мало. Важно еще и правильно организовать процесс. А если кухня большая, то сделать это непросто. Какой выход из ситуации? Реорганизация. Раз­ биваем весь большой кухонный цех на блоки, в каждом блоке есть свой сушеф (главный повар в блоке), у него есть подчиненные. У каждого блока есть своя специализация. Один блок готовит первые блюда, дру­ гой готовит салаты, третий готовит десерты, и так далее. В каждом бло­ ке свои холодильники и боксы с продуктами, предназначенными имен­ но для этого блока. Шеф-повар общается с сушефами в блоках. Им он раздает задания, а они уже контролируют работу своих блоков. То есть сушефы организуют и контролируют работу блока, а шеф-повар руково­ дит работой кухни на уровне взаимодействия разных блоков. А что там происходит внутри каждого отдельного блока - не его забота. Собствен­ но, это и есть объектно ориентированный подход. При таком подходе программа представляет собой �сцену», на которой взаимодействуют 283 Глава 6 �объекты» . � о бъектами» в нашем �кухонном» примере являются бло­ ки. Каждый �объект» содержит в себе данные (холодильники с продук­ тами) и методы, обрабатывающие данные (повара, которые используют продукты), и все это локализовано в объекте. Главное удобство описан­ ного подхода в том, что мы сразу распределяем и группируем данные и программный код, предназначенный для обработки этих данных. Это потенциально уменьшает вероятность ошибочного использования дан­ ных и облегчает создание программы. Можно привести и другую аналогию для иллюстрации разницы меж­ ду процедурным и объектно ориентированным программированием. Допустим, нам нужно построить дом. Мы его можем строить из кирпи­ чей. Если дом не очень большой, то такая технология вполне удачная. Из кирпичей можно выложить практически все что угодно, реализовать любой дизайнерский проект. Но все это хорошо, пока мы не решили строить многоэтажный дом. Если так, то технологию, скорее всего, при­ дется поменять. Намного удобнее строить такой дом из блоков. Каж­ дый блок заранее изготавливается на специальном заводе. Блок содер­ жит окна, двери и другие полезные элементы. И весь дом складывается из блоков. Это будет быстрее и надежнее по сравнению со случаем, ког­ да мы строим из кирпичей. Строительство дома из кирпичей - аналог программы, которая соз­ дается в рамках концепции процедурного программирования. Строи­ тельство дома из блоков - аналог программы, созданной в рамках кон­ цепции ООП. Мы в этом случае �строим» программу не из маленьких �кирпичиков», а из больших �блоков», которые имеют еще и некоторую функциональность. Таким образом, О О П - это способ организации программы через вза­ имодействие отдельных объектов, содержащих данные и методы для работы с этими данными. Обычно в О О П выделяют три базовых прин­ ципа: • инкапсуляция ; • полиморфизм ; • наследование. Любой объектно ориентированный язык содержит средства для реали­ зации этих принципов. Способы реализации могут отличаться, но прин­ ципы неизменны. 284 З нако мст во с класса м и и о бъекта м и Инкапсуляция означает, что данные объединяются в одно целое с про­ граммным кодом, предназначенным для их обработки. Фактически ор­ ганизация программы через взаимодействие объектов является реализа­ цией принципа инкапсуляции. На программном уровне инкапсуляция реализуется путем использования классов и объектов (объекты созда­ ются на основе классов). G) Н А ЗАМ ЕТ КУ Классы и объекты обсуждаются в следующем разделе . Полиморфизм подразумевает использование единого интерфейса для решения однотипных задач. Проявлением полиморфизма является тот факт, что нередко в программе один и тот же метод можно вызы­ вать с разными аргументами. Это удобно. Если вернуться к аналогии с кухней, то ситуация примерно такая: шеф-повар дает задание своим поварам, указывая название блюда. При этом ему нет необходимости уточнять, как именно блюдо готовится. Все это знают повара, выпол­ няющие заказ. Они определяют, какие продукты нужно использовать. Если в какой-то момент поменяется рецепт приготовления блюда (на­ пример, один продукт будет заменен другим), шеф-повар будет назы­ вать все то же блюдо. Изменение рецепта автоматически учтут повара, которые готовят блюдо. G) Н А ЗАМ ЕТ КУ П роя влен ием полиморфизма является перегрузка и переоп ределе­ ние методов , о чем реч ь еще будет идти . Наследование позволяет создавать объекты не на пустом месте, а с исполь­ зованием ранее разработанных утилит. В языке С# наследование позво­ ляет создавать классы на основе уже существующих классов. Эти классы используются для создания объектов. Вообще название этого механизма очень точно отображает его сущность. Он занимает важное место в концеп­ ции языка С#, и мы обязательно вернемся к обсуждению этого вопроса. G) Н А ЗАМ ЕТ КУ На уровне «кухн и » наследование могло бы быть реал изовано следу­ ющим образом . Допусти м , и меется отдел по изготовлению десер­ тов . М ы хоти м расширить возможности этого отдела так, чтобы там 285 Глава б готовили еще и коктейли ( и м еет л и это смысл на реал ьной кухне в данном случае не важн о ) . Какие есть варианты? Можно расфор­ м ировать старый отдел и создать новый «С нул я » , а можно оставить старый отдел , но добавить в него еще одно внутреннее «подразде­ ление» , которое будет зан иматься коктейля м и . В некотором см ысле это напоминает наследование : берем то , что уже есть, и добавляем к нему нечто новое . Далее мы перейдем к более приземленным вопросам. В частности, по­ знакомимся с классами и объектами. Концепция классов и объектов яв­ ляется фундаментальной для понимания принципов реализации ООП в языке С#. Кл ассы и об ъ е кт ы Дальше следует непереводимая игра слов с ис­ пользованием местных идиоматических выра­ жений. из к/ф «Бриллиантовая рука» В первую очередь нам следует понять, чем 1(.Jlacc принципиально отли­ чается от объекта. Для этого мы опять прибегнем к аналогии. Предста­ вим, что имеется автомобильный завод, с конвейера которого выходят автомобили. А где-то есть главный инженер, и у него имеется план или чертеж, по которому эти автомобили собираются. G) Н А ЗАМ ЕТ КУ В действител ьности все не так, все намного сложнее - но здесь это неважно. Нам нужно понять базовые принци п ы , а для этого вполне подойдет сильно уп рощенная схема п роизводства автомобилей . Так вот, чертеж или план, на котором изображен автомобиль со всеми его техническими характеристиками, - это аналог класса. А реальные автомобили, которые выходят с конвейера и которые собраны в соответ­ ствии с чертежом, - аналог объектов. Все автомобили (если они собраны по одному и тому же чертежу) - однотипные. У них одинаковый набор характеристик, и в плане функциональности они эквивалентны. Но фи­ зически автомобили различны, у каждого своя �судьба» . 286 З нако мств о с классами и о бъекта м и G) Н А ЗАМ ЕТ КУ У автомобилей , собран ных по одному чертежу, оди наковый набор характеристик, но значен ия некоторых характеристи к могут отл и ­ чаться . Например, у каждого автомобиля есть такая характеристи ­ ка, как цвет. Но значение этой характеристики у разных автомобилей может быть раз н ы м : скажем , один автомобил ь красны й , а другой сини й . Если обратиться к примеру с постройкой дома, то там аналогом объек­ тов являются блоки, из которых строится дом. Базовый чертеж, на осно­ ве которого на заводе изготовлялся блок, является аналогом класса. До­ пустим, дом строится с использованием блоков пяти разных типов. Это означает, что имеется пять разных чертежей блоков, на основании ко­ торых создаются блоки. Если блоки создавались на основе разных чер­ тежей, то они будут разными. Если блоки создавались на основе одного и того же чертежа, то они будут одинаковыми (точнее, однотипными). G) Н А ЗАМ ЕТ КУ В известном смысл е , класс можно рассматри вать как неки й тип дан­ ных. Но тол ько это специфически й ти п , поскол ьку кроме собственно данных в нем «сп рятана» еще и некоторая фун кционал ьность (спо­ собность выполнять оп ределенные действия ) . Наличие чертежа не означает наличия блока. Блоки нужно создавать. На основании одного чертежа можно создать много блоков. Но также может быть и ситуация, когда чертеж есть, а блоки по нему не создава­ лись. Это же справедливо для классов и объектов. Класс нужен для соз­ дания на его основе объектов. На основе одного класса можно создать много объектов. А можно описать класс и не создавать объектов совсем. Все зависит от решаемой задачи. Аналогии - это хорошо. Но что же такое объект, если речь идет об ис­ пользовании его в программе? Скажем, мы уже знаем, что за переменной «скрывается� область в памяти. В эту область можно записывать значе­ ния и считывать значение оттуда. И для получения доступа к этой па­ мяти достаточно знать имя переменной. Объект во многом похож на пе­ ременную, но только он более «разноплановый� . Объект - это группа переменных, причем в общем случае разного типа. А еще объект - это набор методов. Метод, в свою очередь - это группа инструкций, которые можно выполнить (вызвав метод). 287 Глава б G) Н А ЗАМ ЕТ КУ Здесь и далее, есл и явно н е указано п роти вное, имеются в виду обычные, не статические методы . Обычный метод, в отличие от ста­ тического , «при вязан» к объекту и не может быть вызван , есл и не указан объект, из которого вызываетс я метод. В итоге получается, что если обычная переменная напоминает коробку со значением внутри, то объект - это большая коробка, в которой есть коробки поменьше (переменные) и всякие пружинки и рычажки (набо­ ры инструкций, реализованные в виде методов). С программной точки зрения объект реализуется как некоторая область памяти, содержащая переменные и методы. � ПОДРОБН ОСТИ Откровен но говоря , в памяти коды методов для объектов обычно хранятся отдел ьно от переменных, входя щих в объект. Связано это с опти мизацией . Но для понимания принципов работы объектов и испол ьзован ия их в программе можно п олагать, что и перемен­ ные, и методы объекта находятся в «одной коробке » . Программная �начинка� объекта состоит из переменных и методов. Пе­ ременные называются полями объекта. Объекты, как мы помним, созда­ ются на основе класса. Когда мы описываем класс, мы фактически опре­ деляем, какие поля и методы будут у объектов, создаваемых на основе класса. Поля метода можно использовать как обычные переменные. Методы объекта можно вызывать. В этом случае выполняются инструкции, со­ держащиеся в теле метода. Метод, который вызывается из некоторого объекта, автоматически получает доступ к полям (и другим методам) этого объекта. Также важно помнить, что и поля, и методы у каждо­ го объекта свои. Точно так же, как у каждого автомобиля свой руль, свои колеса и своя коробка передач. И если вы поворачивает ключ за­ жигания, то заведется тот автомобиль, в котором вы поворачиваете ключ. А если вы перекрасите свой автомобиль, то это никак не скажет­ ся на цвете автомобиля вашего соседа (даже если у него автомобиль той же марки ) . 288 З нако мство с классами и о бъекта м и Оп исание кл асса и создан и е об ъ екта Как он может своими мозгами играть, когда он эти куклы первый раз видит ? из к/ф -«Кин-дза-дза» Нам предстоит выяснить как минимум два момента. Во-первых, нам нужно узнать, как описывается класс. И, во-вторых, мы должны нау­ читься создавать на основе классов объекты (и использовать их в про­ грамме). Начнем с описания класса. Описание класса начинается с ключевого слова c l a s s . После ключево­ го слова c l a s s указывается название класса. Описание класса разме­ щается в блоке, выделенном фигурными скобками. Ниже приведен шаб­ лон, в соответствии с которым описывается класс (жирным шрифтом выделены ключевые элементы шаблона): class имя { 1 1 Описание класса В теле класса описываются поля (переменные) и методы. Поля и методы класса называются членами класса. Поля описываются точно так же, как мы ранее объявляли переменные в главном методе программы: указыва­ ется тип поля и его название. Если несколько полей относятся к одному типу, то можно их объявить одной инструкцией, указав тип полей и пе­ речислив их названия через запятую. Методы описываются так: • указывается тип результата ; • название метода ; • перечисляются аргументы (в круглых скобках - указывается тип и название каждого аргумента) ; • команды, выполняемые при вызове метода, размещаются в блоке, выделенном фигурными скобками (тело метода). � ПОДРОБН ОСТИ Обычный (не статически й ) метод (а мы рассматриваем именно такие методы ) вызы вается из объекта . Этот метод и меет доступ к полям 289 Глава б объекта. То есть в описании метода можно обращаться к полям объек­ та, и при этом данные поля как-то дополнительно идентифицировать не нужно (достаточно их описания в теле класса) . Метод может воз­ вращать значение (если метод не возвращает значение, то идентифи­ катором типа результата указывается ключевое слово vo id). В таком случае это значение отождествляется с инструкцией вызова метода. Эту инструкцию можно использовать как операнд в выражениях, под­ разумевая под ней результат, возвращаемый методом . Также методу могут передаваться аргументы . Это значения , которые метод исполь­ зует при вызове. Аргументы описываются в круглых скобках после име­ ни метода. Для каждого аргумента указывается тип . При вызове мето­ да фактически передаваемые методу в качестве аргументов значения указываются в круглых скобках после имени метода. Обычно поля и методы описываются с ключевым словом, определяющим уровень доступа. Если использовать ключевое слово рuЫ i с, то соответ­ ствующее поле или метод будут открытыми. К открытым полям и методам можно обращаться не только в программном коде класса, но и вне класса. Если ключевое слово рuЫ i с не указать, то поле или метод будут закры­ тыми. К закрытым полям и методам доступ имеется только внутри класса. G) Н А ЗАМ ЕТ КУ Кроме кл ючевого слова puЫ i c можно испол ьзовать кл ючевые сло­ ва p r ivate и p r o t e c t e d. С кл ючевым словом p r ivate описывают закрытые члены класса . Други м и слова м и , есл и нужно сделать поле ил и метод закрыты м и , то это поле или метод о п исы ваются с кл юче­ вым словом pr ivate или вообще без кл ючевого слова, оп ределяю­ щего уровень доступа. Кл ючевое слово p r o t e c t e d испол ьзуют при оп исан и и защищенных членов класса. Защищенные члены класса обсуждаются в рам ках тем ы , связан ной с наследованием . Например, ниже приведено описание класса с одним целочисленным полем. c l a s s MyClas s { 1 1 Целочисленное поле : puЫ ic int number ; Класс называется MyC l a s s , и содержит о н всего одно поле, кото­ рое называется numb e r , является целочисленным (тип i n t то есть - 290 З нако мство с классами и о бъекта м и значением поля может быть целое число) и открытым (описано с клю­ чевым словом puЬ l i c ) . А вот еще один пример описания класса: c l a s s MyClas s { 1 1 Целочисленное поле : puЫ ic int number ; 1 1 Символьное поле : puЫ ic char s ymЬol ; 1 1 Метод : puЫ ic vo id show ( ) { 1 1 Отображение значений полей : Console . WriteLine ( " Цeлoe число : " +numЬe r ) ; Console . WriteLine ( " Cимвoл : " + s ymЬo l ) ; У этой версии класса MyC l a s s два поля (целочисленное numb e r и сим­ вольное s ymb o l ) . А еще в классе описан метод s how ( ) . Метод не воз­ вращает результат (описан с ключевым словом vo i d ) , и у него нет ар­ гументов. При вызове метода выполняются две команды, которыми в консольное окно выводятся значения полей numЬ e r и s ymb o l . И вот здесь есть два важных момента. Первый момент связан с тем, что описа­ ние метода в классе еще не означает, что код этого метода выполняется. Чтобы код метода выполнялся, метод следует вызвать. А для вызова ме­ тода нужен объект, поскольку, если метод не статический, то вызывает­ ся он из объекта. Следовательно, необходимо создать объект и вызвать из него метод. � ПОДРОБН ОСТИ Необходимость вызывать метод из объекта поясним еще на одном отвлеченном примере. Допустим , имеются три кота одной породы : Мурч ик, Барси к и Рыжи к. Их мы можем отождествлять с объектами . Это объекты некоторого типа или класса, который можно было б ы на­ звать Кот. У каждого из трех котов есть метод, который условно мож­ но назвать «ловить мышей » . Если мы хотим поймать реальную мышь, то ловить ее должен совершенно конкретн ый кот - то есть Мурчик, Барсик ил и Рыжик. Н и какой абстрактный Кот мышей ловить не будет. 291 Глава б Второй момент связан с тем, что подразумевается под идентификато­ рами numb e r и s ymЬo l в описании метода s how ( ) в классе MyC l a s s . М ы уже знаем, что речь идет о полях. Н о о полях какого объекта? Ответ, наверное, очевиден: имеются в виду поля объекта, из которого вызыва­ ется метод. Другими словами, если из некоторого объекта, созданного на основе класса MyC l a s s, будет вызван метод s how ( ) , то этим методом в консольном окне будут отображены значения полей n umЬe r и s ymbo 1 объекта, из которого вызван метод. Нам осталось выяснить, как создается объект класса. Общая схема похо­ жа на ту, что используется при реализации массивов. Нам понадобится собственно сам объект, а также переменная, через которую мы будем об­ ращаться к объекту. Такая переменная называется объектной перемен­ ной, и ее значением является ссылка на объект. Объектная переменная объявляется так же просто, как и переменная ба­ зового типа, но только в качестве идентификатора типа указывается имя класса. Например, для объявления объектной переменной с именем obj , которая могла бы ссылаться на объект класса MyC l a s s , используем ин­ струкцию MyC l a s s obj . Но объявление объектной переменной не при­ водит к созданию объекта. Объектная переменная может лишь ссылать­ ся на объект. Сам объект нужно создать. Создается объект с помощью оператора new. После оператора new указывается имя класса с пустыми круглыми скобками. Так, для создания объекта класса МуС 1 а s s исполь­ зуем инструкцию new MyC l a s s ( ) . При выполнении этой инструкции создается объект. Инструкция также возвращает результат - ссылку на созданный объект. Эту ссылку можно присвоить значением объект­ ной переменной (в нашем случае это объектная переменная obj ). Поэ­ тому процесс создания объекта класса MyC l a s s может быть описан ко­ мандами: MyC l a s s obj ; obj =new MyC l a s s ( ) ; Эти две команды можно объединить в одну, которой объявляется объ­ ектная переменная и сразу ей значением присваивается ссылка на вновь созданный объект: MyC l a s s obj =new MyC l a s s ( ) ; Понятно, что тип объектной переменной (класс, указанный при объ­ явлении переменной) должен совпадать с типом объекта (классом, на основе которого создан объект), ссылка на который присваивается 292 З нако мство с классами и о бъекта м и переменной. На рис. 6. 1 проиллюстрирован принцип реализации объ­ ектов в языке С#. - " - - - - - .- - - - , - - - , - - - - \ ... ... 1 , __ , � Объе к т н а я п ере мен н а я '. :;�-( ' ' - �- , - Ссыл к а ' ... " _ _ _ _ \. ... ... -... -\ , ... " - - - - - ... �. ) ,\_) �:{_ ... ... ;. ... " Объ е к т - , -} -, �) \ " ----------� / , --- -- - - ' ,� - - - - "' ' ... _ ,_ _ .,,. , , " _ _ ... Клас с переменная = ... " _ _ _ _ _ _ new >, ... ' ,.. " ... " • 1 ... ... ... ... _ _ _ _ ... Клас с ( ) Рис. 6 . 1 Реал изация о бъектов в С # • G) Н А ЗАМ ЕТ КУ Несложно заметить, что способ реал изаци и объектов напоми нает способ реал изаци и массивов : там тоже есть переменная ( перемен­ ная масси ва) , которая ссылается собственно на масси в , а массив создается с помощью оператора new . � ПОДРОБН ОСТИ При создании объекта класса после инструкции new указывается не просто имя класса с кругл ы м и скобками - это вызы вается кон­ структор класса. Конструкторы обсуждаются немного позже. Пока же заметим , что скобки после имени класса могут быть и не пустым и . В общем случае о н и п редназначены для передач и аргументов кон­ структору, вызываемому при создании объекта . При создании объекта под все его поля выделяется место в памяти. С этими полями можно работать как с обычными переменными. Но нуж­ но помнить, что у каждого объекта поля свои. Поэтому нет смысла обра­ щаться к полю, не указав, к какому объекту оно относится. G) Н А ЗАМ ЕТ КУ Пол я , как и методы , бы вают статические и обыч ные ( не статиче­ ские ) . Статическое поле существует безотносител ьно к наличию ил и отсутствию объектов класса . Оно « Не при вязано» н и к какому кон­ кретному объекту. М ы же обсуждаем обыч ные пол я . Обычное поле существует тол ько в контексте объекта . Нет объекта - нет пол я . 293 Глава б При обращении к полю объекта используется �точечный синтаксис»: указывается имя объекта, точка и после нее - название поля. Общий формат ссылки выглядит как объект . поле. Например, если в классе МуС 1 а s s описано поле с названием n umЬe r и на основании этого класса создается объект obj (объектная переменная obj ссылается на создан­ ный объект), то для обращения к полю numЬ e r объекта obj используем инструкцию obj . numb e r . Методы (не статические) вызываются также с использованием �точечного синтаксиса». Для вызова метода из объекта необходимо указать имя объ­ екта и через точку название метода (с аргументами, передаваемыми мето­ ду - если они нужны). Выглядит это как объект . ме т од ( аргументы) . Например, если мы хотим вызвать метод s how ( ) из объекта obj , то коман­ да вызова этого метода такая: obj . s how ( ) . Здесь предполагается, что ме­ тоду s how ( ) при вызове аргументы не передаются (метод без аргументов). Метод show ( ) при этом имеет автоматический доступ к полям и прочим методам объекта obj . Далее мы на конкретных примерах рассмотрим, как в программе описываются классы, создаются объекты, вызываются методы из объектов и выполняется обращение к полям объектов. И спол ьзование об ъ е кт ов Я могу пронизать пространство и уйти в прош­ лое. из к/ф <tИван Васильевич меняет професси ю» Далее мы все, что было описано выше, используем на практике. В про­ грамме описывается класс MyC l a s s , в котором есть два поля (целочис­ ленное numb e r и символьное s ymb o l ), а также метод show ( ) , которым отображаются значения полей. В главном методе программы на основе класса MyC l a s s создается два объекта (А и в), полям объектов присва­ иваются значения, а с помощью метода s how ( ) значения полей объек­ тов выводятся в консольное окно. Соответствующий программный код приведен в листинге 6. 1 . [1i!J Листинг 6 . 1 . Описание класса и соэдание объектов us ing Sys tem; 11 Описание класса : 294 З нако мство с классами и о бъекта м и c l a s s MyClas s { 1 1 Целочисленное поле : puЫ ic int number ; 1 1 Символьное поле : puЫ ic char s ymЬol ; 1 1 Метод : puЫ ic vo id show ( ) { 1 1 Отображение значения целочисленного поля : Console . WriteLine ( " Цeлoчиcлeннoe поле : " +numЬe r ) ; 1 1 Отображение значения символьного поля : Console . WriteLine ( " Cимвoльнoe поле : " + s ymЬol ) ; 1 1 Класс с главным методом : c l a s s Us ingObj s Demo { 1 1 Главный метод : static vo id Main ( ) { 1 1 Первый объект : MyC l a s s A=new MyC l a s s ( ) ; 1 1 Объектная переменная : MyC l a s s В ; 1 1 Второй объект : B=new MyC l a s s ( ) ; 1 1 Присваивание значений полям первого объекта : A . numЬer=1 2 3 ; A . s ymЬol= ' A ' ; 1 1 Присваивание значений полям второго объекта : B . numЬer=32 1 ; B . s ymЬol= ' B ' ; 1 1 Вызов методов : Console . WriteLine ( " Пepвый объект " ) ; А . show ( ) ; 295 Глава б Console . WriteLine ( " Bтopoй объект " ) ; В . show ( ) ; Результат выполнения программы такой. � Результат выполнения программ ы (из листинга 6. 1 ) Первый объект Целочисленное поле : 1 2 3 Символьное поле : А Второй объект Целочисленное поле : 3 2 1 Символьное поле : В В программе, кроме класса с главным методом программы, описан еще и класс MyC l a s s . В методе Ma i n ( ) создаются объекты этого клас­ са. Их всего два. Первый объект создается командой MyC l a s s A=new MyC l a s s ( ) . В данном случае объявляется объектная переменная А класса MyC l a s s и ей значением присваивается ссылка на объект, кото­ рый создается инструкцией new MyC l a s s ( ) . Со вторым объектом си­ туация немного иная. Сначала командой MyC l a s s В объявляется объ­ ектная переменная В класса MyC l а s s. Создание объекта и присваивание ссылки на этот объект переменной В выполняется отдельной командой B=new MyC l a s s ( ) . После того как объекты А и в созданы, их полям присваиваются значе­ ния. Значения полям объекта А присваиваем командами А . numЬe r= 1 2 3 и А . s ymb o 1 = ' А ' . Полям объекта в значения присваиваются с помо­ щью команд В . numbe r= 3 2 1 и В . s ymb o l = ' В ' . Для проверки значений полей объектов А и В используем соответственно команды А . s how ( ) и В . s h o w ( ) . Этими командами из объектов А и В вызывается метод s how ( ) . В классе MyC l a s s метод описан таким образом, что им в кон­ сольное окно выводятся значения полей numЬ e r и s ymb o l . Объект, к ко­ торому относятся эти поля, в описании метода в классе MyC l a s s явно не указан. В таком случае подразумеваются поля того объекта, из которо­ го вызывается метод. Это и происходит на практике: при вызове метода s how ( ) из объекта А отображаются значения полей объекта А, а при вы­ зове метода s h ow ( ) из объекта В отображаются значения поле объекта В. 296 З нако мство с классами и о бъекта м и G) Н А ЗАМ ЕТ КУ В главном методе п рограм м ы обращение к пол я м и методам объ­ ектов вы полняется в виде A . numb e r , B . s ymbo l , A . show ( ) и так да­ лее. Это возможно, поскол ьку поля numЬer и s ymЬ o l , а также метод show ( ) в классе MyC l a s s описаны с кл ючевым словом puЫ i c то есть поля и методы я вл я ются открыты м и . Есл и бы они не были открыты м и , то в главном методе п рограммы м ы не смогл и бы напря ­ мую обратиться к пол я м и методу объектов , созданных на основе класса MyC l a s s . - Еще один небольшой пример, связанный с использованием классов и объектов, представлен в листинге 6.2. Во многом он напоминает пре­ дыдущую программу из листинга 6. 1 , но все же в нем есть и существен­ ные отличия. � Л истинг 6 . 2 . Присваивание объектов u s ing Sys tem; 11 Описание класса : c l a s s MyClas s { 1 1 Целочисленное поле : puЫ ic int number ; 1 1 Метод для отображения значения поля : puЫ ic vo id show ( ) { Console . WriteLine ( " Знaчeниe поля : " +numЬe r ) ; 1 1 Класс с главным методом : c l a s s AnotherOb j s Demo { 1 1 Главный метод : static vo id Main ( ) { 1 1 Объектные переменные : MyC l a s s А, В ; 1 1 Создание объекта : A=new MyC l a s s ( ) ; 1 1 Присваивание объектных переменных : 297 Глава б В=А ; 1 1 Присваивание значения полю через первую 1 1 объектную переменную : A . numЬer= 1 2 3 ; 1 1 Вызов метода через вторую объектную переменную : В . show ( ) ; 1 1 Присваивание значения полю через в торую 1 1 объектную переменную : B . numЬer=32 1 ; 1 1 Вызов метода через первую объектную переменную : А . show ( ) ; Результат выполнения программы представлен ниже. � Результат выполнения программ ы (из листинга 6 . 2) Значение поля : 1 2 3 Значение поля : 3 2 1 В данной программе м ы немного упростили класс MyC l a s s : теперь у класса всего одно целочисленное поле numb e r . Метод s h o w ( ) при вызове отображает в консольном окне значение поля numЬ e r объекта, из которого он вызывается. В главном методе программы мы объявляем две объектные перемен­ ные А и в класса MyC l a s s (команда MyC l a s s А, в). Но объект созда­ ется только один: командой A=new MyC l a s s ( ) ссьшка на созданный объект записывается в переменную А. Затем командой В=А перемен­ ной В присваивается значение переменной А. Это важный момент: мы новый объект не создаем. Переменной В присваивается переменная А, которая уже ссылается на ранее созданный объект. Что же происходит в этом случае? Чтобы получить ответ на вопрос, следует учесть, что фак­ тическим значением переменной А является не объект, а ссылка на объ­ ект. Мы можем эту ситуацию интерпретировать так, как если бы в пе­ ременную А был записан некий адрес объекта, на который ссылается переменная. Если так, то при выполнении команды В=А в переменную В 298 З нако мство с классами и о бъекта м и записывается адрес, который записан в переменную А. В итоге и пере­ менная А, и переменная в содержат адрес одного и того же объекта. Это означает, что обе переменные ссылаются на один и тот же объект. Поэ­ тому когда командой А . n umЬe r= 1 2 3 мы присваиваем значение полю numЬ e r через переменную А, а затем вызываем метод s how ( ) через пе­ ременную В (команда В . show ( ) ), то в обоих случаях речь идет об од­ ном и том же объекте. Аналогично, если изменить значение поля n umЬe r с помощью команды В . numbe r= 3 2 1 , то при вызове метода s how ( ) че­ рез переменную А (команда А . s how ( ) ) в консольном окне отобразится новое значение поля. Закрытые чле н ы кл асса и перегрузка методов Ч его о н меня все нугает ? Ч то меня пугать ? У меня три ножизненных заключения. из к/ф �Формула лю бви» Ранее мы познакомились с перегрузкой статических методов. Но пере­ гружать можно и обычные методы. То есть в классе может быть описа­ но несколько версий одного и того же метода. У таких версий одинако­ вые названия, но разные аргументы. При вызове метода версия метода определяется по количеству и типу аргументов, фактически переданных методу. Перегрузка методов проиллюстрирована в программе в листин­ ге 6.3. Там же используются закрытые члены класса (поля класса опи­ саны с ключевым словом p r i va t e ) . Рассмотрим представленный ниже программный код. � Л истинг 6 . 3 . Закрытые члены класса и перегрузка методов u s ing Sys tem; 11 Описание класса : c l a s s MyClas s { 1 1 Закрытое целочисленное поле : private int numЬer ; 1 1 Закрытое символьное поле : private char s ymЬol ; 1 1 Открытый метод для отображения значения полей : 299 Глава б puЫ ic vo id show ( ) { Console . WriteLine ( " Пoля объекта : " + numЬer + " и " + s ymЬo l ) ; / / Открытый метод для присваивания значений полям . / / Версия с двумя аргументами : puЫ ic vo id set ( int n , char s ) { number=n ; / / Значение целочисленного поля s ymbol=s ; / / Значение символьного поля / / Открытый метод для присваивания значений полям . / / Версия с одним целочисленным аргументом : puЫ ic vo id set ( int n ) { number=n ; / / Значение целочисленного поля s ymbol= ' B ' ; / / Значение символьного поля / / Открытый метод для присваивания значений полям . / / Версия без аргументов : puЫ ic vo id set ( ) { / / Вызов версии метода с двумя аргументами : set ( l O O , ' А ' ) ; 1 1 Главный класс : c l a s s Methods Demo { / / Главный метод : static vo id Main ( ) { / / Создание объекта : MyC l a s s obj =new MyC l a s s ( ) ; / / Присваивание значений полям : obj . set ( ) ; / / Отображение значений полей : obj . show ( ) ; 300 З нако мство с классами и о бъекта м и 1 1 Присваивание значений полям : obj . set ( 2 0 0 ) ; 1 1 Отображение значений полей : obj . show ( ) ; 1 1 Присваивание значений полям : obj . set ( З O O , ' С ' ) ; 1 1 Отображение значений полей : obj . show ( ) ; Результат выполнения программы представлен ниже. � Результат выполнения программы (из листинга 6 . 3) Поля объекта : 1 0 0 и А Поля объекта : 2 0 0 и В Поля объекта : 3 0 0 и С Как отмечалось выше, в данном примере при описании класса MyC l a s s поля n umЬe r и s ymbo 1 описаны с ключевым словом p r i va t e . Это озна­ чает, что поля закрытые. К ним можно обращаться в программном коде класса, но за пределами класса они недоступны для прямого обращения. � ПОДРОБН ОСТИ Мы не можем обратиться к закрытым членам класса вне пределов класса напрямую. Допустим , имеется объект obj класса MyC l a s s . Тог­ да в главном методе программы обратиться к полям numЬer и s ymЬol (при услови и , что они закрытые) через имя объекта не получится . Зато мы можем вызвать открытый метод, который обращается к этим по­ лям. Такой подход использован в рассматриваемой программе. Как и ранее, в классе присутствует метод s h ow ( ) , отображающий в консольном окне значения полей numb e r и s ymb o l . Метод откры­ тый, поэтому мы сможем его вызывать в главном методе программы из объектов класса MyC l a s s (в реальности там всего один объект obj ). Кроме метода s h o w ( ) , в классе MyC l a s s описаны три версии метода s e t ( ) . Метод s e t ( ) предназначен для присваивания значений полям объекта. Дело в том, что поскольку поля закрытые, то после создания 301 Глава 6 объекта напрямую (прямым обращением к полям) значения им присво­ ить не удастся. Но можно вызвать открытый метод, который и присвоит полям значения. Таким методом является s e t ( ) . Если он вызывается с двумя аргументами (целочисленным и символьным), то значения ар­ гументов присваиваются полям объекта, из которого вызывается метод. Если метод вызывается с одним целочисленным аргументом, то значе­ ние аргумента присваивается целочисленному полю numb e r , а полю s ymЬo l присваивается значение ' В ' . Наконец, в классе MyC l a s s опи­ сана версия метода s e t ( ) без аргументов. В описании этой версии мето­ да есть всего одна команда s е t ( 1 О О , ' А ' ) , которой вызывается версия метода с двумя аргументами. В результате поле numЬ e r получает значе­ ние 1 0 0 , а поле s ymbo l получает значение ' А ' . G) Н А ЗАМ ЕТ КУ О рекурсии здесь речь н е идет, поскол ьку одна версия метода вызы­ вает другую ( п р и рекурсии внутри метода вызы вается та же версия метода) . В главном методе программы командой МуС l а s s obj =new MyC l a s s ( ) создается объект obj класса MyC l a s s . Присваивание значений полям объекта obj выполняется с помощью команды o b j . s e t ( ) . В резуль­ тате поле numb e r объекта obj получает значение 1 0 0 , а поле s ymbo l этого объекта получает значение ' А ' . Проверяем значения полей объ­ екта с помощью команды o b j . s h ow ( ) . После выполнения команды o b j . s e t ( 2 0 0 ) поле numb e r объекта o b j получает значение 2 0 0 , а поле s ymb o l получает значение ' в ' . Наконец, после выполнения команды оЬ j . s е t ( 3 О О , ' С ' ) значение поля n umb e r объекта оЬ j равно 3 0 0 , а значение поля s ymbo l объекта obj равно ' С ' . G) Н А ЗАМ ЕТ КУ Поля numЬer и s ymЬ o l закрытые , поэтому доступа из программного кода вне пределов класса к этим полям нет. Но методы s e t ( ) и show ( ) описаны в классе, поэтому они имеют доступ к пол я м . В главном ме­ тоде програм мы м ы не можем напря мую обратиться к закрытым поля м , но можем вызвать открытые методы , которые и меют доступ к пол я м . Таким образо м , получается , что мы «спрятали» поля и вве­ л и «посредников» (методы ) , которые позволяют нам вы полнять опе­ рации с полями - но тол ько те , которые оп ределены в методах. Это очень удобный подход, особенно есл и необходимо огранич ить или упорядоч ить режим доступа к полям ( ил и другим членам ) . 302 З нако мство с классами и о бъекта м и Конструкто р Вот что крест животворящий делает ! из к/ф �иван Ва силье вич меняет професси ю» В рассмотренных ранее примерах после создания объекта нам прихо­ дилось напрямую или с помощью специальных методов присваивать значения полям объекта. Это не всегда удобно. Хорошо было бы иметь возможность задавать значения полей объекта уже на этапе создания объекта. И такая возможность имеется. Связана она с использованием конструктора. Конструктор - это специальный метод, который автоматически вызыва­ ется при создании объекта. Поскольку это метод специальный, то и опи­ сывается он специальным способом. Основные правила описания кон­ структора в классе такие: Имя конструктора совпадает с именем класса. Обычно описывается с ключевым словом puЬl i c . • Конструктор не возвращает результат, и идентификатор типа резуль­ тата для конструктора не указывается (в отличие от обычных методов, которые в подобном случае описываются с идентификатором vo i d). • • У конструктора могут быть аргументы. Аргументы передаются кон­ структору при создании объекта. • Конструктор можно перегружать. Это означает, что в классе может быть описано несколько конструкторов. Каждая версия конструкто­ ра отличается типом и/или количеством аргументов. Если в инструкции создания объекта на основе оператора new после имени класса в круглых скобках указываются определенные значения, то это и есть аргументы, которые передаются конструктору. � ПОДРОБН ОСТИ И нструкция вида new Кла с с ( ) представляет собой команду вызова конструктора Кла с са , п ричем конструктор вызы вается без аргумен­ тов . Команда вида new Кла с с ( а р гумен ты) означает, что п р и созда­ н и и объекта вызы вается конструктор Кла с са и конструктору пере­ даются а р гумен ты. 303 Глава 6 Как отмечалось выше, в классе может быть описано несколько конструк­ торов (или несколько версий конструктора). В таком случае версия кон­ структора, вызываемая при создании объекта, определяется по количеству и типу арrументов, переданных конструктору в команде создания объек­ та. Здесь имеет место полная аналогия с перегрузкой методов. Небольшой пример использования конструкторов представлен в листинге 6.4. [1i!] л истинг 6 . 4 . И спользование конструктора us ing Sys tem; / / Описание класса с конструктором : c l a s s MyClas s { / / Закрытые поля : puЫ ic int num ; / / Целочисленное поле puЫ ic char s ymb ; / / Символьное поле puЫ ic string txt ; / / Текстовое поле / / Открытый метод для отображения значений полей : puЫ ic vo id show ( ) { Console . WriteLine ( " Пoля : { 0 } , \ ' { 1 } \ ' и \ " { 2 } \ " " , num, s ymb , txt ) ; / / Конструктор без аргументов : puЫ ic MyC la s s ( ) { / / Значения полей : num= l O O ; s ymb= ' A ' ; tхt= " Красный " ; / / Конструктор с одним целочисленным аргументом : puЫ ic MyC la s s ( int n ) { / / Значения полей : num=n ; s ymb= ' B ' ; tхt= "Желтый " ; 304 З нако мство с классами и о бъекта м и 1 1 Конструктор с двумя аргументами : puЫ ic MyCla s s ( int n , char s ) { 1 1 Значения полей : num=n ; s ymb=s ; tхt= "Зеленый" ; 1 1 Конструктор с тремя аргументами : puЫ ic MyCla s s ( int n , char s , string t ) { 1 1 Значения полей : num=n ; s ymb=s ; txt=t ; 1 1 Конструктор с одним текстовым аргументом : puЫ ic MyCl a s s ( s t ring t ) { 1 1 Значения полей : num=O ; s ymb= ' Z ' ; txt=t ; } 1 1 Класс с главным методом : c l a s s ConstructorsDemo { 1 1 Главный метод : static vo id Main ( ) { 1 1 Создание объектов . 1 1 Вызывается конструктор без аргументов : MyC l a s s A=new MyC l a s s ( ) ; 1 1 Проверяются значения полей объекта : А . show ( ) ; 1 1 Вызывается конструктор с целочисленным аргументом : 305 Глава 6 MyC l a s s B=new MyC l a s s ( 2 0 0 ) ; 1 1 Проверяются значения полей объекта : В . show ( ) ; 1 1 Вызывается конструктор с двумя аргументами : MyC l a s s C=new MyC l a s s ( З O O , ' С ' ) ; 1 1 Проверяются значения полей объекта : С . show ( ) ; 1 1 Вызывается конструктор с тремя аргументами : MyC l a s s D=new MyC l a s s ( 4 0 0 , ' D ' , " Синий" ) ; 1 1 Проверяются значения полей объекта : D . show ( ) ; 1 1 Вызывается конструктор с символь ным аргументом : MyC l a s s F=new MyC l a s s ( ' А ' ) ; 1 1 Проверяются значения полей объекта : F . show ( ) ; 1 1 Вызывается конструктор с текстовым аргументом : MyC l a s s G=new MyC l a s s ( " Cepый" ) ; 1 1 Проверяются значения полей объекта : G . show ( ) ; Как выглядит результат выполнения программы, показано ниже. [1i!J Результат выполнения программы (из листинга 6 .4) Поля : 1 0 0 , ' А ' и " Красный" Поля : 2 0 0 , ' В ' и "Желтый" Поля : 3 0 0 , ' С ' и " Зеленый" Поля : 4 0 0 , ' D ' и " Синий" Поля : 6 5 , ' В ' и "Желтый" Поля : О , ' Z ' и " Серый" Мы в данном случае имеем дело с классом MyC l a s s , у которого есть три закрытых поля: целочисленное, символьное и текстовое. В классе 306 З нако мство с классами и о бъекта м и описан открытый метод show ( ) , отображающий в консольном окне зна­ чения полей. � ПОДРОБН ОСТИ В теле метода show ( ) использована команда Con s o l e . WriteLine ( " Пол я : { 0 } , \ ' { 1 } \ ' и \ " { 2 } \ " " , num, symЬ, tхt ) . Коман­ дой в консольном окне отображается строка " Поля : { О } , \ ' { 1 } \ ' и \ " { 2 } \ " " , в которой вместо блока { О } отображается значение це­ лочисленного поля num, вместо блока { 1 } отображается значение символьного поля s ymЬ , а вместо блока { 2 } отображается значение текстового поля txt . Также в отображаемой текстовой строке мы ис­ пользуем одинарные и двойные кавычки . Одинарные кавычки добав­ ляем в текстовую строку с помощью инструкции \ ' , а двойные кавычки добавляются в текстовую строку с помощью инструкции \ " . Кроме метода show ( ) , в классе описано пять версий конструктора: без аргументов, с одним целочисленным аргументом, с одним текстовым ар­ гументом, с двумя аргументами (целочисленным и символьным) и с тре­ мя аргументами (целочисленным, символьным и текстовым). Каждая из версий конструктора описана с ключевым словом рuЫ i с . G) Н А ЗАМ ЕТ КУ Конструктор - это метод. Есл и м ы хотим вызы вать конструктор (то есть создавать объекты ) вне п ределов класса (а мы хоти м ) , то конструктор должен быть открыты м . Чтобы о н был открыты м , кон­ структор описы вают с кл ючевым словом puЫ i c - как и в случае, когда в классе описы вается открытый метод. Название конструктора совпадает с названием класса: в данном случае класс называется MyC l a s s , отсюда и название конструктора. В теле кон­ структора размещаются команды, которые выполняются при создании объекта. Например, так описана версия конструктора без аргументов (комментарии удалены): puЫ ic MyClas s ( ) { num= l O O ; s ymЬ= ' A ' ; tхt= " Красный" ; 307 Глава 6 Это означает, что если мы создаем объект и конструктору не передаем аргументы (как это имеет место в команде MyC l a s s A=new MyC l a s s ( ) в главном методе программы), то целочисленному полю создаваемого объекта будет присвоено значение 1 0 0 , символьное поле получит зна­ чение ' А ' , а текстовое поле получит значение " Кр а с ный " . Конструктор с целочисленным аргументом описан таким образом, что его аргумент определяет значение целочисленного поля создаваемого объекта, а символьное и текстовое поле получают соответственно зна­ чения ' В ' и " Желтый " . Конструктору могут передаваться два аргумента: первый целочислен­ ный и второй символьный. Они определяют значения целочислен­ ного и символьного полей объекта. Текстовое поле получает значение " З е л е ный " . Если же конструктору передаются три аргумента (целое число, символ и текст), то они определяют значения полей объекта, ко­ торый создается. Кроме версии конструктора с одним целочисленным аргументом, класс MyC l a s s содержит описание версии конструктора с одним текстовым аргументом. Текстовое значение, переданное аргументом конструктору, задает значение текстового поля создаваемого объекта. При этом цело­ численное поле получает значение О , а символьному полю присваивает­ ся значение ' z ' . В главном методе программы создается несколько объектов. Каждый раз используются разные конструкторы. Значения полей объектов проверя­ ем, вызывая из объектов метод s h o w ( ) . Например, при создании объек­ та командой MyC l a s s B=new MyC l a s s ( 2 0 0 ) вызывается конструк­ тор с одним целочисленным аргументом. Команда MyC l a s s C=new МуС 1 а s s ( 3 О О , ' С ' ) подразумевает создание объекта с использовани­ ем конструктора с двумя аргументами. Конструктор с тремя аргумен­ тами вызывается, когда объект создается командой MyC l a s s D=new MyC l a s s ( 4 0 0 , ' D ' , " Синий " ) . В команде MyC l a s s G=new MyC l a s s ( " Серый " ) конструктору пере­ дается один текстовый аргумент. Такая версия конструктора в классе MyC l a s s описана и будет вызвана при создании объекта. Но вот в ко­ манде MyC l a s s F=new MyC l a s s ( ' А ' ) конструктору передается сим­ вольный аргумент, и такой версии конструктора (с одним символьным аргументом) в классе MyC l a s s нет. Зато в классе MyC l a s s есть вер­ сия конструктора с одним целочисленным аргументом, а в языке С# 308 З нако мство с классами и о бъекта м и разрешено автоматическое преобразование символьного типа в целочис­ ленный. В результате при создании объекта командой MyC l a s s F=new MyC l a s s ( ' А ' ) вызывается конструктор с целочисленным аргументом, а символьное значение ' А ' фактически переданного аргумента преобра­ зуется к числовому значению 6 5 (код символа ' А ' в кодовой таблице). � ПОДРОБН ОСТИ Есл и в классе не оп исан ни оди н конструктор , то при создании объ­ екта испол ьзуется конструктор по умолчанию : у такого конструктора нет аргументов, и н и какие допол н ител ьные действия с объектом он не выполняет. Но есл и в классе оп исана хотя бы одна версия кон ­ структора, т о конструктор по умолчанию бол ьше не доступе н . Так, есл и в рассмотренном выше примере в классе MyC l a s s не оп исать версию конструктора без аргументов, то команда MyC l a s s A=new MyC l a s s ( ) станет некорректно й . Деструкто р Замуровали, демоны ! из к/ф �иван Ва сильевич меняет професси ю» Если конструктор автоматически вызывается при создании объекта, то деструктор - это метод, который автоматически вызывается при удалении объекта из памяти. При описании деструктора нужно придер­ живаться следующих правил. • Имя деструктора начинается с тильды - , после которой указыва­ ется название класса. Если мы описываем деструктор для класса MyC l a s s , то называться такой деструктор будет -MyC l a s s . • Деструктор не возвращает результат, идентификатор типа результа­ та для него не указывается, также не указывается ключевое слово puЬl i c . • У деструктора нет аргументов, и о н н е может быть перегружен (в классе только один деструктор). Перед тем как объект удаляется из памяти, вызывается деструктор. По­ этому в теле деструктора размещаются команды, которые необходимо выполнить перед самым удалением объекта. 309 Глава 6 G) Н А ЗАМ ЕТ КУ Как отмечалось выше, деструктор автоматически вызы вается при удалении объекта из памяти . Но п роблема в том , что выход объек­ та из области видимости ( п отеря ссылки на объект) не означает, что объект будет сразу же удален . Он будет удален , но это может п ро­ изойти через некоторое время . За удаление « нен ужных» объектов отвечает система «сборки мусора» . Она работает автономно и уда­ ляет те объекты , на которые в п рограмме нет ссылок. Поэтому, есл и на объект теряется последняя ссылка , он автоматически становится кандидатом на удаление. Когда именно он будет удале н , « решает» «сборщик мусора» , а не п рограм ма. Деструктор вызы вается при удалении объекта . Получается , что точ ное время вызова деструкто­ ра неизвестно . М ы л и ш ь знаем , что он будет вызван. Эту особен­ ность нужно уч иты вать при испол ьзован и и деструкторов в классах. Иллюстрация к использованию деструктора в программе представлена в листинге 6.5. � Листинг 6 . 5 . Использование деструктора us ing Sys tem; 11 Класс с конструктором и деструктором : c l a s s MyClas s { 1 1 Закрытое текстовое поле : private s tring name ; 1 1 Конструктор : puЫ ic MyC la s s ( string txt ) { 1 1 Присваивание значения полю : name=tx t ; 1 1 Отображение сообщения : Console . WriteLine ( " Coздaн объект \ " { 0 } \ " " , name ) ; 1 1 Деструктор : �MyClas s ( ) { 1 1 Отображение сообщения : Console . WriteLine ( " Yдaлeн объект \ " { 0 } \ " " , name ) ; 310 З нако мство с классами и о бъекта м и } 1 1 Класс с главным методом : c l a s s DestructorDemo { 1 1 Статический метод : static vo id make r ( s tring txt ) { 1 1 Создание анонимного объекта : new MyCl as s ( txt) ; 1 1 Главный метод : static vo id Main ( ) { 1 1 Создание объекта : MyC l a s s A=new МуС l а s s ( " Первый" ) ; 1 1 Создание анонимного объекта : new MyCla s s ( " Bтopoй" ) ; 1 1 Новый объект : A=new МуС l а s s ( " Третий" ) ; 1 1 Вызов статического метода : make r ( "Четвертый" ) ; 1 1 Новый объект : A=new МуСlа s s ( "Пятый" ) ; Результат выполнения программы, скорее всего, будет таким. [1i!J Результат выполнения программы (из листинга 6 . 5) Создан объект " Первый" Создан объект "Второй" Создан объект " Третий" Создан объект "Четвертый" Создан объект "Пятый" Удален объект "Пятый" Удален объект " Первый" 311 Глава 6 Удален объект "Четвертый" Удален объект " Третий" Удален объект "Второй" Мы описываем класс MyC l a s s , и в этом классе есть закрытое тексто­ вое поле n am e . Конструктор у класса всего один. Аргументом кон­ структору передается текстовое значение, которое присваивается полю name . После присваивания значения полю n ame командой C o n s o l e . W r i t e L i n e ( " С о здан о бъе к т \ " { О } \ " " , n ame ) в консольное окно выводится сообщение о том, что создан новый объект. Сообщение содер­ жит значение поля name созданного объекта. (D Н А ЗАМ ЕТ КУ Оди н факт создания объекта означает, что в консол ьном окне по­ является сообщение со значением закрытого текстового поля это­ го объекта . П роще говоря , когда с испол ьзованием оператора new создается объект класса MyC l a s s , в консол ьном окне появляется сообщение. Все это - благодаря конструктору, в котором есть со­ ответствующая команда . Также стоит заметить, что поскол ьку в классе MyC l a s s оп исан тол ько оди н конструктор (с текстовым аргументом ) , то при создании объек­ та класса необходи мо передать конструктору текстовый аргумент. Других способов создания объекта класса нет. Еще в классе есть деструктор. В теле деструктора всего одна команда Con s o l e . W r i t e L i n e ( " Удален объект \ " { О } \ " " , name ) , которой в консольное окно выводится сообщение об удалении объекта и значе­ нии поля n ame этого объекта. (D Н А ЗАМ ЕТ КУ Каждый раз при удалении объекта и з памяти в консол ьном окне по­ является сообщение со значением закрытого текстового поля объ­ екта, который удаляется . Это п роисходит благодаря нал и ч и ю де­ структора в классе MyC l a s s . В главном классе программы кроме метода Ma i n ( ) описан еще и стати­ ческий метод ma ke r ( ) . У метода есть текстовый аргумент (обозначен как t x t ) . В теле метода всего одна команда new MyC l a s s ( t xt ) , ко­ торой создается объект класса MyC l a s s . Пикантность ситуации в том, 312 З нако мство с классами и о бъекта м и что ссылка на созданный объект не записывается в объектную перемен­ ную. Получается, что объект создается, но нет переменной, через кото­ рую можно было бы получить доступ к этому объекту. Такие объекты обычно называют анонимными. (D Н А ЗАМ ЕТ КУ Анон имные объекты испол ьзуются в тех случаях, когда нужен «одно­ разовый» объект - то есть объект, который испол ьзуется оди н раз , после чего бол ьше не нуже н . В таком случае инструкция , создающая объект, не п р исваи вается объектной перемен ной , а размещается в том месте в вы раже н и и , где должна быть ссыл ка на объект. В данном случае мы не планируем каким-либо образом использовать созданный объект, поэтому нет необходимости записывать в отдельную переменную ссылку на него. В главном методе программы командой МуС l а s s A=new MyC l a s s ( " Пер­ вый " ) создается первый объект. Вследствие вызова конструктора при вы­ полнении этой команды в консольном окне появляется сообщение Создан объект " Первый " . После этого командой new MyC l a s s ( " Вт орой " ) создается второй объект, и в консольном окне появляется сообщение Создан объект " Вт орой " . Созданный объект анонимный, поскольку ссылка на него в объектную переменную не записывается. Раз так, то по­ лучается, что программа не содержит переменных со ссылками на создан­ ный объект, и он становится кандидатом на удаление. Начиная с этого момента, в консольном окне потенциально может появиться сообщение об удалении объекта (но в данном случае пока не появляется). Командой A=new MyC l a s s ( " Тр е тий " ) создается третий объект (при создании объекта в консоли появляется сообщение С о зд а н объект " Тр е т ий " ), и ссылка на него записывается в переменную А. Раньше в эту переменную была записана ссьшка на первый объект. Теперь пе­ ременная ссылается на третий объект. Следовательно, на первый объект ссылок в программе не осталось. Поэтому первый объект также стано­ вится кандидатом на удаление и может быть удален системой �сборки мусора� . При вызове статического метода ma ke r ( ) с аргументом " Ч е т в ертый " создается анонимный объект. При его создании появляется сообщение Со здан объект " Ч е т в ертый " . Поскольку объект анонимный, то он сразу после создания попадает в категорию подлежащих удалению. 313 Глава 6 Наконец, последней командой A=new MyC l a s s ( " Пя тый " ) в главном методе создается еще один (пятый по счету) объект (в консоли появля­ ется сообщение С о здан объект " Пя тый " ), и ссьшка на него записыва­ ется в переменную А. До этого переменная А ссылалась на третий объект. Следовательно, третий объект подлежит удалению, поскольку на него в программе ссылок нет. На последний, пятый объект, в программе ссылка есть (записана в пере­ менную А). Но программа завершает работу. А при завершении выполне­ ния программы из памяти удаляются все переменные и объекты. Поэтому пятый объект также должен быть удален. В итоге мы получаем, что все пять созданных объектов класса MyC l a s s должны быть удалены. И они начинают удаляться. Как видно из результата выполнения программы, первым удаляется объект " Пя тый " . Затем удаляется объект " Первый " , затем " Ч е т в ертый " , " Тре тий " и, наконец, " Второй " . Здесь еще раз под­ черкнем, что удалением объектов из памяти занимается система «сборки мусора>.>, поэтому нельзя однозначно предугадать, когда именно объекты будут удалены. Можно быть уверенным лишь в том, что это произойдет. Стати ч еские ч лены кл асса Приедут тут всякие : без профессии , без подушек" . из к/ф « д евчата>.> Поля и методы могут быть статическими. Особенность статических по­ лей и методов в том, что они могут использоваться без создания объектов. G) Н А ЗАМ ЕТ КУ Со статически м и методами м ы уже встречал ись ранее и обсужда­ ли их. Но там реч ь шла о статических методах, описанных в том же классе , что и главный метод п рограм м ы . Соответственно, мы ис­ пол ьзовал и статические методы в том же классе , в котором они были оп исан ы . Но в общем случае статический метод может быть оп исан в одном классе , а использован в другом . Статические члены класса описываются с ключевым словом s t a t i c . Для обращения к статическому члену класса (полю или методу) указы­ вается имя класса, после которого через точку указывают имя поля или имя метода (с аргументами или без). Таким образом, если при обращении 314 З нако мство с классами и о бъекта м и к обычным полям и методам нам нужно указать объект, то при обращении к статическим полям и методам вместо объекта указывается класс. Если статическое поле или метод используются в том же классе, где они описа­ ны, то имя класса при обращении к полю или методу можно не указывать. (D Н А ЗАМ ЕТ КУ Статические поля играют рол ь глобал ьных переменных. Они «не при­ вязан ы » к какому-то кон кретному объекту и существуют «сами по себе » . Единственное огран ичение - статические поля нужно описы вать в классе и при обращении указы вать имя этого класса . Аналогично, статические методы в языке С# и грают п римерно ту же рол ь, что и фун кци и в языке С++ . Статический метод - это , по сути , блок команд, которые не подразумевают испол ьзован ия объекта , и поэтому для выполнения этих команд ( вызова метода) объект как таково не нужен . � ПОДРОБН ОСТИ При обращении к статическому пол ю или при вызове статическо­ го метода вместо имени класса можно указать и имя объекта это­ го класса , п ричем объект может быть любым ( главное, чтобы того класса , в котором оп исан статический член ) . Во всех случаях реч ь идет о статических членах. Но обращаться к статическому члену че­ рез объект все же нежелател ьно, поскол ьку в этом случае создается иллюзия , что соответствующее поле или метод я вляются обыч н ы м и (не статически м и ) , а это не так. При работе со статическими методами существует очевидное ограниче­ ние: статический метод может обращаться только к статическим полям и методам в классе. Объяснение простое: поскольку статический метод существует без привязки к объекту, то нет смысла обращаться к полям и методам объекта (поскольку объекта как такового нет). В листинге 6.6 представлена программа. В ней используется класс со ста­ тическим полем и методом. � Листинг 6 . 6 . Знакомство со статическими полями и методами u s ing Sys tem; 11 Класс со статическим полем и методом : c l a s s MyClas s { 1 1 Статическое поле : 315 Глава б puЫ ic static int code=l O O ; 1 1 Статический метод : puЫ ic static void show ( ) { Console . WriteLine ( " Cтaтичecкoe поле : " +code ) ; 1 1 Класс с главным методом : c l a s s StaticDemo { 1 1 Главный метод : static vo id Main ( ) { 1 1 Вызов статического метода : MyClas s . show ( ) ; 1 1 Обращение к статическому полю : MyClas s . code=2 0 0 ; 1 1 Вызов статического метода : MyClas s . show ( ) ; Ниже представлен результат выполнения программы. � Результат выполнения программы ( из листинга 6 . 6) Статическое поле : 1 0 0 Статическое поле : 2 0 0 В программе описьmается класс MyC l a s s , у которого есть открытое статиче­ ское целочисленное поле code с начальным значением 1 О О , а также статиче­ ский метод show ( ) (без аргументов и не возвращающий результат). Метод при вызове отображает в консольном окне значение статического поля code. В главном методе программы командой MyC l a s s . s h o w ( ) вызы­ вается статический метод s h o w ( ) класса MyC l a s s . В результате в консоли отображается текущее значение статического поля c o d e . Мы это значение можем изменить - например, с помощью команды MyC l a s s . c o de = 2 0 0 , после чего значение статического поля стано­ вится равным 2 0 0 . Проверить это легко - достаточно вызвать метод 316 З нако мство с классами и о бъекта м и s h ow ( ) (команда MyC l a s s . s how ( ) ) . В данном случае примечателен факт, что мы не создали ни одного объекта класса MyC l a s s , но это не ме­ шает нам использовать статические члены этого класса. Еще один, на этот раз �математический� пример с использованием ста­ тических полей и методов представлен в листинге 6.7. В программе опи­ сан класс МуМа th, в котором есть статические методы для вычисления значений синуса и экспоненты. В классе также имеется константное ста­ тическое поле, значение которого определяет иррациональное число л. G) Н А ЗАМ ЕТ КУ Что касается статических полей , то нередко они испол ьзуются как статические константы : для такого поля указы вается идентифика­ тор типа (и есл и нужно - спецификатор уровня доступа ) , но вместо кл ючевого слова s t a t i c указы вается идентификатор c o n s t . При­ ч и на в том , что константн ые поля по умолчанию реал изуются как статические. Значение константного поля указы вается при объя в­ лении и впоследстви и не может быть изменено. � ПОДРОБН ОСТИ Дл я вычисления экспоненты испол ьзуется следующее вы ражение: "п х2 х3 х" � ехр (х) ::::: 1 + х + 2Т + 31 ." + nг = Lk=O k! . В п рограмме описывается статический метод для выч исления сум м ы Lk=O qk q0 + q , + + qn , где при заданно �значен и и аргумента х слагаем ые в сум ме вычис­ = ··· ля ются как qk = k!· Сум ма вычисляется с помощью оператора цик­ ла, в котором после п рибавления к сум ме очередного слагаемого рассч иты вается слагаемое для следующей итераци и . При этом мы исходим из того , что есл и выч ислена добавка qk для текущей итераци и , то добавка qk+i для следующей итера ци и выч исляется как qk+ 1 qk х qk+ i = qk x k+ l = (легко п роверить, что есл и qk ), k..!.__ +1 = � � k! и qk = (k+ l)! ' то Аналоги ч н ы м образом выч исляется си нус : испол ьзуется формула (-1 )"х>п+1 - " п (-1 )kx>k+1 xl xs х' s 1 n (x) - х - 31 + 51 - 71 + ". + (2n + l ) ! - Lk=O ( 2k + l ) ! . П одход испол ьзу • ем тот же , что и при вычислении экспоненты - то есть выч исляет- Lk=O qk - qo + q, + ". + qn , _ (-1 J k+ 1x2k+з qk+ 1 х' и тоrДа qk+ I qk+ 7 ( 2k + 3) ! ' qk - (2k + 3)(2k + 2) ' ся сум ма вида _ но на этот раз = qk х (-1 )х' (- 1 )kx2k+ 1 q k= (2k + l )! , (2k + 3)(2k + 2) 317 Глава б Для определения значения параметра п ( верхняя граница в сум мах, через которые вычисляется си нус и экспонента) в програм ме (в со­ ответствующем классе) объя влено статическое целоч исленное поле. А значение тт=З , 1 4 1 592 определяется через статическую константу. Проанализируем представленный ниже программный код. [1i!J Л истинг 6. 7. И спользование статических полей и методов us ing Sys tem; / / Класс со статическими методами и полями : c l a s s MyMath { / / Константное поле ( число " пи" ) : puЫ ic const douЫe Рi=З . 1 4 1 5 92 ; / / Закрытое статическое поле ( граница суммы) : private static int N=l O O ; / / Статический метод для вычисления экспоненты : puЫ ic static douЫe exp ( douЫe х ) { / / Сумма и добавка к сумме : douЫe s=O , q= l ; / / Вычисление суммы : for ( int k=O ; k<=N ; k++ ) { s+=q; / / Прибавление добавки к сумме q*=x/ ( k+ l ) ; / / Добавка для следующей итерации / / Резуль тат : return s ; / / Статический метод для вычисления синуса : puЫ ic static douЫe sin ( douЫe х ) { / / Сумма и добавка к сумме : douЫe s=O , q=x ; / / Вычисление суммы : for ( int k=O ; k<=N ; k++ ) { s+=q; 318 / / Прибавление добавки к сумме З накомство с классами и о бъектам и / / Добавка для следующей итерации : q*= ( - l ) *x*x/ ( 2 * k+2 ) / ( 2 * k+3 ) ; 1 1 Резуль тат : return s ; / / Главный класс : c l a s s StaticDemo { / / Главный метод : static vo id Main ( ) { / / Аргумент для статических методов : douЫe z=l ; / / Вычисление экспоненты : Console . WriteLine ( 11 ехр ( { О } ) = { 1 ) 11 , z , MyMath . exp ( z ) ) ; / / Контрольное значение : Console . WriteLine ( 11 Koнтpoльнoe значение : { 0 } 11 1 Math . Exp ( z ) ) ; / / Новое значение аргумента : z=MyMath . Pi / 4 ; / / Вычисление синуса : Console . WriteLine ( 11 sin ( { О } ) = { 1 } 11 , z , MyMath . s i n ( z ) ) ; / / Контрольное значение : Console . WriteLine ( 11 Koнтpoльнoe значение : { 0 } 11 1 Math . S in ( z ) ) ; Результат выполнения программы такой. [1i!J Результат выполнения программы ( из листинга 6. 7) ехр ( 1 ) =2 , 7 1 8 2 8 1 8 2 8 4 5 9 0 5 Контрольное значение : 2 , 7 1 8 2 8 1 8 2 8 4 5 9 0 5 s i n ( 0 , 7 8 5 3 9 8 ) =0 , 7 0 7 1 0 6 6 6 5 6 4 7 0 9 4 Контрольное значение : О , 7 0 7 1 0 6 6 6 5 6 4 7 0 9 4 319 Глава б Мы описываем класс МуМа th, в котором описаны статические методы ехр ( ) и s i n ( ) для вычисления соответственно экспоненты и синуса. Оба метода реализованы по общему принципу: объявляются две локаль­ ные числовые переменные s и q. В первую записывается значение для суммы, через которую вычисляется экспонента или синус, а во вторую заносится значение добавки к сумме. Начальное значение суммы в обо­ их случаях нулевое. При вычислении экспоненты первая добавка еди­ ничная, а для синуса первая добавка к сумме равна значению аргумента, переданного методу. Сумма вычисляется с помощью оператора цикла, в котором за каждую итерацию к сумме прибавляется очередная добавка, а затем вычисля­ ется добавка для следующей итерации. Количество слагаемых в сумме определяется значением статического целочисленного поля N ( значе­ ние поля равно 1 О О ) . Поле описано со спецификатором уровня доступа pr i va t e . Поэтому поле является закрытым и доступно только внутри кода класса МуМа th. Поле P i описано с идентификатором c o n s t . Это означает, что поле кон­ стантное (его значение изменить нельзя). Константные поля автомати­ чески реализуются как статические. Поэтому в главном методе програм­ мы доступ к этому полю получаем с помощью инструкции MyMa t h . P i . Там ( в главном методе) вычисляются значения для экспоненты и сину­ са. Для сравнения приведены контрольные значения, которые вычисля­ ются с помощью статических методов Ехр ( ) и S i n ( ) класса Math. Не­ сложно заметить, что совпадение более чем приемлемое. G) Н А ЗАМ ЕТ КУ Библ и отеч н ы й класс Math ( п ространство имен S y s t em) содержит статические методы , через которые реал изуются основные матема­ тические фун кци и . Там также есть статические поля со значен иями некоторых математических констант ( основание натурал ьного лога­ рифма и числ о « ПИ » ) . 320 З нако мство с классами и о бъекта м и Кл ю чевое слово thi s - Владимир Николаевич, а может быть, мы все-таки на . . . - Да, типичные марсиане. из к/ф -«Кин-дза-дза» Мы уже знаем, что если в программном коде метода встречается назва­ ние поля, то имеется в виду поле того объекта, из которого вызывается метод. Концептуальная проблема кроется в том, что при вызове метода из объекта этот объект обычно �принимает участие» в работе метода: ме­ тод может использовать поля и другие методы объекта. Когда мы описы­ ваем метод в классе, то определяем команды, выполняемые при вызове метода. Но на этапе описания класса объектов еще нет (ведь нельзя со­ здать объект на основе класса, которого не существует - сначала нужно описать класс). Получается, что когда мы описываем метод, то, с одной стороны, команды метода могут быть связаны с объектом, из которого вызывается метод, а с другой стороны, такого объекта на момент описа­ ния метода нет. Если речь идет об использовании полей (или других ме­ тодов) объекта, то действует правило, упомянутое выше: имя поля в коде метода означает поле объекта, из которого вызывается метод. Но далеко не всегда дело ограничивается использованием полей. Часто нужно по­ лучить доступ к объекту как таковому. Выход из ситуации находят в ис­ пользовании специального зарезервированного ключевого слова t h i s , обозначающего объект, и з которого вызывается метод. Это ключевое слово в теле метода используется так же, как и любая объектная пере­ менная. Просто нужно помнить, что речь идет о том объекте, из которого вызывается метод, содержащий данное ключевое слово. Допустим, в программе описан такой класс: c l a s s MyClas s { puЫ ic int code ; puЫ ic int get ( ) { return code ; В этом классе описано целочисленное поле c ode и метод g e t ( ) , кото­ рый результатом возвращает значение поля c o d e . В описании метода 32 1 Глава б get ( ) ссьшка на поле дается его названием code. Вместе с тем мы мог­ ли бы использовать и полную ссылку t h i s . c o d e , в которой объект, из которого вызывается метод, явно указан с помощью ключевого слова this: c l a s s MyClas s { puЫ ic int code ; puЫ ic int get ( ) { return this . code ; В данном случае использование ключевого слова t h i s не является обя­ зательным. Но бывают ситуации, когда без него не обойтись. Рассмо­ трим еще один способ описания класса: c l a s s MyClas s { puЫ ic int code ; puЫ ic vo id set ( int n ) { code=n ; Теперь в классе MyC l a s s кроме поля c o de описан метод s e t ( ) с це­ лочисленным аргументом n, который присваивается значением полю c o de . Команда c o de = n в теле метода пока особых вопросов не вызы­ вает. Но давайте представим, что мы захотели назвать аргумент метода s e t ( ) точно так же, как название поля c o de . Сразу возникает вопрос: если мы в теле используем идентификатор c o de, то что подразумевает­ ся - поле или аргумент? Ответ однозначный и состоит в том, что в та­ ком случае идентификатор c o de будет отождествляться с аргументом метода. А как же тогда обратиться к одноименному полю? И вот здесь пригодится ключевое слово t h i s : c l a s s MyClas s { puЫ ic int code ; puЫ ic vo id set ( int code ) { thi s . code=code ; 322 З нако мство с классами и о бъектам и В теле метода s e t ( ) инструкция th i s . c o de означает поле c o de объ­ екта, из которого вызывается метод, а идентификатор c o de означает ар­ гумент метода. Небольшой пример, в котором используется ключевое слово t h i s , пред­ ставлен в листинге 6.8. � Л истинг 6 . 8 . Кл ючевое слово this u s ing Sys tem; 11 Класс : c l a s s MyClas s { 1 1 Закрытое целочисленное поле : private int code ; 1 1 Открытый метод : puЫ ic int get ( ) { 1 1 Исполь зовано ключевое слово thi s : return this . code ; 1 1 Открытый метод : puЫ ic vo id set ( int code ) { 1 1 Исполь зовано ключевое слово thi s : thi s . code=code ; 1 1 Конструктор : puЫ ic MyCla s s ( int code ) { 1 1 Исполь зовано ключевое слово thi s : thi s . code=code ; 1 1 Исполь зовано ключевое слово thi s : Console . Wri teLine ( " Создан объект : " +this . get ( ) ) ; 1 1 Класс с главным методом : c l a s s Us i ngThi s Demo { 1 1 Главный метод : 323 Глава б static vo id Main ( ) { 1 1 Создание объекта : MyC l a s s obj =new MyC l a s s ( l O O ) ; 1 1 Присваивание значения полю : obj . set ( 2 0 0 ) ; 1 1 Проверка значения поля : Console . Wri teLine ( " Новое значение : " +obj . get ( ) ) ; Результат выполнения программы такой. � Результат выполнения программы (из листинга 6 . 8) Создан объект : 1 0 0 Новое значение : 2 0 0 Пример очень простой. В классе MyC l a s s есть закрытое целочисленное поле code. Значение полю присваивается методом s e t ( ) , аргумент ко­ торого имеет такое же название, как и имя поля. Поэтому в теле метода при обращении к полю используется полная ссылка t h i s . code. То же замечание справедливо и для конструктора, аргумент которого назван как c o de. Поэтому в теле конструктора идентификатор c o de обозна­ чает аргумент, а инструкция t h i s . c o de является обращением к полю объекта, для которого вызван конструктор. Ключевое слово c o de ис­ пользовано еще в нескольких местах, хотя острой необходимости в этом не бьто (имеется в виду инструкция th i s . c o de в теле метода get ( ) и инструкция t h i s . get ( ) в теле конструктора). В главном методе соз­ дается объект o b j класса MyC l a s s . При вызове конструктора значе­ ние поля созданного объекта отображается в консольном окне. Затем с помощью метода s e t ( ) полю объекта присваивается новое значение, а с помощью метода get ( ) выполняется проверка значения поля. Ключевое слово t h i s используется и в иных ситуациях. Например, при перегрузке конструкторов в классе ключевое слово t h i s с круглыми скобками (и аргументами, если необходимо) используется для вызова одной версии конструктора в другой версии конструктора. Выражение вида t h i s ( ар гуме н т ы ) указывается через двоеточие после закрываю­ щей круглой скобки в описании конструктора. Оно означает выполне­ ние программного кода той версии конструктора, которая соответствует 324 З нако мство с классами и о бъекта м и аргуме н т ам, указанным с ключевым словом t h i s . Общий шаблон вы­ глядит так (жирным шрифтом выделены основные элементы шаблона): puЫic Конструктор ( аргументы) : this ( аргументы) { 1 1 Команды в теле конструктора Для большей наглядности рассмотрим небольшой пример, представлен­ ный в листинге 6.9. � Л истинг 6 . 9 . Конструкторы и ключевое слово this u s ing Sys tem; 11 Класс с перегрузкой конструкторов : c l a s s MyClas s { 1 1 Целочисленные поля : puЫ ic int a lpha ; puЫ ic int bravo ; 1 1 Конструктор с одним аргументом : puЫ ic MyCla s s ( int а ) { 1 1 Сообщение в консоль ном окне : Console . WriteLine ( " Koнcтpyктop с одним аргументом" ) ; 1 1 Значения полей : alpha=a; bravo=alph a ; 1 1 Отображение значений полей : Console . WriteLine ( " Oбa поля равны " + a lpha ) ; 1 1 Конструктор с двумя аргументами : puЫ ic MyCla s s ( int а , int Ь ) : this ( a ) { 1 1 Сообщение в консоль ном окне : Console . WriteLine ( " Koнcтpyктop с двумя аргументами" ) ; 1 1 Значение второго поля : bravo=b ; 1 1 Отображение значений полей : Console . WriteLine ( " Пoля " + a lpha+ " и " +bravo ) ; 325 Глава б 1 1 Конструктор без аргументов : puЫ ic MyC l a s s ( ) : this ( 4 0 0 , 5 0 0 ) { 1 1 Сообщение в консоль ном окне : Console . WriteLine ( " Koнcтpyктop без аргументов " ) ; 1 1 Отображение значений полей : Console . WriteLine ( " Знaчeния " +alpha+" и " +bravo ) ; 1 1 Класс с главным методом : c l a s s ConstrAndThi s Demo { 1 1 Главный метод : static vo id Main ( ) { 1 1 Вызов конструктора с одним аргументом : MyC l a s s A=new MyC l a s s ( l O O ) ; Console . WriteLine ( ) ; 1 1 Вызов конструктора с двумя аргументами : MyC l a s s B=new MyC l a s s ( 2 0 0 , 3 0 0 ) ; Console . WriteLine ( ) ; 1 1 Вызов конструктора без аргументов : MyC l a s s C=new MyC l a s s ( ) ; Ниже показано, как выглядит результат выполнения программы. � Результат выполнения программы (из листинга 6 . 9) Конструктор с одним аргументом Оба поля равны 1 0 0 Конструктор с одним аргументом Оба поля равны 2 0 0 326 З нако мство с классами и о бъекта м и Конструктор с двумя аргументами Поля 2 0 0 и 3 0 0 Конструктор с одним аргументом Оба поля равны 4 0 0 Конструктор с двумя аргументами Поля 4 0 0 и 5 0 0 Конструктор без аргументов Значения 4 0 0 и 5 0 0 В примере описан класс MyC l a s s с двумя целочисленными полями alpha и b r avo. Кроме них в классе есть три версии конструктора: с од­ ним аргументом, с двумя аргументами и без аргументов. Конструктор с одним аргументом простой: появляется сообщение о том, что у кон­ структора один аргумент, обоим полям присваиваются одинаковые значения (определяется аргументом конструктора), и присвоенное значение отображается в консольном окне. Команда MyC l a s s A=new MyC l a s s ( 1 0 0 ) в главном методе программы дает пример использова­ ния этого конструктора. Конструктор с двумя аргументами описан так, что сначала вызывается версия конструктора с одним аргументом (первый из двух аргументов, переданных конструктору с двумя аргументами), а затем появляется со­ общение о том, что вызван конструктор с двумя аргументами, второму полю присваивается новое значение, и конечные значения полей отобра­ жаются в консольном окне. Пример использования конструктора с дву­ мя аргументами дается командой МуС l а s s B=new MyC l a s s ( 2 0 0 , 3 0 0 ) в главном методе программы. Наконец, конструктор без аргументов описан так, что сначала выпол­ няется код конструктора с двумя аргументами 3 О О и 4 О О . Выполне­ ние этого конструктора, как мы уже знаем, начинается с выполнения кода конструктора с одним аргументом. И только в самом конце вы­ полняются команды, непосредственно описанные в теле конструктора без аргументов. Конструктор без аргументов используется при созда­ нии объекта командой МуС l а s s C=new MyC l a s s ( ) в главном методе программы. 327 Глава б Р ез ю ме Что е й надо, я тебе потом скажу. из к/ф «Бриллиантовая рука» • Класс представляет собой шаблон, на основе которого создаются объекты. Описание класса начинается с ключевого слова c l a s s , по­ сле которого указывают имя класса, а в блоке из фигурных скобок описывают поля и методы класса. Поля описываются так же, как и объявляются переменные: указыва­ ется идентификатор типа и имя поля. При описании методов указы­ ваются идентификатор типа возвращаемого результата, имя метода, список аргументов и команды, формирующие тело метода (выделя­ ются фигурными скобками). • • Поля и методы класса называются членами класса. По умолчанию члены класса являются закрытыми - они доступны только внутри кода класса. Чтобы сделать поле или метод открытым (доступным вне пределов класса), его описывают со спецификатором уровня до­ ступа puЬl i c . Закрытые члены класса можно описывать с иденти­ фикатором уровня доступа pr i va t e . Методы могут перегружаться: в классе может быть описано несколь­ ко версий метода с одним и тем же именем, но разными аргумента­ ми. Решение о том, какая версия метода используется, принимается на основе команды вызова метода с учетом количества и типа аргу­ ментов, фактически переданных методу. • • Конструктор является методом, автоматически вызываемым при создании объекта. Конструктор описывается особым образом: имя конструктора совпадает с именем класса, он не возвращает резуль­ тат, и идентификатор типа результата для конструктора не указыва­ ется. Обычно конструктор описывается с ключевым словом рuЫ i с . У конструктора могут быть аргументы, и конструктор можно пере­ гружать (в классе может быть несколько версий конструктора). • 328 Если в классе конструктор не описан, то используется конструктор по умолчанию: у него нет аргументов, и он не выполняет никаких до­ полнительных действий. Если в классе описана хотя бы одна версия конструктора, то конструктор по умолчанию больше не доступен. З нако мство с классами и о бъекта м и • Деструктор является методом, автоматически вызываемым при уда­ лении объекта из памяти. Имя деструктора получается объединени­ ем символа тильды - и названия класса. У деструктора нет аргумен­ тов, он не возвращает результат, и идентификатор типа результата для деструктора не указывается. В классе может быть только один деструктор. • Для создания объекта класса используют оператор n ew, после ко­ торого указываются имя класса и аргументы (в круглых скобках), которые передаются конструктору. Ссылка на созданный объект за­ писывается в объектную переменную. Объектная переменная объ­ является так же, как и переменная простого типа, только в качестве идентификатора типа указывается имя класса. • Обращение к нестатическим полям и методам выполняется с ука­ занием объекта: после имени объекта через точку следует название поля или имя метода (с аргументами в круглых скобках или пустыми круглыми скобками, если аргументы не передаются). • Статические поля и методы описываются с ключевым словом s t a t i с. Статические поля и методы «не привязаны� к объекту и существу­ ют, даже если ни один объект класса не создан. Обращение к стати­ ческим полям и методам выполняется так: имя класса, точка и на­ звание статического поля или метода (с аргументами или без). Если поле или метод используются в классе, в котором они описаны, то имя класса можно не указывать. • Ключевое слово t h i s в теле методов (в том числе конструкторов и деструкторов) может использоваться для обозначения объекта, из которого вызывается метод. Это же ключевое слово используется как команда вызова в одной версии конструктора другой версии кон­ структора. В таком случае в описании конструктора после закрыва­ ющей круглой скобки указываются двоеточие, ключевое слово th i s и круглые скобки, в которых можно указать аргументы, которые передаются вызываемой версии конструктора. Вызываемая версия конструктора определяется по переданным ей аргументам. 329 Глава б Задания для самостоятел ьной работы Перезагрузка завершена. Счастливого пути ! из к/ф -«Гостья из будущего » 1 . Напишите программу с классом, в котором есть закрытое символь­ ное поле и три открытых метода. Один из методов позволяет присво­ ить значение полю. Еще один метод при вызове возвращает результатом код символа. Третий метод позволяет вывести в консольное окно символ (значение поля) и его код. 2. Напишите программу с классом, у которого есть два символьных поля и метод. Он не возвращает результат, и у него нет аргументов. При вы­ зове метод выводит в консольное окно все символы из кодовой табли­ цы, которые находятся �между� символами, являющимися значениями полей объекта (из которого вызывается метод). Например, если полям объекта присвоены значения ' А ' и ' D ' , то при вызове метода в консоль­ ное окно должны выводиться все символы от ' А ' до ' D ' включительно. 3. Напишите программу с классом, у которого есть два целочисленных поля. В классе должны быть описаны конструкторы, позволяющие соз­ давать объекты без передачи аргументов, с передачей одного аргумента и с передачей двух аргументов. 4. Напишите программу с классом, у которого есть символьное и цело­ численное поле. В классе должны быть описаны версии конструктора с двумя аргументами (целое число и символ - определяют значения полей), а также с одним аргументом типа douЬ l e . В последнем случае действительная часть аргумента определяет код символа (значение сим­ вольного поля), а дробная часть (с учетом десятых и сотых) определяет значение целочисленного поля. Например, если аргументом передается число 6 5 1 2 6 7 , то значением символьного поля будет символ ' А ' с ко­ дом 6 5 , а целочисленное поле получит значение 1 2 (в дробной части учитываются только десятые и сотые). • 5 . Напишите программу с классом, у которого есть закрытое целочис­ ленное поле. Значение полю присваивается с помощью открытого мето­ да. Методу аргументом может передаваться целое число, а также метод может вызываться без аргументов. Если метод вызывается без аргумен­ тов, то поле получает нулевое значение. Если метод вызывается с цело­ численным аргументом, то это значение присваивается полю. Однако 330 З нако мство с классами и о бъекта м и если переданное аргументом методу значение превышает 1 О О , то значе­ нием полю присваивается число 1 О О . Предусмотрите в классе конструк­ тор, который работает по тому же принципу, что и метод для присваи­ вания значения полю. Также в классе должен быть метод, позволяющий проверить значение поля. 6. Напишите программу с классом, в котором есть два закрытых цело­ численных поля (назовем их max и min). Значение поля max не может быть меньше значения поля m i n . Значения полям присваиваются с по­ мощью открытого метода. Метод может вызываться с одним или двумя целочисленными аргументами. При вызове метода значения полям при­ сваиваются так: сравниваются текущие значения полей и значения аргу­ мента или аргументов, переданных методу. Самое большое из значений присваивается полю max, а самое маленькое из значений присваивает­ ся полю m i n . Предусмотрите конструктор, который работает по тому же принципу, что и метод для присваивания значений полям. В классе так­ же должен быть метод, отображающий в консольном окне значения по­ лей объекта. 7. Напишите программу с классом, в котором есть два поля: символьное и текстовое. В классе должен быть перегруженный метод для присваива­ ния значений полям. Если метод вызывается с символьным аргументом, то соответствующее значение присваивается символьному полю. Если метод вызывается с текстовым аргументом, то он определяет значение текстового поля. Методу аргументом также может передаваться сим­ вольный массив. Если массив состоит из одного элемента, то он опре­ деляет значение символьного поля. В противном случае (если в массиве больше одного элемента) из символов массива формируется текстовая строка и присваивается значением текстовому полю. 8. Напишите программу с классом, в котором есть закрытое статическое целочисленное поле с начальным нулевым значением. В классе должен быть описан статический метод, при вызове которого отображается те­ кущее значение статического поля, после чего значение поля увеличи­ вается на единицу. 9. Напишите программу с классом, в котором есть статические методы, которым можно передавать произвольное количество целочисленных аргументов (или целочисленный массив). Методы, на основании пере­ данных аргументов или массива, позволяют вычислить: наибольшее зна­ чение, наименьшее значение, а также среднее значение из набора чисел. 331 Глава 6 1 0 . Напишите программу со статическим методом для вычисле(-1 ) "x2n х2 х4 хб 41 6 , + ... + -- . 1 ния косинуса. Используите формулу cos(x) = 1 - -::7+ 2 . (2n) ! u - - · . В классе также должны быть статические методы для вычисления ги3 s 3! 5! x2n+1 перболического синуса sh(x) = х + � + � + ... + -- и гиперболического ко2 х2п х х" синуса ch(x) = 1 +1 +41 + ... + --. 2. . ( 2n) ! (2n + 1 )! Гл ава 7 РА БОТА С Т ЕКСТОМ Так, значит, русский язык знаем. Зачем потре­ бовалось скрывать ? из к/ф -«Кин-дза-дза» С текстом мы уже имели дело много раз. Вместе с тем наше знакомство было скорее поверхностным. В этой главе мы рассмотрим некоторые технические вопросы, связанные в первую очередь со способом реали­ зации текстовых значений в языке С#. Также мы узнаем о некоторых методах, приемах и подходах, полезных (или просто незаменимых) при работе с текстовыми значениями. В частности, далее речь пойдет о сле­ дующем: • познакомимся с классом S t r i n g, объекты которого используются для реализации текстовых значений ; • проанализируем особенности создания текстовых объектов; • исследуем основные операции, выполняемые с текстом ; • познакомимся с методами, предназначенными для работы с текстом ; • узнаем о методе T o S t r i n g ( ) , который играет особую роль при преобразовании объектов в текст. Теперь настало время поближе познакомиться с классом, объекты кото­ рого используются для реализации текстовых значений. 333 Глава 7 Кл асс Stri ng - Вот вы, папример, кто ? - Я Марк Туллий Цицерон ! - Я не в этом смысле ! По пачпорту кто ? - Анна Петровпа Спешнева по паспорту. из к/ф -« 0 бедном гусаре замолвите слово» Ранее в программах мы объявляли переменные «типа» s t r i n g и зна­ чениями таким переменным присваивали текст. В действительности текстовые значения реализуются как объекты библиотечного класса S t r i n g из пространства имен S y s t em. Идентификатор s t r i n g , ис­ пользуемый нами для обозначения «текстового типа», в действительно­ сти является псевдонимом для инструкции S y s t em . S t r i n g то есть фактически это название класса S t r i n g с учетом пространства имен, к которому относится класс. - Как и для всех прочих объектов в языке С#, для работы с объектами класса S t r i n g используются объектные переменные. Каждый раз, ког­ да мы объявляли переменную типа s t r i ng , на самом деле мы объявля­ ли объектную переменную класса S t r i ng. Объекты класса S t r i n g при этом явно не создавались, хотя они и присутствовали незримо «За кули­ сами». Например, мы переменным типа s t r i n g значениями присваива­ ли текстовые литералы (текст, заключенный в двойные кавычки). Внеш­ не это выглядело так, как если бы переменной некоторого «текстового» типа присваивалось текстовое значение. В действительности текстовые литералы реализуются как объекты класса S t r i ng. Поэтому, когда мы присваиваем текстовый литерал переменной типа s t r i ng, то объект­ ной переменной класса S t r i n g присваивается ссылка на объект класса S t r i ng, через который реализован текстовый литерал. Не очень триви­ альным образом выполняются и прочие операции с текстовыми значе­ ниями. Мы их проанализируем и посмотрим на ситуацию под несколько иным углом зрения. Но это будет немного позже. Сейчас же мы остано­ вимся на «технических» подробностях, связанных с реализацией объек­ та класса S t r ing. Как отмечалось выше, при работе с текстом приходится иметь дело с объектной переменной класса S t r i n g, которая ссылается на объ­ ект с текстом. Схематически эта ситуация проиллюстрирована на рис. 7 . 1 . 334 Работа с текстом ... <.' ... ' , ... ... - - "< \J - - ... ... - - ... , } , ... - - " , s t r i nq- nep eм e • • • • ССЫJi а , к ... , ,\, ' ... ... _ _ _ , ... _ _;': (Т \ ... ... .. / , , , ... .... - - - ... (. ) }/ :O'('l 1 _ ... : ... - - - " ') , ... - - - ...,... ... ... - - ... ' � ... ... , S t r i ng - oбъe к т j1 _ ) к Те е" ' - -, ... " ... _ _ _ ... ... , , ' ' ,,' ... _ _ _ , ... ... ... - - ... ... Рис. 7 . 1 . Схема реал изации текста Реальный текст «спрятан� в объекте (откуда он взялся - это вопрос другой). 00 ПОДРОБН ОСТИ В действител ьности внутри текстового объекта содержится сим­ вол ьный масси в . Элементы этого массива - символ ы , которые формируют текст. В принципе, до этого массива можно «добраться» (с помощью указателей ) . Особенность текстового объекта в том, что после создания его нельзя изменить (здесь речь идет об объекте, а не об объектной переменной). На первый взгляд, данное утверждение может показаться странным ведь раньше мы с успехом изменяли текстовые значения, реализован­ ные с помощью переменных типа s t r ing. Но противоречия здесь нет. Мы изменяли значение объектной переменной, а не самого объекта. На­ пример, предположим, что в программе объявлена текстовая перемен­ ная t e x t : s t ring tехt=" текст " ; Что происходит в этом случае? Объявляется объектная переменная t e x t , и значением ей присваивается ссылка на объект класса S t r i ng, в который записано текстовое значение " т е к с т " . Текстовая переменная t e x t не содержит значение " т е к с т " - она на него ссылается! Теперь представим, что выполняется следующая команда: text+=" eщe один текст " ; Такого типа команды мы всегда интерпретировали как добавление к те­ кущему текстовому значению переменной t e x t текста " еще один т е к с т " , указанного справа в выражении. Конечный результат таким и будет: если после выполнения команды мы проверим значение пе­ ременной t e x t , то это будет объединение текстовых строк " т е кс т " 335 Глава 7 и " еще один т е к с т " . Но достигается данный результат не так просто, как может показаться на первый взгляд. Мы уже знаем, что сначала переменная t e x t ссылается на текстовый объект с текстом " т е к с т " . Команда t e x t + = " eщe один т е к с т " вы­ полняется следующим образом. Текст " еще один т е к с т " не добав­ ляется в объект, на который ссылается переменная t e x t . Вместо этого создается новый текстовый объект. В него записывается текст, который получается объединением двух текстовых значений: текста из объекта, на который ссылается переменная t e x t , и текста " еще один т е к с т " из правой части команды присваивания. Ссылка на этот новый объект записывается в переменную t e x t . G) Н А ЗАМ ЕТ КУ То есть текст " еще один текст " не доп исывается в текущи й объект, на который ссылается перемен ная text , а создается новый тексто­ вый объект с новым значением , и ссыл ка на этот новый объект запи­ сывается в переменную text . В резул ьтате создается илл юзия , что к текущему текстовому значению было дописано значение " еще один текст " . � ПОДРОБН ОСТИ Есл и быть до конца откровен н ы м , то нужно отметить, что способ измен ить текстовый объект все же существует. Для этого п ридется испол ьзовать указател и . С их помощью удается «прони кнуть» внутрь текстового объекта и добраться до массива, в котором хранятся символ ы , формирующие текстовое значение. И эти сим вол ы можно поменять. Все это м ы обсуди м , когда будем знаком иться с указате­ лями. С оздан ие текстового об ъ екта Болтают все. Не на всех пишут. из к/ф -« 0 бедном гусаре замолвите слово» Теперь остановимся на способах создания объектов класса S t r i n g. Один путь нам уже известен - можно воспользоваться текстовым лите­ ралом. Но существуют и иные возможности. 336 Работа с текстом � ПОДРОБН ОСТИ Еще раз напом н и м , что текстовый л итерал (то есть текст в двой н ых кавыч ках) реал изуется как объект класса S t r i n g . Значение тексто­ вого л итерала, которое мы восприни маем именно как текст, в дей ­ ствител ьности является ссылкой на объект, в котором этот текст «сп рятан » . Поэтому когда объектной переменной типа s t r i n g ( ил и S t r ing) присваи вается значение, т о на самом деле этой перемен­ ной п р исваи вается ссылка на текстовый объект. Способы создания текстовых объектов определяются версиями кон­ структора, которые есть у класса S t r i n g . Фактически речь идет о том, какие аргументы можно передавать конструктору класса S t r i ng. Все варианты рассматривать не будем, но основные перечислим. И так, тек­ стовый объект можно создать на основе символьного массива. В этом случае из элементов символьного массива формируется текст, и этот текст записывается в созданный текстовый объект. Пример создания текста на основе символьного массива представлен ниже: char [ ] s ymbs= { ' Я ' , ' з ' , ' ы ' , ' к ' , ' ' , ' С ' , ' # ' } ; S t ring txt=new String ( symbs ) ; В данном случае текстовый объект создается на основе символьного массива s ymb s. Для создания объекта вызывается конструктор класса S t r i n g, и аргументом ему передается символьный массив. Посколь­ ку массив s ymb s создавался с использованием списка инициализации { ' я ' , ' з ' , ' ы ' , ' к ' , ' ' , ' с ' , ' # ' } , то в объект, на который ссылает­ ся переменная txt, будет записан текст 11 Язык С # 11 • Если при создании текста на основе символьного массива конструктору класса S t r i n g, кроме имени массива, передать еще и два целочисленных аргумента, то они будут определять индекс начального элемента в масси­ ве для формирования текста и количество символов, используемых в тек­ сте. Например, используется такая последовательность команд: char [ ] s ymbs= { ' M ' , ' ы ' , ' 1 , ' и ' , ' з ' , ' у ' , ' ч ' , ' а ' , ' е ' , ' м ' , 1 ' , ' С ' , ' # ' } ; S t ring txt=new String ( symbs , 3 , 9 ) ; Исходный массив, на основе которого создается текстовый объект, инициализируется списком { ' м ' , ' ы ' , ' ' , ' и ' , ' з ' , ' у ' , ' ч ' , ' а ' , ' е ' , ' м ' , ' ' , ' с ' , ' # ' } . При создании текстового объекта кон­ структору класса S t r i n g первым аргументом передается имя мас­ сива s ymb s . Есть еще второй и третий аргументы. Второй аргумент 3 337 Глава 7 означает, что из массива s ymЬ s при формировании текста нужно брать символы, начиная с элемента с индексом 3 (четвертый по порядку эле­ мент в массиве со значением ' и ' ). Третий аргумент 9 означает, что всего нужно взять 9 символов. В итоге получается, что созданный текстовый объект содержит текст 11 изучаем с 11 • Если конструктору класса S t r i n g передать символьное значение и це­ лое число, то в созданном текстовом объекте текст будет сформирован повторением символа (первый аргумент) в количестве, определяемом числом (второй аргумент). Пример такой ситуации приведен ниже: String txt=new String ( ' * ' , 5 ) ; В результате создается текстовый объект с текстом 11 * * * * * 11 (сим­ вол ' * ' повторяется 5 раз). Иногда такой подход бывает удобен. G) Н А ЗАМ ЕТ КУ Существуют и другие способы создания текстовых объектов . В част­ ности , аргументом конструктору можно передавать указател ь на си м ­ вольный ил и целочисленный (байтовы й ) массив с кодами символов, с допол н ительными целочисленными аргументами или без них. Ука­ зателя м посвя щена отдел ьная глава во второй части кн иги. Коп и ю текстового объекта можно создать с помощью статического метода Сору ( ) класса S t r i n g . Копи руемая строка передается ар­ гументом методу Сору ( ) . Резул ьтатом метода возвращается копия этой строки . Небольшой пример, в котором текстовые объекты создаются разными способами, представлен в листинге 7. 1 . � Л истинг 7. 1 . Создание текстовых объектов using Sys tem; c l a s s StringDemo { s tatic void Mai n ( ) { 11 Символьный массив : char [ ] s ymЬs= { ' Я ' , ' з ' , ' ы ' , ' к ' , ' ' , ' С ' , ' # ' } ; Console . WriteLine ( " Cимвoльный массив : " ) ; 1 1 Отображение содержимого символьного массива : Console . WriteLine ( s ymЬs ) ; 1 1 Текстовый объект создается 338 Работа с текстом 1 1 на основе символьного массива : String A=new String ( s ymЬs ) ; 1 1 Проверка значения текстового объекта : Console . WriteLine ( "A : \ " { 0 } \ " " , А) ; 1 1 Текстовый объект создается на основе 11 нескольких элементов из символь ного массива : String B=new String ( s ymЬs , 1 , 5 ) ; 1 1 Проверка значения текстового объекта : Console . WriteLine ( " B : \ " { 0 } \ " " , В ) ; 1 1 Текстовый объект создается как 11 последователь ность одинаковых символов : String C=new String ( ' ы ' , 7 ) ; 1 1 Проверка значения текстового объекта : Console . WriteLine ( " C : \ " { 0 } \ " " , С ) ; 1 1 Создание текстового объекта путем вызова 11 статического метода с двумя символь ными 1 1 аргументами : A=getText ( ' А ' , ' Н ' ) ; 1 1 Проверка значения текстового объекта : Console . WriteLine ( "A : \ " { 0 } \ " " , А) ; 1 1 Создание текстового объекта путем вызова 11 статического метода с двумя символь ными 1 1 аргументами : B=getText ( ' Н ' , ' А ' ) ; 1 1 Проверка значения текстового объекта : Console . WriteLine ( " B : \ " { 0 } \ " " , В ) ; 1 1 Создание текстового объекта путем вызова 11 статического метода с аргументом - символь ным 1 1 массивом : C=getText ( s ymЬs ) ; 1 1 Проверка значения текстового объекта : Console . WriteLine ( " C : \ " { 0 } \ " " , С ) ; 339 Глава 7 1 1 Статический метод для создания текстового объекта 11 на основе символьного массива : s tatic String getText ( char [ ] s ymЬ s ) { 1 1 Локаль ная текстовая переменная : String txt= " " ; 1 1 Перебор элементов в массиве : for ( int k=O ; k<s ymЬs . Length ; k++ ) { 1 1 Дописывание символа к текстовой строке : txt+=symbs [ k ] ; 1 1 Резуль тат метода : return txt ; 1 1 Статический метод для создания текстового объекта 11 на основе двух символь ных значений : s tatic String getText ( char а , char Ь ) { 11 Локаль ная текстовая переменная : String txt= " " ; 1 1 Локаль ная символьная переменная : char s=a ; 1 1 Формирование текстовой строки : whi l e ( s <=b ) { 1 1 Дописывание символа к текстовой строке : txt+=s ; 1 1 Следующий символ для строки : s++; 1 1 Резуль тат метода : return txt ; 340 Работа с текстом Результат выполнения программы представлен ниже. [1i!J Результат выполнения программы (из листинга 7 . 1 ) Символь ный массив : Язык С# А: "Язык С#" В: " зык С" С: " ыыыыыыы " А : "ABCDEFGH " В: "" С : "Язык С # " В программе мы несколькими способами создаем текстовые объек­ ты и затем проверяем их значение (текст, содержащийся в этих объек­ тах). Также в программе описывается две версии статического метода g e t T e x t ( ) , который результатом возвращает текстовое значение. Одна версия метода подразумевает, что аргументом передается символьный массив, а результатом метод возвращает текстовую строку, сформиро­ ванную из символов массива. Здесь мы фактически продублировали конструктор класса S t r i ng, позволяющий создавать текстовые объек­ ты на основе символьных массивов. Еще одна версия метода g e t T e x t ( ) подразумевает, что аргументами методу передаются два символьных значения (предполагается, что код первого символа-аргумента не боль­ ше кода второго символа-аргумента, хотя это и не принципиально). Как работают эти методы? Идея достаточно простая. В теле метода (в обе­ их версиях) объявляется локальная текстовая переменная txt, началь­ ное значение которой - пустая текстовая строка. Дальше все зависит от того, какой аргумент или аргументы переданы методу. Если методу передан символьный массив, то запускается оператор цикла f o r , в ко­ тором последовательно перебираются элементы символьного массива­ аргумента и каждый символ дописывается к текстовой строке (команда t x t + = s ymb s [ k ] в теле оператора цикла f o r , где через s ymb s обозна­ чен переданный аргументом методу символьный массив). По заверше­ нии оператора цикла значение из переменной txt возвращается резуль­ татом метода. Если же аргументами методу g e t T e x t ( ) переданы два символьных значения, то алгоритм выполнения метода немного другой. Локальной символьной переменной s присваивается значение перво­ го символьного аргумента а. После этого запускается оператор цикла 341 Глава 7 wh i l e , в котором проверяется условие s < =b (через Ь обозначен вто­ рой символьный аргумент). Поскольку s и Ь - символьные перемен­ ные, то при вычислении значения выражения s <=b они автоматически приводятся к целочисленному формату. В итоге сравниваются коды символов. Фактически оператор цикла выполняется до тех пор, пока символ из переменной s в кодовой таблице (или алфавите, если речь идет о буквах) находится до символа из аргумента Ь. За каждый цикл командой t x t + = s к текстовой строке дописывается очередной символ, а затем командой s + + значение переменной s �увеличивается на еди­ ницу� - получаем следующий символ в кодовой таблице (следующую букву в алфавите). После того как текстовая строка сформирована, она возвращается результатом метода. G) Н А ЗАМ ЕТ КУ Есл и аргументом методу ge tText ( ) передать два символа, причем код первого символа будет бол ьше кода второго символа (то есть буквы -аргументы указаны не в алфавитном порядке ) , то резул ьтатом метод возвращает пустую текстовую строку. Причина в том , что при первой проверке условия s <=b в условном операторе оно окажется ложн ы м , и команды в теле оператора цикла выполняться не будут. В итоге переменная txt , возвращаемая как результат метода , будет ссылаться на свое начал ьное значение - на пустую текстовую строку. В главном методе программы командой сhаr [ ] s ymb s = { ' Я ' , ' з ' , ' ы ' , ' к ' , ' ' , ' с ' , ' # ' } создается и инициализируется символьный массив, который мы используем для создания текстовых объектов. Но сначала проверяется содержимое этого массива, для чего массив s ymb s переда­ ется аргументом методу W r i t e L i n e ( ) . В этом случае содержимое сим­ вольного массива печатается в консольном окне как текст. � ПОДРОБН ОСТИ Метод Wr i t e L i n e ( ) оп ределен так, что есл и ему аргументом пере­ дается имя сим вольного массива, то в консол ьное окно выводятся сим волы из массива. Например, есл и s ymЬs - символьный масси в , т о в резул ьтате вы полнения команды C o n s o l e . W r i teLine ( s ymЬs ) отображается содержимое массива s ymЬ s . Но есл и мы воспол ьзу­ емся командой Con s o l e . WriteLine ( 11 11 + s ymЬ s ) (аргументом методу передается сум ма текстового л итерала и имени сим вольного мас­ сива) или командой Con s o l e . Wri teLine ( 11 { О } 11 , s ymЬ s ) , то в кон­ сол ьном окне появится не содержимое массива s ymЬ s , а сообщение 342 Работа с текстом S y s tem . Char [ ] . Дело в том , что в первом случае ( команда Con­ s o l e . W r i t e L i n e ( s ymЬ s ) ) вызы вается версия метода W r i t e L i n e ( ) для обработки сим вол ьного массива, переданного аргумен­ том . А при выполнении команд C o n s o l e . W r i t e L i n e ( 11 11 + s ymЬ s ) и C o n s o l e . W r i t e L i n e ( 11 { О } 11 , s ymb s ) выполняется поп ытка пре­ образовать знач ение переменной s ymЬ s к текстовому формату. В этом случае вызы вается метод T o S t r i n g ( ) ( обсуждается в од­ ном из следующих раздел ов ) . М етод возвращает текстовое п ред­ ставление для п реобразуемого объекта ( в данном случае это s ymЬ s ) . По умолчанию метод возвращает идентифи катор с назва­ нием класса для объекта . Для сим вол ьного массива это идентиф и ­ катор S y s tem . Char [ ] . Первый текстовый объект создается командой S t r i n g A= n e w S t r i n g ( s ymЬ s ) на основе символьного массива. Содержимое текстового объекта проверяем командой Соn s о l е . WriteLine ( " А : \ " { О } \ " " , А ) . G) Н А ЗАМ ЕТ КУ Командой C on s o l e . Wri t e L i n e ( 11 А : \ 11 { О } \ 11 11 , А ) отображается текстовая строка 11 А : \ 11 { О } \ 11 11 , в которой вместо блока { О } под­ ставляется значение текстовой перемен ной А (точнее, подставляет­ ся текст из объекта, на который эта переменная ссылается ) . Причем отображаем ый текст закл ючается в двойные кавычки . Двойные ка­ вычки в текстовую строку добавляем с помощью инструкции \ 11 • Аналогичным способом проверяются значения п рочих текстовых объектов . В данном случае текст получается объединением в одну строку симво­ лов из массива. Еще один объект создается командой S t r i n g B=new S t r i n g ( s ymb s , 1 , 5 ) . Теперь текстовый объект создается на осно­ ве того же массива s ymb s , но используются не все символы массива, а только символы, начиная со второго (с индексом 1 второй аргумент), и используется 5 символов (третий аргумент). - Текстовый объект, который создается командой S t r i n g C = n e w S t r i n g ( ' ы ' , 7 ) , содержит текст из семи букв ' ы ' . В результате выполнения команды A=ge t T e x t ( ' А ' , ' Н ' ) создается текстовый объект, который состоит из символов от ' А ' до ' Н ' включи­ тельно, а ссьшка на этот объект записывается в переменную А. При выполнении команды B=ge t T e x t ( ' Н ' , ' А ' ) в переменную В за­ писывается ссылка на текстовый объект с пустой текстовой строкой, 343 Глава 7 поскольку в данном случае аргументы метода g e t T e x t ( ) указаны не в алфавитном порядке. Наконец, с помощью команды C=ge t T e x t ( s ymb s ) на основе массива s ymb s создается текстовый объект, а ссьшка на него присваивается зна­ чением переменной С. О пера ц и и с текстов ы м и об ъ ектам и Ну, вы мой полк не марайте. Мои орлы газет не читают , книг в глаза пе видели - никаких идей пе имеют ! из к/ф -« 0 бедном гусаре замолвите слово» При выполнении базовых операций с текстовыми значениями полезно помнить, что на базовом уровне текст реализуется как символьный мас­ сив (правда, еще нужно уметь добраться до этого массива). В этом смыс­ ле некоторые методы работы с текстовыми объектами неожиданными не станут. Например, узнать количество символов в тексте можно с помо­ щью свойства Length. Скажем, если txt - объектная переменная класса S t r i ng, то значением выражения txt . Lеngth является количество сим­ волов в тексте, содержащемся в объекте, на который ссьшается перемен­ ная txt. Причем поскольку текстовые литералы реализуются в виде объ­ ектов класса S t r i n g, то свойство Length есть и у текстовых литералов. Поэтому выражение " текст " . Length имеет смысл. Значение этого вы­ ражения равно 5 - это количество символов в тексте " т е к с т " . Если нам нужен отдельный символ из текста, то мы можем проиндексировать текст: после текстовой переменной или текстового литерала в квадратных скоб­ ках указывается индекс символа. Как и в случае с одномерным массивом, индексация начинается с нуля. То есть индекс первого символа в тексте равен О , а индекс последнего символа в тексте на единицу меньше коли­ чества символов в тексте. Так, если txt является текстовой переменной, то значением выражения txt [ О ] является первый символ в тексте, зна­ чением выражения txt [ 1 ] является второй символ в тексте, а значением выражения txt [ txt . Length - 1 ] является последний символ в тексте. G) Н А ЗАМ ЕТ КУ Нужно учесть, что txt . Length это кол ичество сим волов в тексте (на который ссылается перемен ная txt ) , а и ндекс последнего сим­ вола на еди н и цу меньше кол ичества сим волов в тексте . - 344 Работа с текстом Индексировать можно и текстовые литералы. Так, значением выраже­ ния " те кс т " [ 2 ] является символ ' к ' - третья по счету (с индексом 2 ) буква в тексте " т е к с т " . Пример операций, связанных с индексирова­ нием текстовых значений и перебором символов в тексте, представлен в программе в листинге 7 .2. [1iJ Л истинг 7 . 2 . Перебор символов в тексте using Sys tem; c l a s s I ndexingStringDemo { s tatic void Main ( ) { 1 1 Отображение текстового литерала с помощью 11 оператора цикла по коллекции : foreach ( char s in " Текст " ) { Console . Wr i te ( s+ " " ) ; 1 1 Переход к новой строке : Console . WriteLine ( ) ; 1 1 Отображение текстового литерала с помощью 11 оператора цикла : for ( int k=O ; k< " Teкcт " . Length ; k++ ) { Console . Wr i te ( " Teкcт " [ k ] + " _ " ) ; 1 1 Переход к новой строке : Console . WriteLine ( ) ; 1 1 Текстовая переменная : String А= "Изучаем С # " ; 1 1 Отображение текстового значения : for ( int k=O ; k<A . Length ; k++ ) { Console . Wr i te (A [ k ] ) ; 1 1 Переход к новой строке : Console . WriteLine ( ) ; 1 1 Отображение текста в обратном порядке : 345 showReversed (A) ; 1 1 Проверка значения текстового объекта : Console . WriteLine (A) ; 1 1 Новый текстовый объект ( текст в обратном порядке ) : String B=getReversed (A) ; 1 1 Отображение текстового значения : Console . WriteLine ( B ) ; 1 1 Статический метод для создания текстового объекта , 1 1 в котором текст записан в обратном порядке : s tatic String getReversed ( String txt ) { 1 1 Локаль ная текстовая переменная : String str=" " ; 1 1 Формирование текстовой строки : foreach ( char s in txt ) { 1 1 Добавление символа в начало строки : str=s + s t r ; 1 1 Резуль тат метода : return s t r ; 1 1 Статический метод для отображения текста 11 в обратном порядке : s tatic vo id showReversed ( String txt ) { 11 Перебор символов в тексте (в обратном порядке ) : for ( int k=txt . Length - 1 ; k>=O ; k - - ) { 1 1 Отображение в консоли символа : Console . Write ( txt [ k ] ) ; 1 1 Переход к новой строке : Console . WriteLine ( ) ; 346 Работа с текстом Результат выполнения программы будет таким. [1i!J Результат выполнения программы (из листинга 7 . 2 ) т е к с т т е к с т Изучаем С # #С меачузИ Изучаем С # #С меачузИ В главном методе программы с помощью оператора цикла по коллек­ ции f о r e a c h посимвольно отображается текстовый литерал " Т е к с т " . Переменная s , объявленная в операторе цикла, относится к типу c h a r (символ и з текста). При отображении символов и з литерала после каж­ дого символа добавляется пробел. Еще один пример посимвольного отображения литерала основан на ис­ пользовании оператора цикла f о r. Символы в тексте (речь идет о тексто­ вом литерале " Т е к с т " ) в этом случае перебираются с использованием индекса. Индексная переменная k пробегает значения от О и до верхней границы, которая на единицу меньше количества символов в текстовом литерале. Длину текстового литерала (количество символов) вычисля­ ем с помощью выражения " Т е к с т " . Length. Отдельный символ в ли­ терале вычисляется инструкцией " Т е к с т " [ k ] . В данном случае после текстового литерала мы просто указали в квадратных скобках индекс символа. При отображении символов в консоли после каждого символа добавляется подчеркивание. Командой S t r i n g А= " Изучаем С # " объявляется и инициализирует­ ся текстовая переменная А. Мы можем отобразить текст из соответству­ ющего объекта с помощью команды C on s o l e . W r i t e L i n e ( А ) (то есть просто передав переменную А аргументом методу W r i t e L i n e ( ) ) . А мо­ жем отобразить текст посимвольно, для чего используется оператор цик­ ла f o r . Индексная переменная k последовательно перебирает индексы символов в тексте. Начальное значение индекса равно О , а количество символов в тексте вычисляется командой А . Length. Обращение к от­ дельному символу выполняется в формате А [ k ] . В программе описано два статических метода. Метод s howReve r s e d ( ) при вызове отображает в консоли в обратном порядке текстовую строку, 347 Глава 7 переданную аргументом методу. Поэтому при выполнении команды s h owReve r s e d ( А ) текст из переменной А (текст из объекта, на кото­ рый ссылается переменная) отображается в обратном порядке, но сам текстовый объект при этом не меняется. G) Н А ЗАМ ЕТ КУ И ндексированием текстовой переменной м ы можем п роч итать сим­ вол в строке , но не можем изменить его значение. П роще говоря , есл и А текстовая перемен ная и k и ндекс символа , то мы мо­ жем узнать, что это за символ , с помощью инструкци и А [ k ] . В дан ­ ном случае с сим волом м ы обращаемся как с элементом массива. Но п рисвоить значение «элементу» А [ k ] м ы не може м . - � - ПОДРОБН ОСТИ Аргументом методу showReve r s e d ( ) передается текстовая строка (аргумент обозначен как txt ) . В теле массива с помощью операто­ ра цикла переби раются сим вол ы из текстового аргумента , но тол ь­ ко сим вол ы переби раются в обратном порядке . И ндексная пере­ мен ная k прини мает начал ьное значение txt . Length - 1 . Это и ндекс последнего сим вола в тексте . Оператор ци кла выполняется , пока исти нно условие k>=O ( и ндекс неотри цател ьный ) . За кажды й цикл значение и ндексной переменной уменьшается на еди н и цу ( команда k- - ) . Сим вол ы отображаются по одному, все в одной строке ( коман­ да Cons o l e . Write ( txt [ k ] ) в теле оператора цикла) . П осле завер­ шения отображения текста выполняется переход к новой строке . При вызове метода g e t Reve r s e d ( ) создается новый текстовый объ­ ект. Текст в этом объекте записан в обратном порядке по сравнению с объектом, который передавался аргументом методу при вызове. Сле­ довательно, когда выполняется команда S t r i n g B=getReve r s e d ( А ) , то в текстовую переменную В записывается ссылка на вновь созданный объект. В нем текст записан в обратном порядке, если сравнивать с тек­ стом из объекта, на который ссылается переменная А. � ПОДРОБН ОСТИ Статическому методу getReve r s e d ( ) аргументом передается текст, и результатом метода я вляется также текстовое значение. В теле ме­ тода объя вляется локал ьная текстовая переменная str с пустой тек­ стовой строкой в качестве начал ьного значени я . Затем запускается 348 Работа с текстом оператор цикла по коллекции fore ach . Символ ьная (тип char) пере­ менная s в операторе цикла последовател ьно принимает значения сим волов из текстового аргумента txt . За кажды й цикл командой s t r=s + s t r сч итан ный из аргумента символ записывается в начало текстовой строки s t r . После завершения оператора цикла сформи­ рованная текстовая строка возвращается как результат метода. Мы уже знаем, что к текстовым значениям может применяться такая арифметическая операция, как сложение. Если складываются (с по­ мощью оператора +) два текстовых значения, то создается новый тек­ стовый объект, который содержит текст, получающийся объединением складываемых текстовых значений. Результатом выражения возвраща­ ется ссылка на созданный объект. G) Н А ЗАМ ЕТ КУ Объеди нение текстовых строк назы вается кон катенацией строк. Если из двух складываемых операндов лишь один является текстовым, то второй автоматически приводится к текстовому формату (если это возможно), и уже после этого текстовые строки объединяются. Но здесь нужно быть очень аккуратными. Как пример рассмотрим следующий фрагмент кода: S t ring tхt="Четыре " +2 + 2 ; Вопрос в том, какой текст в итоге присваивается переменной t x t . Ответ такой: переменной txt присваивается текст " Ч е тыре 2 2 " . Выражение " Ч е тыре " + 2 + 2 вычисляется слева направо. Сначала вычисляется зна­ чение выражения " Ч е тыр е " + 2 . В этом выражении один операнд тек­ стовый, а другой целочисленный. Целочисленный операнд приводит­ ся к текстовому типу, складываемые текстовые строки объединяются, и в итоге получается строка " Ч е тыр е 2 " . К этому текстовому значению прибавляется целочисленное значение 2 , то есть вычисляется значение выражения " Ч е тыр е 2 " + 2 . Второй целочисленный операнд приводит­ ся к текстовому типу, и в итоге получаем строку " Ч е тыре 2 2 " . Резуль­ тат будет иным, если при определении значения переменной t x t вос­ пользоваться следующей командой: S t ring tхt="Четыре " + ( 2 +2 ) ; В этом случае круглые скобки изменяют порядок выполнения опера­ ций: сначала вычисляется значение выражения 2 + 2 (получаем число 4 ), 349 Глава 7 а затем полученное значение прибавляется к текстовой строке "Че тыре " В итоге переменной txt присваивается текст " Ч е тыре 4 " . G) Н А ЗАМ ЕТ КУ Дл я объединения строк можно испол ьзовать статический метод Concat ( ) класса S t r i n g . Аргументами методу передаются тексто­ вые строки (или массив из текстовых строк) . Резул ьтатом метод возвращает текстовую строку, получающуюся объеди нением аргу­ ментов метода ( ил и элементов текстового массива ) . При объеди не­ нии текстовых строк полезн ы м может быть и метод Jo i n ( ) . Это еще один статический метод класса S t r i ng . Аргументам и методу пере­ дается текстовая строка , я вля ющаяся раздел ителем при объеди не­ нии текстовых строк, и массив с объеди няем ы м и текстовыми стро­ кам и . Резул ьтатом метода возвращается текстовая строка , которая получается объеди нением текстовых элементов массива ( второй аргумент метода) , и при объеди нении строк испол ьзуется раздел и ­ тел ь, оп ределяемый п е р в ы м аргументом метода . Еще одна операция, имеющая важное прикладное значение - сравне­ ние текстовых строк на предмет равенства/неравенства. Выполняется сравнение строк с помощью операторов == (проверка на равенство тек­ стовых строк) и ! = (проверка на неравенство текстовых строк). Если А и В - объектные переменные класса S t r i ng , то результатом выражения А==В является значение t ru e , если объекты, на которые ссылаются пе­ ременные А и В, содержат одинаковый текст. Если эти объекты содержат разный текст, то значение выражения А==В равно f a l s e . Значение выражения А ! =В равно t rue в случае, если переменные А и В ссылаются на объекты, содержащие разный текст. Если эти объекты со­ держат одинаковый текст, то значение выражения А ! =В равно fa l s e . � ПОДРОБН ОСТИ Есл и А и В я вляются объектн ы м и и перемен н ы м и некоторого класса , то в общем случае при выч ислении значения вы ражения А==В или А ! =В сравниваются фактические значения перемен ных - то есть адреса объектов , на которые ссылаются переменные. Поэтому, на­ пример, значение вы ражения А==В равно t rue , есл и перемен ные А и В ссылаются на оди н и тот же объект. А есл и объекты физически раз­ ные ( пускай при этом и совершенно оди наковые ) , то резул ьтатом вы­ ражения А==В будет значение fa l s e . Подобная ситуация и меет ме­ сто и при вычислен и и вы ражения вида А ! =В . Но это в общем случае. 350 Работа с текстом А дл я текстовых объектов (то есть когда А и В я вл я ются объектны­ м и перемен ными класса S t r i ng) при выч ислении выражений А==В и А ! =В сравниваются не адреса объектов , а сам и объекты . То есть для объектов класса S t r i n g сравнение с помощью операторов == и ! вы полняется по-особому. Отметим , что для сравнения тексто­ вых объектов также может испол ьзоваться метод Equ a l s ( ) . Благодаря механ изму перегрузки операторо в , существующему в С#, для объектов пол ьзовател ьских классов можно измен ить спо­ соб сравнения объектов . = Скромная программа, в которой в разных ситуациях используется срав­ нение текстовых значений, представлена в листинге 7.3. � Л истинг 7 . З . Сравнение текстовых строк u s ing Sys tem; c l a s s CompStringDemo { 1 1 Статический метод для сравнения текстовых строк : s tatic bool StrCmp ( String Х , String У ) { 1 1 Е сли строки разной длины : i f ( X . Length ! =Y . Length ) return fal s e ; 1 1 Е сли строки одинаковой длины : for ( int k=O ; k<X . Length ; k++ ) { 1 1 Если символы в текстовых строках разные : i f ( X [ k ] ! =Y [ k] ) return fal s e ; 1 1 Результат метода , если строки одинаковой длины 1 1 и все символы в текстовых строках совпадают : return true ; 1 1 Главный метод : s tatic vo id Main ( ) { 1 1 Символьный массив : char [ ] smЬ= { ' Я ' , ' з ' , ' ы ' , ' к ' , ' ' , ' С ' , ' * ' ) ; 1 1 Текстовый объект : String А= "Язык С# " ; 351 Глава 7 1 1 Отображение текстовой строки : Console . WriteLine ( "A : \ " { 0 } \ " " , А) ; 1 1 Создание нового текстового объекта : String B=new String ( smЬ ) ; 1 1 Отображение текстовой строки : Console . WriteLine ( " B : \ " { 0 } \ " " , В ) ; 1 1 Еще один текстовый объект : String С="ЯЗЫК С# " ; 1 1 Отображение текстовой строки : Console . WriteLine ( " C : \ " { 0 } \ " " , С ) ; Console . WriteLine ( " Cpaвнeниe строк" ) ; 1 1 Сравнение текстовых строк : Console . WriteLine ( "А==В : { О } " , А==В ) ; Console . WriteLine ( " S trCmp (A, В ) : { 0 } " , StrCmp (A, В ) ) ; Console . WriteLine ( "А==С : { О } " , А==С ) ; Console . WriteLine ( " S trCmp (A, С ) : { 0 } " , StrCmp (A, С ) ) ; Console . WriteLine ( " B ! =C : { 0 } " , В ! =С ) ; Console . WriteLine ( " S trCmp (A, \ " С # \ " ) : { 0 ) " , S trCmp (A , " C # " ) ) ; При выполнении программы получаем такой результат. l1ii] Результат выполнения программы (из листинга 7 .З ) А : "Язык С # " В : "Язык С # " С : "ЯЗЫК С # " Сравнение строк А==В : True StrCmp (A, В ) : True А==С : Fal se StrCmp (A, С ) : Fal se В ! =С : True StrCmp (A , " C # " ) : False 352 Работа с текстом В этой простой программе с помощью операторов == и ! = сравнивает­ ся несколько текстовых строк. Также в программе есть описание стати­ ческого метода S t rCmp ( ) , предназначенного для сравнения текстовых строк. Анализ программы начнем с кода метода S t rCmp ( ) . G) Н А ЗАМ ЕТ КУ Строго говоря , нет необходи мости описы вать специальный метод для сравнения текстовых строк. Сравн и вать строки можно с по­ и ! мощью операторов В програм ме соответствующий метод приводится как пример реал изаци и алгоритма, который может ис­ пол ьзоваться для сравнения текстовых значен и й . Этот же п одход, с м и н и мал ьн ы м и изменения м и , может быть испол ьзован для срав­ нения массивов на предмет равенства/неравенства. == =. Метод S t rCmp ( ) описан как статический в том же классе, что и главный метод программы. Поэтому метод S t rCmp ( ) можно вызывать в мето­ де Ma i n ( ) без указания названия класса. У метода S t rCmp ( ) два тек­ стовых аргумента (объектные переменные Х и У класса S t r i n g ) . Через эти переменные методу передаются текстовые значения для сравнения. Если тексты одинаковые, то метод должен возвращать результатом ло­ гическое значение t ru e . Если сравниваемые тексты отличаются, то ре­ зультатом метода должно быть значение f a l s e . При выполнении кода метода сначала сравнивается длина текстовых значений. Понятно, что если два текста имеют разную длину, то совпадать они не могут. Это, так сказать, формальная проверка. Выполняется она с помощью услов­ ного оператора (в упрощенной форме), в котором проверяется условие Х . Length ! = У . Length. Если условие истинно, то инструкцией r e t u r n f a l s e выполнение кода метода завершается, а результатом возвраща­ ется значение fa l s e . Если же условие ложно, то начинает выполнять­ ся оператор цикла, в котором посимвольно проверяется содержимое двух текстовых аргументов метода. Здесь мы учли, что раз уж оператор цикла выполняется, то обе текстовые строки имеют одинаковую дли­ ну (иначе выполнение метода завершилось бы при выполнении услов­ ного оператора). В теле оператора цикла для каждого фиксированного значения индекса k с помощью условного оператора проверяется усло­ вие Х [ k ] ! = У [ k ] . Истинность этого условия означает, что две буквы, находящиеся на одинаковых позициях, отличаются. В таком случае ко­ мандой r e t u rn f a l s e завершается выполнение кода метода, а резуль­ татом метода возвращается значение f a l s e . Команда r e t u r n t ru e в самом конце тела метода выполняется только в том случае, если при 353 Глава 7 выполнении оператора цикла оказалось, что все буквы (на одинаковых позициях) в сравниваемых текстовых значениях совпадают. Это означа­ ет, что текстовые значения одинаковые и результатом метода возвраща­ ется значение t ru e . В главном методе создается несколько текстовых объектов, которые затем сравниваются (с помощью операторов == и ! =, а также метода S t rCmp ( ) ). Объект А создается присваиванием литерала " Я зык С # " . Объект В создается на основе предварительно объявленного и инициа­ лизированного символьного массива, но в итоге получается объект с та­ ким же текстовым значением, как и объект А. Объект С получает зна­ чением текстовый литерал " Я З Ы К С # " . От значений объектов А и В данный текст отличается лишь регистром букв. G) Н А ЗАМ ЕТ КУ Напом н и м , что регистр букв и меет значение: одна и та же буква в разных регистрах ( большая и мален ькая ) и нтерпретируется как разный символ . Также стоит заметить, что текстовые объекты реал изуются п о следу­ ющей схеме: объектная переменная ссылается на объект, содержа­ щий текст. П оэтому присваивание текстового значения переменной следует понимать в том смысл е , что перемен ная будет ссылаться на объект, содержащий п рисвоенный текст. Дл я удобства, есл и это не при водит к недоразумения м , мы иногда не будем заострять вни­ мание на таких деталях. Для сравнения текстовых значений используются выражения А==В (ра­ венство текстовых значений в объектах, на которые ссылаются перемен­ ные А и В результат равен t r u e ) , А==С (проверка на предмет совпаде­ ния текстовых значений в объектах, на которые ссылаются переменные Аи С результат равен f a l s e ), В ! =С (проверка на предмет неравен­ ства текстовых значений в объектах, на которые ссьшаются переменные В и С результат равен t ru e ). Аналогичные проверки выполняются с помощью описанного в программе статического метода S t r Cmp ( ) . Как несложно заметить, результаты проверок вполне ожидаемые. - - - � ПОДРОБН ОСТИ Хотя для сравнения текстовых значений обычно испол ьзуют опе­ и ! это не единствен н ы й способ сравнения текстовых раторы строк. Для сравнения текстовых строк можно испол ьзовать метод == 354 = , Работа с текстом Equa l s ( ) ( вообще , диапазон при менения метода Equa l s ( ) более ш и рок, чем сравнение текстовых значений - этот метод исполь­ зуется для сравнения объектов разных классов , в том ч исле и объ­ ектов класса S t r i n g ) . У метода есть нескол ько верси й . И меется статическая версия метода , которая вызы вается из класса S t r i n g . Аргументами методу в таком случае передаются сравн и ваемые текстовые строки . Резул ьтатом метод возвращает логическое зна­ чение t rue ( строки совпадают) или fa l s e (строки не совпадают) . Нестатическая версия метода вызы вается из текстового объекта , а еще оди н текстовый объект передается аргументом методу. Срав­ ни вается текстовое значение для объекта , из которого вызы вается метод, и текстовое значение для объекта , переданного аргументом методу. Резул ьтатом метода является л огическое значение. Напри­ мер, есл и А и В я вля ются объектн ы м и перемен н ы м и класса S t r i n g , то результатом вы ражения А . Equa l s ( В ) или S t r i n g . Equ a l s ( А , В ) является значение t rue , есл и переменные А и В ссылаются на тек­ стовые объекты с оди наковым текстом . В п роти вном случае резул ь­ татом я вляется значение fa l s e . Текстовые значения по умолчан и ю сравн и ваются с учетом состояния регистра (то есть бол ьшая и ма­ лен ькая буквы сч итаются разн ы м и сим волам и ) . Есл и нужно прове­ сти сравнение текстовых значений без учета состояния регистра (то есть при сравнении текстов бол ьшие и мален ькие буквы и нтер­ п рети ровать как оди н и тот же символ ) , допол нител ьным аргументом методу Equa l s ( ) следует передать вы ражение S t r i ngC ompa r i s on . Ordina l i gn o r e C a s e ( константа Ordina l i gn o r e C a s e перечисления S t r i ngCompa r i s o n ) . Так, при выполнении команд вида А . Equa l s ( В , S t r i ngC ompar i s on . Ordina l i gn o reCa s e ) или S t r i ng . Equ a l s ( A , В , S t r i n gC ompa r i s on . Ordina l i gn o r e Ca s e ) сравнение текстовых значений выполняется без учета состояния регистра . Кстати , есл и испол ьзовать вместо константы Ordina l i gn o r e C a s e константу Ordinal , то при сравнении текстовых строк испол ьзуется режи м по умолчанию, при котором регистр букв имеет значение. Иногда для сравнен ия текстовых значений без учета состоя ния регистра можно текстовые значения п редварител ьно переве­ сти в верхн и й или нижн и й регистр . Для перевода сим волов текста в верхн и й регистр испол ьзуется метод T oUpper ( ) , а для перевода сим волов текста в нижний регистр испол ьзуют метод ToLower ( ) ( методы обсуждаются в следующем разделе) . Например, чтобы сравн ить значения текстовых переменных А и В на п редмет равен­ ства без учета состояния регистра , можно испол ьзовать вы ражения А . T oUpper ( ) В ToUpp e r ( ) ил и А . ToLower ( ) В ToLower ( ) . При этом значения перемен н ых А и В не меня ются . == . == . 355 Глава 7 М етоды для работы с текстом - А зачем вы мне это говорите ? - Сигнализирую . из к/ф �девчата» Существует большое количество методов, предназначенных для работы с текстовыми объектами. Эти методы позволяют выполнять самые раз­ ные операции со строками. Мы очень кратко обсудим наиболее важные и интересные методы. G) Н А ЗАМ ЕТ КУ Напом н и м , что текстовый объект изменить нел ьзя : после того как текстовый объект созда н , содержащийся в нем текст не может быть изменен . Поэтому здесь и далее все операци и , в которых упомина­ ется изменение текста , следует пони мать в том смысл е , что на ос­ нове одного текстового объекта создается другой текстовый объект. Методы, предназначенные для работы с текстом, условно можно разбить на несколько групп в соответствии с типом решаемой задачи. Так, есть методы для выполнения поиска в текстовой строке. Метод I nde x O f ( ) позволяет выполнять поиск в текстовой строке символа или подстроки. Метод вызывается из объекта текстовой строки, в котором выполняется поиск. Символ или подстрока для поиска передаются аргументом мето­ ду. Результатом метод возвращает целое число - индекс первого вхож­ дения в текстовую строку искомого фрагмента или символа. Если при поиске совпадение не найдено (искомая подстрока или символ отсут­ ствуют в исходной текстовой строке), то метод возвращает значение - 1 . Методу можно передать второй целочисленный аргумент. Если так, то этот аргумент определяет индекс символа в строке, начиная с которо­ го выполняется поиск. Третий необязательный целочисленный аргумент метода I nde x O f ( ) определяет количество элементов в строке, по кото­ рым выполняется поиск. Метод La s t i n de x O f ( ) похож на метод I n dexO f ( ) , но только по­ иск совпадений начинается с конца строки. Если аргументом методу La s t i ndexO f ( ) передать подстроку или символ, то результатом метод возвращает индекс последнего вхождения данной подстроки или сим­ вола в текст, из которого вызван метод. В случае, если совпадений нет, 356 Работа с текстом метод возвращает значение - 1 . Второй необязательный целочисленный аргумент метода определяет индекс элемента, начиная с которого вы­ полняется поиск совпадений. Поиск выполняется в обратном порядке от конца в начало текста. Третий необязательный целочисленный аргу­ мент определяет количество элементов, по которым выполняется поиск. Методы I n de x O fAn y ( ) и L a s t i n d e x O fA n y ( ) подобны методам I nde x O f ( ) и La s t i ndexO f ( ) соответственно, но им первым аргумен­ том передается символьный массив. А поиск выполняется на предмет совпадения хотя бы одного из символов из массива с символом в тек­ сте, из которого вызывается метод. Небольшая иллюстрация к исполь­ зованию методов I n d e x O f ( ) и L a s t i n d e x O f ( ) , I n de x O fA n y ( ) и L a s t i ndexO fAn y ( ) представлена в программном коде в листинге 7.4. � Листинг 7 . 4 . Поиск по тексту u s ing Sys tem; c l a s s SearchStringDemo { s tatic vo id Main ( ) { 1 1 Текс т , в котором выполняется поиск : String tхt="Итак, два плюс два и умножить на два равно шести" ; 1 1 Отображение текстового значения : Console . WriteLine ( "Иcxoдный текст : " ) ; Console . WriteLine ( " \ " { 0 } \ " " , txt ) ; 1 1 Текстовая строка для поиска : String s t r= " двa " ; 1 1 Отображение текстового значения : Console . WriteLine ( " Cтpoкa для поиска : " ) ; Console . WriteLine ( " \ " { 0 } \ " " , s t r ) ; 1 1 Переменная для записи значения индекса : int index ; 1 1 Индекс первого вхождения строки в текст : index=txt . I ndexOf ( s t r ) ; Console . WriteLine ( " Пepвoe совпадение : { 0 } " , index ) ; 1 1 Индекс второго вхождения строки в текст : index=txt . I ndexOf ( s t r , index+ l ) ; 357 Глава 7 Console . WriteLine ( " Bтopoe совпадение : { 0 } " , index } ; 1 1 Индекс последнего вхождения строки в текст : index=txt . La s t indexOf ( st r ) ; Console . WriteLine ( " Пocлeднee совпадение : { 0 } " , index ) ; 1 1 Индекс для начала поиска и количество символов : int а=7 , Ь= 9 ; Console . WriteLine ( " Пoиcк с { 0 } - го индекса п о { 1 } символам : " , а , Ь } ; 1 1 Индекс первого вхождения строки на интервале : index=txt . I ndexOf ( s t r , а , Ь } ; Console . WriteLine ( " Coвпaдeниe на индексе : { 0 } " , index ) ; 1 1 Изменяем количество символов для поиска : Ь+=З ; Console . WriteLine ( " Пoиcк с { 0 } - го индекса по { 1 } символам : " , а , Ь } ; index=txt . I ndexOf ( s t r , а , Ь } ; Console . WriteLine ( " Coвпaдeниe на индексе : { 0 } " , index ) ; 1 1 Символ для поиска : char s ymЬ= ' а ' ; Console . Wri teLine ( " Теперь ищем букву \ ' { О } \ "' , s ymb ) ; 1 1 Поиск первого совпадения : index=txt . I ndexOf ( s ymЬ ) ; Console . WriteLine ( " Пepвoe совпадение : { 0 } " , index } ; 1 1 Поиск второго совпадения : index=txt . I ndexOf ( s ymЬ , index+ l ) ; Console . WriteLine ( " Bтopoe совпадение : { 0 } " , index } ; 1 1 Последнее совпадение : index=txt . La s t indexOf ( s ymЬ } ; Console . WriteLine ( " Пocлeднee совпадение : { 0 } " , index ) ; 1 1 Предпоследнее совпадение : index=txt . La s t indexOf ( s ymЬ , index - 1 } ; Console . WriteLine ( " Пpeдпocлeднee совпадение : { 0 } " , index ) ; 1 1 Поиск на интервале : index=txt . I ndexOf ( s ymЬ , а , Ь ) ; 358 Работа с текстом Console . WriteLine ( " Пoиcк на интервале индексов от ( 0 ) до { 1 } " , а, а+Ь - 1 ) ; Console . WriteLine ( " Пepвoe совпадение на интервале : { 0 } " , index ) ; 1 1 Последнее совпадение на интервале : index=txt . La s t indexOf ( s ymЬ , Ь , b+ l ) ; Console . WriteLine ( " Пoиcк до индекса { 0 } " , Ь ) ; Console . WriteLine ( " Пocлeднee совпадение на интервале : { 0 } " , index ) ; 1 1 Новый символ для поиска : s ymb= ' ы ' ; Console . WriteLine ( "Ищeм букву \ ' { 0 } \ ' " , s ymЬ ) ; 1 1 Поиск первого совпадения : index=txt . I ndexOf ( s ymЬ ) ; Console . WriteLine ( " Пepвoe совпадение : { 0 } " , index ) ; 1 1 Массив с буквами для поиска : char [ ] s= { ' ы ' , ' а ' , ' д ' } ; 1 1 Отображение символов для поиска : Console . Wri te ( "Ищeм букву \ ' { 0 } \ "' , s [ O ] ) ; for ( int k=l ; k<s . Length - 1 ; k++ ) { Console . Write ( " , \ ' " + s [ k ] + " \ ' " ) ; Console . WriteLine ( " или \ "' + s [ s . Length - 1 ] + " \ ' : " ) ; 1 1 Первое совпадение : index=txt . I ndexOfAny ( s ) ; Console . WriteLine ( " Пepвoe совпадение : { 0 } " , index ) ; 1 1 Второе совпадение : index=txt . I ndexOfAny ( s , index+l ) ; Console . WriteLine ( " Bтopoe совпадение : { 0 } " , index ) ; 1 1 Последнее совпадение : index=txt . La s t indexOfAny ( s ) ; Console . WriteLine ( " Пocлeднee совпадение : { 0 } " , index ) ; Console . WriteLine ( " Пoиcк на интервале индексов от ( 0 ) до { 1 } " , а , а+Ь - 1 ) ; 1 1 Первое совпадение на интервале : index=txt . I ndexOfAny ( s , а , Ь ) ; 359 Глава 7 Console . WriteLine ( " Пepвoe совпадение на интервале : { 0 } " , index ) ; 1 1 Последнее совпадение на интервале : index=txt . La s t indexOfAny ( s , а+Ь - 1 , Ь ) ; Console . WriteLine ( " Пocлeднee совпадение на интервале : { 0 } " , index ) ; Результат выполнения программы такой. l1ii] Результат выполнения п рограммы (из листинга 7 . 4) Исходный текст : "Итак , два плюс два и умножить на два равно шести" Строка для поиска : "два " Первое совпадение : 6 Второе совпадение : 1 5 Последнее совпадение : 3 3 Поиск с 7 - го индекса п о 9 символам : Совпадение на индексе : - 1 Поиск с 7 - го индекса по 1 2 символам : Совпадение на индексе : 1 5 Теперь ищем букву ' а ' Первое совпадение : 2 Второе совпадение : 8 Последнее совпадение : 3 8 Предпоследнее совпадение : 3 5 Поиск н а интервале индексов от 7 д о 1 8 Первое совпадение н а интервале : 8 Поиск до индекса 1 2 Последнее совпадение н а интервале : 8 Ищем букву ' ы ' Первое совпадение : - 1 Ищем букву ' ы ' , ' а ' или ' д ' : 360 Работа с текстом Первое совпадение : 2 Второе совпадение : 6 Последнее совпадение : 3 8 Поиск н а интервале индексов о т 7 д о 1 8 Первое совпадение н а интервале : 8 Последнее совпадение на интервале : 1 7 В программе текст, в котором выполняется поиск подстрок и символов, записывается в переменную txt (значением является литерал " Ит а к , д в а плюс д в а и умножит ь на д в а р а в н о ше с ти " ). В переменную s t r записывается текст " д в а " . Это строка, которую мы будем искать в тексте. Для записи значения индекса, определяющего позицию строки или символа в тексте, объявляется целочисленная переменная i ndex. Первое значение переменной i n d e x присваивается командой i n de x = t x t . I n dexO f ( s t r ) . Это индекс первого вхождения строки s t r в текст из переменной t x t (значение 6 ) . После выполнения ко­ манды i n de x = t x t . I n dexO f ( s t r , i n d e x + 1 ) переменная i n de x получает значение 1 5 . Значение выражения t x t . I n de x O f ( s t r , i nde x+ 1 ) - это индекс первого вхождения строки s t r в текст txt, на­ чиная с индекса i n de x + l включительно. На предыдущем этапе в пе­ ременную i n dex бьш записан индекс первого вхождения строки s t r в текст txt, если искать с самого начала текста. Значит, следующее со­ впадение - второе по счету. Командой i ndex=t x t . La s t i ndexO f ( s t r ) в переменную вычисляет­ ся индекс последнего вхождения строки s t r в текст t x t (значение 3 3 ). Далее мы используем две целочисленные переменные (переменная а со значением 7 и переменная Ь со значение 9 ). Переменные использу­ ются в команде i n de x = tx t . I n de x O f ( s t r , а , Ь ) . Значение пере­ менной i ndex после этой команды - индекс первого включения строки s t r в текст txt, если поиск начинать с индекса со значением а (индекс 7 ), причем поиск ограничен количеством символов, определяемых зна­ чением переменной Ь (просматривается 9 символов). Получается, что поиск идет по тексту " в а плюс д " , в котором нет строки " д в а " . Поэто­ му метод I n de x O f ( ) возвращает значение - 1 . А вот если мы проделаем ту же операцию, предварительно увеличив значение переменной Ь на 3 (новое значение этой переменной равно 1 2 ), то поиск строки " д в а " бу­ дет выполняться в тексте " в а плюс д в а " . Такая строка в тексте есть. 361 Глава 7 Индекс, определяющий позицию строки, определяется на основе всего текста из переменной txt. Получается значение 1 5 . В символьную переменную s ymb записывается символ ' а ' , кото­ рый мы будем искать в тексте t x t . Первое появление буквы в тексте определяется индексом, который вычисляется командой i n de x = t x t . I n de x O f ( s ymb ) . В тексте из переменной t x t буква ' а ' первый раз встречается на третьей позиции (индекс на единицу меньше и равен 2 ). Индекс позиции, где буква ' а ' появляется второй раз, вычисляется ко­ мандой i n dex=tx t . I ndexO f ( s ymb , index+ l ) (переменная i ndex получает значение 8 ). Индекс позиции, в которой имеет место последнее вхождение сим­ вольного значения переменной s уmЬ в текст t x t , вычисляем командой i n dex=t x t . L a s t i n dexO f ( s ymb ) и получаем значение 3 8 . Индекс предпоследней позиции буквы ' а ' (значение переменной s уmЬ) вычис­ ляется командой i ndex=tx t . L a s t i ndexO f ( s ymЬ , index- 1 ) . Здесь мы учли, что текущее (до выполнения команды) значение переменной i ndex - это индекс позиции, в которой буква ' а ' появляется послед­ ний раз. Следовательно, индекс предыдущей буквы в тексте txt равен i ndex - 1 . Метод La s t i nde x O f ( ) выполняет поиск в направлении на­ чала текста. Поэтому искать индекс предпоследней позиции буквы ' а ' в тексте нужно, начиная с позиции с индексом i ndex - 1 . Предпоследняя позиция буквы ' а ' в тексте из переменной txt определяется индексом со значением 3 5 . Командой i n de x = t x t . I n dexO f ( s ymb , а , Ь ) вычисляется индекс позиции буквы ' а ' (значение переменной s ymЬ ) в тексте t x t , начиная с позиции с индексом 7 (значение переменной а ) , и проверяется 1 2 сим­ волов (значение переменной Ь ) . � ПОДРОБН ОСТИ Есл и поиск нач инается с сим вола в позиции с индексом а , а кол иче­ ство п роверяемых сим волов равно Ь , то это означает, что поиск вы­ полняется на интервале индексов от а до а+Ь - 1 вкл юч ител ьно. Для значений 7 и 1 2 для перемен ных а и Ь получаем интервал индексов от 7 до 1 8 включ ител ьно. При выполнении команды i n d e x= t x t . L a s t i n de x O f ( s ymb , Ь , b + l ) вычисляется индекс последнего (от начала текста) вхождения буквы, являющейся значением переменной s ymb, в текст из переменной 362 Работа с текстом t x t , причем поиск выполняется в направлении начала текста от пози­ ции с индексом, определяемым значением переменной Ь (значение 1 2 ). В итоге переменная i n dex получает значение 8 (такое же, как было до этого). � ПОДРОБН ОСТИ Если символ находится на позиции с индексом Ь , то от начала текста до этого символа всего находится Ь+ 1 символов. При выполнении ин­ струкции txt . La s t i ndexOf ( s ymЬ , Ь, b+l ) текст из переменной txt просматривается на предмет наличия в нем символа, являющегося значением переменной s ymЬ . Текст просматривается , начиная с сим­ вола с индексом, определяемым значением второго аргумента Ь ме­ тода La s t i ndexOf ( ) . Текст просматривается в направлении к началу текста, до первого совпадения . Первое совпадение с конца - это по­ следнее совпадение с начала. Количество символов, которые просма­ триваются , определяется третьим аргументом метода La s t i ndexOf ( ) . Значение этого аргумента Ь+ 1 (при значении второго аргумента Ь) оз­ начает, что просматриваются все символы до начала текста. Затем переменной s уmЬ присваивается новое значение ' ы ' , и командой i ndex= tx t . I ndexOf ( s уmЬ ) вычисляется индекс первого вхождения буквы ' ы ' в текст из переменной t x t . Но поскольку такой буквы в тек­ сте нет, то переменная i n dex получает значение - 1 . Наконец, мы объявляем и инициализируем символьный массив s , со­ стоящий из трех элементов (буквы ' ы ' , ' а ' и ' д ' ). В результате выпол­ нения команды i ndex= txt . I n dexO fAny ( s ) вычисляется индекс пер­ вого вхождения любой из перечисленных букв в текст t x t . Буквы ' ы ' , как мы уже знаем, в тексте нет совсем. Буква ' а ' в тексте из переменной t x t впервые встречается в позиции с индексом 2 . Буква ' д ' первый раз встречается в позиции с индексом 6. То есть буква ' а ' встречает­ ся раньше всех прочих букв из массива s. Следовательно, переменная i n dex получает значение 2 . Командой i n dex= t x t . I n de x O fAny ( s , i ndex+ 1 ) вычисляется индекс второго вхождения в текст t x t любой из букв из массива s. По факту выполняется поиск первого вхождения букв из массива s в текст t x t , начиная с индекса i ndex+ 1 . Так получа­ ется, что это индекс 6 . При выполнении команды i n de x = tx t . La s t i n d e x O fAny ( s ) вычис­ ляется индекс последнего вхождения в текст из переменной t x t букв из массива s. Получаем значение 3 8 (последнее вхождение буквы ' а ' ) . 363 Глава 7 Командой i ndex= t x t . I ndexO fAn y ( s , а , Ь ) вычисляется первое вхождение в текст txt букв из массива s, начиная с индекса, определя­ емого переменной а (значение 7 ), и проверяются символы в тексте в ко­ личестве, определяемом значением переменной Ь (значение 1 2 ). Дру­ гими словами, исследуется (на предмет наличия совпадений) интервал индексов от 7 до 1 8 включительно (от а до а +Ь - 1 ). Получаем значе­ ние 8 для переменной i ndex. Koмaндa i nde x=txt . La s t i ndexO fAn y ( s , а + Ь - 1 , Ь) используется для вычисления последнего вхождения букв из массива s в текст из пе­ ременной txt, причем поиск начинается с элемента с индексом а+Ь- 1 (значение 1 8 ) и выполняется в направлении к началу текста по 1 2 сим­ волам (значение переменной Ь ). То есть речь идет все о том же диапазо­ не индексов от 7 до 1 8 включительно. Переменная i n dex получает зна­ чение 1 7 (индекс последнего вхождения буквы ' а ' в указанном выше интервале). G) Н А ЗАМ ЕТ КУ Метод C o n t a i n s ( ) позволяет п роверить, содержится л и подстро­ ка в строке . М етод вызы вается из объекта текста , в котором прове­ ряется наличие подстроки . Подстрока передается аргументом ме­ тоду. Резул ьтатом возвращается логическое значение (ти п boo l ) : значение t rue есл и подстрока содержится в тексте , и значение fa l s e есл и подстрока не содержится в тексте . Методы S t a r t sWi th ( ) и EndsWi th ( ) позволяют п роверить, нач ина­ ется или заканчи вается текст определенной подстроко й . Подстро­ ка передается аргументо м , а соответствующий метод вызывается из текстового объекта , который проверяется на наличие в начале ( метод S t a r t sWi th ( ) ) или конце ( м етод EndsWi th ( ) ) подстроки . Резул ьтатом возвращается логическое значение, оп ределяющее наличие ил и отсутствие подстроки в начале/конце текста . - - Методы I n s e r t ( ) , Remove ( ) и Rep l a c e ( ) используются соответ­ ственно для вставки одной строки в другую, удаления части строки и замены одной части строки на другую. G) Н А ЗАМ ЕТ КУ Еще раз подчеркнем , что во всех переч исленных случаях реч ь идет не об изменении исходного текстового объекта, а о создании нового текстового объекта на основе уже существующего . 364 Работа с текстом Методу I n s e r t ( ) при вызове передается первый целочисленный ар­ гумент, определяющий место вставки подстроки, и собственно подстро­ ка, предназначенная для вставки в исходную строку. Результатом метод возвращает новый созданный текстовый объект. Методу Remove ( ) передается два целочисленных аргумента: первый определяет позицию, начиная с которой в текстовой строке удаляются символы, а второй аргумент определяет количество удаляемых симво­ лов. Новая строка, сформированная путем удаления из исходного текста символов в указанном диапазоне индексов, возвращается результатом метода. Методу Rep l a c e ( ) аргументами могут передаваться два символа или две текстовые строки. При вызове метода из текстового объекта форми­ руется (и возвращается результатом) новая текстовая строка, которая получается заменой в исходном тексте символа или подстроки, указан­ ной первым аргументом, на символ или подстроку, указанную вторым аргументом. Также стоит упомянуть метод S ub s t r i n g ( ) , который позволяет из­ влечь подстроку из текста. Аргументом методу передается целочислен­ ное значение, определяющее индекс, начиная с которого извлекается подстрока (подстрока извлекается до конца текста). Если передать вто­ рой целочисленный аргумент, то он будет определять количество сим­ волов, включаемых в извлекаемую подстроку. Подстрока возвращается результатом метода. Исходный текст при этом не меняется. Как перечисленные выше методы используются на практике, иллюстри­ рует программа в листинге 7.5. � Л истинг 7 . 5 . В ставка и замещение текста u s ing Sys tem; c l a s s ReplaceStringDemo { s tatic vo id Main ( ) { 1 1 Исходный текст : String tхt="Мы изучаем С # " ; 1 1 Проверка исходного текста : Console . WriteLine ( txt ) ; 1 1 Текстовая переменная : 365 Глава 7 String s t r ; 1 1 Вставка слова в текст : s t r=txt . I nsert ( l l , " язык " ) ; 1 1 Проверка текста : Console . WriteLine ( s t r ) ; 1 1 Вставка в текст слова с последующим замещением 11 другого текстового блока : str=str . Insert ( 3 , "не " ) . Replace ( " С # " , " Java " ) ; 1 1 Проверка текста : Console . WriteLine ( s t r ) ; 1 1 Замена пробелов на подчеркивания : str=str . Replace ( ' ' , ' _ ' ) ; 1 1 Проверка текста : Console . WriteLine ( s t r ) ; 1 1 Извлечение подстроки : str=str . Substring ( 2 , 1 2 ) ; 1 1 Проверка текста : Console . WriteLine ( s t r ) ; 1 1 Извлечение подстроки : s t r=txt . Substring ( З ) ; 1 1 Проверка текста : Console . WriteLine ( s t r ) ; 1 1 Проверка исходного текста : Console . WriteLine ( txt ) ; Ниже представлен результат выполнения программы. � Результат выполнения п рограммы (из листи нга 7 . 5) Мы изучаем С # Мы изучаем язык С # Мы не изучаем язык Java 366 Работа с текстом Мы н е _ _ не изучаем_ я з ык_ J а v а _ изучаем_ _ изучаем Мы С# изучаем С# В программе объявляется текстовая переменная txt со значением " Мы изучаем С # " . Значение этой переменной выводится в консоль. Также объявляется текстовая переменная s t r . Первое значение этой перемен­ ной присваивается командой s t r=txt . I n s e r t ( 1 1 , " я зык " ) . Значе­ нием является текст, который получается добавлением строки " я зык " в текстовое значение, на которое ссылается переменная t x t . Строка вставляется, начиная с позиции с индексом 1 1 . При выполнении команды s t r = s t r . I n s e r t ( 3 , " н е " ) . Rep l a c e ( " С # " , " J а v а " ) переменная s t r получает новое текстовое значение, которое вычисляется следующим образом. Сначала на основе текуще­ го (вычисленного на предыдущем шаге) значения переменой s t r пу­ тем вставки в позицию с индексом 3 строки " не " формируется новый текст. Ссылка на этот текст возвращается инструкцией s t r . I n s e r t ( 3 , " н е " ) . Другими словами, мы можем интерпретировать выражение s t r . I n s e r t ( 3 , " н е " ) как текстовый объект. Из этого текстового объекта вызывается метод Rep l a c e ( ) с аргументами " С # " и " Java " . Это означает, что на основе текста, сформированного командой s t r . I n s e r t ( 3 , " не " ) , создается новый текст: вместо строки " С # " встав­ ляется строка " Java " . То, что получилось, присваивается значением пе­ ременной s t r. ' ) в тексте, на который ссыла­ Командой s t r= s t r . Rep l a c e ( ' ется переменная s t r, пробелы (символ ' ' ) заменяются на подчерки­ вания (символ ' _ ' ). Получающийся в результате текст присваивается новым значением переменной s t r . Командой s t r= s t r . S ub s t r i ng ( 2 , 1 2 ) и з текущего текстового значе­ ния, на которое ссылается переменная s t r , извлекается подстрока: текст формируется из 1 2 символов, начиная с позиции с индексом 2 (при этом исходная срока не меняется). Сформированное значение присваивается переменной s t r. Еще один пример извлечения подстроки дает команда s t r= t x t . S ub s t r i ng ( 3 ) . В данном случае из текста, на который ссы­ лается переменная t x t , извлекается подстрока: начиная с позиции с ин­ дексом 2 и до конца текста. Подстрока присваивается значением пере­ менной s t r. Значение переменной txt не меняется. 367 Глава 7 G) Н А ЗАМ ЕТ КУ Метод T r im ( ) , есл и о н вызван без аргументов, позволяет удал ить начал ьные и конеч ные п робел ы в тексте , из которого он вызван ( ме­ тод возвращает новую текстовую строку) . Есл и методу передать сhа r-аргументы ( п роизвол ьное кол ичество ) , то они оп ределя ют символ ы , которые удаля ются из начала и кон ца текста . Есть и другие полезные методы, предназначенные для работы с тек­ стом. Скажем, с помощью метода T o Upp e r ( ) на основе текстового объекта, из которого вызывается метод, формируется текстовая стро­ ка, состоящая из прописных (больших) букв исходного текста. Метод T o Lowe r ( ) возвращает строку, состоящую из строчных (маленьких) букв исходного текста. Можно сказать и проще: метод T oUpp e r ( ) де­ лает все буквы большими, а метод T o L owe r ( ) делает все буквы ма­ ленькими. Нужно только помнить, что исходные текстовые объекты (из которых вызываются методы) не меняются, а речь идет о возвраща­ емом методами тексте. Метод ToCharArray ( ) результатом возвращает ссьшку на символьный массив (тип char [ ] ) из букв исходного текста (текстового объекта, из ко­ торого вызывается метод). Метод полезен в случаях, когда текстовое зна­ чение нужно разбить на отдельные символы. Похожая задача решается с помощью метода Sp l i t ( ) . Метод результатом возвращает текстовый массив (тип s tr ing [ ] ), элементами которого являются подстроки из ис­ ходного текста. Текстовый объект, из которого вызывается метод, разби­ вается на подстроки, причем символы, которые являются разделителями для определения мест разбивки исходного текста, передаются аргумента­ ми методу. Количество таких символьных аргументов произвольно. Если первым аргументом методу передать символьный массив (элементы ко­ торого играют роль разделителей при разбивке строки), а вторым аргу­ ментом указать целое число, то оно будет определять максимальное ко­ личество подстрок, на которые разбивается исходная текстовая строка. Если методу Spl i t ( ) аргументы не переданы, то по умолчанию разде­ лителем считается пробел. Пример использования методов ToUppe r ( ) , ToLower ( ) , To CharAr ray ( ) и Spl i t ( ) представлен в листинге 7.6. [1i!] л истинг 7 .6. Разбиение строк и регистр символов us ing Sys tem; c l a s s SplittingStringDemo { 368 Работа с текстом s tatic vo id Main ( ) { 1 1 Исходная текстовая строка : String tхt="Дорогу осилит идущий" ; 1 1 Отображение исходного текста : Console . WriteLine ( txt ) ; 1 1 Текстовая строка : String s t r ; 1 1 Текст с символами в верхнем регистре : s t r=txt . ToUppe r ( ) ; 1 1 Проверка текстового значения : Console . WriteLine ( s t r ) ; 1 1 Текст с символами в нижнем регистре : s t r=txt . ToLowe r ( ) ; 1 1 Проверка текстового значения : Console . WriteLine ( s t r ) ; 1 1 Переменная для текстового массива : String [ ] words ; 1 1 Разбивка текста на подстроки : words=txt . Sp l i t ( ) ; 1 1 Отображение подстрок : for ( int k=O ; k<words . Length ; k++ ) { Console . WriteLine ( ( k+ 1 ) + " : " +words [ k ] ) ; Console . WriteLine ( ) ; 1 1 Разбивка текста на подстроки : words=txt . Spl i t ( ' у ' , ' и ' ) ; 1 1 Отображение подстрок : for ( int k=O ; k<words . Length ; k++ ) { Console . WriteLine ( ( k+ 1 ) + " : " +words [ k ] ) ; Console . WriteLine ( ) ; 1 1 Разбивка текста на подстроки : 369 Глава 7 words=txt . Sp l i t ( new char [ ] { ' у ' , ' и ' } , 3 ) ; 1 1 Отображение подстрок : for ( int k=O ; k<words . Length ; k++ ) { Console . WriteLine ( ( k+ 1 ) + " : " +words [ k ] ) ; 1 1 Переменная для символьного массива : char [ ] s ymЬ s ; 1 1 Массив и з символов , формирующих текст : s ymbs=txt . ToCharArray ( ) ; 1 1 Отображение символов из массива : for ( int k=O ; k<s ymЬ s . Length ; k++ ) { Console . Write ( s ymЬs [ k ] + " " ) ; Console . WriteLine ( ) ; Результат программы такой. � Результат выполнения п рограммы ( из листинга 7 . 6) Дорогу осилит идущий ДОРОГУ ОСИЛИТ ИДУЩИЙ дорогу осилит идущий 1 : Дорогу 2 : осилит 3 : идущий 1 : Дорог 2: 3: л 4: т 5: д 6: щ 370 ос Работа с текстом 7: й 1 : Дорог 2: ос 3 : лит идущий д о р о г у о с и л и т и д у щ и й В программе для выполнения операций с текстом объявляется тексто­ вая переменная txt со значением " Доро г у о силит идущий " . Данное текстовое значение отображается в консольном окне. Мы используем еще одну текстовую переменную s t r , значение которой присваивает­ ся командой s t r=txt . T oUpp e r ( ) . В результате переменной s t r при­ сваивается текст " ДОРОГУ ОСИЛИТ ИДУЩИЙ " , в котором все буквы в верхнем регистре (большие). После выполнения команды s t r= t x t . T o L o w e r ( ) переменная s t r получает значением текст " дорогу о с и ­ л и т идущий " , состоящий и з строчных (маленьких) букв. Затем в программе объявляется переменная w o r d s для текстового од­ номерного массива. При выполнении команды w o r d s = t x t . Sp l i t ( ) текстовая строка, на которую ссылается переменная t x t , разбивается на подстроки. Создается текстовый массив, количество элементов кото­ рого определяется количеством подстрок, на которые разбивается исход­ ный текст. Подстроки становятся элементами массива. Ссылка на мас­ сив записывается в переменную w o r d s . Поскольку метод S p l i t ( ) вызывается без аргументов, то разделителем при разбивке текста на под­ строки является пробел. При этом сами пробелы в подстроки не вклю­ чаются. С помощью оператора цикла каждая подстрока отображается в отдельной строке в консольном окне. Еще один пример разбивки тек­ ста на строки дается командой w o r d s = t x t . S p l i t ( ' у ' , ' и ' ) . В этом случае исходный текст, на который ссьшается переменная txt, разбива­ ется на подстроки, и индикатором для разбивки на подстроки являются символы ' у ' и ' и ' (сами эти символы в подстроки не включены). Под­ строки формируют текстовый массив, ссьшка на который записывается в переменную w o r d s . Подстроки отображаются в консоли (некоторые подстроки содержат по одной букве). При выполнении команды w o r d s = t x t . S p l i t ( new c h a r [ ] { ' у ' , ' и ' } , 3 ) исходный текст разбивается на подстроки, разделителями являются символы ' у ' и ' и ' , но количество подстрок не превышает 371 Глава 7 значение 3 . То есть текст разбивается на подстроки, но как только их становится 3 , то дальше текст на подстроки не разбивается. � ПОДРОБН ОСТИ Первым аргументом методу Sp l i t ( ) передается анонимный символь­ ный массив, который создается инструкцией new char [ ] { ' у ' , ' и ' } . В программе объявляется переменная символьного массива s ymb s . Ко­ мандой s ymb s = t x t . T o Ch a rAr r a y ( ) на основе текста t x t создает­ ся символьный массив, и ссылка на него записывается в переменную s ymb s . Массив состоит из букв текста, на который ссылается перемен­ ная txt. Элементы массива отображаются в консольном окне через пробел. G) Н А ЗАМ ЕТ КУ При всех переч исленных выше операциях текст, на который ссыла­ ется перемен ная txt , не меняется . � ПОДРОБН ОСТИ Есл и метод ToCharArray ( ) создает символ ьн ы й массив, то метод СоруТо ( ) позволяет скопировать текстовое значение посим вол ь­ но в уже существующи й масси в . Метод статический ( вызывается из класса S t r ing) и не возвращает резул ьтат. Аргументам и методу передаются : • • • • 372 индекс позиции ( целое число) , нач и ная с которой в исходном тек­ сте выполняется коп и рование ; ссыл ка на символ ьн ы й массив (тип char [ ] ) , в который выполня­ ется коп и рован ие ; индекс элемента ( целое ч исло ) , начи ная с которого в массив ко­ п ируются сим вол ы из текста ; кол ичество сим волов ( целое ч исло ) , которые копи руются из тек­ ста в массив. Работа с текстом М етод ToS tring ( ) - Фамилия . - Рюриковичи мы . из к/ф �иван Ва сильевич меняет професси ю» При создании класса в нем можно описать метод T o S t r i n g ( ) . Этот ме­ тод особый. Специфика его в том, что метод автоматически вызывается каждый раз, когда нужно преобразовать объект класса к текстовому фор­ мату. Представим себе ситуацию, что имеется некоторый класс и на ос­ нове этого класса создан объект. И вот этот объект передается аргумен­ том методу W r i t e L i n e ( ) . Если в классе описан метод T o S t r i n g ( ) , то этот метод автоматически будет вызван и текст, который метод вернет результатом, будет передан аргументом методу W r i t e L i n e ( ) вместо ссылки на объект. Аналогичная ситуация имеет место, если в некотором выражении к объекту прибавляется текст (или к тексту прибавляется объект). Если так, то из объекта вызывается метод T o S t r i n g ( ) , а ре­ зультат (текст), который возвращается методом, подставляется в выра­ жение вместо ссылки на объект. � ПОДРОБН ОСТИ Есл и назы вать вещи своими именам и , то здесь мы имеем дело с на­ следованием класса и переоп ределением метода . Эти м темам по­ священа отдел ьная глава кн иги. Пока что м ы при ведем лишь крат­ кую справку, необходимую для понимания принципов реал изаци и п рограм много кода . Наследование позволяет создавать новые ( назы ваются п роизво­ дн ы м и ) классы на основе уже существующих (они называются ба­ зовы м и ) . « П ризом» в таком подходе я вляется то , что открытые чле­ ны исходного класса ( на основе которого создается новый класс) наследуются в создаваемом классе : хотя они в новом классе явно не описан ы , но они там есть. Существует особый класс Obj e c t (из п ространства и м е н S y s t em) , который находится в вершине ие­ рархи и наследования . Л юбые классы, в том числе и оп исываемые нами в п рограм ме, я вля ются прямыми ил и косвенными наследн и ка­ м и класса Ob j e c t . В классе Obj ect есть метод T o S t r i ng ( ) , поэто­ му, даже есл и мы в нашем классе такой метод не описываем , он там есть. Метод вызы вается при необходи мости п реобразовать ссылку на объект к текстовому формату. Другое дел о , что есл и явно метод T o S t r i ng ( ) в классе не описать, то п реобразование выполняется 373 Глава 7 так: вместо ссылки на объект получаем название класса , на осно­ ве которого объект создан. Это « поведение» метода T o S t r i ng ( ) по умолчанию - как оно определено в классе Obj e c t . М ы его можем « переоп редел ить». Описывая в нашем классе метод T o S t r i n g ( ) , м ы как раз выполняем переоп ределение этого метода . Это п роявление общего ун иверсал ьного механизма, который позволяет переопре­ делять в п роизводном классе методы , унаследован ные из базового класса . При переопределении методов испол ьзуется кл ючевое сло­ во ove r r i de . Таким образом, у нас имеется возможность задать способ преобразования ссьmок на объекты пользовательского класса к текстовому формату. Для этого в данном классе следует описать метод T o S t r ing ( ) . Формальные требования, предъявляемые к описанию метода T o S t r ing ( ) , следующие. • Метод описывается с ключевым словом puЬ l i c (метод открытый). • Метод описывается с ключевым словом ove r r i de , что означает пе­ реопределение унаследованного метода. Причина в том, что метод T o S t r i n g ( ) описан в классе Ob j e c t , который находится в вер­ шине иерархии наследования. В пользовательском классе метод T o S t r i n g ( ) наследуется, но мы, описывая его в явном виде, за­ даем новый алгоритм выполнения этого метода. Ключевое слово ove r r i de является признаком того, что речь идет именно о перео­ пределении метода, а не об описании нового метода. • Результатом метод возвращает текст (то есть значение типа S tr i n g ). • Метод, как отмечалось, называется T o S t r i ng ( ) , и у него нет аргу­ ментов. Если все перечисленные условия выполнены, то это означает, что мы переопределили в классе метод T o S t r i n g ( ) и, следовательно, объекты этого класса готовы для использования в различных выражениях в ка­ честве текстовых �заменителей� . Пример переопределения и использо­ вания метода T o S t r i n g ( ) приведен в листинге 7.7. [1i!J Листинг 7. 7 . Переопределение метода ToString() us ing Sys tem; 11 Класс с переопределением метода ToString ( ) : c l a s s MyClas s { 1 1 Целочисленное поле : 374 Работа с текстом puЫ ic int num ; 1 1 Символьное поле : puЫ ic char s ymЬ ; 1 1 Конструктор : puЫ ic MyCl a s s ( int n , char s ) { 1 1 Присваивание значений полям : num=n ; s ymb=s ; 1 1 Отображение сообщения о создании объекта . 1 1 Неявно вызывается метод ToString ( ) : Console . WriteLine ( " Coздaн новый oбъeкт \ n " +thi s ) ; 1 1 Переопределение метода ToString ( ) : puЫ ic override String ToString ( ) { 1 1 Локальная текстовая переменная : String tхt="Числовое поле : " +num; tхt+=" \nСимвольное поле : " + s ymЬ ; 1 1 Результат метода : return txt ; 1 1 Класс с главным методом программы : c l a s s ToStringDemo { 1 1 Главный метод : s tatic vo id Main ( ) { 1 1 Создание нового объекта : MyC l a s s obj =new MyC l a s s ( l O O , ' А ' ) ; 1 1 Новые значения полей объекта : obj . num=2 0 0 ; obj . symЬ= ' B ' ; Console . WriteLine ( " Hoвыe значения полей объекта " ) ; 1 1 Отображение значений полей объекта . 375 Глава 7 1 1 Неявно вызывается метод ToString ( ) : Console . WriteLine ( obj ) ; 1 1 Разбивка текста с описанием объекта на подстроки . 1 1 Метод ToString ( ) вызывается в явном виде : String [ ] s tr=obj . ToSt ring ( ) . Split ( ' \n ' ) ; Console . WriteLine ( "Явный вызов метода ToString ( ) " ) ; 1 1 Отображение подстрок, содержащих значения 11 полей объекта : for ( int k=O ; k<s t r . Length ; k++ ) { Console . WriteLine ( " [ * " + s t r [ k ] + " * ] " ) ; Эта программа дает такой результат. � Результат выполнения программы (из листинга 7. 7) Создан новый объект Числовое поле : 1 0 0 Символьное поле : А Новые значения полей объекта Числовое поле : 2 0 0 Символьное поле : В Явный вызов метода ToString ( ) [ * Числовое поле : 2 0 0 * ] [ * Символь ное поле : В * ] В программе описан класс MyC l a s s . У этого класса есть два открытых поля: целочисленное num и символьное s ymЬ. В классе переопределяет­ ся метод T o S t r i n g ( ) . Описывается метод следующим образом. В теле метода объявляется локальная текстовая переменная t x t . Начальное значение переменной формируется как объединение текста " Число ­ в о е поле : " и значения поля num. Затем к текущему значению пере­ менной txt дописывается текст " \ nСимв оль ное поле : " и значение поля s ymЬ. Таким образом, переменная txt содержит текст со значениями 376 Работа с текстом полей объекта (подразумевается объект, из которого будет вызываться ме­ тод T o S t ring ( ) ). После того как значение переменной txt сформирова­ но, командой return txt это значение возвращается результатом метода. G) Н А ЗАМ ЕТ КУ Текст, возвращаем ы й в качестве резул ьтата методом T o S t r i n g ( ) , содержит сим вол перехода к новой строке ' \ n ' . Это обстоятел ь­ ство впоследстви и испол ьзуется при разбиении текста на подстроки . В классе также описан конструктор с двумя аргументами, которые опре­ деляют значения полей создаваемого объекта. Еще конструктор содержит команду C on s o l e . W r i t e L i n e ( " С о здан новый oбъeкт \ n " +th i s ) . Этой командой в консольном окне отображается сообщение о создании нового объекта и значении полей этого объекта. Причем при выполнении команды вызывается метод T o S t r i ng ( ) (метод вызывается, хотя явной команды вызова этого метода нет). Дело в том, что аргументом методу W r i t e L i n e ( ) передается выражение, представляющее собой сумму тек­ стового литерала и ссьтки thi s , обозначающей созданный объект. Таким образом, мы имеем дело с выражением, в котором к тексту прибавляется ссылка на объект. Это тот случай, когда вызывается метод T o S t r i ng ( ) и полученный в результате текст подставляется вместо ссылки thi s . В главном методе программы командой M y C l a s s o b j = n e w My C l a s s ( 1 О О , ' А ' ) создается объект. При его создании (вследствие вызова конструктора) отображается сообщение со значениями полей соз­ данного объекта. Затем полям объекта присваиваются новые значения. Ко­ мандой Con s o l e . Wri teLine ( obj ) отображаются новые значения полей объекта obj . Здесь для преобразования объекта obj к текстовому форма­ ту вызывается метод T o S t r ing ( ) . Но этот метод можно вызьmать и в яв­ ном виде, как обычный метод. Пример такой ситуации дается инструкцией obj . T o S t r ing ( ) . Spl i t ( ' \ n ' ) . Здесь из объекта obj вызьmается метод ToString ( ) . Результатом является текстовая строка. Из объекта этой стро­ ки вызьmается метод Spl i t ( ) с аргументом ' \ n ' . В результате создается массив, состоящий из подстрок, на которые разбивается исходный текст (ре­ зультат инструкции obj . T o S t r ing ( ) ). Индикатором для разбивки текста на подстроки является символ ' \ n ' . Такой символ в тексте, возвращаемом методом ToS tr ing ( ) , всего один, поэтому текст разбивается на две подстро­ ки. Ссьтка на массив с подстроками записывается в переменную s tr. С по­ мощью оператора цикла значения массива s tr отображаются в консольном окне, заключенные в комбинацию из квадратных скобок и звездочек. 377 Глава 7 Р ез ю ме Граф - добрый. Мухи н е обидит. Сатрап - он и есть сатрап. из к/ф -« 0 бедном гусаре за.м олвите слово» • Текстовые значения реализуются в виде объектов класса S t r i n g и з пространства имен S y s t em. Идентификатор s t r i n g является псевдонимом для инструкции S y s tem . S t r i ng. Текстовая перемен­ ная ссылается на объект, содержащий текстовое значение. • Текстовые литералы реализуются в виде объектов класса S t r i ng. • Создать текстовый объект можно, присвоив текстовой переменной значением литерал. Также имеется возможность создать текстовый объект на основе символьного массива. Существуют и некоторые иные способы создания текстовых объектов. • Размер текста (количество символов в тексте) можно узнать с по­ мощью свойства Length, а для обращения к отдельным символам в тексте текстовый объект индексируется как массив. Индексация символов в тексте начинается с нуля. • После создания текстового объекта его содержимое изменить нель­ зя. Изменения в текст вносятся путем создания новых текстовых объектов, а ссылка на вновь созданный объект записывается в тек­ стовую переменную. • Сравнивать текстовые строки на предмет совпадения или несовпа­ дения можно с помощью операторов == и ! =. Также для этой цели может использоваться метод E qu a l s ( ) . • Существует много методов, предназначенных для выполнения раз­ личных операций с текстовыми значениями. В частности, методы позволяют: выполнять поиск символов и подстрок в строке, разби­ вать строку на подстроки, извлекать подстроку из текста, разбивать текст на символы, выполнять удаление подстрок, замену символов и подстрок и многое другое. При выполнении операций с текстом новая текстовая строка является результатом операции, а исходный текст при этом не меняется. • Описав в классе метод T o S t r i n g ( ) , можно задать способ преобра­ зования объектов этого класса к текстовому формату. 378 Работа с текстом Задания для самостоятел ьной работы Мы месяц п о галактике � маму � попоем и плапета у пас в кармане. из к/ф -«Кин-дза-дза» 1 . Напишите программу, в которой есть статический метод. Аргументом методу передается текстовое значение. Результатом метод возвращает текст, в котором, по сравнению с текстом-аргументом, между символами вставлены пробелы. 2. Напишите программу, в которой есть статический метод, возвращаю­ щий результатом текстовое значение и получающий аргументом текст. Результат метода - это переданный аргументом текст, в котором слова следуют в обратном порядке. Словами считать блоки текста, разделен­ ные пробелами. 3. Напишите программу со статическим методом для сравнения тексто­ вых строк. Строки на предмет совпадения сравниваются посимвольно. Правило сравнения символов такое: два символа считаются одинаковы­ ми, если их коды отличаются не больше, чем на единицу. Текстовые стро­ ки совпадают, если у них совпадают символы (в указанном выше смысле). 4 . Напишите программу со статическим методом, который выполняет сравнение текстовых строк. Текстовые строки сравниваются следующим образом: для каждого текстового значения определяется набор разных букв, входящих в текст (при этом количество вхождений буквы в текст значения не имеет). Текстовые строки считаются равными, если они со­ держат одинаковые наборы букв. 5. Напишите программу со статическим методом, определяющим пози­ ции, на которых в тексте находится определенный символ. Аргументами методу передаются текст и символ. Результатом метод возвращает цело­ численный массив, значения элементов которого - это индексы пози­ ций, на которых символ (второй аргумент) находится в тексте (первый аргумент). Если символ в тексте не встречается, то метод результатом возвращает массив из одного элемента, значение которого равно - 1 . 6 . Напишите программу со статическим методом, аргументом которому передается текст, а результатом возвращается символьный массив, со­ стоящий из букв (без учета пробелов и знаков препинания), из которых 379 Глава 7 состоит текст. Если буква несколько раз встречается в тексте, в массиве она представлена одним элементом. Буквы в массиве-результате долж­ ны быть отсортированы в алфавитном порядке. 7 . Напишите программу со статическим методом, который эмулирует работу метода S ub s t r i n g ( ) . Аргументами статическому методу пере­ дается текст и два целочисленных аргумента. Результатом метод воз­ вращает текстовую строку, которая состоит из символов текста (первый аргумент), начиная с позиции с индексом, определяемым вторым аргу­ ментом метода. Третий аргумент статического метода определяет коли­ чество символов, которые включаются в подстроку. 8. Напишите программу с классом, у которого есть текстовое поле. Зна­ чение текстовому полю присваивается при создании объекта класса. Также в классе должен быть метод, позволяющий вставить подстроку в текст из текстового поля. Аргументами методу передается подстрока для вставки в текст, а также индекс позиции, начиная с которой выпол­ няется вставка. Переопределить в классе метод T o S t r i n g ( ) так, чтобы он возвращал значением текст из текстового поля. 9. Напишите программу с классом, в котором есть текстовое поле и сим­ вольное поле. Значение полям присваивается при создании объекта класса. В классе должен быть метод, возвращающий результатом мас­ сив из текстовых строк. Такие строки получаются разбиением на под­ строки значения текстового поля. Символ, являющийся индикатором для разбивки на подстроки, определяется значением символьного поля. Переопределить в классе метод T o S t r i n g ( ) так, чтобы он возвращал текст со значениями полей объекта и подстроки, на которые разбивается текст из текстового поля. 1 0 . Напишите программу с классом, у которого есть поле, являющее­ ся ссылкой на целочисленный массив. При создании объекта массив заполняется случайными числами. Переопределите в классе метод T o S t r i ng ( ) так, чтобы метод возвращал текстовую строку со значени­ ями элементов массива. Также строка должна содержать информацию о количестве элементов массива и среднем значении для элементов мас­ сива (сумма значений элементов, деленная на количество элементов). Гл ава 8 П Е Р Е Г РУЗКА О П Е РАТО РОВ От пальца не прикуривают, врать не буду. А искры из глаз летят. из к/ф � Формула лю бви» Эта глава посвящена перегрузке операторов. Далее мы будем обсуждать вопросы, связанные с тем, как действие операторов �доопределяется» для случаев, когда операндом в выражении является объект пользова­ тельского класса. В частности, нас будут интересовать такие темы: • общие принципы описания операторных методов ; • перегрузка арифметических операторов ; • особенности перегрузки операторов сравнения ; • перегрузка логических операторов ; • правила перегрузки операторов приведения типа. Рассмотрим мы и другие смежные вопросы, связанные с перегрузкой операторов . Операто рные методы Желтые штаны ! Два раза 4Ку » ! из к/ф �кин-дза-дза» Как мы уже знаем, в языке С# есть много различных операторов, ко­ торые позволяют выполнять всевозможные операции со значения­ ми разных типов. Например, мы можем с помощью оператора �плюс» + сложить два числа и получить в результате число. Мы можем �сло­ жить» с помощью того же оператора два текстовых значения и получить в результате текст. Существуют и другие ситуации, когда мы можем 381 Глава В использовать оператор + (или какой-нибудь иной оператор). Все они предопределены: то есть случаи, когда мы можем использовать тот или иной оператор, определены заранее и зависят от типа операндов, уча­ ствующих в операции. Другими словами, имеется ограниченное коли­ чество ситуаций, в которых могут использоваться операторы языка С#, а результат выполнения операций зависит от типа операндов. Напри­ мер, мы можем к тексту прибавить число, но не можем текст умножить на число. В этом смысле наши возможности ограничены и строго детер­ минированы. Но есть в языке С# очень мощный и эффективный меха­ низм, который связан с перегрузкой операторов. Основная идея в том, что для объектов пользовательских классов (то есть классов, которые мы описываем в программе) доопределяется действие встроенных опе­ раторов языка С#. Скажем, описывая класс, возможно путем неслож­ ных манипуляций добиться того, что объекты этого класса можно будет складывать, как числа. Или, например, к объекту класса можно будет прибавить число, и так далее. Смысл каждой такой операции определя­ ем мы, когда описываем класс. Чтобы определить способ применения того или иного оператора к объ­ ектам класса, в классе описывается специальный метод, который назы­ вается операторным. Это метод, но немножко специфический. Название операторного метода состоит из ключевого слова ope r a t o r и симво­ ла оператора, для которого определяется способ применения к объек­ там класса. Например, если мы хотим определить операцию сложения для объектов класса с помощью оператора +, то в классе нужно описать операторный метод с названием ope r a t o r + . Если в классе описан опе­ раторный метод с названием ope ra t o r * , то для объектов класса опре­ делена операция �умножения� : к таким объектам можно применять опе­ ратор умножения * . Ну и так далее. G) Н А ЗАМ ЕТ КУ Перегружать можно не все операторы . Доступные для перегрузки операторы и особенности перегрузки некоторых операторов обсуж­ даются в этой главе. Но мало знать название операторного метода. Есть некоторые требова­ ния, которым должны соответствовать операторные методы. Вот они. • 382 Операторный метод описывается в классе, для объектов которого определяется действие соответствующего оператора. Пере груз ка о перато р ов • Операторный метод описывается как статический (то есть с ключе­ вым словом s t a t i c ) и открытый (с ключевым словом puЬ l i c ). • Операторный метод должен возвращать результат (то есть иденти­ фикатором типа результата не может быть ключевое слово vo i d). Результат операторного метода - это значение выражения, в кото­ рое входят соответствующий оператор и операнды, отождествляе­ мые с аргументами операторного метода. • Аргументы операторного метода отождествляются с операндами вы­ ражения, обрабатываемого операторным методом. Аргумент может быть один, если речь идет об операторном методе для унарного опе­ ратора, и аргументов может быть два, если речь идет об операторном методе для бинарного оператора. Как минимум один аргумент опе­ раторного метода должен быть объектом того класса, в котором опе­ раторный метод описан. � ПОДРОБН ОСТИ Допусти м , и меется класс , в котором описан операторный метод с названием ope rator+ . А еще есть выражение вида А+В , в котором хотя бы один из операндов А и В я вляется объектом упомя нутого клас­ са. Тогда для выч исления результата вы ражения А+В вызывается опе­ раторный метод с названием ope ra tor+ . При этом операнд А интер­ прети руется как первый аргумент операторного метода, а операнд В передается вторым аргументом операторному методу. Выше реч ь шла о бинарном операторе + . У бинарного оператора два операнда (в вы ражении А+В операнды - это А и В ) . Но бы вают опе­ раторы унарные, у которых оди н операнд. Примером унарного опе­ ратора может быть и н кремент + + ил и декремент - - . Есл и обрабаты­ вается выражение вида А++ и А я вляется объектом класса , в котором оп исан операторный метод с названием ope r a t o r + + , то именно этот метод будет вызван для обработки вы ражения А++ . П оскол ьку операторный метод определен для унарного оператора, то у метода всего оди н аргумент. Эти м аргументом методу будет передан опе­ ранд А. В языке С# перегружаться могут многие операторы, но не все. Напри­ мер, среди бинарных операторов для перегрузки доступны такие: +, - , * , / , % , & , [ , л , < < и > > . Также можно перегружать следующие унар ­ ные операторы: +, - , ! , - , + + , - - , t ru e и f a l s e . Несложно заметить, что выше перечислены в основном арифметические и побитовые операторы. 383 Глава В � ПОДРОБН ОСТИ Операторы « ПЛЮС» + и « м и нус» - упомя нуты и среди бинарных, и сре­ ди унарных. Ошибки здесь нет, поскол ьку дан ные операторы могут испол ьзоваться и как унарные, и как бинарн ые. В вы ражен иях вида А+В и А-В операторы испол ьзуются как бинарные. Но еще операто­ ры « пл юс» и «минус» могут испол ьзоваться в следующем формате : +А ил и -А. Здесь речь идет об унарных операторах. С унарным опера­ тором «минус» мы стал киваемся каждый раз, когда записываем от­ рицател ьные числа: унарный оператор «минус» служит инди катором отрицател ьного числа. Унарный оператор « ПЛЮС» может испол ьзо­ ваться как индикатор положительного ч исла. Но на п рактике он обыч ­ но не испол ьзуется . Тем не менее, есл и мы и меем дело с объектами , то вполне можем испол ьзовать с этими объектами н е тол ько бинар­ ные, но и унарные операторы « ПЛЮС» и «минус» . Для этого достаточно в соответствующем классе описать операторные методы для указан ­ н ы х операторов. Отдел ьного внимания заслужи вают операторы t rue и fa l s e . Реч ь идет о том , что можно оп редел ить способ п роверки объекта на п ред­ мет «исти нности » или «ложности » . Объекты , для которых оп ределе­ на такая проверка ( перегружены операторы t rue и fa l s e ) , могут испол ьзоваться в качестве условий в условных операторах и опера­ торах цикла. Операторы t rue и fa l s e перегружаются тол ько в паре : ил и оба, или ни одного. Перегрузке этих операторов посвя щен от­ дел ьный раздел в главе. Перегружать можно и операторы сравнения, но такие операторы долж­ ны перегружаться парами: == и ! =, < и >, <= и >=. Также в языке С# есть специальные операторы приведения типа, которые позволяют задавать закон преобразования объекта одного типа в объект другого типа. В поль­ зовательском классе можно описать оператор явного приведения типа ( ехр 1 i с i t-форма) или неявного приведения типа ( imp 1 i c i t-форма ). Не перегружаются сокращенные операторы присваивания, такие как +=, - = , * = , / = , % = , & =, 1 =, л =, < < = и >>=. Но при этом мы можем так пе­ реопределить базовые операторы (имеются в виду бинарные операторы +, -, * , / , % , &, 1 , л , < < и >>), что удастся использовать и сокращенные формы оператора присваивания. Похожая ситуация имеет место с ло­ гическими операторами & & и 1 1 они не перегружаются. Однако если �правильным образом� перегрузить операторы & и 1 , то в программном коде к операндам, являющимся объектами класса с перегруженными операторами & и 1 , можно будет применять и операторы & & и 1 1 . - 384 Пере груз ка о перато р ов G) Н А ЗАМ ЕТ КУ Ситуацию с «правильной перегрузкой» операторов & и 1 (для возмож­ ности испол ьзования операторов & & и 1 1 ) мы рассмотрим отдел ьно. Есть операторы, которые не перегружаются. Среди них = (присваивание), . (оператор �точка» ), ? : (тернарный оператор), - > (оператор �стрелка», используемый при работе с указателями на структуры), = > (оператор �стрелка», используемый при описании лямбда-выражений), new (опера­ тор создания нового объекта) и ряд других. В том числе не перегружается оператор [ ] �квадратные скобки» (в общем случае это оператор, который позволяет индексировать объекты - то есть указывать в квадратных скоб­ ках после имени объекта индекс или индексы). Вместе с тем в языке С# в классах можно определять индексаторы. Через механизм создания ин­ дексаторов реализуется возможность индексировать объекты. G) Н А ЗАМ ЕТ КУ Перегрузка операторов подразумевает, что для одного бинарного оператора может описы ваться нескол ько операторных методов , которые отл ичаются ти пом аргументов и , соответственно, которые обрабаты вают вы ражения ( на основе данного оператора) с операн­ дам и разного типа. Далее мы более подробно и на конкретных примерах рассмотрим спосо­ бы перегрузки разных групп операторов. П ерегруз ка арифмети ч еских и побитовых операторов - Герой ! А в корнеты, господип губернатор, он разжалован за дуэль. - Дуэль был из-за дамы ? - Ну разумеется , сударыпя. Из-за двух ! из к/ф -« 0 бедном гусаре замолвите слово» Как иллюстрацию к перегрузке операторов мы рассмотрим небольшой и очень простой пример, в котором выполняется перегрузка оператора сложения для объектов пользовательского класса. Пример представлен в листинге 8. 1 . 385 Глава В [1i!] л истинг 8. 1 . Знакомство с перегрузкой операторов us ing Sys tem; 11 Класс с перегрузкой оператора сложения : c l a s s MyClas s { 1 1 Целочисленное поле : puЫ ic int numbe r ; 1 1 Конструктор с целочисленным аргументом : puЫ ic MyCla s s ( int n ) { 1 1 Присваивание значения полю : numbe r=n ; 1 1 Операторный метод для перегрузки оператора сложения : puЫ ic static int operator+ (MyC l a s s а , MyC l a s s Ь ) { 1 1 Локаль ная целочисленная переменная : int m=a . numbe r+b . numЬer; 1 1 Резуль тат метода : return m; 1 1 Класс с главным методом : c l a s s OverloadingOpe ratorPlus Demo { 1 1 Главный метод : static vo id Main ( ) { 11 Создание объектов класса MyCl as s : MyC l a s s A=new MyC l a s s ( l O O ) ; MyC l a s s B=new MyC l a s s ( 2 0 0 ) ; 1 1 Целочисленная переменная : int num; 11 Вычисление суммы объектов : num=A+B ; 1 1 Отображение резуль тата вычисления суммы объектов : Console . WriteLine ( "A+B= " + num) ; 386 Пере груз ка о перато р ов Результат выполнения программы будет таким. � Результат выполнения программы ( из листинга 8. 1 ) А+В=З О О Проанализируем программный код примера. В первую очередь нас бу­ дет интересовать класс MyC l a s s , поскольку в нем есть операторный ме­ тод, с помощью которого выполняется перегрузка оператора сложения для объектов класса. У класса есть целочисленное поле numb e r , а также конструктор с од­ ним аргументом (который определяет значение поля). Но нас интере­ сует программный код с описанием операторного метода ope r a t o r + ( ) (комментарии в программном коде удалены) : puЫ ic static int operator+ ( MyC l a s s а , MyC l a s s Ь ) { int m=a . numЬer+b . numЬe r ; return m ; О чем свидетельствует этот программный код? Атрибуты р uЫ i с и s t a t i c являются обязательными. Название метода ope r a t o r + озна­ чает, что метод соответствует оператору �плюс� +. Аргументов у метода два - то есть речь идет о бинарной операции. Оба аргумента описаны как такие, что относятся к классу MyC l a s s . Следовательно, оператор­ ный метод будет вызываться для вычисления значения выражений вида А+ В, в котором оба операнда А и В являются объектами класса MyC l a s s . Идентификатором типа результата для операторного метода указано ключевое слово i n t . Поэтому результатом выражения вида А+В бу­ дет целое число. Чтобы понять, какое именно, необходимо проанали­ зировать команды в теле метода. Там их всего две. Сначала командой m=a . numbe r+b . numb e r вычисляется сумма значений полей складыва­ емых объектов, и результат записывается в переменную m. Затем значе­ ние переменной m возвращается результатом метода. Все это означает, что если вычисляется выражение вида А+ В, в котором операнды А и В являются объектами класса MyC l a s s , то результатом такого выраже­ ния является сумма значений полей numb e r складываемых объектов. В том, что это действительно так, убеждаемся при выполнении главного 387 Глава В метода. Там создаются два объекта А и В класса MyC l a s s . Значения полей объектов равны 1 О О и 2 О О соответственно (значение поля определяется ар­ гументом, переданным конструктору при создании объекта). При выполне­ нии команды n um=A +В целочисленной переменной num значением присва­ ивается результат выражения А+В. Проверка показывает, что переменная n um получает значение 3 О О (сумма чисел 1 О О и 2 О О ), как и должно быть. Мы могли переопределить оператор сложения и по-другому, как в плане операндов (аргументов метода), так и в плане результата метода. G) Н А ЗАМ ЕТ КУ Способ, которым м ы оп редел или операторный метод в рассмотрен­ ном выше примере, подразумевает, что скл адываются два объекта класса MyC l a s s . Но мы при этом не сможем выч исл ить, нап ример, сум му объекта и ч исла или сум му числа и объекта . Чтобы такие опе­ раци и стал и «зако н н ы м и » , необходимо описать соответствующие версии операторного метода. Еще одна версия программы с перегрузкой оператора �плюс» представ­ лена в листинге 8.2. На этот раз в классе МуС 1 а s s представлено несколь­ ко операторных методов с названием ope r a t o r + . � Л истинг 8 . 2 . Еще один способ перегрузки оператора ссплюс» us ing Sys tem; 11 Класс с перегрузкой оператора " плюс " : c l a s s MyClas s { 1 1 Целочисленное поле : puЫ ic int numbe r ; 1 1 Конструктор с одним аргументом : puЫ ic MyCla s s ( int n ) { 1 1 Присваивание значения полю : numbe r=n ; 1 1 Переопределение метода ToString ( ) : puЫ ic ove rride String ToString ( ) { 1 1 Резуль тат метода : return " Поле numЬer : " +numЬ e r ; 388 Пере груз ка о перато р ов 1 1 Операторный метод для вычисления суммы двух объектов : puЫ ic static MyC l a s s ope rator+ ( MyC l a s s а , MyC l a s s Ь ) { 1 1 Целочисленная локаль ная переменная : int m=a . numbe r+b . numЬer; 1 1 Создание объекта класса : MyC l a s s t=new MyC l a s s (m) ; 1 1 Результат метода : return t ; 1 1 Операторный метод для вычисления суммы объекта 11 и целого числа : puЫ ic static MyC l a s s ope rator+ ( MyC l a s s а , int х ) { 1 1 Целочисленная локаль ная переменная : int m=a . numbe r+x ; 1 1 Результат метода : return new MyCl as s (m) ; 1 1 Операторный метод для вычисления суммы целого 11 числа и объекта : puЫ ic static MyC l a s s ope rator+ ( int х , MyC l a s s а ) { 1 1 Результат метода : return а+х ; 1 1 Операторный метод для унарного оператора " плюс " : puЫ ic static int operator+ (MyC l a s s а ) { 1 1 Результат метода : return a . numЬer; 1 1 Класс с главным методом : c l a s s MoreOverl oadingPlu s Demo { 389 Глава В 1 1 Главный метод : static vo id Main ( ) { 1 1 Создание объектов : MyC l a s s A=new MyC l a s s ( l O O ) ; MyC l a s s B=new MyC l a s s ( 2 0 0 ) ; 1 1 Вычисление суммы объектов : MyC l a s s С=А+В ; 1 1 Проверка резуль тата : Console . WriteLine (A) ; Console . WriteLine ( B ) ; Console . WriteLine ( C ) ; 1 1 Вычисление суммы объекта и целого числа : C=A+ l ; 1 1 Проверка резуль тата : Console . WriteLine ( C ) ; 1 1 Вычисление суммы целого числа и объекта : C=l O +A ; 1 1 Проверка резуль тата : Console . WriteLine ( C ) ; Console . Write ( " Yнapный оператор : " ) ; 1 1 Исполь зуется унарный " плюс " : Console . WriteLine ( + C ) ; int num= ( +A ) + ( +B) ; 1 1 Проверка резуль тата : Console . WriteLine ( " Сумма чисел : " +num) ; При выполнении программы получаем такой результат. � Результат выполнения п рограм мы (из листинга 8 . 2 ) Поле numЬer : 1 0 0 Поле numЬer : 2 0 0 390 Пере груз ка о перато р ов Поле numЬe r : 3 0 0 Поле numЬe r : 1 0 1 Поле numЬe r : 1 1 0 Унарный оператор : 1 1 0 Сумма чисел : 3 0 0 В этой программе (по сравнению с предыдущим примером) класс MyC l a s s немного �расширился� . Кроме целочисленного поля numЬ e r и конструктора с одним аргументом, у класса MyC l a s s теперь есть пере­ определенный метод T o S t r i n g ( ) . Результатом метод возвращает текст со значением поля numb e r объекта класса. Но нас конечно же интере­ суют в первую очередь операторные методы. Их в классе четыре. Все они называются o p e r a t o r + . Отличаются методы типом и количеством аргументов, а также типом возвращаемого результата. Версия метода с двумя аргументами, являющимися объектами класса МуС 1 а s s, резуль­ татом возвращает объект класса MyC l a s s . То есть теперь, если мы будем складывать два объекта класса MyC l a s s , то в результате получим новый объект класса MyC l a s s . G) Н А ЗАМ ЕТ КУ В предыдущей верси и п рограммы из листинга 8 . 1 при сложении двух объектов класса MyC l a s s резул ьтатом получал и целое числ о . В теле метода командой m= a . numЬe r+b . numbe r вычисляется сумма полей numbe r объектов а и Ь, переданных аргументами методу (то есть операндов выражения, которое будет обрабатываться этим операторным методом). После этого командой MyC l a s s t=new MyC l a s s ( m ) созда­ ется новый объект класса МуС 1 а s s. Значение поля n umЬe r этого объек­ та равно сумме значений полей объектов а и Ь. Созданный таким обра­ зом объект возвращается результатом операторного метода. Еще одна версия операторного метода с названием ope ra t o r + подразу­ мевает, что аргументами (операндами) являются объект класса МуС 1 а s s и целое число. � ПОДРОБН ОСТИ Порядок аргументов в операторном методе и , соответственно, по­ рядок операндов в вы ражении с перегружаем ы м оператором име­ ет значен ие. Ситуаци и , когда к объекту прибавляется ч исло и когда 391 Глава 8 к числу прибавляется объект, обрабатываются разными версиями операторного метода для оператора сложения . Поэтому в п рограм ­ ме мы отдел ьно описы ваем версию операторного метода , в которой первый аргумент - объект, а второй - целое число, а в еще одной версии операторного метода первый объект - целое число, а вто­ рой аргумент - объект. М ы оп ределяем резул ьтат сложения числа и объекта так же , как и резул ьтат сложения объекта и числа. Но в об­ щем случае резул ьтат таких операций может быть разл и ч н ы м . Результатом выражения, в котором к объекту класса MyC l a s s прибав­ ляется целое число, является объект класса MyC l a s s . Его поле numЬ e r равно сумме значений поля n umЬe r первого аргумента (объект а) и вто­ рого целочисленного аргумента (обозначен как х ). Поэтому в теле мето­ да командой m=a . numЬ e r + x вычисляется сумма значения поля объекта и целого числа, а затем результатом метода возвращается объект, создан­ ный инструкцией new MyC l a s s ( m ) . � ПОДРОБН ОСТИ И нструкцией new MyC l a s s ( m ) создается новый объект, но ссылка на него в объектную переменную не зап исы вается . Такие объекты на­ зы вают анон и м н ы м и . В данном случае вы ражение new MyC l a s s ( m ) указано после инструкци и return . При выполнении инструкци и new MyC l a s s ( m ) , как отмечалось выше, создается новый объект. А ре­ зультатом инструкции я вляется ссылка на этот созданный объект. Поэтому получается , что операторный метод резул ьтатом возвра­ щает ссылку на созданный объект, что нам и нужно . В принципе, м ы могл и бы объя вить объектную перемен ную класса MyC l a s s , записать в нее ссылку на созданный объект, а затем ука­ зать дан ную объектную перемен ную в rеturn-и нструкци и . Так м ы поступ или в версии операторного метода , в которой о б а аргумен­ та - объекты класса MyC l a s s . В данном случае м ы обошл ись без вспомогател ьной объектной перемен ной. Еще в программе описана версия операторного метода для оператора �плюс�, в которой первый аргумент целочисленный (обозначен как х), а второй (обозначен как а) является объектом класса MyC l a s s . В теле этой версии метода всего одна команда r e t u rn а + х . И это примеча­ тельный момент. Результатом метода возвращается сумма объекта и це­ лого числа. Выражение а +х имеет смысл, только если в классе описана версия операторного метода для оператора �плюс�, в которой первый аргумент является объектом, а второй аргумент - целое число. Такая 392 Пере груз ка о перато р ов версия метода в классе MyC l a s s описана. Именно она вызывается для вычисления значения выражения а +х. G) Н А ЗАМ ЕТ КУ Образно вы ражаясь, операторный метод для выч исления сум м ы целого ч исла и объекта м ы оп ределяем так: п р и вычислении сум м ы целого числа и объекта нужно выч ислить сум му этого объекта и це­ лого ч исла . В этом смысле операция выч исления сум м ы целого ч ис­ ла и объекта я вляется ком мутати вной : не и меет значения , в каком порядке указаны операнды . Но это не есть общее правило. П росто мы так оп редел или п равила сложения объектов и чисел . В принци ­ пе, м ы могл и оп редел ить и х иначе, и резул ьтат сложения объекта и ч исла отличался бы (не тол ько по значен и ю , но и по типу резул ьта­ та) от резул ьтата сложения целого ч исла и объекта . В классе MyC l a s s есть версия операторного метода ope r a t o r + ( ) с од­ ним аргументом (объект класса MyC l a s s ). Эта версия операторного метода перегружает унарный оператор �плюс�. Результат выражения с унарным оператором �плюс� является целым числом - значением поля numЬ e r объекта-операнда. В главном методе программы проверяется функциональность описан­ ных в классе MyC l a s s операторных методов. Там создается два объекта ( А и в) класса MyC l a s s со значениями соответственно 1 0 0 и 2 0 0 для поля numЬ e r . Объектная переменная С класса MyC l a s s значение полу­ чает при выполнении команды С=А +В. То есть это сумма двух объектов. Результатом является новый объект, поле numЬ e r которого равно 3 0 0 (сумма полей объектов А и в). При выполнении команды C=A+ l полу­ чаем новый объект (ссылка на который записывается в переменную с), и значение поля numЬ e r этого объекта равно 1 0 1 (сумма поля numbe r объекта А и второго числового операнда 1 ). Наконец, при выполнении команды C = l O +A создается еще один объект со значением поля 1 1 0 . Ссылка на объект записывается в переменную С. Значение поля вычис­ ляется как сумма первого операнда 1 О и значения поля n umЬe r объекта А. Значением выражения +С является целое число - это значение поля numb e r объекта С. Здесь при вычислении выражения +С вызывает­ ся версия операторного метода ope r a t o r + ( ) для унарного оператора �плюс� . Поэтому при выполнении команды C on s o l e . W r i t e L i n e ( + С ) в консольное окно выводится значение поля numЬ e r объекта С . А вот при выполнении команды C o n s o l e . W r i t e L i n e ( С ) в консоли 393 Глава 8 отображается текст, возвращаемый методом T o S t r i n g ( ) (то есть здесь речь идет о преобразовании объекта С к текстовому формату). Аналогично, значением выражения +А есть значение поля numЬ e r объ­ екта А, а значением выражения +В есть значение поля n umЬe r объекта В. Поэтому при выполнении команды n um= ( +А ) + ( +В ) целочисленная пе­ ременная num получает значением сумму полей объектов А и В. В завершение раздела рассмотрим еще один пример, в котором исполь­ зуется перегрузка разных арифметических и побитовых операторов. Программа представлена в листинге 8.3. � Л истинг 8.3. Перегрузка арифметических и по б итовых операторов us ing Sys tem; / / Класс с перегрузкой арифметических и / / побитовых операторов : c l a s s MyData { private int code ; / / Закрытое целочисленное поле private char s ymЬ ; / / Закрытое символьное поле private s tring text ; / / Закрытое текстовое поле / / Конструктор с тремя аргументами : puЫ ic MyData ( int n , char s , string t ) { code=n ; / / Значение целочисленного поля s ymb=s ; / / Значение символьного поля text=t ; / / Значение текстового поля / / Перегрузка метода ToString ( ) : puЫ ic override s t ring ToString ( ) { / / Локаль ная текстовая переменная : s tring tхt=" Поля объекта : \n " ; tхt+="Числовое поле : " +code + " \ n " ; tхt+=" Символьное поле : \ "' + s ymЬ+ " \ ' \n " ; tхt+=" Текстовое поле : \ " " +text+ " \ " \ n " ; txt+=" - - - - - - - - - - - - - - - - - - - - - - - 11 ; / / Резуль тат метода : 394 Пере груз ка о перато р ов return txt ; 1 1 Операторный метод для вычисления суммы объекта 11 и целого числа : puЫ ic static MyData operator+ (MyData obj , int n ) { return new MyData ( + obj + n , - obj , Nobj ) ; 1 1 Операторный метод для вычисления разности объекта 11 и целого числа : puЫ ic static MyData operator - (MyData obj , int n ) { return new MyData ( + obj - n , - obj , Nobj ) ; 1 1 Операторный метод для вычисления суммы целого 11 числа и объекта : puЫ ic static int operator+ ( int n , MyData obj ) { return n+ ( +obj ) ; 1 1 Операторный метод для вычисления разности целого 11 числа и объекта : puЫ ic static int operator - ( int n , MyData obj ) { return n - ( + obj ) ; 1 1 Операторный метод для вычисления суммы объекта 11 и текстовой строки : puЫ ic static MyData operator+ (MyData obj , s tring t ) { return new MyData ( + obj , - obj , t ) ; 1 1 Операторный метод возвращает результатом текст : puЫ ic static string operator N (MyData obj ) { return obj . text ; 1 1 Операторный метод возвращает результатом число : 395 Глава 8 puЫ ic static int operator+ (MyData obj ) { return obj . code ; / / Операторный метод возвращает результатом символ : puЫ ic static char ope rator - (MyData obj ) { return obj . s ymЬ ; / / Операторный метод возвращает результатом / / символ из текста : puЫ ic static char ope rator>> (MyData obj , int k) { return ( N obj ) [ k ] ; / / Операторный метод возвращает результатом / / символ из текста : puЫ ic static char ope rator<< (MyData obj , int k) { return ( N obj ) [ ( N obj ) . Length - k - 1 ] ; / / Операторный метод возвращает результатом объект : puЫ ic static MyData operator л (MyData а , MyData Ь ) { s tring txt=Na+" & " + N b ; / / Локаль ная переменная return new MyData ( + a , - b , txt ) ; / / Резуль тат метода / / Операторный метод для увеличения значения / / целочисленного поля объекта : puЫ ic static MyData operator++ ( MyData obj ) { obj . code+=l O ; / / Увеличение значения поля return obj ; / / Результат метода / / Операторный метод для уменьшения значения / / целочисленного поля объекта : puЫ ic static MyData operator - - ( MyData obj ) { obj . code - =1 0 ; 396 / / Уменьшение значения поля Пере груз ка о перато р ов / / Результат метода return obj ; / / Класс с главным методом : c l a s s OverloadingOperators Demo { / / Главный метод : static vo id Main ( ) { / / Создание объектов : MyData A=new MyData ( 1 0 0 , ' А ' , "Alpha " ) ; MyData B=new MyData ( 2 0 0 , ' В ' , " Bravo " ) ; / / Новый объект : MyData С=Алв ; / / Проверка резуль тата : Console . WriteLine (A) ; Console . WriteLine ( B ) ; Console . WriteLine ( C ) ; / / Новый объект : С=В ЛА; / / Проверка резуль тата : Console . WriteLine ( C ) ; / / Переменные : int n=+A ; / / Значение поля code объекта А char s=-A; / / Значение поля s ymb объекта А s tring t=NA ; / / Значение поля text объекта А / / Проверка резуль тата : Console . WriteLine ( " Oбъeкт А : поля { 0 } , \ ' { 1 } \ ' и \ " { 2 } \ " \ n " , n , s , t ) ; / / Увеличение значения поля code объекта А : А++ ; / / Проверка резуль тата : Console . WriteLine (A) ; / / Увеличение значения поля code объекта А и / / проверка резуль тата : 397 Глава 8 Console . WriteLine ( ++A) ; 1 1 Уменьшение значения поля code объекта В и 1 1 проверка резуль тата : Console . WriteLine ( B -- ) ; 1 1 Уменьшение значения поля code объекта В : - -В ; 1 1 Проверка резуль тата : Console . WriteLine ( B ) ; 1 1 К объекту прибавляется число : C=A+ l O O O ; 1 1 Проверка резуль тата : Console . WriteLine ( C ) ; 1 1 Из объекта вычитается число : С=А - 1 0 0 ; 1 1 Проверка резуль тата : Console . WriteLine ( C ) ; 1 1 Сумма и разность числа и объекта : Console . WriteLine ( " Cyммa и разност ь : { 0 } и { 1 } \ n " , 2 0 0 0+A, 1 0 0 0 - A) ; 1 1 Прибавление к объекту текстовой строки : C=A+ "Charlie " ; 1 1 Проверка резуль тата : Console . WriteLine ( C ) ; 1 1 Посимвольное отображение значения текстового поля 11 объекта С : for ( int k=O ; k< ( NC ) . Length ; k++ ) { Console . Write ( ( C>>k) + " " ) ; Console . WriteLine ( ) ; 1 1 Символы из текстового поля объекта С 1 1 отображаются в обратном порядке : for ( int k=O ; k< ( NC ) . Length ; k++ ) { Console . Write ( ( C«k) + " " ) ; 398 Пере груз ка о перато р ов Console . WriteLine ( ) ; 1 1 Прибавление объекта к текстовой строке : t=" Объект С . " + С ; 1 1 Проверка резуль тата : Console . WriteLine ( t ) ; При выполнении этой программы получаем результат, представленный ниже. [1i!J Результат выполнения п рограммы ( из листи нга 8 . 3 ) Поля объекта : Числовое поле : 1 0 0 Символьное поле : ' А ' Текстовое поле : "Alpha " Поля объекта : Числовое поле : 2 0 0 Символьное поле : ' В ' Текстовое поле : " Bravo " Поля объекта : Числовое поле : 1 0 0 Символьное поле : ' В ' Текстовое поле : "Alpha & Bravo " Поля объекта : Числовое поле : 2 0 0 Символьное поле : ' А ' Текстовое поле : " Bravo & Alpha " Объект А : поля 1 0 0 , ' А ' и "Alpha" 399 Глава 8 Поля объекта : Числовое поле : 1 1 0 Символьное поле : ' А ' Текстовое поле : 11Alpha 11 Поля объекта : Числовое поле : 1 2 0 Символьное поле : ' А ' Текстовое поле : 11Alpha 11 Поля объекта : Числовое поле : 1 9 0 Символьное поле : ' В ' Текстовое поле : 11 B ravo 11 Поля объекта : Числовое поле : 1 8 0 Символьное поле : ' В ' Текстовое поле : 11 B ravo 11 Поля объекта : Числовое поле : 1 1 2 0 Символьное поле : ' А ' Текстовое поле : 11Alpha 11 Поля объекта : Числовое поле : 2 0 Символьное поле : ' А ' Текстовое поле : 11Alpha 11 Сумма и разность : 2 1 2 0 и 8 8 0 400 Пере груз ка о перато р ов Поля объекта : Числовое поле : 1 2 0 Символьное поле : ' А ' Текстовое поле : " Charl i e " С h а r е i 1 1 i е r а h С Объект С . Поля объекта : Числовое поле : 1 2 0 Символьное поле : ' А ' Текстовое поле : " Charl i e " Код достаточно большой, но одновременно и несложный. Мы проком­ ментируем лишь основные моменты. Основу кода составляет класс MyDa t a . У класса есть три закрытых поля: целочисленное поле c o d e , символьное поле s ymЬ и текстовое поле t e x t . У класса всего один кон­ структор с тремя аргументами, которые определяют значения полей создаваемого объекта. Также в классе описан метод T o S t r i n g ( ) , воз­ вращающий результатом текстовую строку с информацией о значении полей объекта. Для удобства восприятия информации текстовая стро­ ка содержит несколько инструкций \ n (для отображения текста в но­ вой строке консоли), а в конце отображается импровизированная линия из дефисов. G) Н А ЗАМ ЕТ КУ В п рограмме в качестве идентификатора текстового типа испол ьзо­ вано кл ючевое слово s t r i n g . Напом н и м , что идентификатор s t r i n g является псевдонимом для инструкци и S y s tem . S t r i n g . На п ракти­ ке для обозначения текстового типа обычно испол ьзуют идентифи­ катор s t r i n g . Для доступа к полям мы перегружаем унарные операторы + , - и - . Если obj объект класса MyDa t a , то значением выражения + o b j является значение поля c o de объекта ob j , значением выражения - ob j является значение поля s ymb объекта o b j , а значением выражения - ob j явля­ ется текстовое значение поля t e x t объекта obj . Именно такие ссылки - 401 Глава 8 на поля в основном используются в программном коде класса MyDa t a . Кроме этого определяются такие операции с объектами класса MyDa t a : • Сумма объекта и целого числа: результатом является объект класса MyDa t a . Значение поля c o de объекта-результата получается сумми­ рованием значения поля c ode объекта-операнда и числа, прибавля­ емого к объекту. Все остальные поля у объекта-результата такие же, как и у объекта-операнда. • Разность объекта и целого числа: результатом является новый объ­ ект класса MyDa t a . Целочисленное поле этого объекта вычисляется как разность поля c o de объекта-операнда и целого числа (второй операнд), вычитаемого из объекта. • Сумма целого числа и объекта: результатом является целое число. Это сумма числового поля объекта и числового операнда. • Разность целого числа и объекта: результатом является целое чис­ ло, получающееся вычитанием из числа (первый операнд) значения числового поля объекта (второй операнд). • Сумма объекта и текстовой строки: результатом является новый объ­ ект. У него числовое и символьное поле совпадает с соответствую­ щими полями объекта-операнда, а значение текстового поля опреде­ ляется прибавляемой текстовой строкой. • Операторы инкремента + + и декремента - - переопределяются так, что у объекта-операнда числовое поле соответственно увеличивается на 1 0 и уменьшается на 1 0 . Причем результатом выражения на осно­ ве оператора инкремента/декремента является ссылка на объект-о­ перанд. • Бинарный оператор л переопределен таким образом, что результа­ том возвращается ссылка на новый объект класса MyDa t a (при том, что оба операнда - тоже объекты данного класса). Значения полей объекта-результата вычисляются так. Значение числового поля со­ впадает со значением числового поля первого операнда. Значение символьного поля совпадает со значением символьного поля второго операнда. Значение текстового поля получается объединением зна­ чений текстовых полей операндов. • Бинарные операторы > > и < < перегружены таким образом, что ре­ зультатом возвращается символ. Если ob j - это объект класса MyDa t a , а k - целое число, то результатом выражения obj > > k явля­ ется символ с индексом k в текстовом поле объекта obj , а результатом 402 Пере груз ка о перато р ов выражения оЬ j < < k тоже является символ из текстового поля объек­ та obj , но отсчет ведется с конца текстового поля. � ПОДРОБН ОСТИ Текст из текстового поля объекта obj возвращается вы ражением - ob j . Сим вол с и ндексом k в этом текстовом значении выч исляется выражением ( - obj ) [ k ] . Круглые скобки нужны для изменения по­ рядка применения оператора - и квадратных скобок. Есл и под k подразумевать и ндекс , отсч итываемый с кон ца тек­ стовой строки , то он соответствует «обычному» (отсч иты ваемо­ му с начала строки ) и ндексу ( - obj ) . Length - k - 1 . Здесь следует учесть , что кол ичество сим волов в тексте вычисляется вы ражением ( - ob j ) . Length . Пол ная ссыл ка на нужн ый сим вол выглядит как ( - obj ) [ ( - obj ) Length- k- 1 ] . . В главном методе программы проверяется работа перегруженных опе­ раторов. Мы создаем два объекта А и в класса MyDa t a , после чего вы­ полняем некоторые операции с ними. На что стоит обратить внимание? Наиболее интересные позиции. • Результат выполнения команд С=Ал в и С=В л А разный. То есть в вы­ ражении на основе оператора л имеет значение, какой операнд пер­ вый, а какой - второй. • С помощью унарных операторов +, - и - можно получить значения полей объекта. Например, для объекта А выражение +А - это значе­ ние поля code, выражение -А - это значение поля s ymЬ, а выраже­ ние -А - это значение поля t e x t . • Операторы инкремента и декремента можно использовать в пре­ фиксной ( + +А и - - В) и постфиксной (А+ + и В- - ) формах. Выра­ жение на основе оператора инкремента или декремента (например, ++А или В - - ) является ссылкой на объект-операнд. Скажем, значе­ ние выражения ++А или А++ - это ссылка на объект А. • Прибавляя к объекту число или вычитая из объекта число, получаем объект (например, команды С=А + 1 О О О и С=А- 1 О О ). А вот прибавляя к числу объект и вычитая из числа объект, получаем число (напри­ мер, команды 2 0 0 0 +А и 1 0 0 0 -А). • Узнать длину текста в текстовом поле, например объекта С , мож­ но с помощью инструкции ( - С ) . Le n gt h . Инструкция C > > k дает значение символа с индексом k, а инструкция C< < k дает значение 403 Глава 8 символа с индексом k, если текстовую строку индексировать с конца в начало. Для изменения порядка выполнения операторов использу­ ются круглые скобки. • Если к объекту прибавляется текст, как в команде C=A+ " Ch a r l i e " , то результатом получаем новый объект. Такая операция обрабаты­ вается с помощью соответствующего операторного метода. Если же к тексту прибавляется объект, как в команде t = " Объе к т С . " + С , т о для такого случая специальный операторный метод н е предусмот­ рен. Поэтому объект С вызовом метода T o S t r i n g ( ) преобразуется к текстовому формату, происходит объединение строк и результат присваивается текстовой переменной t . G) Н А ЗАМ ЕТ КУ Есл и оператор в принципе перегружаем , то выпол нять перегрузку можно по- разному. Другими слова м и , кон кретн ы й способ перегруз­ ки оператора оп ределяется решаемой задачей и фантазией про­ граммиста. Однако есть общая рекомендация , состоящая в том , что перегрузку операторов желател ьно вы полнять с учетом «основного» назначения оператора . Например, есл и перегружается бинарный оператор « пл юс» для объектов оп ределен ного класса , то желател ь­ но, чтобы оп ределяемая при перегрузке операция могла и нтерпре­ тироваться в некотором смысле как сложение объектов . Но это л и ш ь рекомендация , не более того . П ерегрузка операторов сравнен ия Девочка, вы тут самые умные ? Это вам кто-ни­ будь сказал, или вы сами решили? из к/ф -«Кин-дза-дза» Операторов сравнения всего шесть, и, как отмечалось выше, перегружа­ ются они парами. Правила такие: • Если перегружен оператор «меньше» <, то должен быть перегружен и оператор «больше» >, и наоборот. • Операторы «меньше или равно» <= и «больше или равно» >= пере­ гружаются в паре. • В паре перегружаются операторы «равно» == и «не равно» ! =. При перегрузке этих операторов обычно также переопределяют методы 404 П ерегрузка операторо в E qu a l s ( ) и G e t H a s hC ode ( ) , наследуемые из класса Ob j e c t . Этот вопрос мы обсудим более детально, когда будем рассматривать пере­ грузку операторов «равно» и «не равно» . В своих «оригинальных» версиях операторы сравнения возвращают ре­ зультатом значения логического типа. Обычно операторы сравнения для объектов пользовательских классов перегружаются так, чтобы результат был логического типа. Но это не есть обязательное условие перегрузки операторов сравнения. В принципе, результат операторного метода для операторов сравнения может быть любого типа, не обязательно логиче­ ского. Также важно понимать, что подразумевается под «парной» перегрузкой операторов сравнения. Дело в том, что для одного и того же операто­ ра может быть описано несколько версий операторного метода, которые отличаются типом аргументов и/или типом результата. То, что операто­ ры сравнения перегружаются парами, означает, что если описана версия операторного метода для оператора сравнения с аргументами и резуль­ татом определенного типа, то должна быть описана версия операторно­ го метода для другого оператора из пары с аргументами и результатом такого же типа. Например, если мы описали в классе операторный ме­ тод для оператора >= с результатом логического типа и аргументами, ко­ торые оба являются объектами класса, то в классе должен быть описан и операторный метод для оператора <= с результатом логического типа и аргументами, которые являются объектами класса. Небольшой пример перегрузки операторов сравнения < = и >= представ­ лен в листинге 8.4. [1i!J Листинг 8 . 4 . Знакомство с перегрузкой операторов сравнения u s ing Sys tem; 11 Класс с перегрузкой операторов сравнения : c l a s s MyClas s { 1 1 Символьное поле : puЫ ic char s ymЬ ; 1 1 Конструктор с одним аргументом : puЫ ic MyCl as s ( char s ) { 1 1 Присваивание значения полю : s ymb=s ; 405 Глава В 1 1 Перегрузка оператора "меньше или равно " : puЫ ic static bool ope rator<= (MyC l a s s а , MyC l a s s Ь ) { i f ( a . s ymЬ<=b . s ymЬ ) return true ; e l s e return fa l s e ; 1 1 Перегрузка оператора " больше или равно " : puЫ ic static bool ope rator>= (MyC l a s s а , MyC l a s s Ь ) { i f ( a . s ymЬ>=b . s ymЬ ) return true ; e l s e return fa l s e ; 1 1 Класс с главным методом : c l a s s OverloadingCompOperators Demo { 1 1 Главный метод : static vo id Main ( ) { 1 1 Создание объектов : MyC l a s s A=new MyC l a s s ( ' А ' ) ; MyC l a s s B=new MyC l a s s ( ' B ' ) ; MyC l a s s C=new MyC l a s s ( ' А ' ) ; 1 1 Использование операторов сравнения : Console . WriteLine ( "A<=B дает { 0 } " , А<=В ) ; Console . WriteLine ( "A>=B дает { 0 } " , А>=В ) ; Console . WriteLine ( "A<=C дает { 0 } " , А<=С ) ; Console . WriteLine ( "A>=C дает { 0 } " , А>=С ) ; Результат выполнения программы такой. [1iJ Результат выполнения п рограммы (из листи нга 8 . 4) А<=В дает True А>=В дает False 406 П ерегрузка операторо в А<=С дает True А>=С дает True Мы в программе описали класс MyC l a s s с символьным полем s ymb, конструктором с одним аргументом, а еще в классе описаны два опе­ раторных метода для операторов <= и >=. Подразумевается, что с по­ мощью операторов сравниваются объекты класса MyC l a s s . Методы возвращают логическое значение t ru e или fa l s e в зависимости от ре­ зультатов сравнения значений символьных полей объектов. В главном методе создаются три объекта, и для их сравнения используются опера­ торы >= и <=. Ситуация достаточно простая. Поэтому хочется верить, что более подробных пояснений код программы и результат ее выпол­ нения не требуют. Еще один пример, более сложный, связанный с перегрузкой операторов сравнения (на этот раз перегружаются операторы < , <=, > и >=), пред­ ставлен в листинге 8.5. Там, кроме прочего, встречается ситуация, когда оператор сравнения результатом возвращает значение, тип которого от­ личается от логического. [1i!] л истинг 8 . 5 . Перегрузка нескольких операторов сравнения u s ing Sys tem; 11 Класс с перегруженными операторами сравнения : c l a s s MyClas s { 1 1 Целочисленное поле : puЫ ic int code ; 1 1 Конструктор с одним аргументом : puЫ ic MyCla s s ( int n ) { 1 1 Присваивание значения полю : code=n ; 1 1 Перегрузка оператора "меньше или равно " : puЫ ic static MyC l a s s ope rator<= (MyC l a s s а , MyC l a s s Ь ) { i f ( a . code<=b . code ) return а ; e l s e return Ь ; 407 Глава В 1 1 Перегрузка оператора " больше или равно " : puЫ ic static MyC l a s s ope rator>= ( MyC l a s s а , MyC l a s s Ь ) { i f ( a . code>=b . code ) return а ; e l s e return Ь ; 1 1 Перегрузка оператора "меньше или равно " : puЫ ic static bool ope rator<= (MyC l a s s а , int х ) { i f ( a . code<=x - 1 ) return t rue ; e l s e return fa l s e ; 1 1 Перегрузка оператора " больше или равно " : puЫ ic static bool ope rator>= (MyC l a s s а , int х ) { i f ( a . code>=x+ l ) return t rue ; e l s e return fa l s e ; 1 1 Перегрузка оператора "меньше " : puЫ ic static bool ope rator< (MyC l a s s а , MyC l a s s Ь ) { return a . code<b . code ; 1 1 Перегрузка оператора " больше " : puЫ ic static bool ope rator> (MyC l a s s а , MyC l a s s Ь ) { return a . code>b . code ; 1 1 Перегрузка оператора "меньше " : puЫ ic static int operator< (MyC l a s s а , int х ) { return x - a . code ; 1 1 Перегрузка оператора " больше " : puЫ ic static int operator> (MyC l a s s а , int х ) { return a . code - x ; 408 П ерегрузка операторо в 1 1 Класс с главным методом : c l a s s MoreOverloadingComps Demo { 1 1 Главный метод : static vo id Main ( ) { 1 1 Создание объектов : MyC l a s s A=new MyC l a s s ( l O O ) ; MyC l a s s B=new MyC l a s s ( 2 0 0 ) ; 1 1 Проверка резуль тата : Console . WriteLine ( " Oбъeкт А : { 0 } " , A . code ) ; Console . WriteLine ( " Oбъeкт В : { 0 } " , B . code ) ; 1 1 Использование операторов "меньше " и " больше " : Console . WriteLine ( "A<B дает { 0 } " , А<В ) ; Console . WriteLine ( "A>B дает { 0 } " , А>В ) ; 1 1 Объектная переменная : MyC l a s s С ; 1 1 Использование оператора " больше или равно " : С=А>=В ; 1 1 Новое значение поля : С . соdе=З О О ; 1 1 Проверка резуль тата : Console . WriteLine ( " Oбъeкт В : { 0 } " , B . code ) ; 1 1 Использование оператора "меньше или равно " : С=А<=В ; 1 1 Новое значение поля : C . code=1 5 0 ; 1 1 Проверка резуль тата : Console . WriteLine ( " Oбъeкт А : { 0 } " , A . code ) ; 1 1 Новое значение поля : ( В<=А) . code=S O O ; 1 1 Проверка резуль тата : Console . WriteLine ( " Oбъeкт А : { 0 } " , A . code ) ; 1 1 Целочисленные переменные : 409 Глава В int х=4 0 0 , у=5 0 0 , z=б О О ; 1 1 Использование операторов "меньше или равно " и 1 1 " больше или равно " : Console . WriteLine ( "A<= { O } дает { 1 } 11 1 х , А<=х ) ; Console . WriteLine ( "A>= { O } дает { 1 } 11 1 х , А>=х ) ; Console . WriteLine ( "A<= { O } дает { 1 } 11 1 у , А<=у) ; Console . WriteLine ( "A>= { O } дает { 1 } 11 1 у , А>=у) ; Console . WriteLine ( "A<= { O } дает { 1 } 11 1 z , A<=z ) ; Console . WriteLine ( "A>= { O } дает { 1 } 11 1 z , A>=z ) ; 1 1 Использование операторов "меньше " и " больше " : Console . WriteLine ( "A< { O } дает { 1 } 11 1 z , A< z ) ; Console . WriteLine ( "A> { O } дает { 1 } 11 1 х , А>х ) ; На этот раз при выполнении программы получаем следующее. [1iJ Результат выполнения п рограммы (из листи нга 8 . 5) Объект А: 1 0 0 Объект В : 2 0 0 А<В дает True А>В дает False Объект В: 3 0 0 Объект А : 1 5 0 Объект А : 5 0 0 А<=4 0 0 дает Fal se А>=4 0 0 дает T rue А<=5 0 0 дает Fal se А>=5 0 0 дает Fal se А<= б О О дает T rue А>= б О О дает Fal se А< б О О дает 1 0 0 А> 4 0 0 дает 1 0 0 410 П ерегрузка операторо в Операторные методы перегружаются для класса MyC l a s s . У класса есть целочисленное поле c o de и конструктор с одним аргументом (значение поля). В классе перегружаются такие операторы: • Операторы < = и >= в случае, если оба операнда - объекты клас­ са MyC l a s s , результатом возвращают ссылку на объект класса MyC l a s s . Мало того, это ссылка на один из двух объектов-операн­ дов. Например, если А и В являются объектами класса MyC l a s s , то результатом выражения А<=В будет ссылка на объект А, если зна­ чение поля c o de этого объекта не превышает значения поля c o de объекта В. Если значение поля c o de объекта А больше значения поля c o de объекта В, то результатом выражения А<=В является ссылка на объект В. Аналогично, результатом выражения А>=В является ссылка на объект А, если его поле c o de больше или равно значению поля c o de объекта В. Если значение поля c o de объекта А меньше значения поля c o de объекта В, то результатом выражения А>=В яв­ ляется ссылка на объект В. • Операторы <= и >= можно использовать и в случае, если первый опе­ ранд является объектом класса MyC l a s s , а второй операнд являет­ ся целым числом. Результат операции с такими операндами явля­ ется логическим значением t ru e или f a l s e . Допустим, А является объектом класса MyC l a s s , а через х обозначим некоторое целочис­ ленное значение. Результат выражения А<=х будет равен t ru e , если значение поля c o de объекта А меньше или равно числу х - 1 . В про­ тивном случае результат выражения А<=х равен f a l s e . А вот резуль­ тат выражения А>=х равен t ru e , если значение поля c o de объек­ та А больше или равно числу х+ 1. Иначе значение выражения А>=х равно f a l s e . Несложно заметить, что операторы <= и >= в данном случае перегружены так, что если значение поля c o de объекта А равно х, то значение выражения А<=х равно f a l s e , но и значение выражения А>=х также равно f a l s e . Это �поведение» операторов < = и >= не очень согласуется с нашей повседневной логикой, взра­ щенной на сравнении числовых значений. Вместе с тем, перегружая операторы, мы можем добиться и такого �экзотического» поведения. Главное, чтобы в этом была необходимость. • Операторы < и > для случая, когда сравниваются объекты класса MyC l a s s , перегружаются очевидным образом: операторным мето­ дом возвращается результат сравнения (с помощью соответствую­ щего оператора) значений полей c o de объектов-операндов. 411 Глава В • Операторы < и > также перегружаются для случая, когда первый операнд является объектом класса MyC l a s s , а второй операнд яв­ ляется целым числом. В этом случае результатом операции сравне­ ния возвращается целое число. Для оператора < оно вычисляется как разность значений числового операнда и значения поля c o de пер­ вого операнда-объекта. Для оператора > результат вычисляется как разность поля c o de первого объекта-операнда и числового значения, указанного вторым операндом. В главном методе программы мы проверяем функциональность перегру­ женных операторов. Для этого создаем два объекта А и В класса МуС 1 а s s со значениями полей 1 О О и 2 О О соответственно. Затем выполняются различные операции с использованием операторов < , >, < = и >=. При вычислении выражений А<В и А>В результатом является t ru e и fa l s e в зависимости от значений полей сравниваемых объектов. G) Н А ЗАМ ЕТ КУ Логические значения t rue и f a l s e при вы воде в консольное окно отображаются с бол ьшой букв ы . М ы объявляем объектную переменную С класса MyC l a s s . При выпол­ нении команды С=А>=В в переменную С записывается ссьшка на тот же объект, на который ссылается переменная В. Поэтому когда выполняет­ ся команда С . с о dе = З О О , то в действительности значение присваивает­ ся полю c o de объекта В. А вот при выполнении команды С=А< =В в пе­ ременную С записывается ссылка на объект А. Командой С . c o d e = 1 5 0 присваивается новое значение полю c o de объекта А. Значением выражения В<=А является ссылка на объект А. Поэтому ко­ манда ( В<=А ) . code=5 0 0 означает, что полю c o de объекта А присваи­ вается значение 5 О О . Также мы используем три целочисленные переменные х , у и z со значе­ ниями 4 О О , 5 О О и 6 О О соответственно. Эти переменные сравниваются с объектом А. Использование для сравнения операторов < = и >= дает в результате логическое значение. Достоин внимания результат сравне­ ния объекта А с переменной у (команды А<= у и А>=у). Здесь имеет ме­ сто ситуация, упомянутая ранее: значение поля c o de объекта А и зна­ чение переменной у совпадают. Поэтому оба выражения А< =у и А>=у значением дают f a l s e . 412 П ерегрузка операторо в Сравнение объекта А с целочисленными переменными с помощью опе­ раторов > и < (команды A< z и А>х) результатом дает целое число (для оператора > это разность поля c o de объекта и числа, а для оператора < это разность числа и поля c o de объекта). Похожим образом обстоят дела с перегрузкой операторов «равно» == и «не равно» ! =. Правда, есть и небольшие особенности. Чтобы понять их суть, необходимо сделать небольшое отступление. Даже если мы не перегружаем операторы == и ! = объектов класса, мы все равно можем использовать эти операторы (в отличие от операторов <, >, <= и >=) для сравнения объектов. Например, если объекты А и В относятся к одному и тому же классу, то операции вида А==В и А ! =В вполне законны и имеют смысл. По умолчанию, если операторы == и ! = не перегружались, при вьmолнении команды А== В проверяются на предмет совпадения значе­ ния объектных переменных А и В. А в эти переменные фактически записаны адреса объектов, на которые переменные ссылаются. Поэтому результатом выражения А==В является значение t rue, если переменные А и В ссьmа­ ются на один и тот же объект. Если переменные ссьmаются на физически разные объекты, результат выражения А==В равен fal s e. При выполне­ нии инструкции А ! =В все происходит аналогичным образом, но проверка выполняется на предмет неравенства - то есть результат выражения А ! =В равен t rue, если переменные А и В ссьmаются на разные объекты, а если они ссьmаются на один и тот же объект, то результат выражения А ! =В ра­ вен fa l s e. Если нас такое «поведение» операторов == и ! = не устраива­ ет, то мы при описании класса можем эти операторы перегрузить. Особых ограничений (за исключением того, что операторы == и ! = перегружаются в паре) здесь нет, в том числе и касательно типа результата, возвращаемо­ го соответствующими операторными методами. Исключительно простой пример перегрузки операторов сравнения представлен в листинге 8.6. [1i!J Листинг 8 . 6 . Перегрузка операторов ссравно» и сене равно» u s ing Sys tem; 11 Класс без перегрузки операторов сравнения : c l a s s Alpha { 1 1 Целочисленное поле : puЫ ic int code ; 1 1 Конструктор с одним аргументом : puЫ ic Alpha ( int n ) { 413 Глава В 1 1 Присваивание значения полю : code=n ; 1 1 Класс с перегрузкой операторов сравнения : c l a s s Bravo { 1 1 Целочисленное поле : puЫ ic int code ; 1 1 Конструктор с одним аргументом : puЫ ic Bravo ( int n ) { 1 1 Присваивание значения полю : code=n ; 1 1 Перегрузка оператора "равно " : puЫ ic static bool ope rator== ( Bravo а , Bravo Ь ) { return a . code==b . code ; 1 1 Перегрузка оператора " не равно " : puЫ ic static bool ope rator ! = ( Bravo а , Bravo Ь ) { return ! ( а==Ь ) ; 1 1 Класс с главным методом : c l a s s OverloadingEqvOperatorDemo { 1 1 Главный метод : static vo id Main ( ) { 1 1 Создание объектов класса Alpha : Alpha Al=new Alpha ( l O O ) ; Alpha A2=new Alpha ( l O O ) ; Alpha AЗ=new Alpha ( 2 0 0 ) ; 1 1 Проверка объектов на предмет 1 1 равенства или неравенства : 414 П ерегрузка операторо в Console . WriteLine ( 11Al==A2 дает { 0 } 11 1 Al ==A2 ) ; Console . WriteLine ( 11Al ! =A2 дает { 0 } 11 1 Al ! =A2 ) ; Console . WriteLine ( 11Al==AЗ дает { 0 } 11 1 Аl ==АЗ ) ; Console . WriteLine ( 11Al ! =AЗ дает { 0 } 11 1 Аl ! =АЗ ) ; 1 1 Создание объектов класса Bravo : Bravo Bl =new Bravo ( l O O ) ; Bravo B2 =new Bravo ( l O O ) ; Bravo BЗ=new Bravo ( 2 0 0 ) ; 1 1 Проверка объектов на предмет 1 1 равенства или неравенства : Console . WriteLine ( 11 B l ==B2 дает { 0 } 11 1 Вl ==В2 ) ; Console . WriteLine ( 11 B l ! =B2 дает { 0 } 11 1 В1 ! =В2 ) ; Console . WriteLine ( 11 B l ==BЗ дает { 0 } 11 1 Вl ==ВЗ ) ; Console . WriteLine ( 11 B l ! =BЗ дает { 0 } 11 1 В1 ! =ВЗ ) ; В принципе, эта программа компилируется, но с предупреждением. Предупреждение - это не ошибка. Это скорее признак непоследователь­ ного подхода к составлению программного кода. Суть предупреждения в данном случае в том, что в программе перегружены операторы == и ! =, но не переопределены методы E qu a l s ( ) и G e t H a s hC o de ( ) , наследуе­ мые из класса Ob j e c t . Методы обсудим далее. Сначала проанализируем код программы и результат ее выполнения. [1iJ Результат выполнения программы (из листинга 8 . 6) Al==A2 дает Fal s e Al ! =A2 д а е т True Аl==АЗ дает Fal s e Аl ! =АЗ д а е т True B l ==B2 дает True B l ! =B2 дает Fal s e Вl==ВЗ д а е т Fal s e В l ! =ВЗ д а е т True 415 Глава В В программе описаны классы A l p h a и B r av o . В каждом из классов есть целочисленное поле c o de и конструктор с одним аргументом. Принципиальное отличие между классами в том, что в классе Alpha операторы == и ! = не перегружаются, а в классе B r avo данные опе­ раторы перегружены. Перегружены они следующим образом. Опера­ торный метод для оператора == описан с двумя аргументами класса B r avo, которые обозначены как а и Ь. Результатом метода возвраща­ ется значение выражения а . c o de ==b . c o d e . В этом выражении ис­ пользуется оператор сравнения ==, но здесь он применяется по отно­ шению к значениям целочисленного типа (поля c o de объявлены как целочисленные), и поэтому в данном случае действие оператора == предопределено характеристиками языка С#. Результат выражения а . c o de ==b . c o de равен t ru e , если значения полей c o de объектов а и Ь совпадают. Иначе значение выражения а . c o de ==b . c o de равно fa l s e . Таким образом, результат операторного метода для оператора == равен t ru e , если у сравниваемых объектов значения полей c o de совпадают (при этом сами объекты могут быть физически разными) . Если поля c o de имеют разные значения, операторный метод для опе­ ратора == возвращает значение f a l s e . Операторный метод для оператора сравнения ! = определен так: для ар­ гументов а и Ь (объекты класса B r avo) результатом метода возвращает­ ся значение выражения ! ( а==Ь ) . Здесь вычисляется значение выраже­ ния а==Ь. Поскольку а и Ь - объекты класса B r avo, то для вычисления выражения а==Ь используется операторный метод для оператора ==. Результатом является логическое значение. Затем с помощью оператора логического отрицания ! результат выражения а==Ь заменяется на про­ тивоположный. Следовательно, оператор ! = работает по такой схеме. Если для двух указанных операндов оператор == возвращает значение t ru e , то оператор ! = для тех же операндов возвращает значение f a l s e . И наоборот, если оператор == возвращает значение f a l s e , т о оператор ! = с теми же операндами вернет значение t ru e . В главном методе программы создается п о три объекта класса A l p h a и Bravo. Эти объекты сравниваются с помощью операторов == и ! =. Ин­ терес представляет результат сравнения объектов Al и А2 класса Al pha, с одной стороны, и объектов B l и В 2 класса B r avo - с другой. Приме­ чательны объекты тем, что у них одинаковые значения полей. При этом результатом сравнения объектов Al и А2 на предмет равенства (выра­ жение A l ==A2 ) является значение f a l s e , а при сравнении на предмет 416 П ерегрузка операторо в равенства объектов B l и В 2 получаем значение t ru e . Причина в том, что при вычислении значения выражения A l ==A2 (поскольку в клас­ се Alpha операторы сравнения не перегружались) сравниваются ссыл­ ки на объекты (а они разные). А при вычислении значения выражения B l == B 2 , в соответствии с перегрузкой операторов сравнения в классе B r avo, сравниваются не ссылки, а значения полей объектов. Теперь вернемся к методам E qua l s ( ) и G e t H a s h C o de ( ) , которые ре­ комендуется переопределять при перегрузке операторов == и ! =. И сна­ чала несколько слов о месте и роли этих методов. Метод E qu a l s ( ) из класса Ob j e c t предназначен для сравнения двух объектов на предмет равенства. Метод вызывается из одного объекта, а другой объект передается аргументом методу. При этом объекты мо­ гут относиться к разным классам. Сравнение объектов (того, из которого вызывается метод, и того, который передан аргументом методу) на пред­ мет равенства выполняется на уровне сравнения ссылок на объекты. Ме­ тод E qua l s ( ) возвращает значение t ru e , если обе объектные перемен­ ные ссылаются на один и тот же объект. Если переменные ссылаются на разные объекты, метод E qu a l s ( ) возвращает значение fa l s e . � ПОДРОБН ОСТИ Метод Equ a l s ( ) описан в классе Obj e c t , и его аргумент объя влен как относя щийся классу Obj e c t . Класс Obj ect находится в вершине иерархи и наследования , поэтому все класс ы , вкл ючая и описы ва­ емые нами в п рограмме, неявно «получают в наследство» методы из класса Obj e c t . Это касается и метода Equ a l s ( ) . Мы можем вы­ зы вать этот метод из объекта описанного нами класса , даже есл и мы в классе такой метод не описал и . Аргументом методу мы мо­ жем передавать объект л юбого класса, поскольку аргумент метода Equa l s ( ) относится к классу Ob j e c t , а объектная перемен ная клас­ са Obj e c t может ссылаться на объект л юбого класса . Метод G e t H a s hC ode ( ) также описан в классе Ob j e c t , поэтому насле­ дуется во всех классах. У метода нет аргументов, а результатом метод возвращает целое число. Это целое число называется хэш-кодом объек­ та, из которого вызывается метод. Сам по себе хэш-код особого смысла не имеет. Хэш-коды используются для сравнения двух объектов на пред­ мет равенства. Главное правило такое: если два объекта считаются оди­ наковыми (равными), то у них должны быть одинаковые хэш-коды. 417 Глава В G) Н А ЗАМ ЕТ КУ Есл и два объекта рав н ы , то их хэш - коды должны совпадать. Н о есл и у двух объектов совпадают хэш - коды , то это не означает равенства объектов. Таким образом, каждый раз при описании класса, вне зависимости от на­ шего желания, в этом классе будут «неявно присутствовать» методы Equa l s ( ) и G e t H a s h C ode ( ) - мы можем вызвать эти методы из объ­ екта класса, несмотря на то что лично мы методы в классе и не описыва­ ли. По умолчанию эти методы работают по определенным алгоритмам, но важно то, что анализируются ссылки для сравниваемых объектных переменных. По умолчанию операторы == и ! = реализуются в строгом соответствии с таким подходом. То есть мы имеем дело с «великолеп­ ной четверкой» (методы Equa l s ( ) и G e t H a s h C o de ( ) и операторы == и ! = ), которая функционирует в согласованном, «гармонизированном» режиме. И как только мы перегружаем операторы сравнения == и ! =, ме­ няя способ определения «равенства» объектов, то эта «гармония» нару­ шается. Поэтому, перегружая операторы == и ! =, желательно переопре­ делить и методы E qu a l s ( ) и G e t H a s hC o de ( ) . Как это можно было бы сделать, показано далее. G) Н А ЗАМ ЕТ КУ Переоп ределение методов - механизм , связан н ы й с наследова­ нием ( наследование и перегрузка обсуждаются в одной из следую­ щих гла в ) . Методы Equ a l s ( ) и GetHashCode ( ) наследуются в поль­ зовател ьском классе , и м ы их можем переоп редел ить (оп исать новые верси и этих методо в ) . При переоп ределении методы описы­ ваются с кл ючевым словом ove r r i de . В листинге 8. 7 вниманию читателя представлена программа, в которой наряду с перегрузкой операторов == и ! = еще и переопределяются мето­ ды Equa l s ( ) и G e t H a s hC ode ( ) . G) Н А ЗАМ ЕТ КУ Сразу отметим , что м ы уп ростил и ситуаци ю настол ько , наскол ько это возможно . Цел ь была в том , чтобы п роилл юстрировать общий подход. 418 П ерегрузка операторо в [1i!J Листинг 8. 7 . Перегрузка операторов сравнения и переопределение методов u s ing Sys tem; / / Класс с перегрузкой операторов сравнения == и ! = / / и переопределением методов GetHashCode ( ) и Equals ( ) : c l a s s MyClas s { / / Целочисленное поле : puЫ ic int code ; / / Символьное поле : puЫ ic char s ymЬ ; / / Конструктор с двумя аргументами : puЫ ic MyCla s s ( int n , char s ) { code=n ; / / Значение целочисленного поля s ymb=s ; / / Значение символь ного поля / / Переопределение метода GetHashCode ( ) : puЫ ic override int GetHashCode ( ) { / / Вычисление хэш - кода : return code л s ymЬ ; / / Переопределение метода Equals ( ) : puЫ ic override bool Equa l s ( Obj ect obj ) { / / Локальная объектная переменная : MyC l a s s t= (MyC l a s s ) obj ; / / Результат метода : i f ( code==t . code & & symЬ==t . s ymЬ ) return t rue ; e l s e return false ; / / Перегрузка оператора "равно " : puЫ ic static bool ope rator== (MyC l a s s а , MyC l a s s Ь ) { / / Вызов метода Equals ( ) : return a . Equals ( b ) ; 419 Глава В 1 1 Перегрузка оператора " не равно " : puЫ ic static bool ope rator ! = (MyC l a s s а , MyC l a s s Ь ) { 1 1 Использование оператора "равно " : return ! ( а==Ь ) ; 1 1 Класс с главным методом : c l a s s OverridingEquals Demo { 1 1 Главный метод : static vo id Main ( ) { 11 Создание объектов : MyC l a s s A=new MyC l a s s ( l O O , ' А ' ) ; MyC l a s s B=new MyC l a s s ( l O O , ' В ' ) ; MyC l a s s C=new MyC l a s s ( 2 0 0 , ' А ' ) ; MyC l a s s D=new MyC l a s s ( l O O , ' А ' ) ; 1 1 Вычисление хэш - кодов : Console . WriteLine ( " Xэш - кoд А : { 0 } 11 1 A . GetHa shCode ( } ) ; Console . WriteLine ( " Xэш - кoд В : { 0 } 11 1 B . GetHa shCode ( } ) ; Console . WriteLine ( " Xэш - кoд С : { 0 } 11 1 C . GetHa shCode ( } ) ; Console . WriteLine ( " Xэш - кoд D : { 0 } 11 1 D . GetHa shCode ( } ) ; 1 1 Сравнение объектов на предмет 1 1 равенства и неравенства : Console . WriteLine ( "A==B дает { 0 } 11 1 А==В ) ; Console . WriteLine ( "A ! =B дает { 0 } 11 1 А ! =В ) ; Console . WriteLine ( "A==C дает { 0 } 11 1 А==С ) ; Console . WriteLine ( "A ! =C дает { 0 } 11 1 А ! =С ) ; Console . WriteLine ( "A==D дает { 0 } 11 1 A==D ) ; Console . WriteLine ( "A ! =D дает { 0 } 11 1 A ! =D ) ; 420 П ерегрузка операторо в Результат выполнения программы такой. [1i!J Результат выполнения п рограммы (из листи нга 8. 7) Хэш - код А : 3 7 Хэш - код В : 3 8 Хэш - код С : 1 3 7 Хэш - код D : 3 7 А==В дает False А ! =В дает True А==С дает False А ! =С дает True A==D дает True A ! =D дает False Основу нашего программного кода составляет класс MyC l a s s , у ко­ торого есть целочисленное поле c o de и символьное поле s ymЬ. Класс оснащен конструктором с двумя аргументами. Также в классе перегру­ жены операторы == и ! =, а также переопределены методы E qu a l s ( ) и G e t H a s h C o de ( ) . Два объекта класса мы будем полагать равными, если у них совпадают значения полей. Метод Ge t H a s h C o de ( ) в конкретно нашем примере играет декора­ тивную роль (но мы его все равно переопределяем). Относительно остальных участников процесса, то оператор == перегружается так, что вызывается метод E qu a l s ( ) , а перегрузка оператора ! = базируется на использовании оператора ==. Поэтому основа нашего кода - это код метода E qu a l s ( ) . G) Н А ЗАМ ЕТ КУ через вызов метода В данном случае перегрузка оператора Equa l s ( ) не есть инструкция к действи ю . Это п росто илл юстрация совместного испол ьзования нескол ьких методов . == Начнем м ы с анализа кода G e t H a s h C ode ( ) . Метод результатом воз­ вращает целое число. Результат вычисляется как значение выражения c o de л s ymb. Здесь использован оператор «побитового исключающего или» л , а операндами являются поля c o de и s ymb (для символьного 42 1 Глава 8 поля берется код из кодовой таблицы) объекта, из которого вызывается метод. В данном случае не очень важно, какое именно получится чис­ ло. Важно, чтобы для объектов с одинаковыми значениями полей c o de и s ymЬ хэш-коды были одинаковыми (при этом случайно могут совпа­ дать хэш-коды разных объектов). Метод Equal s ( ) переопределяется так. В теле метода объявляется ло­ кальная объектная переменная t класса MyC l a s s. Значение переменной присваивается командой t= ( MyC l a s s ) obj , где через obj обозначен ар­ гумент метода E qu a l s ( ) . В этой команде выполняется явное приведе­ ние переменной obj класса Ob j e c t к ссылке класса MyC l a s s . Это оз­ начает, что мы собираемся обрабатывать аргумент метода как объектную переменную класса MyC l a s s . � ПОДРОБН ОСТИ М ы не определяем «С нул я » метод Equa l s ( ) , а переопределяем его версию, унаследован ную из класса Obj e c t . А там аргумент метода оп исан как тако й , что относится к классу Obj e c t . При этом мы исхо­ дим из того , что при вызове метода аргументом будет передавать­ ся объектная перемен ная класса MyC l a s s . П оэтому в теле метода вы полняется явное при ведение аргумента к классу MyC l a s s . Есл и при вызове метода передать аргумент другого типа, в общем случае возникает ошибка . Нередко вместе с переопределением метода Equ a l s ( ) выполняется еще и его перегрузка (описывается еще одна версия метода) с аргумен­ том пол ьзовательского класса (в данном случае это класс MyC l a s s ) . Результат метода вычисляется с помощью условного оператора. Если со­ впадают значения полей объекта, из которого вызывается метод, и объ­ екта, переданного аргументом методу, то метод E qu a l s ( ) результатом возвращает значение t ru e , а в противном случае результат метода равен fa l s e . Операторный метод для оператора «равно» описывается так, что для ар­ гументов а и Ь класса MyC l a s s результатом возвращается значение вы­ ражения а . E qu a l s ( Ь ) . А операторный метод для оператора «не равно» возвращает значением выражение ! ( а ==Ь ) , что есть «противоположное» значение к результату сравнения объектов а и Ь на предмет равенства. В главном методе программы создается несколько объектов, для этих объектов вычисляется хэш-код, а также объекты сравниваются 422 П ерегрузка операторо в на предмет равенства и неравенства. Легко заметить, что объекты с оди­ наковыми полями интерпретируются как равные. П ерегрузка операторов true и fal se - Как ты посмел обмануть ребенка? ! - Я не могу ждать, пока она вырастет ! из к/ф �А й болит - 66 » Операторы t ru e и f a l s e являются унарными и перегружаются в паре (соответствующие операторные методы называются ope r a t o r t ru e и ope r a t o r f a l s e ) . Оба оператора результатом возвращают значение логического типа. � ПОДРОБН ОСТИ Объекты можно п роверять на предмет «истинности» ил и «ложности » . П р и п роверке объекта н а «исти нность» вызы вается операторный метод для оператора t rue . Есл и операторный метод возвращает значение t rue , то такой объект я вляется «исти н н ы м » . Есл и опера­ торный метод возвращает значение fa l s e , то объект не сч итается « ИСТИ Н Н Ы М » . При п роверке объекта н а «ложность» вызы вается операторный метод для оператора f a l s e . Если операторный метод возвращает значение t rue , то объект сч итается «ложн ы м » . Есл и операторный метод воз­ вращает значение fal s e , то объект не считается «ложным». Строго говоря , есл и объект не является « исти н н ы м » , то это не оз­ начает, что он «ложн ы й » . Из того , что объект не я вляется «ложн ы м » , не следует, что объект «исти н н ы й » . П роверка объекта на «исти н ­ ность» и «ложность» вы полняется в разных ситуациях, которые об­ суждаются далее. Это особые операторы в том смысле, что не сразу понятно, когда и как они применяются. Вместе с тем ситуация не такая уж и сложная. Опе­ раторный метод для оператора t ru e вызывается в тех случаях, когда объект указан условием - например, в условном операторе или опера­ торе цикла. Также операторный метод для оператора t ru e вызывает­ ся при проверке первого операнда в выражении на основе оператора 1 1 (сокращенная форма оператора �логическое или�). Операторный ме­ тод для оператора f a l s e вызывается при проверке первого операнда 423 Глава 8 в выражении, на основе оператора & & (сокращенная форма оператора �логическое и»). G) Н А ЗАМ ЕТ КУ Специфику перегрузки операторов t rue и fa l s e при работе с опе­ раторами 1 1 и & & м ы обсудим в этой главе, но немного позже . Небольшой пример, иллюстрирующий перегрузку операторов t ru e и f a l s e , представлен в листинге 8.8. � Листинг 8.8. Перегрузка операторов true и false us ing Sys tem; 11 Класс с перегрузкой операторов true и false : c l a s s MyClas s { 1 1 Целочисленное поле : puЫ ic int code ; 1 1 Конструктор с одним аргументом : puЫ ic MyCla s s ( int n ) { 1 1 Значение поля : code=n ; 1 1 Перегрузка оператора true : puЫ ic static bool ope rator true ( MyC l a s s obj ) { 1 1 Проверяется значение поля объекта : i f ( obj . code>=S ) return true ; e l s e return fal s e ; 1 1 Перегрузка оператора fal s e : puЫ ic static bool ope rator false (MyC l a s s obj ) { 1 1 Исполь зуется операторный метод для оператора true . 1 1 Объект исполь зован как условие : i f ( obj ) return fal s e ; e l s e return true ; 424 П ерегрузка операторо в 1 1 Класс с главным методом : c l a s s OverloadingTrueFa l s e Demo { 1 1 Главный метод : static vo id Main ( ) { 1 1 Создание объекта класса : MyC l a s s obj =new MyC l a s s ( l O ) ; 1 1 Использование операторного метода для 11 оператора true . Объект использован как условие : whi l e ( obj ) { 1 1 Отображение значения поля объекта : Console . Write ( obj . code + " " ) ; 1 1 Уменьшение значения поля объекта : obj . code -- ; Console . WriteLine ( ) ; Ниже представлен результат выполнения программы. [1iJ Результат выполнения программы (из листинга 8 . 8) 10 9 8 7 6 5 Мы в классе MyC l a s s с целочисленным полем c ode и конструктором (с одним аргументом) описываем еще два операторных метода, которые называются ope r a t o r t ru e и ope r a t o r f a l s e . Оба операторных метода описаны с одним аргументом (обозначен как obj ), являющимся объектом класса MyC l a s s . Результатом методами возвращается значе­ ние типа b o o l . В методе ope r a t o r t rue ( ) результат вычисляется так: в условном операторе проверяется условие o b j . c o d e > = 5 , состоящее в том, что значение поля c o de объекта-операнда больше или равно 5 . Если так, то результатом метода возвращается значение t ru e . В против­ ном случае метод возвращает значение fa l s e . 425 Глава 8 G) Н А ЗАМ ЕТ КУ Получается так, что есл и у объекта значение целоч исленного поля бол ьше или равно 5 , то при проверке на «исти нность» этот объект признается « исти н н ы м » . Есл и значение поля меньше 5 , то при про­ верке на исти нность объект «исти н н ы м » не признается . Описание метода ope r at o r fa l s e ( ) тоже базируется на использова­ нии условного оператора. Но на этот раз условием в условном операто­ ре указан объект obj (аргумент метода). Если объект указан в качестве условия, то проверка условия сводится к проверке объекта на «истин­ ность»: если объект признается «истинным», то это интерпретируется как истинность условия, а если объект истинным не признается, то ус­ ловие будет интерпретироваться как ложное. Проще говоря, объект obj в условии в условном операторе означает, что для этого объекта будет вызван операторный метод ope r a t o r t rue ( ) . Результат, возвращае­ мый методом, дает значение условия в условном операторе. Если объект obj истинный, то результатом метода o p e r a t o r f al s e ( ) возвращает­ ся значение f a l s e . Если объект не является истинным, то результатом метода ope r a t o r fa l s e ( ) возвращается значение t r u e . G) Н А ЗАМ ЕТ КУ М ы определ или методы ope r a t o r t rue ( ) и ope r a t o r fa l s e ( ) так, что есл и оди н метод возвращает значение t rue , то другой воз­ вращает значен ие fa l s e , и наоборот. П олучается , что есл и объект не « ИСТИ Н Н Ы Й » , то он «ЛОЖН Ы Й » , а есл и объект не «ЛОЖН Ы Й » , то он «исти н н ы й » . Но в общем случае все могло бы быть и наче . В главном методе программы создается объект o b j класса MyC l a s s , и целочисленное поле этого объекта получает значение 1 О . После этого выполняется оператор цикла wh i l e , в котором условием указан объ­ ект o b j . Каждый раз при проверке условия для объекта obj вызыва­ ется операторный метод ope r a t o r t ru e ( ) . Пока значение поля объ­ екта больше или равно 5 , объект интерпретируется как «истинный» и условие в операторе цикла тоже истинно. За каждый цикл коман­ дой C o n s o l e . W r i te ( ob j . c o de + " " ) текущее значение поля c o de объекта оЬ j отображается в консольном окне, после чего командой o b j . c o de - - значение поля уменьшается на единицу. В результате в консольном окне появляется последовательность числовых значений от 1 О до 5 включительно. 426 П ерегрузка операторо в П ерегрузка логи ч еских операторов Я же говорил, что я всегда бываю нрав ! из к/ф �А й болит - 66 » Бинарные операторы & и 1 и унарный оператор ! могут перегружаться произвольным образом. Причем тип результата для соответствующих операторных методов не обязательно должен быть логическим значе­ нием. Операторы & & и 1 1 не перегружаются (то есть их нельзя пере­ грузить). Но в классе можно так перегрузить операторы & и 1 , а также операторы t rue и f a l s e , что с объектами класса станет возможно ис­ пользовать операторы & & и 1 1 . Именно такой способ перегрузки опера­ торов & , [ , t rue и f a l s e мы и рассмотрим. Чтобы уловить идею, на которой основан весь подход по перегрузке опера­ торов, имеет смысл проанализировать способ вычисления выражений вида А& &В и А 1 1 В. Мы исходим из того, что операнды А и В являются объекта­ ми некоторого класса (в котором мы планируем перегружать операторы). Итак, при вычислении выражения А& &В первый операнд А проверя­ ется на предмет ложности. Для этого вызывается операторный метод для оператора f a l s e . Если операнд А «ложный» (операторный метод ope rator fal s e ( ) возвращает значение t rue), то результатом выраже­ ния А& &В возвращается операнд А. Если операнд А «ложным» не признает­ ся (операторный метод ope rator fa l s e ( ) возвращает значение fal s e), то результатом выражения А & & В возвращается значение выражения А & В. При вычислении значения выражения А 1 1 В операнд А проверяется на предмет «истинности» , и для этого вызывается операторный метод ope rat o r t ru e ( ) . Если метод возвращает значение t ru e , то результа­ том выражения А 1 1 В возвращается операнд А. Если операторный метод ope rat o r t rue ( ) возвращает значение f a l s e , то результатом выра­ жения А 1 1 В возвращается результат выражения А 1 В. Отсюда становятся понятны принципы, определяющие способ пере­ грузки операторов &, [ , t ru e и f a l s e для возможности использования операторов & & и 1 1 . Они таковы: • Операторы & и 1 должны быть перегружены для пользовательского класса, причем операнды должны быть объектами этого класса, и ре­ зультат - объект этого же класса. 427 Глава 8 • Для пользовательского класса необходимо описать операторные ме­ тоды для операторов t rue и f a l s e . Как эта схема реализуется н а практике, показано в программе в листин­ ге 8.9. � Л истинг 8 . 9 . Перегрузка логических операторов us ing Sys tem; / / Класс с перегрузкой операторов : c l a s s MyClas s { / / Символьное поле : puЫ ic char s ymb ; / / Конструктор с одним аргументом : puЫ ic MyCl as s ( char s ) { s ymb=s ; / / Значение поля / / Перегрузка оператора true : puЫ ic static bool ope rator true ( MyC l a s s obj ) { switch ( obj . s ymЬ ) { case ' А ' : case ' Е ' : case ' I ' : case ' О ' : case ' U ' : case ' У ' : return true ; de faul t : return fal s e ; / / Перегрузка оператора fal s e : puЫ ic static bool ope rator false (MyC l a s s obj ) { i f ( obj ) return obj . symЬ== ' Y ' ; 428 П ерегрузка операторо в e l s e return true ; 1 1 Перегрузка оператора & : puЫ ic static MyC l a s s operator & ( MyC l a s s а , MyC l a s s Ь ) { i f ( a ) return Ь ; e l s e return а ; 1 1 Перегрузка оператора 1 : puЫ ic static MyC l a s s operator l ( MyC l a s s а , MyC l a s s Ь ) { i f ( a ) return а ; e l s e return Ь ; 1 1 Класс с главным методом : c l a s s OverloadingLogOp s Demo { 1 1 Главный метод : static vo id Main ( ) { 1 1 Создание объектов : MyC l a s s A=new MyC l a s s ( ' A ' ) ; MyC l a s s B=new MyC l a s s ( ' B ' ) ; MyC l a s s E=new MyC l a s s ( ' E ' ) ; MyC l a s s Y=new MyC l a s s ( ' Y ' ) ; 1 1 Использование логических операторов : Console . WriteLine ( " Выражение А & & В : { О } " , (А& & В ) . s ymb ) ; Console . WriteLine ( " Выражение В & &А : { О } " , ( В & &А) . s ymb ) ; Console . WriteLine ( " Выражение А & & Е : { О } " , (А& & Е ) . s ymb ) ; Console . WriteLine ( " Bыpaжeниe Е & &А : { 0 } " , ( Е & &А) . symb ) ; Console . WriteLine ( " Bыpaжeниe А & & У : { 0 } " , (А&&У ) . symb ) ; Console . WriteLine ( " Bыpaжeниe У & & А : { 0 } " , ( У & &А) . symb ) ; Console . Wri teLine ( " Выражение А 1 1 В : { О } " , (А 1 1 В ) . s ymb ) ; Console . Wri teLine ( " Выражение В 1 1 А : { О } " , (В 1 1 А) . s ymb ) ; Console . Wri teLine ( " Выражение А 1 1 Е : { О } " , (А 1 1 Е ) . s ymb ) ; 429 Глава 8 Console . Wri teLine ( " Выражение Е 1 1 А : { О } " , (Е 1 1 А) . s ymb ) ; Console . Wri teLine ( " Выражение А 1 1 У : { О } " , (А 1 1 У ) . s ymb ) ; Console . Wri teLine ( " Выражение У 1 1 А : { О } " , (У 1 1 А) . s ymb ) ; Результат выполнения программы представлен ниже. � Результат выполнен и я про гра ммы ( и з листинга 8 . 9) Выражение А& &В : В Выражение В & & А : В Выражение А & & Е : Е Выражение Е & & А : А Выражение А& &У : У Выражение У & & А : У Выражение А 1 1 В : А Выражение В 1 1 А : А Выражение А 1 1 Е : А Выражение Е 1 1 А : Е Выражение А 1 1 У : А Выражение У 1 1 А : У Класс MyC l a s s имеет одно поле (символьное поле с названием s ymЬ) и конструктор с одним аргументом (определяет значение поля). В клас­ се описаны четыре операторных метода. В теле операторного метода ope rat o r t rue ( ) с помощью оператора выбора проверяется значение символьного поля объекта-операнда. Если это большая английская глас­ ная буква, то метод возвращает результатом t ru e . В противном случае результат равен fa l s e . В теле операторного метода ope r a t o r f a l s e ( ) есть условный опе­ ратор. В условном операторе условием указан объект o b j (аргу­ мент метода). Если объект �истинный» (а это определяется вызо­ вом операторного метода ope r a t o r t ru e ( ) ), то результатом метода ope rator fa l s e ( ) возвращается значение выражения obj . s ymЬ== ' У ' (равно f a l s e за исключением случая, когда значение поля равно ' У ' ). Если объект ob j не является �истинным» , то результатом метода ope rat o r f a l s e ( ) возвращается значение t ru e . 430 П ерегрузка операторо в G) Н А ЗАМ ЕТ КУ Получается , что есл и значением п оля объекта является оди н из сим­ волов ' А ' , ' Е ' , ' I ' , ' О ' , ' U ' или ' У ' , то объект и нтерп рети руется как « исти н н ы й » . Как «ложн ый» интерпрети руется л юбой не я вляю­ щийся « исти н н ы м » объект, а также объект со значением ' У ' сим­ вол ьного пол я . Други м и словам и , есл и у объекта поле равно ' У ' , то такой объект и нтерпрети руется и как «исти н н ы й » , и как «ложны й » . Такой вот парадокс (зако н н ый тем не менее ) . Операторный метод а р е r а t о r & ( ) описан так, что если первый аргумент (объект а класса MyC l a s s ) «истинный», то результатом возвращается ссылка на второй аргумент (объект Ь класса MyC l a s s ). Если первый опе­ ранд не «истинный», то результатом возвращается ссылка на него. Операторный метод ope r a t o r 1 ( ) возвращает ссылку на первый опе­ ранд, если он «истинный» . Если первый операнд не «истинный», то ре­ зультатом метода возвращается ссылка на второй операнд. В главном методе программы создается четыре объекта (название объ­ екта совпадает с символом, являющимся значением символьного поля объекта). Объекты используются для вычисления выражений на основе операторов & & и 1 1 . Для проверки выводится значение поля s уmЬ объ­ екта-результата. � ПОДРОБН ОСТИ Кратко п роанализируем результат выполнения операци й с операто­ рам и & & и 1 1 . • • • При выч ислен и и вы ражения А & & В объект А п роверяется на «лож­ ность» . Объект не «ложн ы й » , поэтому резул ьтатом возвращается значение вы ражения А & В . При выч ислен и и значения вы ражения А & В объект А п роверяется на «исти нность». Он «исти н н ы й » , поэто­ му резул ьтатом возвращается ссылка на объект В . При выч ислен и и вы ражения В & &А на «ложность» п роверяется объ­ ект В. Он «ложн ы й » , поэтому резул ьтатом возвращается ссыл ка на объект В . П р и вычислении выражения А & & Е н а «ложность» п роверяется пер­ вый операнд А. Он не «ложн ы й » , п оэтому резул ьтатом возвраща­ ется значение вы ражения А & Е . При вычислении вы ражения А & Е объект А п роверяется на « исти нность». Объект « исти н н ы й » , и поэ­ тому результатом выражения А&Е является ссылка на объект Е . 43 1 Глава 8 • • • • • • • • • При выч ислении вы ражения Е & &А на «ложность» п роверяется объект Е. Он не «ложн ы й » , и поэтому резул ьтатом вы ражения воз­ вращается вы ражение Е &А. В данном вы ражении на «исти нность» п роверяется объект Е . Он « исти н н ы й » , п оэтому резул ьтатом воз­ вращается ссылка на объект А. При выч ислении вы ражения А & & У проверяется на «ложность» объ­ ект А , и поскол ьку он не «ложн ы й » , то резул ьтатом я вляется значе­ ние вы ражения А & У . Поскол ьку объект А «исти н н ы й » , то значение вы ражения А&У - это ссылка на объект У. При вычислении выражения У & &А на «ложность» проверяется объект У. Он «ложный», поэтому результатом является ссылка на объект У. При вычислен ии вы ражения А 1 1 В объект А проверяется на «исти нность» . Он «истинный», поэтому результатом является ссылка на А. При выч ислении вы ражения В 1 1 А объект В п роверяется на «ис­ тинность». Объект не «исти н н ы й » , поэтому резул ьтат выч исляется как значение вы ражения В 1 А. Поскол ьку объект В не «исти н н ы й » , т о результат последнего вы ражения - ссылка на объект А. Выч исление вы ражения А 1 1 Е нач инается с проверки на « исти н­ ность» объекта А. Он « исти н н ы й » , поэтому ссылка на объект А воз­ вращается как значение вы ражения . При выч ислении вы ражения Е 1 1 А на « исти нность» п роверяется объект Е . Поскол ьку объект Е «исти н н ы й » , ссылка на него возвра­ щается как результат. Вы ражение А 1 1 У выч исляется проверкой на « исти нность» объекта А. Объект « исти н н ы й » . Поэтому резул ьтатом я вляется ссыл ка на объект А . П р и выч ислении значения вы ражения У 1 1 А на « истинность» проверяется объект У . Объект У « исти н н ы й » , и ссылка на него я вляет­ ся значен ием вы ражен и я . П ерегруз ка операторов п ри ведения ти пов Пустите доброго человека ! Пустите доброго человека, а не то он выломает дверь ! из к/ф �А й болит - 66 » Приведение (преобразование) типов состоит в том, что значение одного типа преобразуется в значение другого типа. Есть два типа преобразо­ вания: явное и неявное. При явном преобразовании типа перед значе­ нием (преобразуемым к другому типу) в круглых скобках указывает­ ся название типа, к которому выполняется преобразование. Неявное 432 П ерегрузка операторо в преобразование выполняется автоматически в случаях, когда по кон­ тексту некоторой команды в определенном месте должно быть значение определенного типа, а по факту указано значение другого типа. G) Н А ЗАМ ЕТ КУ Конеч но, м ы исходим из того , что п реобразование возможно в прин­ ци пе. Случаи, когда выполняется неявное преобразование, и правила выпол­ нения явного и неявного преобразований для базовых типов предопре­ делены. Если необходимо выполнять преобразования, в которых исход­ ный тип (значение этого типа преобразуется) или конечный тип (тип, к которому преобразуется значение) относятся к пользовательскому классу, мы можем определить способ такого преобразования, описав в классе соответствующий операторный метод. Операторные методы, определяющие способ преобразования из пользо­ вательского типа или в пользовательский тип, описываются следующим образом и с учетом таких правил. • Операторный метод для выполнения приведения (преобразования) типов описывается как унарный. Аргумент операторного метода отождествляется с преобразуемым значением. • � название» операторного метода состоит из двух �слов» : ключевого слова ope r a t o r и идентификатора типа, к которому выполняется преобразование. При этом тип результата для операторного метода не указывается (он совпадает с идентификатором типа после ключе­ вого слова ope r a t o r). • Один из двух типов (тип аргумента или тип, к которому выполняет­ ся преобразование) должен быть классом, в котором описан опера­ торный метод. • При описании операторного метода для выполнения приведения типа используется ключевое слово imp l i c i t или expl i c i t . Если опе­ ратор описан с ключевым словом impl i c i t , то это оператор для не­ явного приведения типа. Если оператор описан с ключевым словом expl i с i t, то такой оператор определяет явное приведение типа. • Может быть описан операторный метод только для одной формы приведения типа (явной или неявной). Если определено неявное 433 Глава 8 приведение типа, то в таком случае явное приведение также опре­ делено (выполняется тем же операторным методом, что и неявное приведение). Пример, в котором иллюстрируется работа операторных методов для выполнения приведения типов, представлен в листинге 8. 1 0. [1iJ Листинг 8 . 1 О. Перегрузка операторов приведения типа us ing Sys tem; / / Класс с перегрузкой операторов приведения типа : c l a s s MyClas s { puЫ ic int code ; / / Целочисленное поле puЫ ic char s ymb ; / / Символьное поле puЫ ic String text ; / / Текстовое поле / / Конструктор с тремя аргументами : puЫ ic MyCla s s ( int n , char s , String t ) { code=n ; / / Значение числового поля s ymb=s ; / / Значение символь ного поля text=t ; / / Значение текстового поля / / Переопределение метода ToString ( ) : puЫ ic override String ToString ( ) { / / Локаль ная текстовая строка : String tхt=" Поля объекта : \n " ; tхt+="Числовое поле : " +code + " \ n " ; tхt+=" Символьное поле : \ "' + s ymЬ+ " \ ' \n " ; tхt+=" Текстовое поле : \ " " +text+ " \ " \ n " ; txt+=" - - - - - - - - - - - - - - - - - - - - - - - - 11 ; / / Резуль тат метода : return txt ; / / Метод для явного приведения к текстовому типу : puЫ ic static exp l i c i t operator String (MyC l a s s obj ) { return obj . text ; 434 П ерегрузка операторо в 1 1 Метод для неявного приведения к типу int : puЫ ic static imp l i c i t operator int (MyC l a s s obj ) { return obj . code ; 1 1 Метод для неявного приведения к типу char : puЫ ic static imp l i c i t operator char (MyC l a s s obj ) { return obj . symЬ ; 1 1 Метод для неявного преобразования из типа int : puЫ ic static imp l i c i t operator MyClas s ( int n ) { MyC l a s s t=new MyC l a s s ( n , ' В ' , " Bravo " ) ; return t ; 1 1 Метод для явного преобразования из типа char : puЫ ic static exp l i c i t operator MyCl a s s ( char s ) { return new MyCl as s ( З O O , s , " Char l i e " ) ; 1 1 Метод для неявного преобразования из текстового типа : puЫ ic static imp l i c i t operator MyClas s ( String t ) { return new MyCl as s ( t . Length , t [ O ] , t ) ; 1 1 Класс с главным методом : c l a s s Ove rloadingimplExpl Demo { 1 1 Главный метод : static vo id Main ( ) { 1 1 Создание объектов и проверка резуль тата . 1 1 Явное создание объекта : MyC l a s s A=new MyC l a s s ( l O O , ' А ' , "Alpha " ) ; 1 1 Неявно вызывается метод ToString ( ) : Console . Wri teLine ( " Объект А . " +А) ; 435 Гл ава В 11 Создание объекта преобразованием из типа int : MyC l a s s В=2 0 0 ; 1 1 Неявно вызывается метод ToString ( ) : Console . WriteLine ( " Oбъeкт В . " +В ) ; 1 1 Создание объекта преобразованием из типа char : MyC l a s s C= (MyCla s s ) ' С ' ; 1 1 Неявно вызывается метод ToString ( ) : Console . WriteLine ( " Oбъeкт С . " + С ) ; 1 1 Создание объекта преобразованием из текста : MyC l a s s D=" De l ta " ; 1 1 Неявно вызывается метод ToString ( ) : Console . WriteLine ( " Oбъeкт D . " + D ) ; Console . WriteLine ( " Eщe раз поля объекта А : " ) ; 1 1 Явное преобразование в тип int : Console . WriteLine ( "Чиcлo : " + ( int ) A) ; 1 1 Явное преобразование в тип char : Console . WriteLine ( " Символ : " + ( char ) A) ; 1 1 Явное преобразование в текст : Console . WriteLine ( " Teкcт : " + ( String ) A+ " \ n " ) ; Console . WriteLine ( " Paзныe операции : " ) ; 1 1 Целочисленная переменная : int n ; 1 1 Неявное преобразование к типу int : n=A+B ; Console . WriteLine ( " Знaчeниe A+B= " +n ) ; 1 1 Неявное преобразование к типу char : char s=B; Console . WriteLine ( " Cимвoл : " +s ) ; 1 1 Последовательное преобразование из текстового 11 типа к типу MyCl as s , а затем к типу int : Console . WriteLine ( "Число : " + ( in t ) (MyClas s ) "Echo " ) ; 436 П ерегрузка операторо в При выполнении программы получаем такой результат. � Результат выполнения программы (из листинга 8. 1 О) Объект А . Поля объекта : Числовое поле : 1 0 0 Символьное поле : ' А ' Текстовое поле : 11Alpha 11 Объект В . Поля объекта : Числовое поле : 2 0 0 Символьное поле : ' В ' Текстовое поле : 11 Bravo 11 Объект С . Поля объекта : Числовое поле : 3 0 0 Символьное поле : ' С ' Текстовое поле : 11 Cha rlie 11 Объект D . Поля объекта : Числовое поле : 5 Символьное поле : ' D ' Текстовое поле : 11 Del ta 11 Еще раз поля объекта А : Число : 1 0 0 Символ : А Текст : Alpha Разные операции : Значение А+В= З О О Символ : В Число : 4 437 Гл ава В Проанализируем программный код и результат его выполнения. Мы имеем дело с классом MyC l a s s с тремя полями (целочисленным, сим­ вольным и текстовым). У класса есть конструктор с тремя аргументами, в классе переопределен метод T o S t r i n g ( ) , а еще там описано несколь­ ко операторных методов для выполнения приведения типов. Мы опре­ деляем такие способы приведения типов: • При явном преобразовании объекта класса MyC l a s s в значение типа S t r i ng (инструкция exp l i c i t ope r a t o r S t r i n g ( MyC l a s s obj ) в описании метода) результатом является текстовое поле t e x t объекта. • Неявное преобразование объекта класса MyC l a s s в значение типа i n t (инструкция imp l i c i t ope r a t o r i n t ( MyC l a s s ob j ) в опи­ сании метода) состоит в том, что результатом возвращается значение целочисленного поля c o de объекта. • При неявном преобразовании объекта класса MyC l a s s к типу c h a r (инструкция imp l i c i t op e r a t o r c h a r ( MyC l a s s obj ) в описа­ нии метода) результатом возвращается значение символьного поля s ymb объекта. • В программе описан метод для неявного преобразования значе­ ния типа i n t в объект класса MyC l a s s (инструкция imp l i c i t ope r a t o r MyC l a s s ( i n t n ) в описании метода). В теле операторно­ го метода командой MyC l a s s t=new MyC l a s s ( n , ' В ' , " B ravo " ) создается объект класса MyC l a s s , после чего этот объект возвраща­ ется результатом (это и есть результат приведения типов). Таким об­ разом, число, преобразуемое к объекту класса MyC l a s s , определяет значение поля c ode этого объекта. Символьное поле у объекта будет равно ' в ' , а текстовое поле объекта получает значение " B ravo " . • Метод для явного преобразования значения типа char в объект клac­ ca MyC l a s s (инструкция e xp l i c i t ope r a t o r MyC l a s s ( ch a r s ) в описании метода) возвращает результатом объект, который созда­ ется командой new MyC l a s s ( 3 0 0 , s , " Ch a r l i e " ) . Это означает, что преобразуемое к объекту класса MyC l a s s символьное значение задает значение символьного поля s уmЬ созданного объекта, цело­ численное поле c o de этого объекта равно 3 О О, а текстовое поле t e x t объекта есть " Ch a r l i e " . • Метод для неявного преобразования текстового значения в объект класса MyC l a s s (инструкция i mp l i c i t op e r a t o r MyC l a s s ( S t r i n g t ) в описании метода) результатом возвращает 438 П ерегрузка операторо в объект, создаваемый командой nеw MyC l a s s ( t . Lengt h , t [ О ] , t ) . Целочисленное поле этого объекта - количество символов в тексто­ вом значении, преобразуемом к объекту класса MyC l a s s . Символь­ ное поле объекта - это первый символ в тексте. Текстовое поле объ­ екта - собственно текст, преобразуемый к объекту класса MyC l a s s . В главном методе программы проверяется работа операторных методов для выполнения явного и неявного приведения типов. Объект А созда­ ется обычным способом, с вызовом конструктора. Еще три объекта (в, С и D) создаются с использованием операторов приведения типа. Так, при выполнении команды MyC l a s s В = 2 0 0 , которой формально объ­ ектной переменной класса MyC l a s s присваивается целое число, вызы­ вается операторный метод для приведения целочисленного типа к типу MyC l a s s . В результате создается объект, и ссылка на него записывается в переменную В. Похожая ситуация имеет место при выполнении ко­ манды MyC l a s s С= ( MyC l a s s ) ' С ' , но поскольку операторный метод описан для явного приведения типа c ha r к типу MyC l a s s , то в данном случае приведение выполняется явно - перед символьным значением ' С ' , которое присваивается значением объектной переменной, указана инструкция ( МуС 1 а s s ) явного приведения типа. � ПОДРОБН ОСТИ Есл и в команде MyC l a s s С= ( MyC l a s s ) ' С ' не испол ьзовать инструк­ ци ю явного при ведения типа (то есть есл и воспол ьзоваться коман­ дой MyC l a s s С= ' С ' ) , то п роизойдет следующее . Неявного п реобра­ зован ия из типа char в тип MyC l a s s нет. Зато есть автоматическое ( встроенное) п р иведение типа char к типу i n t . А в классе MyC l a s s оп исан оператор нея вного при ведения ти па i n t к типу MyC l a s s . По­ этому при выполнении команды MyC l a s s С= ' С ' сим вол ьное зна­ чение ' С ' будет сначала п реобразовано к типу int (значение 6 7 ) , а затем будет задействован оператор неявного при ведения типа int к ти пу МуС l а s s . Еще один объект создан командой MyC l a s s D= " De l t a " . Здесь объ­ ектной переменной значением присваивается текст, поэтому вызывает­ ся оператор неявного приведения значения класса S t r i n g к значению класса MyC l a s s : создается объект, и ссылка на него записывается в пе­ ременную D. В классе MyC l a s s описан метод T o S t r i n g ( ) и оператор явного при­ ведения к текстовому типу. Результат у них разный. При выполнении 439 Гл ава В команд вида " Объект А . " +А, когда к тексту прибавляется объект класса MyC l a s s , для преобразования объекта в текст вызывается опера­ тор T o S t r i ng ( ) . Примером явного приведения объекта класса MyC l a s s к текстовому формату может быть инструкция ( S t r i n g ) А. Здесь ин­ струкция приведения к типу S t r i n g указана явно перед именем объекта. G) Н А ЗАМ ЕТ КУ У созданного объекта значение поля code равно 5 ( кол ичество сим­ волов в тексте " De l t a " ) , значение поля s ymЬ равно ' О ' ( первый сим вол в тексте " De l t a " ) , а значение поля text равно " De l t a " . Инструкции ( i n t ) А и ( ch a r ) А являются примером явного преобра­ зования объекта класса MyC l a s s в значение типа i n t и значение типа c h a r соответственно. А вот когда выполняется команда n=A+B, кото­ рой целочисленной переменной n значением присваивается сумма двух объектов класса MyC l a s s , выполняется автоматическое (неявное) пре­ образование объектов к целочисленному типу. В итоге значение пере­ менной n это сумма целочисленных полей объектов А и В. Неявное преобразование к типу c ha r имеет место и при присваивании символь­ ной переменной s значением объекта В (команда c h a r s =B). - � ПОДРОБН ОСТИ Есл и м ы описал и оператор явного при ведения ти па, то при ведение типа может выполняться тол ько в явном виде . Есл и оп исан оператор неявного при ведения типа, то при ведение типа может выполнять­ ся и в явном , и в неявном виде . Хотя в п рограмме описан оператор явного при ведения к текстовому ти пу, неявное п реобразование так­ же выполняется . Но в этом случае вызы вается переоп ределенный в классе MyC l a s s метод T o S t r i n g ( ) . Еще одна инструкция, достойная внимания, имеет вид ( i n t ) ( МуС 1 а s s ) " E c h o " . Здесь имеют место два последовательных явных приведе­ ния типа. Сначала текст приводится к типу MyC l a s s (инструкция ( MyC l a s s ) " E c h o " ). Результатом является объект класса MyC l a s s . Его целочисленное поле c o de имеет значение 4 (количество символов в тексте " E ch o " ), значение поля s ymb равно ' Е ' (первый символ в тек­ сте " E ch o " ), а текстовое поле t e x t имеет значение " E cho " . При явном приведении этого объекта к целочисленному типу получаем значение 4 (значение поля c o de объекта). 440 П ерегрузка операторо в Команды п р исва и ва н ия и перегруз ка операто ров - Но ведь т ы ж е обещал ! - Ну мало ли что я наобещаю ! З наешь, какой я подлый ? Я обещаю одно, а делаю совершен­ но другое ! из к/ф �А й болит - 66 » В начале главы утверждалось, что сокращенные формы оператора при­ сваивания ( +=, * = , / = и так далее) не перегружаются, но можно так пе­ регрузить базовые операторы (например, +, * или / ), что сокращенные формы операторов можно будет использовать в программе. Главный критерий здесь состоит в том, чтобы тип результата, возвращаемого опе­ раторным методом для базового оператора, соответствовал типу пере­ менной, которой присваивается значение с помощью сокращенной фор­ мы оператора присваивания. Но и здесь не все так очевидно. Как пример, иллюстрирующий сказанное, рассмотрим программу в листинге 8. 1 1 . � Л истинг 8. 1 1 . Операции присваивания и перегрузка операторов using Sys tem; / / Класс с перегрузкой операторов : c l a s s MyClas s { / / Целочисленное поле : puЫ i c int code ; / / Конструктор с одним аргументом : puЫ i c MyCl a s s ( int n ) { code=n ; / / Присваивание значения полю / / Операторный метод для сложения объектов : puЫ i c static MyC l a s s ope rator+ ( MyC l a s s а , MyC l a s s Ь ) { return new MyCl as s ( a . code+b . code ) ; / / Операторный метод для умножения объектов : puЫ i c static int operator* (MyC l a s s а , MyC l a s s Ь ) { 44 1 Гл ава В return a . code*b . code ; 1 1 Операторный метод для неявного приведения типа 11 ( из типа int к типу MyClas s ) : puЫ i c static imp l i c i t operator MyClas s ( int n ) { return new MyC l a s s ( n ) ; 1 1 Класс с главным методом : c l a s s OverloadingAndAs s igningDemo { s tatic void Mai n ( ) { 1 1 Создание объекта : MyC l a s s A=new MyC l a s s ( 7 ) ; 1 1 Проверка резуль тата : Console . WriteLine ( " Oбъeкт А : { 0 ) " , A . code ) ; 1 1 Создание объекта : MyC l a s s B=new MyC l a s s ( 8 ) ; 1 1 Проверка резуль тата : Console . WriteLine ( " Oбъeкт В : { 0 ) " , B . code ) ; 1 1 Вычисление суммы объектов с использованием 11 сокращенной формы оператора присваивания : А+=В ; 1 1 Проверка резуль тата : Console . WriteLine ( " Oбъeкт А : { 0 ) " , A . code ) ; 1 1 Вычисление произведения объектов с использованием 11 сокращенной формы оператора присваивания : А*=В ; 1 1 Проверка резуль тата : Console . WriteLine ( " Oбъeкт А : { 0 ) " , A . code ) ; Результат выполнения программы такой, как показано ниже. 442 П ерегрузка операторо в [1i!J Результат выполнения программы ( из листинга 8. 1 1 ) Объект А : 7 Объект В : 8 Объект А : 1 5 Объект А : 1 2 0 В классе MyC l a s s ( с числовым полем и конструктором с одним аргу­ ментом) описаны операторные методы для вычисления суммы объек­ тов класса MyC l a s s , произведения объектов класса MyC l a s s , а также оператор для неявного приведения целочисленного значения к объекту класса MyC l a s s . В главном методе программы создаются два объекта А и В класса MyC l a s s , после чего выполняются команды А+=В и А * =В. При выполнении команды А+=В сначала вычисляется сумма А+ В. Резуль­ татом является объект, целочисленное поле которого равно сумме полей объектов А и В. Ссьшка на этот объект записывается в переменную А. При выполнении команды А * =В сначала вычисляется произведение А *В. Значением этого выражения является целое число, равное произведению значений полей объектов А и В. Вообще, число присвоить значением объ­ ектной переменной нельзя. Но в нашем случае в классе MyC l a s s описан оператор неявного приведения типа i n t к типу MyC l a s s . Этот оператор­ ный метод будет задействован, и на основе числового значения А *В созда­ ется объект, а ссьшка на этот объект записывается в переменную А. Р ез ю ме Солнце есть. Песок есть. Притяжение есть. Где мы ? Мы на З емле. из к/ф �кин-дза-дза» • • В языке С# можно определять действие операторов на операнды, являющиеся объектами пользовательских классов. Этот механизм называется перегрузкой операторов и реализуется путем описания в пользовательском классе операторных методов. Операторный метод описывается с ключевыми словами рuЫ i с и static то есть метод должен быть открытым и статическим. Операторный метод обязательно должен возвращать результат. - 443 Гл ава В Название операторного метода получается объединением ключевого слова o p e r a t o r и символа оператора. Аргументы операторного ме­ тода отождествляются с операндами выражения на основе перегру­ жаемого оператора. У операторных методов для бинарных операто­ ров должно быть два аргумента, у операторных методов для унарных операторов - один аргумент. Хотя бы один аргумент операторного метода должен быть объектом класса, в котором этот операторный метод описан. • Операторы сравнения перегружаются парами (< и >, < = и >=, == и ! = ). При перегрузке операторов == и ! = обычно переопределяют и методы E qu a l s ( ) и G e t H a s hC ode ( ) . • Операторы t ru e и f a l s e используются для проверки объектов на «истинность� и «ложность� . Операторы перегружаются в паре и должны возвращать результатом логическое значение. Оператор t ru e вызывается, если объект указан условием в условном опера­ торе или операторе цикла, а также при проверке первого операнда в выражении на основе оператора 1 1 . Оператор f a l s e вызывается при проверке первого операнда в выражении на основе оператора & & . • Операторы & & и 1 1 не перегружаются. Н о есть способ так перегру­ зить операторы & , 1 , t ru e и f a l s e , что операторы & & и 1 1 можно будет использовать. • Можно описать операторные методы для выполнения явного и неяв­ ного приведения типов (при условии, что одним из типов является пользовательский класс). Операторы для явного приведения типов описываются с ключевым словом е хр 1 i с i t, операторы для неявно­ го приведения типов описываются с ключевым словом imp l i c i t. • Сокращенные формы операторов присваивания не перегружают­ ся. Но можно так перегрузить базовые операторы, что сокращенные формы операторов также будут рабочими. 444 П ерегрузка операторо в Задания для самостоятел ьной работы - Мы договорились? - Да, нринц ! - З начит, я ставлю ультиматум. - Да. А я захожу сзади. из к/ф �Формула лю бви» 1 . Напишите программу, в которой есть класс с символьным полем и следующими перегруженными операторами: оператором инкремента + + и декремента - - , бинарным оператором �плюс» + и бинарным опе­ ратором �минус» - . Правила перегрузки операторов такие. Применение оператора инкремента к объекту приводит к тому, что поле значением по­ лучает следующий (по отношению к текущему значению) символ в ко­ довой таблице. Применение оператора декремента приводит к тому, что поле получает значением предыдущий (по отношению к текущему значе­ нию) символ в кодовой таблице. Значением выражения на основе опера­ тора инкремента/декремента является ссьmка на объект-операнд. Бинар­ ный оператор �плюс» можно применять для вычисления суммы объекта и целого числа, а также суммы целого числа и объекта. В обоих случа­ ях результатом возвращается новый объект, значение символьного поля которого определяется прибавлением целого числа (один из операндов) к коду символа из объекта-операнда. С помощью бинарного оператора �минус» можно вычислять разность двух объектов. Результатом являет­ ся целое число - разность кодов символов из объектов-операндов. 2 . Напишите программу, в которой есть класс с полем, являющим­ ся ссылкой на одномерный целочисленный массив. У класса есть кон­ структор с одним целочисленным аргументом, определяющим размер массива. При создании объекта все элементы массива получают нулевые значения. В классе перегружаются следующие операторы. Унарный опе­ ратор � перегружен таким образом, что результатом возвращается тек­ стовая строка со значениями элементов массива (на который ссылает­ ся поле объекта, к которому применяется оператор). Унарный оператор инкремента + + перегружен так, что его применение к объекту приводит к добавлению в массив нового элемента с нулевым значением. Результа­ том возвращается ссьmка на объект-операнд. При применении к объекту оператора декремента - - из массива удаляется один элемент (например, последний), а результатом возвращается ссылка на объект-операнд. Би­ нарный оператор сложения + должен быть определен так, чтобы можно 445 Гл ава В было вычислять сумму двух объектов, объекта и числа, а также числа и объекта. Во всех случаях результатом возвращается новый объект. Если в операции участвуют два объекта-операнда, то в объекте-резуль­ тате массив формируется объединением массивов складываемых объек­ тов. Если вычисляется сумма объекта и числа, то в объекте-результате массив получается добавлением нового элемента к массиву из объек­ та-операнда. Значение добавляемого элемента определяется значением числа-операнда. Если к числу прибавляется объект, то новый элемент добавляется в начало массива. Если к объекту прибавляется число, то новый элемент добавляется в конец массива. 3 . Напишите программу, в которой есть класс с двумя целочисленными полями. Опишите для этого класса операторные методы, которые позво­ ляют сравнивать объекты класса на предмет «меньше» или «больше» . Исходите и з того, что один объект меньше/больше другого, если сумма квадратов значений его полей меньше/больше суммы квадратов значе­ ний полей другого объекта. 4. Напишите программу, в которой есть класс с целочисленным полем и текстовым полем. Выполните перегрузку всех операторов сравнения. Сравнение на предмет «больше» или «меньше» выполняется на основе сравнения длины текстовых значений (имеются в виду текстовые поля сравниваемых объектов). При сравнении на предмет «больше или рав­ но» или «меньше или равно» сравниваются значения целочисленных полей объектов. При сравнении на предмет «равно» или «не равно» сравниваются и целочисленные, и текстовые поля объектов. Также пред­ ложите способ переопределения методов E qu a l s ( ) и G e t H a s h C o de ( ) . 5. Напишите программу; в которой есть класс с целочисленным полем и сим­ вольным полем. Перегрузите операторы t rue и fal s e так, чтобы «истин­ ным» считался объект, у которого разность значения целочисленного поля и кода символа из символьного поля не превьШiает величину 1 О . Исполь­ зуйте объект данного класса (в качестве условия в условном операторе) для того, чтобы отобразить последовательность символов в консольном окне. 6. Напишите программу, в которой есть класс с целочисленным полем. Перегрузите операторы & , 1 , t ru e и f a l s e так, чтобы с объектами клас­ са можно бьшо использовать операторы & & и 1 1 . Перегрузку следует ре­ ализовать так, чтобы объект считался «истинным», если значение его числового поля равно 2 , 3, 5 или 7 . Объект должен рассматриваться как «ложный», если значение его числового поля меньше 1 или больше 1 О . 446 П ерегрузка операторо в 7. Напишите программу, в которой есть класс с текстовым полем. Опи­ шите в классе операторные методы для выполнения приведения типов. Необходимо определить следующие способы преобразований. При пре­ образовании объекта в целое число результатом возвращается количе­ ство символов в значении текстового поля. При преобразовании объекта в символ результатом является первый символ в тексте. При преобразо­ вании числа в объект создается (и возвращается результатом соответ­ ствующего операторного метода) объект, текстовое поле которого содер­ жит текстовую строку из символов ' А ' . Количество символов в тексте определяется преобразуемым числом. 8. Напишите программу, в которой есть класс с полем, являющим­ ся ссылкой на целочисленный массив. Опишите в классе операторные методы для выполнения приведений типов. Необходимо реализовать следующие правила приведения типов. При преобразовании объекта в текст возвращается текстовая строка со значениями элементов мас­ сива. При преобразовании объекта в число возвращается сумма элемен­ тов массива. При преобразовании числа в объект результатом является новый объект, размер массива в котором определяется преобразуемым числом. Массив в объекте должен быть заполнен нулями. 9. Напишите программу, в которой есть класс с целочисленным полем и перегрузкой операторов +, - и * . Предложите такой способ перегруз­ ки этих операторов, чтобы с объектами класса можно бьшо использовать операторы +=, - = и * = . 1 0 . Напишите программу, в которой есть класс с символьным полем и перегрузкой операторов + и - . Операторы должны быть перегруже­ ны так, чтобы применение оператора + к объектам класса давало резуль­ татом текст, получающийся объединением значений символьных полей суммируемых объектов. При применении оператора - к объектам класса результатом должно возвращаться целое число (разность кодов симво­ лов из вычитаемых объектов). Предложите такие способы перегрузки операторов приведения типа, чтобы с объектами класса можно было ис­ пользовать операторы += и - = . Гл ава 9 СВО Й СТВА И И Н Д ЕКСАТО Р Ы - Друг, у вас какая система ? Разрешите взгля­ нуть ? - Система обычная. Нажал на кнопку и дома. из к/ф �кин-дза-дза» Эта глава посвящена двум достаточно экзотическим членам классов, ко­ торые встречаются (и очень часто используются) в языке С#. Речь пой­ дет о свойствах и индексаторах. В частности, нам предстоит узнать: • что такое свойство и как оно описывается; • как определить режимы доступа к свойству ; • что такое индексатор и как он описывается ; • как задается режим использования индексатора ; • как создается многомерный индексатор. Также мы рассмотрим немалое количество примеров, объясняющих и иллюстрирующих пользу, красоту и эффективность свойств и индек­ саторов. Знакомство со сво йствами Пусть они посмотрят, сколько у меня всякого добра ! О, у меня еще в чулане сколько ! из к/ф �А й болит - 66 » Мы начинаем знакомство со свойствами. Свойство - это член клас­ са, как поле или метод. Но это и не поле, и не метод. Свойство в язы­ ке С# - это нечто среднее между полем и методом. Если исходить из того, как свойство используется, то оно, конечно, очень напоминает 448 С в ойст ва и индекс аторы поле. Но принципы реализации свойства больше напоминают описание методов. Чтобы бьто легче понять, как свойство описывается в классе, начнем с того, как оно используется. Используется свойство, как отмечалось выше, практически так же, как поле. Для большей конкретики представим, что существует некий объ­ ект и у этого объекта есть поле (например, числовое). Что мы можем де­ лать с этим полем? В принципе, есть две базовые операции: считывание значения поля и присваивание значения полю. И в том, и в другом слу­ чае мы указываем имя объекта и название поля (через точку). Реакция на такую инструкцию достаточно простая: выполняется обращение к об­ ласти памяти, в которой хранится значение поля. Если в команде значе­ ние поля считывается, то это значение будет прочитано в соответству­ ющей области памяти. Если значение полю присваивается, то значение будет записано в соответствующую область памяти. Подчеркнем, что все это для поля. В случае со свойством формально все выглядит очень по­ хоже. Как и у поля, у свойства есть тип и есть название. Если мы хотим узнать значение свойства, то действуем подобно случаю с полем: после имени объекта через точку указывается название свойства. Если мы хо­ тим присвоить значение свойству, то указываем имя объекта, название свойства (через точку) и, после оператора присваивания, присваиваемое значение. То есть все, как и в случае с полем. В чем же разница? А разни­ ца в действиях, которые выполняются при считывании значения свой­ ства и при присваивании значения свойству. При описании свойства определяются два специальных метода, которые непосредственно связаны со свойством. Эти методы называются аксес­ сорами. Один метод (называется gеt-аксессором) вызывается при счи­ тывании значения свойства. Другой метод (называется sеt-аксессором) вызывается при присваивании значения свойству. Проще говоря, при считывании значения свойства и при присваивании значения свойству вызываются специальные методы (в отличие от случая с полем, когда об­ ращение к полю означает обращение к области памяти, в которой хранит­ ся значение поля). Что будет происходить при вызове методов-аксессо­ ров, определяем мы, когда описываем свойство в классе. Это может быть банальное считывание/присваивание значения некоторому полю, опре­ деленный вычислительный процесс, обращение к массиву или другому объекту - все зависит от нашей фантазии и той задачи, которая решается. В этом смысле свойство близко к методу, причем не одному методу, а сра­ зу двум методам. Считывание значения свойства означает вызов метода, возвращающего значение (значение свойства). Присваивание значения 449 Гл ава 9 свойству подразумевает вызов другого метода, который может присваи­ вать значения полям или выполнять иные полезные действия. G) Н А ЗАМ ЕТ КУ Существуют свойства, доступ ные тол ько для чтения или тол ько для зап иси . То есть свойство может быть таки м , что можно узнать его значение, но нел ьзя присвоить значение свойству. А может быть и наоборот: свойству можно п рисвоить значение, но нел ьзя узнать значение свойства . Описывается свойство немного сложнее, чем поле (оно и понятно в общем случае со свойством связано два метода-аксессора, которые также нужно описать при описании свойства). Вначале указывается тип и название свойства (как и при описании поля), затем в блоке в фигур­ ных скобках описываются методы-аксессоры. Эти методы (их обычно два) описываются специальным образом. G) Н А ЗАМ ЕТ КУ Свойство может быть и открыты м , и закрытым ( п ри оп исании свой­ ства можно испол ьзовать кл ючевое слово, определя ющее уровен ь доступа) , а также свойство может быть статически м . Метод, вызываемый при считывании значения свойства ( g e t -aкceccop ) , описывается следующим образом: указывается ключевое слово g e t , по­ сле которого в фигурных скобках описываются команды, выполняемые при считывании значения свойства. То есть никаких формальных атри­ бутов метода (тип результата, название, список аргументов) здесь нет. Объяснение простое. Уникальное название методу не нужно, посколь­ ку метод «самостоятельной ценности� не имеет и вызывается при об­ ращении к свойству (то есть имя свойства «замещает� имя метода). Ар­ гументы методу не нужны по определению, поскольку при считывании значения свойства никакие дополнительные значения или атрибуты не используются. А вот результат метод возвращает (хотя идентифи­ катор типа результата явно никак не обозначен). Возвращаемый g е t ­ аксессором результат - это значение свойства. Поэтому тип результата g et-aкceccopa совпадает с типом свойства. Метод, который вызывается при присваивании значения свойству ( s е t ­ аксессор ), описывается таким образом: указывается ключевое слово 450 С в ойст ва и индекс аторы s e t , а в фигурных скобках описываются команды, выполняемые при присваивании значения свойству. Данный метод-аксессор не возвраща­ ет результат, и у него нет аргументов. Но один параметр методу все же нужен: это значение, которое присваивается свойству. Поскольку ар­ гумента у метода нет, то аргументом данное значение в метод передать нельзя. Поэтому присваиваемое свойству значение в s е t -аксессор пере­ дается с помощью ключевого слова val u e . Это ключевое слово, которое в блоке описания s e t-aкceccopa отождествляется со значением, присва­ иваемым свойству. Ниже представлен шаблон, в соответствии с которым описывается свой­ ство (основные элементы шаблона выделены жирным шрифтом): тип имя{ qet{ 1 1 Код get - aкceccopa set{ 1 1 Код set - aкceccopa При описании свойства можно определить не два, а только один ак­ сессор: или g e t -aкceccop, или s e t-aкceccop. Если для свойства опре­ делен только g e t -aкceccop, то значение такого свойства можно прочи­ тать, но такому свойству нельзя присвоить значение. Если для свойства определен только s e t -aкceccop, то значение свойству можно присвоить, но нельзя его прочитать. G) Н А ЗАМ ЕТ КУ Далее м ы рассмотрим разные способы определения свойств , вклю­ чая и свойства с одним аксессором . Пока же замети м , что примером свойства, доступ ного только для чтения, но не доступного для присва­ ивания , может быть свойство Length у масси вов и текстовых строк. Еще одна важная особенность свойства состоит в том, что под свойство место в памяти не выделяется. То есть из того факта, что мы описали свойство, не следует, что при создании объекта под это свойство вы­ деляется память. В этом смысле свойство принципиально отличается 45 1 Гл ава 9 от поля. Поэтому если мы собираемся при работе со свойством запо­ минать и использовать некоторое значение, то нам придется для этого специально описать поле (обычно закрытое). Фактически свойство это некая �ширма�, за которой может �скрываться� все, что угодно. G) Н А ЗАМ ЕТ КУ Поскол ьку свойство не оп ределяет область памяти и н е связано с областью памяти , то свойство нел ьзя испол ьзовать с идентифика­ торам и re f и out . В листинге 9. 1 представлена программа, в которой есть класс, а в этом классе есть свойство. � Л истинг 9 . 1 . Знакомство со свойствами us ing Sys tem; 11 Класс со свойством : c l a s s MyClas s { / / Закрытые целочисленные поля : private int num ; private int min ; private int max ; / / Конструктор с двумя аргументами : puЫ ic MyC la s s ( int а , int Ь ) { / / Присваивание значения полям : min=a ; max=b ; / / Присваивание значения свойству : code= ( max+mi n ) / 2 ; / / Описание целочисленного свойства : puЫ ic int code { / / Метод вызывается при считывании значения свойства : get { / / Значение свойства : 452 С в ойст ва и индекс аторы return num; 11 Метод вызывается при присваивании 1 1 значения свойству : set { 1 1 Если присваиваемое значение меньше минимально 11 допустимого : i f ( va lue<mi n ) num=min ; 1 1 Если присваиваемое значение больше максимально 11 допустимого : e l s e i f ( va lue>max) num=max ; 1 1 Если присваиваемое значение попадает в 1 1 допустимый диапазон : e l s e num=value ; 1 1 Класс с главным методом : c l a s s Us ingPropsDemo { 1 1 Главный метод : s tatic vo id Main ( ) { 1 1 Создание объекта : MyC l a s s obj =new MyC l a s s ( l , 9 ) ; 1 1 Проверка значения свойства : Console . WriteLine ( " Cвoйcтвo code : " +obj . code ) ; 1 1 Присваивание значения свойству : obj . code=7 ; 1 1 Проверка значения свойства : Console . WriteLine ( " Cвoйcтвo code : " +obj . code ) ; 1 1 Присваивание значения свойству : obj . code=2 0 ; 1 1 Проверка значения свойства : 453 Гл ава 9 Console . WriteLine ( " Cвoйcтвo code : " +obj . code ) ; 1 1 Присваивание значения свойству : obj . code= - 1 0 ; 1 1 Проверка значения свойства : Console . WriteLine ( " Cвoйcтвo code : " +obj . code ) ; Результат выполнения программы такой. � Результат выполнения п рограм мы (из листи нга 9 . 1 ) Свойство code : 5 Свойство code : 7 Свойство code : 9 Свойство code : 1 В программе мы описываем класс с названием MyC l a s s . В классе опи­ саны три целочисленных закрытых поля num, m i n и max. В классе есть конструктор с двумя аргументами (код конструктора мы осудим поз­ же). Но наиболее интересное для нас место в классе - описание свой­ ства с названием code. Свойство описано с идентификаторами puЬl i c и i n t , что означает, что свойство открытое и целочисленное. Свойство описано с двумя методами-аксессорами. В теле ge t-аксессора всего одна команда r e t u r n num, которой значением свойства возвращается зна­ чение поля num. � ПОДРОБН ОСТИ Поскол ьку свойство code оп исано как такое, что относится к типу int , то get-aкceccop, описан н ы й для этого свойства , должен воз­ вращать значение типа i n t . При присваивании значения свойству c o de будет вызываться s e t ­ aкceccop, описанный для свойства. В соответствии с кодом этого аксес­ сора выполняются вложенные условные операторы. В этих условных операторах проверяется присваиваемое свойству значение (это значе­ ние отождествляется с ключевым словом va l ue ). Если это значение меньше значения поля m i n (условие v a l u e < m i n истинно), то поле num 454 С в ойст ва и индекс аторы получает значение m i n (команда num=min). Если присваиваемое свой­ ству значение больше значения поля max (условие va l u e >max истин­ но), то полю n um присваивается значение max (команда n um=max ). Ина­ че (если присваиваемое свойству значение попадает в диапазон значений от m i n до max) полю num присваивается то значение, которое собствен­ но указано справа от оператора присваивания (команда num=va l ue ). � ПОДРОБН ОСТИ М ы не объя вляли переменную с названием va lue . Это кл ючевое слово, обозначающее присваиваемое свойству значение. Вполне разумно отождествлять идентифи катор va lue с некоторой пере­ менной . П ричем у переменной есть ти п - это тип свойства, которо­ му п р исваи вается значение. В нашем при мере va l ue отождествля ­ ется с целочисленным значением (тип i nt ) . Что в итоге получается? Получается, что при запросе значения свойства c o de в действительности возвращается значение поля num. Само поле num закрытое, поэтому вне кода класса значение этому полю напрямую присвоить не удастся. Значение поля num изменяется при присваивании значения свойству c o de . Алгоритм присваивания не сводится к копи­ рованию присваиваемого значения в поле num. Мы хотим, чтобы значе­ ние поля num не могло быть меньше значения поля m i n , а также чтобы оно не могло быть больше значения поля max. Поэтому если присваи­ ваемое свойству c o de значение попадает в диапазон значений от m i n д о max, т о поле n u m получает т о значение, которое реально присваива­ ется. Если же присваиваемое значение выходит за пределы допустимо­ го диапазона значений, то поле num получает минимально/максимально возможное значение (в зависимости от того, за какую границу �выска­ кивает� присваиваемое значение). Что касается конструктора класса MyC l a s s , то два его аргумента опре­ деляют минимальное и максимальное значение для свойства c o de (зна­ чения полей m i n и max), а командой c o de = ( ma x +m i n ) / 2 свойству c o de (а значит, и полю num) присваивается начальное значение. G) Н А ЗАМ ЕТ КУ После создания объекта класса MyC l a s s значения п олей min и max этого объекта уже измен ить нельзя . Значение поля num изменяется при п рисваиван и и значения свойству code объекта . 455 Гл ава 9 В главном методе программы командой МуС l а s s obj =new MyC l a s s ( 1 , 9 ) создается объект obj класса MyC l a s s . Таким образом, значение свой­ ства c o de объекта оЬ j не может выходить за пределы значений от 1 до 9 включительно. Начальное значение свойства c o de равно 5 (поскольку ( 1 +9)/2=5). При выполнении команды o b j . c o d e = 7 свойство c o de (поле num) получит значение 7 . Выполнение команды o b j . c o de = 2 О приводит к тому, что свойство c o de (поле num) получит значение 9 . На­ конец, выполнение команды obj . c o de = - 1 О приведет к тому, что свой­ ство c o de (поле num) получит значение 1 . G) Н А ЗАМ ЕТ КУ Подумайте , что п роизойдет, есл и п р и создании объекта класса MyC l a s s значение первого аргумента конструктора будет бол ьше значения второго аргумента конструктора . Предложите способ ре­ организации программного кода конструктора , чтобы при создании объекта класса MyC l a s s значение поля max не могло быть меньше значения п оля min . И спол ьзование свойств - Куда вы меня несете? - Навстречу твоему счастью. из к/ф �Ирония судьбы, WlU С легким паром» В этом разделе мы проанализируем несколько примеров, содержащих классы, в которых описываются и используются свойства. Сначала рас­ смотрим пример, представленный в листинге 9.2. [1i!] л истинг 9 . 2 . Испол ьзование свойства us ing Sys tem; 11 Класс со свойством : c l a s s MyClas s { 1 1 Закрытые целочисленные поля : private int f i r s t ; private int l a s t ; 1 1 Конструктор с двумя аргументами : 456 С в ойст ва и индекс аторы puЫ ic MyCla s s ( int а, int Ь) { f i r s t=a; / / Значение первого поля l a s t=b ; / / Значение второго поля / / Переопределение метода ToString ( ) : puЫ ic override s tring ToString ( ) { / / Формирование текстового значения : s t ring tхt=" Поля объекта : " ; txt+=fi r s t + " и " + l a s t ; / / Результат метода : return txt ; / / Целочисленное свойство : puЫ ic int number { / / Метод вызывается при считывании значения свойства : get { / / Запоминается значение второго поля : int t=la s t ; / / Новое значение второго поля : l a s t= f i r s t ; / / Новое значение первого поля : f i r st=t ; / / Значение свойства : return t ; / / Метод вызывается при присваивании / / значения свойству : set { / / Новое значение второго поля : l a s t= f i r s t ; / / Присваивание значения первому полю : f i r st=value ; 457 Гл ава 9 1 1 Класс с главным методом : c l a s s MoreProps Demo { 1 1 Главный метод : s tatic vo id Main ( ) { 11 Создание объекта : MyC l a s s obj =new MyC l a s s ( l , 9 ) ; 1 1 Проверка значений полей объекта : Console . WriteLine ( obj ) ; 1 1 Проверка значения свойства : Console . WriteLine ( " Cвoйcтвo numЬer : { 0 } " , obj . numЬer ) ; 1 1 Проверка значений полей объекта : Console . WriteLine ( obj ) ; 1 1 Присваивание значения свойству : obj . numЬer=S ; 1 1 Проверка значений полей объекта : Console . WriteLine ( obj ) ; 1 1 Проверка значения свойства : Console . WriteLine ( " Cвoйcтвo numЬer : { 0 } " , obj . numЬer ) ; 1 1 Проверка значений полей объекта : Console . WriteLine ( obj ) ; 1 1 Проверка значения свойства : Console . WriteLine ( " Cвoйcтвo numЬer : { 0 } " , obj . numЬer ) ; 1 1 Проверка значений полей объекта : Console . WriteLine ( obj ) ; 1 1 Присваивание значения свойству : obj . numЬer=З ; 1 1 Проверка значений полей объекта : Console . WriteLine ( obj ) ; 458 С в ойст ва и индекс аторы Ниже представлен результат данной программы. [1i!J Результат выполнения п рограм мы (из листи нга 9 . 2 ) Поля объекта : 1 и 9 Свойство numЬer : 9 Поля объекта : 9 и 1 Поля объекта : 5 и 9 Свойство numЬer : 9 Поля объекта : 9 и 5 Свойство numЬer : 5 Поля объекта : 5 и 9 Поля объекта : 3 и 5 В программе описан класс MyC l a s s с двумя закрытыми полями цело­ численного типа f i r s t и l a s t , конструктором с двумя аргументами и свойством numb e r . При вызове конструктора аргументы, переданные ему, присваиваются значениями полям объекта. Свойство numb e r реа­ лизовано таким образом, что мы можем и прочитать значение свойства, и присвоить значение свойству. Но последовательность операций, кото­ рые выполняются при считывании значения свойства и при присваива­ нии значения свойству, не самая тривиальная. А именно, если мы счи­ тываем значение свойства numb e r , то значением возвращается текущее значение поля l a s t , но при этом свойства f i r s t и l a s t меняются зна­ чениями. Если мы присваиваем значение свойству numb e r , то присва­ иваемое значение записывается в поле f i r s t , а текущее значение поля f i r s t присваивается полю l a s t. В главном методе программы командой МуС l а s s obj =new MyC l a s s ( 1 , 9 ) создается объект o b j , поля f i r s t и l a s t которого получают значе­ ния 1 и 9 соответственно. Проверка с помощью команды C o n s o l e . W r i t e L i n e ( ob j ) подтверждает это. G) Н А ЗАМ ЕТ КУ В классе MyC l a s s переоп ределен метод T o S t r i ng ( ) , который ре­ зул ьтатом возвращает текстовую строку со значения м и полей f i r s t и l a s t объекта . Также напом н и м , что идентификатор s t ring я вляет­ ся псевдонимом для вы ражения S y s tem . S t ring . 459 Гл ава 9 При проверке значения свойства numb e r (инструкция o b j . numb e r ) значение свойства равно 9 , а поля f i r s t и l a s t обмениваются значе­ ниями (новые значения 9 и 1 ). При присваивании значения 5 свойству numЬer (команда obj . numЬer=5) поле f i r s t получает значение 5, а значение 9 присваивается полю l a s t. Проверка свойства numb e r дает 9, а значения полей объекта ста­ нут равны 9 и 5 (при проверке значения свойства поля обмениваются значениями). Если мы еще раз проверим свойство numb e r, его значение будет равно 5 , а значения полей станут равны 5 и 9. Далее выполняется команда obj . numbe r = З , вследствие выполнения которой поля получа­ ют значения 3 и 5 (поле f i r s t получает значение 3, а �старое� значе­ ние 5 поля f i r s t присваивается значением полю l a s t). В листинге 9.3 представлена программа, в которой есть класс со свой­ ством, для которого описан только get-aкceccop, а s e t-aкceccop не опре­ делен. Поэтому такое свойство доступно только для чтения, но не для присваивания значения. � Л истинг 9 . 3 . Свойство без set-aкceccopa us ing Sys tem; 11 Класс со свойством : c l a s s MyClas s { / / Открытые целочисленные поля : puЫ ic int f i r s t ; puЫ ic int l a s t ; / / Конструктор с двумя аргументами : puЫ ic MyC la s s ( int а , int Ь ) { f i r s t=a ; / / Значение первого поля l a s t=b ; / / Значение второго поля / / Целочисленное свойство (доступно только для чтения ) : puЫ ic int sum { / / Метод вызывается при считывании значения свойства : get { / / Значение свойства : 460 С в ойст ва и индекс аторы return first+las t ; 1 1 Класс с главным методом : c l a s s WithoutSetDemo { 1 1 Главный метод : s tatic vo id Main ( ) { 1 1 Создание объекта : MyC l a s s obj =new MyC l a s s ( l , 9 ) ; 1 1 Проверка значения свойства : Console . WriteLine ( " Cyммa полей : " + obj . sum) ; 1 1 Присваивание значения полю : obj . first=б ; 1 1 Проверка значения свойства : Console . WriteLine ( " Cyммa полей : " + obj . sum) ; 1 1 Присваивание значения полю : obj . l a s t=2 ; 1 1 Проверка значения свойства : Console . WriteLine ( " Cyммa полей : " + obj . sum) ; Результат выполнения такой. � Результат выполнения программы (из листинга 9 .3 ) Сумма полей : 1 0 Сумма полей : 1 5 Сумма полей : 8 В программе описан класс, у которого имеется два открытых (чтобы проще было менять значения) поля. Свойство s um, описанное в клас­ се, имеет только g e t -aкceccop. В качестве значения свойства возвраща­ ется сумма полей объекта. Если мы меняем значения полей, то меняет­ ся и значение свойства. С другой стороны, понятно, что мы не можем 461 Гл ава 9 в принципе присвоить значение такому свойству. Здесь, фактически, свойство играет роль, характерную для метода, который вызывается из объекта класса и возвращает результат, вычисляемый на основе зна­ чений полей объекта. На практике такие ситуации встречаются доста­ точно часто. Более экзотическим является случай, когда свойство доступно для при­ сваивания значения, но не доступно для его считывания. Тем не менее такое тоже возможно. Соответствующее свойство описывается с s е t ­ аксессором, н о без get-aкceccopa. Соответствующий пример представ­ лен в листинге 9.4. [1i!J Листинг 9 . 4 . Свойство без get-aкceccopa us ing Sys tem; 11 Класс со свойством : c l a s s MyClas s { / / Закрытое текстовое поле : private s tring code ; / / Конструктор с одним аргументом : puЫ ic MyC la s s (uint n ) { / / Присваивание значения свойству : number=n ; / / Переопределение метода ToString ( ) : puЫ ic override s tring ToString ( ) { return code ; / / Результат метода / / Свойство без get - aкceccopa : puЫ ic uint number { / / Метод вызывается при присваивании / / значения свойству : set { / / Локальная переменная : uint num=va lue ; / / Начальное значение текстового поля : 462 С в ойст ва и индекс аторы code= " " ; 1 1 Вычисление битов числа : do { 1 1 Значение последнего (младшего) бита 11 дописывается слева к тексту : code= ( num% 2 ) +code ; 1 1 Сдвиг кода числа на одну позицию вправо : num»=l ; } wh i l e ( num ! =O ) ; 1 1 Класс с главным методом : c l a s s WithoutGetDemo { 1 1 Главный метод : s tatic vo id Main ( ) { 1 1 Создание объекта : MyC l a s s obj =new MyC l a s s ( 5 ) ; 1 1 Бинарный код целого числа : Console . WriteLine ( " Бинapный код числа 5 : \ t " +obj ) ; 1 1 Присваивание значения свойству : obj . numЬer=4 5 ; 1 1 Бинарный код целого числа : Console . WriteLine ( " Бинapный код числа 4 5 : \ t " +obj ) ; Ниже приведен результат выполнения программы. [1iJ Результат выполнения программы (из листинга 9 . 4) Бинарный код числа 5 : 101 Бинарный код числа 4 5 : 101101 463 Гл ава 9 В данном случае мы имеем дело с классом MyC l a s s , у которого есть свойство numbe r типа u i n t (неотрицательное целое число) и закры­ тое текстовое поле code. Свойству можно присвоить значение, но нель­ зя прочитать значение свойства. При присваивании значения свойству numb e r в текстовое поле c o de записывается (в виде текстовой строки) бинарный код этого числа. Метод T o S t r i n g ( ) переопределен в клас­ се так, что результатом метода возвращается значение текстового поля code. Конструктору передается аргументом целое неотрицательное чис­ ло, которое присваивается значением свойству numb e r . В s e t-aкceccope свойства numb e r присваиваемое свойству значение за­ писывается в локальную переменную num (команда u i n t num=va l u e ) . Текстовое поле c o de получает начальным значением пустой текст (ко­ манда c o de = " " ). После этого запускается оператор цикла do. В теле оператора цикла командой c o de = ( num% 2 ) + c o de к текущему текстово­ му значению поля c o de слева дописывается значение последнего бита в числовом значении n um. Для вычисления последнего (младшего) бита в числе (значение переменой num) мы вычисляем остаток от деления этого числа на 2 (выражение num% 2 ). Остаток от деления на 2 это число О или 1 . После того как значение младшего (на данный момент) бита записано, командой n um> >= 1 бинарный код числового значения переменной num сдвигается вправо на одну позицию. Младший бит те­ ряется, а новым младшим битом становится тот, который был до этого предпоследним. На следующей итерации этот бит будет прочитан и до­ писан к текстовому значению поля code. Так будет продолжаться до тех пор, пока значение переменной num отлично от нуля (условие num ! = О в инструкции wh i l e ). - � ПОДРОБН ОСТИ В s e t -aкceccope поле code получает начал ьное значение 11 11 • Есл и свойству numЬ e r присваи вается значение О , то оператор ци кла do­ wh i l e будет вы полнен оди н раз , и в резул ьтате текстовое поле code получ ит значение 11 О 11 • В главном методе мы создаем объект с передачей значения 5 аргументом конструктору и в итоге получаем бинарный код для этого числа. Затем свойству numb e r присваивается новое значение 4 5 , а в результате при­ ведения объекта к текстовому формату в консоли появляется его бинар­ ный код. 464 С в ойст ва и индекс аторы Еще одна программа, иллюстрирующая использование сразу несколь­ ких свойств в одном классе, представлена в листинге 9.5. � Л истинг 9 . 5 . И спользование различных свойств u s ing Sys tem; 11 Класс со свойствами : c l a s s MyClas s { 1 1 Закрытое поле - массив : private int [ ] nums ; 1 1 Текстовое свойство без set - aкceccopa : puЫ ic string content { 1 1 Метод вызывается при считывании значения свойства : get { 1 1 Если ссылка на массив пустая : i f ( nums==nu l l ) return 11 { } 11 ; 1 1 Формирование текстовой строки : string txt= 11 { 11 +nums [ O ] ; for ( int k= l ; k<nums . Length ; k++ ) { txt+=11 , 11 +nums [ k ] ; txt+= 11 ) 11 ; 1 1 Значение свойства : return txt ; 1 1 Целочисленное свойство без get - aкceccopa : puЫ ic int elemen t { 1 1 Метод вызывается при присваивании 1 1 значения свойству : set { 1 1 Если ссылка на массив пустая : i f ( nums==nu l l ) { 465 Гл ава 9 / / Создание массива из одного элемента : nums=new int [ l ] ; / / Значение единственного элемента массива : nums [ O ] =value ; } e l se { / / Если ссыпка не пустая / / Создание массива : int [ ] n=new int [ nums . Length+ l ] ; / / Заполнение массива : for ( int k=O ; k<nums . Length ; k++ ) { n [ k ] =nums [ k ] ; / / Значение последнего элемента в массиве : n [ nums . Length ] =value ; / / Ссылка на созданный массив записывается в / / поле объекта : nums=n ; / / Свойство является ссыпкой на массив : puЫ ic int [ ] data { / / Метод вызывается при считывании значения свойства : get { / / Создание массива : int [ ] res=new int [ nums . Length ] ; / / Заполнение массива : for ( int k=O ; k<nums . Length ; k++ ) { res [ k ] =nums [ k ] ; / / Значение свойства : return re s ; 466 С в ойст ва и индекс аторы 1 1 Метод вызывается при присваивании 1 1 значения свойству : set { 1 1 Создание массива : nums=new int [ value . Length ] ; 1 1 Заполнение массива : for ( int k= O ; k<value . Length ; k++ ) { nums [ k ] =value [ k ] ; 1 1 Класс с главным методом : c l a s s MoreUs ingProps Demo { 1 1 Главный метод : s tatic vo id Main ( ) { 1 1 Создание объекта : MyC l a s s obj =new MyC l a s s ( ) ; 1 1 Проверка содержимого массива из объекта : Console . WriteLine ( obj . content ) ; 1 1 Присваивание значения свойству element : obj . e lement=l O ; 1 1 Проверка содержимого массива из объекта : Console . WriteLine ( obj . content ) ; 1 1 Присваивание значения свойству element : obj . e lement=S ; obj . e lement=7 ; 1 1 Проверка содержимого массива из объекта : Console . WriteLine ( obj . content ) ; 1 1 Считывание значения свойства data : int [ ] A=obj . data ; 1 1 Присваивание значения свойству element : 467 Гл ава 9 obj . element= 1 2 ; 1 1 Отображение содержимого массива А : for ( int k=O ; k<A . Length ; k++ ) { Console . Write (A [ k ] + " " ) ; Console . WriteLine ( ) ; 1 1 Проверка содержимого массива из объекта : Console . WriteLine ( obj . content ) ; 1 1 Создание массива : int [ ] B= { l l , 3 , 6 } ; 1 1 Присваивание значения свойству data : obj . data=B ; 1 1 Изменение значения элемента массива В : В [ О ] =О ; 1 1 Отображение содержимого массива В : for ( int k=O ; k<B . Length ; k++ ) { Console . Write ( B [ k ] + " " ) ; Console . WriteLine ( ) ; 1 1 Проверка содержимого массива из объекта : Console . WriteLine ( obj . content ) ; В результате выполнения программы получаем следующее. � Результат выполнения программы ( из листинга 9.5) {} {10} {10, 5, 7} 10 5 7 { 1 0 , 5 , 7 , 12 } о 3 6 {11, 3,6} 468 С в ойст ва и индекс аторы Класс MyC l a s s имеет закрытое поле nums , представляющее собой ссылку на целочисленный массив. Этот массив используется при опи­ сании свойств c o n t e n t , e l eme n t и da t a . Свойство c o n t e n t тексто­ вое, и у этого свойства есть только g e t -aкceccop (свойство можно про­ читать, но нельзя присвоить). Свойство возвращает текстовую строку со значениями элементов массива. При вызове g e t -aкceccopa для этого свойства в условном операторе проверяется условие nums==nu l l , ис­ тинное, если поле nums не ссылается на массив. Если так, то значением свойства возвращается строка 11 { } 11 • � ПОДРОБН ОСТИ Кл ючевое слово nu l l означает пустую ссылку. Есл и переменной ссылоч ного типа ( переменной массива или объектной перемен ной) не п рисвоено значение, то значение такой переменной - пустая ссылка (то есть равно nul l ) . Если же поле nums ссьшается на массив, то формируется текстовая стро­ ка, содержащая значения элементов массива, и эта строка возвращается как значение свойства c o n t e n t . Целочисленное свойство e l eme n t н е имеет g e t -aкceccopa, поэтому свойству можно присвоить значение, но нельзя узнать значение свой­ ства. При присваивании значения свойству в массив nums добавляется новый элемент с присваиваемым значением. Для этого в теле s e t -aкcec­ copa в условном операторе проверяется условие nums==nu l l . Истин­ ность условия означает, что поле nums на массив не ссьшается. В таком случае командой n um s = n e w i n t [ 1 ] создается массив из одного эле­ мента, и значение этого элемента есть присваиваемое свойству значение (команда nums [ О ] =va l u e ) . Если условие nums==nu l l в условном операторе ложно, то командой i n t [ ] n=new i n t [ nums . L e n g t h + 1 ] создается новый массив, раз­ мер которого на единицу больше размера массива, на который ссьшается поле nums . Сначала выполняется поэлементное копирование значений из массива n ums во вновь созданный массив. Остается незаполненным один последний элемент. Командой n [ nums . L e n g t h ] =va l u e это­ му элементу присваивается значение, которое по факту присваивается свойству e l eme n t . Наконец, командой n um s = n в поле nums записы­ вается ссылка на новый массив. Общий эффект получается такой, что в массиве, на который ссьшается поле num s , появился дополнительный последний элемент. 469 Гл ава 9 Свойство data является ссылкой на целочисленный массив. Для свой­ ства описаны два аксессора. Это означает, что свойству значением мож­ но присвоить ссылку на массив и результатом свойства является также ссылка на массив. В g e t -aкceccope для этого свойства командой i n t [ ] r e s = n e w i n t [ nums . Length ] создается массив такого же размера, что и массив, на который ссьшается поле num s . Затем выполняется копирование мас­ сивов. Значением свойства является ссьшка на новый созданный мас­ сив, являющийся копией массива, на который ссылается поле num s . G) Н А ЗАМ ЕТ КУ Свойство da t a значением возвращает ссылку на масси в , который я вляется копией массива, на который ссылается поле nums объекта . В s e t-aкceccope для свойства da t a командой nums=new i n t [ va l u e . Length ] создается новый массив, и ссьшка на него записывается в поле nums . Здесь следует учесть, что ключевое слово val u e , обозначающее присваиваемое свойству значение, относится к типу i n t [ ] (тип свой­ ства da t a ) то есть является ссылкой на целочисленный массив и обра­ батывается как массив. Размер такого массива вычисляется выражением v a l u e . L e n g t h . С помощью оператора цикла выполняется копирова­ ние значений элементов из присваиваемого массива va l u e в созданный массив num s . - В главном методе программы командой MyC l a s s obj =new MyC l a s s ( ) создается объект o b j класса My C l a s s . Его поле n u m s на мас­ сив не ссылается, в чем легко убедиться с помощью команды C o n s o l e . W r i t e L i n e ( o b j . c o n t e n t ) . Присваивание значения свойству e l eme n t командой o b j . e l eme n t = l O приводит к тому, что в массиве nums появляется один элемент. После выполнения команд o b j . e l eme n t = S и o b j . e l eme n t = 7 там появляется еще два элемента. На следующем этапе командой in t [ ] А=оЬ j . da ta объявляется пе­ ременная массива А и в нее записывается ссылка на копию массива, на который ссылается поле nums объекта obj . Чтобы проверить, что это именно копия, а не сам массив nums, командой obj . e l emen t = 1 2 в мас­ сив nums объекта obj добавляется еще один элемент. При этом массив А остается неизменным, в чем легко убедиться, сравнив результат отобра­ жения содержимого массива А и массива nums из объекта obj . 470 С в ойст ва и индекс аторы Далее командой i n t [ ] В= { 1 1 , 3 , 6 } создается целочисленный мас­ сив В, и командой obj . data=B ссылка на массив присваивается значе­ нием свойству da ta объекта ob j . В результате поле nums объекта по­ лучает значением ссьшку на копию массива В. Действительно, проверка показывает, что после выполнения команды В [ О ] = О массив В меняется, а массив, на который ссьшается поле nums объекта obj , остается таким, как до выполнения команды В [ О ] = О . Свойство может быть статическим. С технической точки зрения особен­ ность такого свойства в том, что оно описывается с ключевым словом s t a t i c . Но поскольку свойство не связано с областью памяти, то на­ личие в классе статического свойства обычно подразумевает, что для реализации этого свойства используются статические поля. Небольшой пример, показывающий, как может быть описано статическое свойство, представлен в листинге 9.6. � Л истинг 9 . 6 . Статическое свойство u s ing Sys tem; 11 Класс со статическим свойством : c l a s s Fibs { 1 1 Закрытые целочисленные статические поля : private s tatic int l a s t= l ; private s tatic int prev= l ; 1 1 Статическое целочисленное свойство : puЫ ic static int numЬe r { 1 1 Метод вызывается при считывании значения свойства : get { 1 1 Локальная переменная : int re s=prev; 11 Изменение значения статических полей : l a s t=las t+prev ; prev=last - prev; 11 Значение свойства : return res ; 471 Гл ава 9 1 1 Метод вызывается при присваивании 1 1 значения свойству : set { 1 1 Начальное значение статических полей : prev=l ; l a s t=l ; 1 1 Изменение значения статических полей : for ( int k=2 ; k<=va lue ; k++ ) { l a s t=las t+prev; prev=last - prev; 11 Класс с главным методом : c l a s s StaticProps Demo { 1 1 Главный метод : s tatic vo id Main ( ) { 1 1 Отображение значения статического свойства : for ( int k=l ; k<=l O ; k++ ) { 11 11 Console . Write ( { О , 4 } , Fibs . numbe r ) ; Console . WriteLine ( ) ; 1 1 Присваивание значения статическому свойству : Fibs . numЬer= б ; 1 1 Отображение значения статического свойства : for ( int k=l ; k<=l O ; k++ ) { Console . Write ( { О , 4 } 11 11 , Fibs . numbe r ) ; Console . WriteLine ( ) ; 1 1 Присваивание значения статическому свойству : Fibs . numЬer=l ; 472 С в ойст ва и индекс аторы 1 1 Отображение значения статического свойства : for ( int k=l ; k<=l O ; k++ ) { Console . Write ( " { О , 4 } " , Fibs . numbe r ) ; Console . WriteLine ( ) ; Результат выполнения программы следующий. � Результат выполнения программы (из листинга 9 . 6) 1 8 1 13 2 3 5 21 34 55 2 3 5 8 13 21 34 55 89 144 233 377 610 8 13 21 34 55 М ы описали класс F i b s с о статическим свойством numЬer. При провер­ ке значения свойства результатом оно возвращает число из последова­ тельности Фибоначчи. Причем каждый раз это число новое. G) Н А ЗАМ ЕТ КУ В последовательности Фибоначчи два первых числа равны 1 , а каждое следующее число - это сумма двух предыдущих чисел . Получается последовательность чисел 1 , 1 , 2 , 3 , 5 , 8 , 1 3 , 2 1 , 3 4 , 5 5 и так далее. При присваивании значения свойству numbe r число, которое присва­ ивается свойству, определяет порядковый номер числа из последова­ тельности Фибоначчи, которое будет возвращаться в качестве значения свойства. Например, если мы присваиваем свойству n umЬe r значение 6 , т о это означает, что при считывании значения свойства м ы получим зна­ чение 8 , поскольку именно число 8 является шестым по счету в после­ довательности Фибоначчи. Если далее мы еще раз прочитаем значение свойства numЬ e r , то получим значение 1 3 , а затем при считывании зна­ чения свойства получим значение 2 1 и так далее. Для реализации такого подхода мы описываем два закрытых целочис­ ленных статических поля l a s t и prev с начальными единичными зна­ чениями. Эти поля нам нужны для того, чтобы записать в них значения двух последних чисел из последовательности Фибоначчи. Нам нужно 473 Гл ава 9 именно два числа, поскольку следующее число в последовательности вы­ числяется как сумма двух предыдущих. Начальные значения полей объ­ ясняются тем, что первые два числа в последовательности - это 1 и 1 . Свойство numb e r описано с ключевым словом s t a t i c . В g e t -aкcec­ cope свойства объявляется локальная переменная r e s , в которую за­ писывается значение поля p r ev. Затем выполняются две команды l a s t= l a s t +prev и prev= l a s t -p rev, которыми полю l a s t значени­ ем присваивается новое число Фибоначчи, а полю prev значением при­ сваивается то число, которое раньше было записано в поле l a s t . После этого значение переменной r e s (то значение, которое в самом начале было записано в поле р rev) возвращается как значение свойства. Таким образом, получается, что при считывании значения свойства numЬe r ре­ зультатом возвращается значение поля p rev, а затем вычисляется новое число Фибоначчи и обновляются значения полей l a s t и prev так, что поле l a s t содержит значение нового вычисленного числа, а в поле prev записывается значение предыдущего числа. � ПОДРОБН ОСТИ Допусти м , поля prev и l a s t содержат два ч исла из последовател ь­ ности Фибонач ч и . Наша задача - сделать «шаг вперед» : выч исл ить новое ч исло Фибоначчи и записать в поля новую пару чисел . Чтобы понять ал горитм вычислен и й , обознач и м исходное значение поля prev как Л , а исходное значение поля l a s t обознач и м как D. Необ­ ходи мо сделать так, чтобы в поле l a s t было записано значение Л+D, а в поле p r e v должно быть записано значение D. После вы полнения команды l a s t = l a s t+prev в поле last зап исы вается значение Л+D. Поле prev остается со значен ием Л. После вы полнения команды prev= l a s t -prev поле prev получает значение D, что нам и нужно . В s e t-aкceccope для свойства numbe r командами prev= l и l a s t = l по­ лям prev и l a s t присваиваются единичные значения. После этого запу­ скается оператор цикла, в котором индексная переменная k принимает значения от 2 до присваиваемого свойству значения val ue. За один цикл выполняются команды l a s t = l a s t +prev и prev= l a s t -p rev. Каждый раз, когда выполняются эти команды (мы их анализировали выше), вы­ числяется новое число Фибоначчи. С учетом того, что начальные еди­ ничные значения полей prev и l a s t соответствуют первой паре чисел в последовательности, а первое значение индексной переменной k в опе­ раторе цикла равно 2 , то после выполнения оператора цикла в поле prev 474 С в ойст ва и индекс аторы будет записано число из последовательности Фибоначчи, порядковый но­ мер которого определяется значением va l u e , присваиваемым свойству. В поле 1 a s t будет записано следующее число в последовательности. В главном методе программы запускается оператор цикла, в котором вы­ полняется 1 О циклов. За каждый цикл в консольном окне отображается значение, возвращаемое инструкцией F i b s . numb e r . � ПОДРОБН ОСТИ В теле оператора цикла вы полняется команда C on s o l e . Wri te ( 11 { О , 4 } 11 , Fibs . numbe r ) . Инструкция { О , 4 } означает, что отображается значение вы ражения Fibs . numb e r и под это значение (число) отводится 4 позици и . В результате в консольном окне отображаются первые 1 О чисел и з по­ следовательности Фибоначчи. Затем командой F i b s . numbe r= б при­ сваивается значение свойству numb e r . Затем снова выполняется опера­ тор цикла и 1 О раз подряд отображается значение статического свойства numb e r . В итоге получаем еще одну строку из 1 О чисел Фибоначчи, но на этот раз числа отображаются, начиная с шестого числа. Наконец, после выполнения команды F i b s . numЬ e r = l и выполнения оператора цикла получаем строку из 1 О чисел Фибоначчи, начиная с первого числа в последовательности. Знакомство с и ндексаторами Братцы, кончайте философствовать, о н сейчас возникнет уже. из к/ф -«Кин-дза-дза» Теперь пришло время познакомиться с индексаторами. Индексатор яв­ ляется специальным членом класса, наличие которого в классе позволя­ ет индексировать объекты класса. Когда мы говорим об индексировании объекта, то подразумеваем, что после имени объекта в квадратных скобках указывается индекс (или индексы) - конечно, при условии, что такое вы­ ражение имеет смысл. Таким образом, описав в классе индексатор, мы по­ лучаем возможность обращаться с объектом класса так, как если бы этот объект бьш массивом. Это очень важная и перспективная возможность. 475 Гл ава 9 Прежде чем приступить к изучению вопроса о том, как описывается индек­ сатор, остановимся на том, что происходит при индексировании объекта (в случае, когда в соответствующем классе описан индексатор). Итак, пред­ ставим, что имеется объект, который индексируется - после имени объек­ та в квадратных скобках указан индекс (для простоты будем полагать, что индекс один). Есть две ситуации, когда такая инструкция может быть ис­ пользована. В одном случае мы хотим узнать (прочитать) значение данно­ го выражения. В другом случае такому выражению может присваиваться значение. И та и другая ситуация разрешается способом, подобным к тому, как это делается для свойства, а именно - с каждый индексатором связано два специальных метода-аксессора. При считывании значения выражения вызывается get-aкceccop. При присваивании значения выражению вызы­ вается s e t-aкceccop. Единственное принципиальное отличие от свойства состоит в том, что в случае с индексатором в операциях считывания значе­ ния и присваивания значения участвует еще и индекс. Описывается индексатор следующим образом. Кроме спецификато­ ра уровня доступа (обычно это ключевое слово puЬ l i c ) , указывается ключевое слово, определяющее тип значения, возвращаемого при ин­ дексировании объекта. Затем указывается ключевое слово th i s . После ключевого слова t h i s в квадратных скобках указывается тип индекса и его формальное обозначение. После этого в блоке из фигурных скобок описываются аксессоры. В общем случае их два (хотя может быть и ин­ дексатор только с одним аксессором): это g e t -aкceccop, который вы­ зывается при считывании значения выражения с проиндексированным объектом, и s e t -aкceccop, который вызывается при присваивании зна­ чения выражению с проиндексированным объектом. Аксессоры описы­ ваются в принципе так же, как и для свойства, но кроме ключевого слова va l u e , определяющего присваиваемое значение в s e t-aкceccope, в обо­ их аксессорах еще используется и переменная, обозначающая индекс. Ниже представлен шаблон для описания индексатора (жирным шриф­ том выделены основные элементы шаблона): тип_индексатора this [ тип_индекса индекс ] { 1 1 Метод вызывается при считывании значения выражения 11 с проиндексированным объектом : qet { 1 1 Код get - aкceccopa 476 С в ойст ва и индекс аторы 11 Метод вызывается при присваивании значения выражению 1 1 с проиндексированным объектом : set{ 1 1 Код set - aкceccopa Часто индексатор описьшается с целочисленным индексом (тип индекса указывается как i n t ). Но это не обязательно - тип индекса может быть и другим. Более того, индексатор можно перегружать - в классе может быть описано несколько индексаторов, которые отличаются количеством и/ или типом индексов. Вместе с тем индексатор не может быть статическим, а также индексатор нельзя использовать с идентификаторами r e f и out. G) Н А ЗАМ ЕТ КУ Напом н и м , что кл ючевое слово t h i s , испол ьзуемое в оп исан и и ин­ дексатора, может иметь и и ное назначение. Так, в п рограм мном коде класса , в описан и и методов ( в том ч исле аксессоров, конструк­ торов и деструкторов ) кл ючевое слово th i s может испол ьзоваться для обозначения объекта , из которого вызы вается метод. В листинге 9.7 представлена программа, в которой есть класс, а в этом классе описан индексатор. � Л истинг 9 . 7 . Знакомство с индексаторами u s ing Sys tem; 11 Класс с индексатором : c l a s s MyClas s { 1 1 Закрытое поле , являющееся ссылкой н а массив : private int [ ] nums ; 1 1 Конструктор с целочисленным аргументом : puЫ ic MyCla s s ( int n ) { 1 1 Создание массива : nums=new int [ n ] ; 1 1 Заполнение массива : for ( int k=O ; k<nums . Length ; k++ ) { 477 nums [ k ] = O ; 1 1 Переопределение метода ToString ( ) : puЫ ic ove rride s tring ToString ( ) { 1 1 Формирование текстовой строки : s t ring txt=" { " +nums [ O ] ; for ( int k=l ; k<nums . Length ; k++ ) { txt+=" , " +nums [ k ] ; txt+=" } " ; 1 1 Резуль тат метода : return txt ; 1 1 Целочисленное свойство : puЫ ic int length { 1 1 Метод вызывается при считывании значения свойства : get { 1 1 Значение свойства : return nums . Length ; 1 1 Целочисленный индексатор с целочисленным индексом : puЫ ic int thi s [ int k] { 11 Метод вызывается при считывании значения 11 объекта с индексом : get { 1 1 Значение выражения : return nums [ k ] ; 1 1 Метод вызывается при присваивании значения 11 объекту с индексом : 478 С в ойст ва и индекс аторы set { 1 1 Присваивание значения элементу массива : nums [ k ] =value ; 1 1 Класс с главным методом : c l a s s Usingindexe rDemo { 1 1 Главный метод : s tatic vo id Main ( ) { 1 1 Создание объекта : MyC l a s s obj =new MyC l a s s ( S ) ; 1 1 Отображение содержимого массива из объекта : Console . WriteLine ( obj ) ; 1 1 Присваивание значений элементам массива из объекта 11 с исполь зованием индексирования объекта : for ( int k=O ; k<obj . length ; k++ ) { 1 1 Исполь зуется индексирование объекта : obj [ k ] =2 * k+ l ; 1 1 Отображение содержимого массива из объекта : Console . WriteLine ( obj ) ; 1 1 Поэлементное отображение массива из объекта 11 с исполь зованием индексирования объекта : for ( int k=O ; k<obj . length ; k++ ) { 1 1 Исполь зуется индексирование объекта : Console . Write ( " " +obj [ k ] ) ; Console . WriteLine ( ) ; 479 Гл ава 9 При выполнении программы получаем такой результат. [1iJ Результат выполнения программы (из листинга 9 . 7) {0, О, О, 0, 0 } {1,3,5,7,9} 1 3 5 7 9 Описывая класс MyC l a s s , мы предусмотрели в нем закрытое поле nums, представляющее собой ссылку на целочисленный массив. В классе пере­ определен метод T o S t r i n g ( ) , так что результатом метода возвращается текстовая строка со значениями элементов массива. Конструктору класса передается один целочисленный аргумент, определяющий размер массива, на который ссьmается поле nums. Массив создается при выполнении кода конструктора. Также все элементы массива получают нулевое значение. G) Н А ЗАМ ЕТ КУ Строго говоря , при создании массива его элементы автоматически получают нулевые значен ия . Тем не менее м ы в конструкторе доба­ вили блок кода (оператор ци кла) , которым элементам массива п р и ­ сваиваются начал ьные значен и я . В классе есть целочисленное свойство l ength, у которого имеется только get-aкceccop. Значением свойства length является размер массива, на ко­ торый ссьmается поле nums (определяется выражением nums . Length). Понятно, что присвоить значение свойству l e ngth нельзя. Описание целочисленного индексатора с целочисленным индексом начи­ нается с ключевого слова puЬl i c. Идентификатор i n t означает, что ре­ зультатом выражения, в котором есть объект с индексом, является целое число. Также целое число можно присвоить значением такому выражению. G) Н А ЗАМ ЕТ КУ Возможность п роч итать значен ие или присвоить значение вы раже­ нию на основе п рои ндексированного объекта оп ределяется нал и ч и ­ ем или отсутствием соответствующего аксессора . После обязательного ключевого слова thi s в квадратных скобках указана инструкция in t k. Она определяет тип и имя индекса. То есть в программ­ ном коде аксессоров для этого индексатора под k следует подразумевать 480 С в ойст ва и индекс аторы индекс, указанный в квадратных скобках после имени объекта. И этот ин­ декс является целочисленным (тип i n t ) . В теле gеt-аксессора всего одна команда r e t u r n nums [ k ] . Таким образом, если после имени объекта в квадратных скобках указать индекс, то значением такого выражения яв­ ляется значение элемента массива n ums с таким же индексом. В s e t-aкceccope выполняется команда nums [ k ] =va lue. Ключевое сло­ во val u e отождествляется с присваиваемым значением. По факту оно присваивается элементу массива nums с индексом k. Получается, что ког­ да мы присваиваем значение объекту с индексом, то в действительности такое значение получит элемент с таким же индексом в массиве объекта. В главном методе программы мы проверяем работу индексатора. Для этого мы командой MyC l a s s o b j =new MyC l a s s ( 5 ) создаем объект obj с массивом из пяти элементов. Вначале этот массив заполнен ну­ лями (проверяем это командой C o n s o l e . W r i t e L i n e ( ob j ) ). За­ тем выполняется оператор цикла, в котором перебираются элемен­ ты массива в объекте оЬ j . Размер массива вычисляется выражением obj . l e ngth. За каждый цикл (при фиксированном индексе k) коман­ дой оЬ j [ k ] = 2 * k + 1 соответствующему элементу массива присваивает­ ся значение. После того как мы проверяем содержимое массива в объ­ екте, с помощью еще одного оператора цикла мы поэлементно выводим значения элементов массива в консоль. При этом используется индекси­ рование объекта obj (инструкция вида obj [ k ] ). И с пол ь зован ие и ндексаторов З ря старались ! Бриллиантов там нет. из к/ф «Бриллиантовая рука» Теперь мы рассмотрим несколько примеров, в которых различными спо­ собами описываются индексаторы. Во всех примерах речь будет идти об индексаторах с одним индексом. Начнем с программы в листинге 9.8, которая наглядно иллюстрирует, что совсем необязательно, чтобы за ин­ дексатором «прятался� массив. [1i!J Л истинг 9 . 8 . Испол ьзование индексатора u s ing Sys tem; 11 Класс с индексатором : 481 Гл ава 9 c l a s s MyClas s { 1 1 Закрытое целочисленное поле : private int code ; 1 1 Конструктор с символьным аргументом : puЫ ic MyC la s s ( char s ) { 1 1 Присваивание значения полю : code=s ; 1 1 Символьный индексатор с целочисленным индексом : puЫ ic char thi s [ int k] { 1 1 Метод вызывается при считывании значения выражения 11 с проиндексированным объектом : get { 1 1 Значение свойства : return ( char) ( code+k) ; 1 1 Метод вызывается при присваивании значения 11 выражению с проиндексированным объектом : set { 1 1 Присваивание значения полю : code=value - k ; 1 1 Класс с главным методом : c l a s s More indexerDemo { 1 1 Главный метод : static vo id Main ( ) { 1 1 Создание объекта : MyC l a s s obj =new MyC l a s s ( ' A ' ) ; 1 1 Индексирование объекта для считывания значения : for ( int k=O ; k<l O ; k++ ) { 482 С в ойст ва и индекс аторы Console . Write ( obj [ k ] + " " ) ; Console . WriteLine ( ) ; 1 1 Присваивание значения выражению с 1 1 индексированным объектом : obj [ S ] = ' Q ' ; 1 1 Индексирования объекта для считывания значения : for ( int k=O ; k<l O ; k++ ) { Console . Write ( obj [ k ] + " " ) ; Console . WriteLine ( ) ; 1 1 Использование отрицательного индекса 11 при индексировании объекта : for ( int k=O ; k<l O ; k++ ) { Console . Write ( obj [ -k ] + " " ) ; Console . WriteLine ( ) ; Результат выполнения программы такой. [1i!J Результат выполнения программы ( из листинга 9 . 8) А В С D Е F G Н I J 1 М N О Р Q R S Т U 1 К J I Н G F Е D С Здесь в классе MyC l a s s есть закрытое целочисленное поле c o de , значе­ ние которому присваивается при создании объекта. Причем аргументом конструктору передается символьное значение. Поэтому в поле c o de за­ писывается код символа, переданного аргументом конструктору. Индексатор описан как такой, что относится к типу cha r (тип присва­ иваемого и возвращаемого значения), а индекс (обозначен как k) име­ ет тип i n t . В g e t -aкceccope индикатора выполняется команда r e t u r n 483 Гл ава 9 ( ch a r ) ( c o d e + k ) , в соответствии с которой значением индексатора возвращается символ, код которого получается прибавлением к значе­ нию поля c o de значения индекса, указанного в квадратных скобках по­ сле имени объекта. В s e t -aкceccope выполняется команда c o de =v a l u e - k, которой значе­ нием полю c o de присваивается разность значения, указанного в опе­ рации присваивания, и индекса, указанного в выражении, которому присваивается значение. Причем подчеркнем, что параметр v a l u e в данном случае является символьным. Поэтому получается, что в поле c o de записывается код символа, который в кодовой таблице находится на k позиций перед тем символом, который указан в команде присва­ ивания. В главном методе командой MyC l a s s obj =new MyC l a s s ( ' А ' ) соз­ дается объект obj . Аргументом конструктору передан символ ' А ' . По­ этому значением выражения оЬ j [ k ] при заданном значении индекса k является символ, код которого на k больше, чем код символа ' А ' . При выполнении оператора цикла, в котором индексная переменная k про­ бегает значения от О до 9 включительно, отображается значение выра­ жения obj [ k ] , в результате чего появляется строка из десяти символов, начиная с символа ' А ' и заканчивая символом ' J ' . При выполнении команды o b j [ 5 ] = ' Q ' в поле c o de объекта o b j за­ писывается код символа, находящегося в кодовой таблице за 5 позиций по отношению к символу ' Q ' . Это символ ' L ' , и его код записывается в поле code. Поэтому при выполнении оператора цикла, в котором ин­ дексная переменная k принимает значения от О до 9 и за каждый цикл отображается значение выражения o b j [ k ] , мы получаем последова­ тельность из десяти символов, начиная с символа ' L ' и заканчивая сим­ волом ' U ' . Наконец, пикантность ситуации в том, что формально нам никто не за­ прещает использовать при индексации объекта отрицательный индекс. Главное, чтобы код аксессоров имел при этом смысл. Если мы выпол­ ним еще один оператор цикла, но отображать будем значение выраже­ ния obj [ - k ] , то получим последовательность символов, но в обратном порядке: от символа ' L ' до символа ' С ' включительно. В следующем примере рассматривается ситуация, когда индексатор имеет только g e t -aкceccop. Интересующая нас программа представле­ на в листинге 9.9. 484 С в ойст ва и индекс аторы [1i!J Л истинг 9 . 9 . И ндексатор без set-aкceccopa u s ing Sys tem; / / Класс с индексатором : c l a s s MyClas s { / / Закрытое целочисленное поле : private int numЬer; / / Конструктор с одним аргументом : puЫ ic MyCla s s ( int n ) { / / Присваивание значения полю : number=n ; / / Целочисленный индексатор с целочисленным индексом : puЫ ic int thi s [ int k] { / / Метод вызывается при считывании значения выражения / / с проиндексированным объектом : get { / / Целочисленная переменная : int n=numЬer; / / "Отбрасывание " цифр из младших разрядов : for ( int i= l ; i<k; i++ ) { n / =1 0 ; / / Значение свойства : return n % 1 0 ; / / Класс с главным методом : c l a s s WithoutSeti ndexerDemo { / / Главный метод : static vo id Main ( ) { / / Создание объекта : 485 Гл ава 9 MyC l a s s obj =new MyClas s ( 1 2 3 4 5 ) ; 1 1 Цифры в десятичном представлении числа : for ( int k=l ; k< 9 ; k++ ) { Console . Write ( " 1 " +obj [ k ] ) ; Console . WriteLine ( " 1 " ) ; Результат выполнения программы вполне ожидаем. [1i!J Результат выполнения программ ы (из листинга 9 . 9) 1 5 1 4 1 3 1 2 1 1 1 о 1 о 1 о 1 У класса MyC l a s s есть закрытое целочисленное поле, значение кото­ рому присваивается при создании объекта. Индексатор, описанный в классе, имеет только get-aкceccop. При индексировании объекта ре­ зультатом возвращается цифра в десятичном представлении числа, за­ писанного в поле numb e r . Индекс, указанный при индексировании объ­ екта, определяет позицию цифры в десятичном представлении числа. G) Н А ЗАМ ЕТ КУ И ндексатор оп ределен так, что наимен ьшему разряду ( еди н и це ) в десятич н о м представл е н и и ч и сл а соответствует и ндекс 1 . Десят­ кам соответствует и ндекс 2 , сотням соответствует и ндекс 3 и так далее. Есл и п р и и ндекси рован и и объекта указать индекс , мень­ ш и й 1 , то резул ьтатом будет цифра в разряде еди н и ц . То есть для отри цател ьного индекса или и ндекса О резул ьтат такой же , как и для и ндекса 1 . Для вычисления значения индексатора в теле g e t -aкceccopa командой i n t n=numЬ e r объявляется локальная переменная n, в которую запи­ сывается значение поля n umbe r. Задача состоит в том, чтобы вычислить цифру, которая находится в десятичном представлении числа в позиции с порядковым номером k (целочисленный индекс, указанный в описа­ нии индексатора). Для этого мы �отбрасываем» в десятичном представ­ лении числа k - 1 цифру, а цифра, оказавшаяся последней, возвращается как результат. Для этого сначала запускается оператор цикла, и в нем 486 С в ойст ва и индекс аторы k - 1 раз выполняется команда n / = 1 0 , которой текущее значение пере­ менной n делится нацело на 1 0 , а результат записывается в перемен­ ную n. Одна такая операция эквивалентна отбрасыванию в десятичном представлении значения переменной n одной цифры в младшем разряде (отбрасывается цифра справа). После выполнения оператора цикла ин­ тересующая нас цифра станет последней. Ее можно вычислить как оста­ ток от деления числа на 1 0 (команда n % 1 0 ). Это и есть результат. В главном методе программы мы создаем объект obj , полю numЬ e r кото­ рого присваивается значение 1 2 3 4 5 . Затем запускается оператор цикла (из восьми циклов), и за каждый цикл отображается значение obj [ k ] . В итоге мы получаем цифры из десятичного представления числа, толь­ ко не справа налево (как они записаны в числе), а слева направо. При­ чем нули соответствуют тем разрядам, которые в числе не представлены. Еще один пример индексатора с одним аксессором представлен в следу­ ющей программе. Там у индексатора есть только s e t-aкceccop. Рассмо­ трим программный код в листинге 9. 1 0. [1i!] л истинг 9 . 1 О. И ндексатор без get-aкceccopa u s ing Sys tem; 11 Класс с индексатором : c l a s s MyString { 1 1 Закрытое текстовое поле : private s t ring text ; 1 1 Конструктор с текстовым аргументом : puЫ ic MyString ( s tring t ) { text=t ; 1 1 Операторный метод для неявного преобразования 11 текстового значения в объект класса MyString : puЫ ic static imp l i c i t operator MyString ( string t ) { return new MyString ( t ) ; 1 1 Переопределение метода ToString ( ) : puЫ ic override s t ring ToString ( ) { return text ; 487 Гл ава 9 1 1 Символьный индексатор с целочисленным индексом : puЫ ic char thi s [ int k] { 1 1 Метод вызывается при присваивании значения 11 выражению с индексированным объектом : set { 1 1 Проверка значения индекса : i f ( k< O I l k>=text . Length ) retu r n ; 1 1 Текстовая переменная : string t=" " ; 1 1 Добавление символов к тексту : for ( int i=O ; i<k; i++ ) { t+=text [ i ] ; 1 1 Добавление в текст присваиваемого символа : t+=value ; 1 1 Добавление символов к тексту : for ( int i=k+ l ; i<text . Length; i + + ) { t+=text [ i ] ; 1 1 Новое значение текстового поля : text=t ; 1 1 Класс с главным методом : c l a s s WithoutGeti ndexerDemo { 1 1 Главный метод : static vo id Main ( ) { 1 1 Создание объекта : MyString txt="Myxa " ; 1 1 Проверка текста : Console . WriteLine ( txt ) ; 488 С в ойст ва и индекс аторы 11 Попытка изменить символ в тексте : txt [ - l ] = ' Ы ' ; 1 1 Проверка текста : Console . WriteLine ( txt ) ; 1 1 Попытка изменить символ в тексте : txt [ 4 ] = ' Ъ ' ; 1 1 Проверка текста : Console . WriteLine ( txt ) ; 1 1 Изменение символа в тексте : txt [ О ] = ' С ' ; 1 1 Проверка текста : Console . WriteLine ( txt ) ; 1 1 Изменение символа в тексте : txt [ 1 ] = ' л ' ; 1 1 Проверка текста : Console . WriteLine ( txt ) ; 1 1 Изменение символа в тексте : txt [ 2 ] = ' о ' ; 1 1 Проверка текста : Console . WriteLine ( txt ) ; 1 1 Изменение символа в тексте : txt [ 3 ] = ' н ' ; 1 1 Проверка текста : Console . WriteLine ( txt ) ; Результат выполнения программы такой. l1ii'] Результат выполнения программы (из листинга 9 . 1 О) Муха Муха Муха Суха 489 Гл ава 9 Слха Слоа Слон В этой программе мы создаем очень скромное подобие класса, предназначен­ ного для работы с текстом. Описанный нами класс с индексатором называ­ ется MyS t r i ng. У класса есть закрытое текстовое поле text и конструктор с текстовым аргументом (переданный конструктору аргумент присваивает­ ся значением текстовому полю объекта). Также мы описали операторный метод для неявного приведения типа s t r i ng в тип MyS t r i n g. Благода­ ря этому объектным переменным класса MyS t r i n g можно присваивать текстовые значения. В результате будет создаваться новый объект класса MyS t r ing, а текстовое поле этого объекта определяется тем текстовым зна­ чением, которое присваивается объектной переменной. Еще в классе MyS t r i n g переопределен метод T o S t r i n g ( ) . Переопреде­ лен он таким образом, что результатом возвращается текст из поля text. Индексатор, описанный в классе MyS t r i ng, имеет целочисленный индекс, а в нем есть только s e t-aкceccop. Поэтому проиндексированному объекту можно только присвоить значение. Поскольку индексатор описан как от­ носящийся к типу char, то и присваиваться должно символьное значение. При присваивании символьного значения выражению с проиндексиро­ ванным объектом создается иллюзия того, что в текстовом поле объек­ та меняется значение символа с соответствующим индексом. Делается это так. Сначала с помощью условного оператора проверяется значе­ ние индекса k. Условие k< O 1 1 k>=text . Length истинно в том случае, если индекс меньше нуля или больше максимально допустимого ин­ декса в тексте (определяется длиной текста). В этом случае выполня­ ется инструкция r e t u rn, которая завершает работу аксессора, и с тек­ стовым полем объекта, соответственно, ничего не происходит. Если же этого не происходит, то индекс попадает в допустимый диапазон значе­ ний и начинается процесс вычисления нового текстового значения для поля t e x t . Для этого объявляется локальная текстовая переменная t с пустой текстовой строкой в качестве начального значения. Затем запу­ скается оператор цикла, в котором к текстовой строке последовательно дописываются начальные символы из поля t e x t , но только до символа с индексом k (этот символ не копируется). Далее командой t + =va l u e к текстовой строке дописывается тот символ, который присваивает­ ся значением выражению с проиндексированным объектом. То есть 490 С в ойст ва и индекс аторы получается, что вместо символа с индексом k из текстового поля t e x t в текстовую строку дописывается тот символ, что указан в операции присваивания (определяется значением параметра val ue ). Затем сно­ ва запускается оператор цикла, с помощью которого к текстовой строке из переменной t дописываются все оставшиеся символы из текстово­ го поля t e x t . По завершении этого оператора цикла командой t e x t = t текстовое поле получает новое значение. Новое значение поля t e x t от­ личается от предыдущего значения одним символом. В главном методе программы командой MyS t r i n g t x t = " Myx a " соз­ дается объект t x t класса MyS t r i n g (здесь использована операция неявного приведения типов). Также главный метод содержит приме­ ры использования индексатора с индексом, значение которого выхо­ дит за допустимый диапазон (команды t x t [ - 1 ] = ' ы ' и t x t [ 4 ] = ' ъ ' ). Есть также команды ( t x t [ O ] = ' C ' , t x t [ l ] = ' л ' , t x t [ 2 ] = ' o ' и txt [ 3 ] = ' н ' ), в которых значение индекса корректно. Для проверки значения текстового поля объекта t x t используется переопределение метода T o S t r i n g ( ) (метод вызывается, когда объект t x t передается аргументом методу W r i t e L i n e ( ) ). В следующем примере описывается класс с индексатором, индекс кото­ рого не является целым числом. Соответствующая программа представ­ лена в листинге 9. 1 1 . � Л истинг 9 . 1 1 . И ндексатор с нечисловым индексом u s ing Sys tem; 11 Класс с индексатором : c l a s s MyClas s { 1 1 Целочисленное поле : puЫ ic int code ; 1 1 Конструктор с одним аргументом : puЫ ic MyCla s s ( int n ) { code=n ; 1 1 Целочисленный индексатор с индексом , который является 11 объектом класса MyCla s s : puЫ ic int this [ MyC l a s s obj ] { 1 1 Метод вызывается при считывании значения 491 Гл ава 9 1 1 выражения с проиндексированным объектом : get { 1 1 Резуль тат : return code - obj . code ; 1 1 Метод вызывается при присваивании значения 11 выражению с проиндексированным объектом : set { 1 1 Присваивается значение полю : code=obj . code+value ; 1 1 Класс с главным методом : c l a s s Non intindexDemo { 1 1 Главный метод : static vo id Main ( ) { 1 1 Создание объекта : MyC l a s s A=new MyC l a s s ( l O O ) ; 1 1 Проверка значения поля объекта : Console . WriteLine ( " Oбъeкт А : { 0 } " , A . code ) ; 1 1 Создание объекта : MyC l a s s B=new MyC l a s s ( 1 5 0 ) ; 1 1 Проверка значения поля объекта : Console . WriteLine ( " Oбъeкт В : { 0 } " , B . code ) ; 1 1 Использование индексатора : int num=A [ B ] ; Console . WriteLine ( " Bыpaжeниe А [ В ] = { О } " , num) ; Console . WriteLine ( " Bыpaжeниe В [А ] = { О } " , В [А ] ) ; А [ В ] =2 0 0 ; 1 1 Проверка значения поля объекта : Console . WriteLine ( " Oбъeкт А : { 0 } " , A . code ) ; 492 С в ойст ва и индекс аторы Результат выполнения программы такой. [1i!J Результат выполнения программы (из листинга 9 . 1 1 ) Объект А : 1 0 0 Объект В : 1 5 0 Выражение А [ В ] = - 5 0 Выражение В [ А] = 5 0 Объект А : 3 5 0 Здесь мы описываем класс MyC l a s s , у которого есть открытое целочис­ ленное поле c o de , конструктор с одним аргументом и индексатор. Тип индексатора определяется ключевым словом i n t , а вот индекс в индекса­ торе описан как MyC l a s s obj . Аксессоры индексатора описаны так, что результатом выражения вида А [ В ] , в котором А и В являются объектами класса MyC l a s s, является разность значений поля c o de объекта А и объ­ екта В. Например, если поле c o de объекта А равно 1 О О , а поле c o de объ­ екта В равно 1 5 О , то результатом выражения А [ В ] будет 5 О (разность значений 1 О О и 1 5 О ). Значение выражения В [ А ] при этом равно 5 О (раз­ ность значений 1 5 О и 1 О О ). Если выражению вида А [ В ] присваивается целочисленное значение, то поле c o de объекта А получит новое значение, которое равно сумме значения поля c o de объекта В и значения, присваи­ ваемого выражению А [ В ] . Так, при значении поля c o de объекта В равном 1 5 О в результате выполнения команды А [ В ] =2 О О поле c o de объекта А получит значение 3 5 О (сумма значений 1 5 О и 2 О О ). - Двумерные и ндексаторы Дело государственной важности. Возможна погоня. из к/ф «Бриллиантовая рука» Двумерный индексатор описывается, в принципе, так же, как и одномер­ ный индексатор. Отличие лишь в том, что теперь в индексаторе опи­ сывается два индекса (могут быть разного типа). Для каждого индекса указывается тип, описания индексов в квадратных скобках разделяют­ ся запятыми. При индексировании объектов также указывается два ин­ декса. В листинге 9. 1 2 представлена программа, дающая представление о том, как описывается и используется двумерный индексатор. 493 Гл ава 9 [1i!J Л истинг 9 . 1 2 . Знакомство с двумерными индексаторами us ing Sys tem; / / Класс с двумерным индексатором : c l a s s MyClas s { / / Закрытое поле , являющееся ссылкой н а двумерный / / символьный массив : private char [ , ] s ymЬ s ; / / Конструктор с двумя аргументами : puЫ ic MyC la s s ( int а , int Ь ) { / / Создание двумерного массива : s ymbs=new char [ a , Ь ] ; / / Заполнение двумерного массива . / / Перебор строк массива : for ( int i=O ; i<symЬs . GetLength ( O ) ; i++ ) { / / Перебор столбцов массива : for ( int j = O ; j < s ymЬs . GetLength ( l ) ; j ++ ) { s ymЬs [ i , j ] = ' O ' ; / / Значение элемента массива / / Метод для отображения содержимого массива : puЫ ic vo id show ( ) { Console . WriteLine ( " Двyмepный массив : " ) ; / / Перебор строк массива : for ( int i=O ; i<symЬs . GetLength ( O ) ; i++ ) { / / Перебор столбцов массива : for ( int j = O ; j < s ymЬs . GetLength ( l ) ; j ++ ) { / / Отображение значения элемента : Console . Write ( s ymЬs [ i , j ] + " " ) ; Console . WriteLine ( ) ; 494 С в ойст ва и индекс аторы 1 1 Двумерный индексатор : puЫ ic char this [ int i , int j ] { 1 1 Метод вызывается при считывании значения 11 выражения с проиндексированным объектом : get { 1 1 Резуль тат : return s ymЬs [ i , j ] ; 1 1 Метод вызывается при присваивании значения 11 выражению с проиндексированным объектом : set { 1 1 Значение элемента массива : s ymЬs [ i , j ] =value ; 1 1 Класс с главным методом : c l a s s TwoDimindexerDemo { 1 1 Главный метод : static vo id Main ( ) { 1 1 Создание объекта : MyC l a s s obj =new MyC l a s s ( 2 , 3 ) ; 1 1 Проверка содержимого массива : obj . show ( ) ; 1 1 Индексирование объекта : obj [ О , О ] = ' А ' ; obj [ 1 , 2 ] = ' z ' ; 1 1 Проверка содержимого массива : obj . show ( ) ; Console . WriteLine ( " Пpoвepкa : " ) ; 1 1 Индексирование объекта : 495 Гл ава 9 Console . WriteLine ( 11 obj [ 0 , 0 ] = { О } 11 , obj [ О , 0 ] ) ; Console . WriteLine ( 11 obj [ 1 , 1 ] = { О } 11 , obj [ 1 , 1 ] ) ; Console . WriteLine ( 11 obj [ 1 , 2 ] = { О } 11 , obj [ 1 , 2 ] ) ; При выполнении программы получаем такой результат. [1i!J Результат выполнения программы ( из листинга 9 . 1 2) Двумерный массив : о о о о о о Двумерный массив : А О О о о z Проверка : obj [ 0 , О ] =А obj [ 1 , 1 ] =0 obj [ 1 , 2 ] =Z В этой программе мы описываем класс MyC l a s s , в котором есть закры­ тое поле s ymЬ s , представляющее собой ссылку на двумерный символь­ ный массив. Сам массив создается при вызове конструктора, которому передается два целочисленных аргумента. Они определяют размеры массива. Все элементы массива получают символьное значение ' О ' . Также в классе описан метод s how ( ) , при вызове которого отображает­ ся содержимое двумерного массива, связанного с объектом, из которого вызывается метод. Двумерный индексатор описан в классе MyC l a s s с ключевым словом c h a r , обозначающим тип возвращаемого и присваиваемого значения. У индексатора два индекса - оба целочисленные. При считывании зна­ чения выражения вида ob j [ i , j ] (где obj является объектом класса MyC l a s s ) результатом возвращается значение соответствующего эле­ мента массива s уmЬ s [ i , j ] , а при присваивании значения выражению вида obj [ i , j ] значение в действительности присваивается элементу массива s уmЬ s [ i , j ] . Выполнение подобных операций проиллюстри­ ровано в главном методе программы. 496 С в ойст ва и индекс аторы Еще один пример использования двумерного индексатора представ­ лен в программе в листинге 9 . 1 3 . В данном примере описывается класс MyC l a s s , в котором имеется три массива (предполагается, что одного и того же размера): символьный, текстовый и целочисленный. С помо­ щью данного класса мы в очень простом варианте реализуем конструк­ цию, напоминающую ассоциативный контейнер. В таком контейнере есть элементы, у каждого элемента имеется значение, а доступ к элемен­ ту осуществляется на основе ключей. Ключ во многом схож с индексом, но в отличие от индекса ключ не обязательно должен быть целочислен­ ным. Мы будем исходить из того, что ключи элементов записываются в символьный и текстовый массивы, а значение элемента хранится в це­ лочисленном массиве. � ПОДРОБН ОСТИ Термин «элемент» в данном при мере испол ьзуется в двух смыслах. Во-первых, м ы условно полагаем , что объект класса содержит неко­ торые «виртуал ьные» элементы , которые имеют значение ( целоч ис­ ленные) и доступ к которым осуществляется п о двум кл ючам (сим­ вол и текст ) . Во- вторых, есть три массива: символ ьн ы й , текстовый и целочислен н ы й . Масси вы оди накового размера. Между элемен­ там и масси вов существует строгое соответствие: есл и зафиксиро­ вать некоторый целочисленный индекс , то элементы в сим вол ьном и текстовом масси вах с эти м индексом оп редел яют значения кл ю­ чей нашего «ви ртуал ьного» элемента , а элемент в целоч исленном массиве с таки м же индексом определяет значение нашего «вирту­ ал ьного» элемента . С помощью индексатора мы реализуем две операции: считывание значе­ ния выражения на основе объекта с двумя индексами (символ и текст) и присваивание значения такому выражению. При считывании зна­ чения в символьном и текстовом массивах объекта выполняется по­ иск элементов, значения которых совпадают со значениями индексов. Причем совпадение должно быть �двойное�: значение элемента в сим­ вольном массиве должно совпадать со значением символьного индекса, а значение элемента на такой же позиции в текстовом массиве долж­ но совпадать со значением текстового индекса. Если такое совпадение есть, то значением выражения возвращается значение соответствую­ щего элемента из целочисленного массива. Если совпадение не найде­ но, то в каждый из трех массивов (символьный, текстовый и целочис­ ленный) добавляется новый элемент. Значение добавляемого элемента 497 Гл ава 9 такое: для символьного и текстового массивов это значения индексов, а для целочисленного массива значение добавляемого элемента равно О . При присваивании значения выражению ( с проиндексированным объ­ ектом) выполняется поиск по индексам. Если элемент найден, то ему присваивается новое значение (точнее, в целочисленный массив для со­ ответствующего элемента записывается новое значение). Если элемента с указанными индексами в объекте нет, то он добавляется в объект (каж­ дый из трех массивов получает по одному новому элементу). � ПОДРОБН ОСТИ Операци ю добавления нового элемента в массив следует понимать в том смысл е , что создается новый масси в , размер которого на еди ­ н и цу больше, чем размер исходного массива. В новый массив ко­ п и руются значения элементов из исходного массива, а последн и й элемент получает значение того элемента, который «добавляется » . Ссыл ка н а новый массив записы вается в переменную массива. Соз­ дается иллюзия , что в исходном массиве поя вился новый элемент. Рассмотрим представленный ниже программный код. [1iiJ Л истинг 9 . 1 З . Использование двумерного индексатора us ing Sys tem; 11 Класс с двумерным индексатором : c l a s s MyClas s { 1 1 Закрытое поле , являющееся ссылкой на 11 целочисленный массив : private int [ ] val s ; 1 1 Закрытое поле , являющееся ссылкой на 11 символьный массив : private char [ ] ckey; 11 Закрытое поле , являющееся ссылкой на 11 текстовый массив : private s t ring [ ] skey; 11 Закрытый метод для добавления новых элементов 11 в массивы : 498 С в ойст ва и индекс аторы private void add ( char а , string Ь , int n ) { / / Локальная переменная для записи размера / / новых массивов : int s i z e ; / / Переменная для символьного массива : char [ ] s ; / / Переменная для текстового массива : s tring [ ] t ; / / Переменная для целочисленного массива : int [ ] v ; / / Определение размера новых массивов : i f ( vals==nu l l ) s i ze=l ; / / Если массива нет else s i z e=val s . Length+ l ; / / Если массив существует / / Создание символьного массива : s=new char [ s i z e ] ; / / Значение последнего элемента созданного массива : s [ s . Length - l ] =a ; / / Создание текстового массива : t=new s t r ing [ s i z e ] ; / / Значение последнего элемента созданного массива : t [ t . Length - l ] =b ; / / Создание целочисленного массива : v=new int [ s i ze ] ; / / Значение последнего элемента созданного массива : v [ v . Length - l ] =n ; / / Заполнение созданных массивов : for ( int k=O ; k<s i z e - 1 ; k++ ) { s [ k ] =ckey [ k ] ; t [ k ] =skey [ k ] ; v [ k ] =val s [ k ] ; / / Новые значения полей : 499 Гл ава 9 ckey=s ; s key=t ; val s =v ; / / Переопределение метода ToString ( ) : puЫ ic ove rride s t ring ToString ( ) { / / Текстовая переменная : s tring tхt= " Содержимое объекта : \ n " ; / / Е сли массив существует : i f (vals ! =nu l l ) { / / Перебор элементов массивов : for ( int k=O ; k<ckey . Length ; k++ ) { / / Добавление текста к текущему значению : txt+=ckey [ k ] + " : " + s key [ k ] + " : " +val s [ k ] + " \ n " ; } el se { / / Если массива нет / / Добавление текста к текущему значению : tхt+=" Пустой объект \ n " ; / / Резуль тат метода : return txt ; / / Целочисленный индексатор с двумя индексами / / символьного и текстового типа : puЫ ic int this [ char а , s t ring Ь ] { / / Метод вызывается при считывании значения / / выражения с проиндексированным объектом : get { / / Если массив существует : i f ( val s ! =nu l l ) { / / Перебор элементов массива : for ( int k=O ; k<ckey . Length ; k++ ) { 500 С в ойст ва и индекс аторы / / Если элемент найден : i f ( a==ckey [ k ] & &b==skey [ k ] ) { / / Значение выражения : return val s [ k ] ; / / Код выполняется , если массив не существует или / / если элемент не найден : int res= O ; / / Значение для нового элемента add ( a , Ь, res ) ; / / Добавление нового элемента return res ; / / Значение выражения / / Метод вызывается при присваивании значения / / выражению с проиндексированным объектом : set { / / Если массив существует : i f ( val s ! =nu l l ) { / / Перебираются элементы массива : for ( int k=O ; k<ckey . Length ; k++ ) { / / Если элемент найден : i f ( a==ckey [ k ] & &b==skey [ k ] ) { / / Присваивание значения элементу : val s [ k ] =value ; / / Завершение метода : return ; / / Если массива нет или если элемент не найден : add ( a , Ь , value ) ; / / Добавление нового элемента 501 Гл ава 9 1 1 Класс с главным методом : c l a s s Us ingTwoDimi ndexerDemo { 1 1 Главный метод : static vo id Main ( ) { 11 Создание объекта : MyC l a s s obj =new MyC l a s s ( ) ; 1 1 Проверка содержимого объекта : Console . WriteLine ( obj ) ; 1 1 Проверка значения элемента : Console . WriteLine ( " Значение элемента : " +obj [ ' А ' , "Первый" ] + " \n " ) ; 1 1 Проверка содержимого объекта : Console . WriteLine ( obj ) ; 1 1 Присваивание значения элементу : obj [ ' В ' , "Второй" ] =2 0 0 ; 1 1 Присваивание значения элементу : obj [ ' С ' , " Третий" ] =З О О ; 1 1 Проверка содержимого объекта : Console . WriteLine ( obj ) ; 1 1 Проверка значения элемента : Console . WriteLine ( " Знaчeниe элемента : " +obj [ ' В ' , "Первый" ] + " \n " ) ; 1 1 Проверка значения элемента : Console . WriteLine ( " Знaчeниe элемента : " +obj [ ' В ' , " Второй" ] + " \n " ) ; 1 1 Проверка значения элемента : Console . WriteLine ( " Значение элемента : " +obj [ ' А ' , " Третий" ] + " \n " ) ; 1 1 Проверка содержимого объекта : Console . WriteLine ( obj ) ; 1 1 Присваивание значения элементу : obj [ ' А ' , " Первый" ] =1 0 0 ; 1 1 Проверка содержимого объекта : Console . WriteLine ( obj ) ; 502 Сво й ства и индексаторы 1 1 Проверка значения элемента : Console . WriteLine ( " Значение элемента : " +obj [ ' А ' , "Первый" ] + " \n " ) ; Результат выполнения программы представлен ниже. [1iiJ Результат выполнения программы (из листинга 9 . 1 3) Содержимое объекта : Пустой объект Значение элемента : О Содержимое объекта : А : Первый : О Содержимое объекта : А : Первый : о В : Второй : 2 0 0 С : Третий : 3 0 0 Значение элемента : О Значение элемента : 2 0 0 Значение элемента : О Содержимое объекта : А : Первый : о В : Второй : 2 0 0 С : Третий : 3 0 0 В : Первый : о 503 Глава 9 А : Третий : О Содержимое объекта : А : Первый : 1 0 0 В : Второй : 2 0 0 С : Третий : 3 0 0 В : Первый : о А : Третий : о Значение элемента : 1 0 0 В классе MyC l a s s , как отмечалось, есть три закрытых массива va l s (целочисленный), c ke y (символьный) и s ke y (текстовый). В классе описан закрытый метод add ( ) с тремя аргументами. При вызове мето­ да add ( ) в каждый из трех массивов добавляется новый элемент. Зна­ чения элементов передаются аргументами методу. При вызове метода проверяется условие va l s ==nu l l , истинное в случае, если поле va l s не ссылается на массив. Если так, то целочисленная переменная s i z е получает значение 1 . Если условие ложно, то целочисленная перемен­ ная s i z e получает значение va l s . Lengt h + l (на единицу больше раз­ мера массива, на который ссылается поле va l s ) . Затем создаются три массива (символьный s, текстовый t и целочисленный v), размер ко­ торых определяется значением переменной s i z е. Последние элементы массивов получают значения, определяемые аргументами метода (ко­ манды s [ s . Length- 1 ] =а, t [ t . Length- 1 ] =Ь и v [ v . Length- 1 ] =n). Прочие элементы массивов заполняются поэлементным копировани­ ем, для чего использован оператор цикла (команды s [ k ] = c ke y [ k ] , t [ k ] = s k e y [ k ] и v [ k ] =va l s [ k ] в теле оператора цикла). Наконец, командами c ke y= s , s ke y=t и va l s =v значениями полям объекта при­ сваиваются ссьтки на новые массивы. Метод T o S t r i n g ( ) переопределен таким образом, что если поля объекта на массивы не ссьтаются, то возвращается текстовая строка с информа­ цией о том, что объект пустой. Если массивы существуют, то возвраща­ ется текстовая строка со значениями элементов трех массивов объекта (в одной строке значения элементов с одним и тем же индексом). В g e t -aкceccope индексатора проверяется условие va l s ! =nu l l . Ис­ тинность условия означает, что целочисленный массив существует 504 Сво й ства и индексаторы (а значит, и два других массива тоже). В этом случае синхронно переби­ раются все элементы символьного и текстового массивов. Для каждого индекса k проверяется условие a== c k e y [ k ] & & b== s ke y [ k ] (первый индекс а совпадает с элементом символьного массива, а второй индекс Ь совпадает с элементом текстового массива). Если совпадение найдено, значением свойства возвращается v a l s [ k ] (элемент целочисленного массива с тем же индексом). В случае, если массивы не существуют или если совпадение не найде­ но, вызывается метод add ( ) , которым в массивы добавляется по ново­ му элементу (или создаются массивы, если их не было), и значение (ну­ левое) элемента, добавленного в целочисленный массив, возвращается результатом выражения. В s e t -aкceccope выполняются аналогичные действия, но последствия немного другие. Если при просмотре содержимого массивов совпаде­ ние найдено (истинно условие a== c ke y [ k ] & &b== s ke y [ k ] ), то коман­ дой val s [ k ] =va l ue элементу целочисленного массива присваивается значение. После этого инструкцией r e t u r n завершается выполнение s е t-аксессора. Если совпадение не найдено, то с помощью метода add ( ) в массивы до­ бавляется по одному новому элементу. В главном методе создается объект ob j класса MyC l a s s. Проверка пока­ зывает (команда C on s o l e . W r i t e L i n e ( ob j ) ), что объект пустой (его поля не ссылаются на массивы). Поэтому при попытке узнать значение выражения ob j [ ' А ' , " Первый " ] получаем О , а в массивы объекта obj добавляется по новому элементу (понятно, что предварительно массивы создаются). При выполнении команд ob j [ ' В ' , " В т о р о й " ] = 2 0 0 и obj [ ' С ' , " Третий" ] = 3 0 0 в массивы добавляется еще по два элемента. Затем последовательно считываются значения выражений obj [ ' В ' , " Первый " ] , obj [ ' В ' , " В т оро й" ] и ob j [ ' А ' , " Тре тий " ] . Для первого и третьего из этих выражений в объекте o b j совпадений индексов не обнаруживается, а для второго выражения совпадение есть. Поэтому для второго выражения возвращается значение 2 О О из цело­ численного массива, а в соответствии с первым и третьим выражениями в массивы объекта добавляются новые элементы (в том числе нулевые значения в целочисленный массив). 505 Глава 9 G) Н А ЗАМ ЕТ КУ Напом н и м , что при и ндекси рован и и объекта символ ьный и тексто­ вый массивы п росматриваются на предмет «двой ного» совпадения : индексы должн ы одновременно совпасть с соответствующи м и эле­ ментами сим вол ьного и текстового масси вов. Командой obj [ ' А ' , " Первый " ] = 1 0 0 присваивается новое значение уже существующему элементу целочисленного массива. Проверка это подтверждает. М ногомерные и ндексаторы Ну, как говорится, на всякий пожарный случай. из к/ф «БрШ1Лuантовая рука» Многомерные индексаторы описываются в полной аналогии с дву­ мерными массивами, но только индексов больше, чем два. Для каждо­ го индекса указывается тип и формальное название. Небольшой при­ мер использования многомерного (с четырьмя индексами) индексатора представлен в листинге 9. 1 4. � Л истинг 9 . 1 4 . М ногомерны й индексатор us ing Sys tem; 11 Класс с многомерным индексатором : c l a s s MyClas s { 1 1 Закрытое целочисленное поле : private int code ; 1 1 Конструктор с целочисленным аргументом : puЫ ic MyCla s s ( int n ) { 1 1 Значение поля : code=n ; 1 1 Переопределение метода ToString ( ) : puЫ ic ove rride s t ring ToString ( ) { 506 Сво й ства и индексаторы 1 1 Результат метода : return " Поле code объекта : " + code ; 1 1 Многомерный индексатор : puЫ ic char this [ s t r ing а , int i , s tring Ь , int j ] { 1 1 Метод вызывается при считывании значения 11 выражения с проиндексированным объектом : get { 1 1 Значение выражения : return ( char) ( a [ i ] - b [ j ] +code ) ; 1 1 Метод вызывается при присваивании значения 11 выражению с проиндексированным объектом : set { 1 1 Значение поля : code=value - ( a [ i ] - b [ j ] ) ; 1 1 Класс с главным методом : c l a s s MultDimi ndexe rDemo { 1 1 Главный метод : static vo id Main ( ) { 1 1 Создание объекта : MyC l a s s obj =new MyC l a s s ( ' А ' ) ; 1 1 Проверка значения поля объекта : Console . WriteLine ( obj ) ; 1 1 Текстовые переменные : s tring a="Alpha " , b=" B ravo " ; 1 1 Целочисленные переменные : int i=2 , j = 4 ; 1 1 Использование индексатора : 507 Глава 9 Console . WriteLine ( 11 obj [ \ 11 { 0 } \ 11 , { 1 } , \ 11 { 2 } \ 11 , { 3 } ] = { 4 } 11 , а , i , b , j , obj [ а , i , b , j ] ) ; 1 1 Использование индексатора : obj [ a , i , Ь , j ] = ' F ' ; 1 1 Проверка значения поля объекта : Console . WriteLine ( obj ) ; 1 1 Использование индексатора : Console . WriteLine ( 11 obj [ \ 11 { 0 } \ 11 , { 1 } , \ 11 { 2 } \ 11 , { 3 } ] = { 4 } 11 , а , i , b , j , obj [ а , i , b , j ] ) ; 1 1 Новые значения переменных : a=11 Cha r l i e 11 ; i=l ; j =2 ; 1 1 Использование индексатора : Console . WriteLine ( 11 obj [ \ 11 { 0 } \ 11 , { 1 } , \ 11 { 2 } \ 11 , { 3 } ] = { 4 } 11 , а , i , b , j , obj [ а , i , b, j ] ) ; Ниже показан результат выполнения программы. [1iiJ Результат выполнения п рограм мы (из листинга 9 . 1 4) Поле code объекта : 6 5 obj [ 11Alpha11 1 2 , 11Bravo 11 1 4 ] =B Поле code объекта : 6 9 obj [ 11Alpha11 1 2 , 11Bravo 11 1 4 ] =F obj [ 11 Charl i e 11 , 1 , 11 Bravo11 , 2 ] =L В этом примере класс MyC l a s s содержит: • закрытое целочисленное поле c o de ; • конструктор с целочисленным аргументом (определяет значение поля) ; • переопределенный метод T o S t r i n g ( ) (возвращает результатом текстовую строку со значением поля c o de ) ; • индексатор символьного типа с четырьмя индексами (текстовый, це­ лочисленный, текстовый и целочисленный). 508 Сво й ства и индексаторы Нас, понятно, интересует индексатор. В его g e t -aкceccope выполняется команда r e t u r n ( ch a r ) ( а [ i ] -Ь [ j ] + c o de ) . Здесь а и Ь - текстовые индексы (первый и третий), а i и j - целочисленные индексы (второй и четвертый). То есть принцип вычисления значения выражения такой: из текстовых индексов берем по одному символу и вычисляем разность их кодов. К полученному значению прибавляется значение целочислен­ ного поля c o de , полученное значение приводится к символьному типу и возвращается как результат. Символы, которые берутся из тексто­ вых индексов, определяются целочисленными индексами в выражении с проиндексированным объектом. В s e t -aкceccope командой c o de=va l u e - ( а [ i ] -Ь [ j ] ) полю c o de присваивается новое значение. Оно вычисляется по такому принципу: от фактически присваиваемого значения (параметр v a l u e ) отнимается разность кодов символов, определяемых индексами в выражении с про­ индексированным объектом. � ПОДРОБН ОСТИ При выполнении арифметических операций с символ ьными значе­ ниями вы полняется автоматическое п реобразование сим вол ьных значений к целочислен ным значен и я м . Други м и словам и , в ариф­ метических операциях вместо сим волов испол ьзуются их коды . В главном методе мы создаем объект o b j класса MyC l a s s , причем ар­ гументом передается не целое число, а символьное значение ' А ' . Это допустимо, поскольку есть автоматическое преобразование значений символьного типа в целочисленное значение. В результате поле c o de значением получает код символа ' А ' (проверка показывает, что это число 6 5 ). Для индексирования объекта o b j мы используем текстовые перемен­ ные а и Ь с начальными значениями 11 Alpha 11 и 11 B r avo 11 , а также цело­ численные переменные i и j с начальными значениями 2 и 4 . Значение выражения obj [ а , i , Ь , j ] в этом случае вычисляется так: в тек­ сте 11 Alpha 11 берем символ с индексом 2 (это буква ' р ' ), а из текста 11 B r avo 11 берем символ с индексом 4 (это буква ' о ' ) . Разница между кодами символов ' р ' и ' о ' равна 1 (между буквами в алфавите одна позиция), и это значение прибавляется к значению 6 5 поля c o de , что в итоге дает число 6 6, и это код символа ' в ' . 509 Глава 9 При выполнении команды оЬj [ а , i , Ь , j ] = ' F ' новое значение поля c o de объекта o b j вычисляется следующим образом: из кода символа ' F ' (число 7 О ) вычитается разность кодов символов ' р ' и ' о ' (раз­ ность кодов равна 1 ), что дает значение 6 9 . Это новое значение поля c o de . Понятно, что, если мы теперь проверим значение выражения obj [ а , i , Ь , j ] , оно окажется равным ' F ' . Наконец, переменная а получает новое значение " Ch a r l i e " (перемен­ ная Ь остается со старым значением " B ravo " ), значение переменной i становится равным 1 , а значение переменной j теперь равно 2 . Если вычислить значение выражения obj [ а , i , Ь , j ] , то получим такое: • Символ с индексом 1 в тексте " Ch a r l i e " - это буква ' h ' . • Символ с индексом 2 в тексте " B ravo " - это буква ' а ' . • Разность кодов символов ' h ' и ' а ' - число 7 (между буквами ' h ' и ' а ' семь позиций). • К числу 7 прибавляется значение 6 9 поля code, и в результате по­ лучается число 7 6 . • Коду 7 6 соответствует буква ' L ' . Непосредственная проверка подтверждает наши выводы. П ерегрузка и ндексаторов Ну, я уверен, что д о этого н е дойдет ! из к/ф «Бриллиантовая рука» Индексаторы можно перегружатъ. Перегрузка индексаторов означа­ ет, что в классе может быть описано несколько индексаторов, которые должны отличаться количеством и/или типом индексов. Пример класса с перегрузкой индексатора можно найти в программе в листинге 9. 1 5. [1i!J Л истинг 9 . 1 5 . Перегрузка индексаторов us ing Sys tem; 11 Класс с несколькими индексаторами : c l a s s MyClas s { 1 1 Закрытое целочисленное поле : 510 Сво й ства и индексаторы private int [ ] nums ; 1 1 Конструктор класса с одним аргументом : puЫ ic MyCla s s ( int s i z e ) { 1 1 Создание массива : nums=new int [ s i z e ] ; 1 1 Заполнение массива : for ( int k=O ; k<nums . Length ; k++ ) { 1 1 Использование индексатора : this [ k ] =k+ l ; 1 1 Переопределение метода ToString ( ) : puЫ ic override s tring ToString ( ) { 1 1 Текстовая переменная : s t ring tхt= " Содержимое объекта : \ n " ; 1 1 Формирование текстовой строки : for ( int k=O ; k<nums . Length ; k++ ) { 1 1 Используется индексатор с целочисленным 1 1 индексом : txt+=thi s [ k ] + ( k==nums . Length - 1 ? " \n " : " " ) ; 1 1 Результат метода : return txt ; 1 1 Индексатор с целочисленным индексом : puЫ ic int thi s [ int k] { 1 1 Аксессор для считывания значения : get { return nums [ k%nums . Length ] ; 1 1 Аксессор для присваивания значения : set { 511 nums [ k%nums . Length ] =value ; 1 1 Индексатор с символьным индексом : puЫ ic int thi s [ char s ] { 1 1 Аксессор для считывания значения : get { 1 1 Исполь зуется индексатор с целочисленным 1 1 индексом : return this [ s - ' a ' ] ; 1 1 Аксессор для присваивания значения : set { 1 1 Исполь зуется индексатор с целочисленным 1 1 индексом : this [ s - ' a ' ] =value ; 1 1 Индексатор с целочисленным и текстовым индексом : puЫ ic int thi s [ int k, s tring t ] { 1 1 Аксессор для считывания значения : get { 1 1 Исполь зуется индексатор с символьным индексом : return this [ t [ k ] ] ; 1 1 Аксессор для присваивания значения : set { 1 1 Исполь зуется индексатор с символьным индексом : this [ t [ k ] ] =value ; 1 1 Индексатор с текстовым и целочисленным индексом : 512 Сво й ства и индексаторы puЫ ic int thi s [ s tring t , int k] { 1 1 Аксессор для считывания значения : get { 1 1 Использование индексатора с целочисленным и 1 1 текстовым индексом : return this [ k , t ] ; 1 1 Аксессор для присваивания значения : set { 1 1 Использование индексатора с целочисленным и 1 1 текстовым индексом : this [ k , t ] =value ; } 1 1 Класс с главным методом : c l a s s OverloadingindexerDemo { 1 1 Главный метод : s tatic vo id Main ( ) { 1 1 Целочисленная переменная : int n= б ; 1 1 Создание объекта : MyC l a s s obj =new MyClas s ( n ) ; 1 1 Проверка содержимого объекта : Console . WriteLine ( obj ) ; 1 1 Поэлементное отображение содержимого массива : for ( int k=O ; k<n+ З ; k++ ) { 1 1 Объект с целочисленным индексом : Console . Wr i te ( obj [ k ] + " " ) ; Console . WriteLine ( " \n " ) ; 1 1 Объект с целочисленным индексом : 513 Глава 9 obj [ 1 ] =7 ; obj [ n+3 ] =8 ; Console . WriteLine ( obj ) ; 1 1 Объект с символь ным индексом : obj [ ' а ' ] = 9 ; obj [ ' k ' ] =О ; 1 1 Проверка содержимого объекта : Console . WriteLine ( obj ) ; Console . WriteLine ( " Пpoвepкa : " ) ; 1 1 Поэлементное отображение содержимого массива : for ( char s= ' a ' ; s< ' a ' +n+ З ; s++ ) { 1 1 Объект с символь ным индексом : Console . Write ( obj [ s ] + " " ) ; Console . WriteLine ( " \n " ) ; 1 1 Объект с целочисленным и текстовым индексом : obj [ 4 , " a lpha " ] =О ; obj [ "bravo " , 0 ] = 6 ; 1 1 Проверка содержимого массива : Console . WriteLine ( obj ) ; 1 1 Текстовая переменная : s t ring txt= " abc" ; Console . WriteLine ( " Пpoвepкa : " ) ; 1 1 Отображение значений элементов массива : for ( int k=O ; k<txt . Length ; k++ ) { 1 1 Объект с двумя индексами : Console . WriteLine ( obj [ k, txt ] + " : " +obj [ tx t , k ] ) ; Результат выполнения программы такой. 514 Сво й ства и индексаторы [1i!J Результат выполнения программы ( из листинга 9 . 1 5) Содержимое объекта : 1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 Содержимое объекта : 1 7 3 8 5 6 Содержимое объекта : 9 7 3 8 о 6 Проверка : 9 7 3 8 о 6 9 7 3 Содержимое объекта : о 6 3 8 о 6 Проверка : о о 6 6 3 3 В классе MyC l a s s есть закрытый целочисленный массив num s . Массив создается и заполняется при вызове конструктора. Для заполнения мас­ сива запускается оператор цикла. Там (при заданном индексе k) выпол­ няется команда t h i s [ k ] = k, в которой использован индексатор. В этой команде ключевое слово th i s обозначает объект, из которого вызывает­ ся метод (в данном случае речь идет об объекте, при создании которого вызывается конструктор). G) Н А ЗАМ ЕТ КУ Напом н и м , что кл ючевое слово th i s я вляется зарезерви рован н ы м и обозначает объект, из которого вызы вается метод. 515 Глава 9 Инструкция t h i s [ k ] означает, что индексируется объект, для которого вызван конструктор. Этому выражению присваивается значение k. Дан­ ная команда выполняется в соответствии с тем, как описан s e t -aкcec­ cop индексатора с целочисленным индексом (в классе описано несколь­ ко индексаторов). В соответствии с кодом s e t-aкceccopa выполняется команда nums [ k % nums . Length ] =va l ue, которой значение присваива­ ется элементу массива num s . Индекс элемента вычисляется выражени­ ем k % n ums . Length. Результатом является остаток от деления факти­ ческого значения индекса k на размер массива nums . L e n g t h . Общий эффект такой, что если положительный индекс k не превышает верхнюю допустимую границу (значение nums . Length - 1 ), то значение выраже­ ния k% nums . Length совпадает со значением k, а при выходе индекса k за верхнюю допустимую границу выполняется циклическая перестанов­ ка индекса. Такой же подход использован и в ge t-аксессоре индексатора: результатом возвращается значение элемента nums [ k % nums . Length ] , в котором индекс определяется выражением k % nums . Length. Индексатор с целочисленным индексом мы используем и при переопре­ делении метода T o S t r i ng ( ) : в теле метода при формировании тексто­ вой строки (возвращаемой результатом метода) использовано выраже­ ние th i s [ k ] , смысл которого такой же, как описывалось выше. � ПОДРОБН ОСТИ Значение выражения k==nums . Length- 1 ? 11 \ n 11 : 11 11 на основе тернар­ ного оператора ? : вычисляется так: если значение переменной k рав­ но значению выражения nums . Length - 1 (значение индекса последнего элемента массива nums) , результатом выражения является текст 11 \ n 11 • В противном случае результатом выражения является текст 11 11 • Но в классе MyC l a s s описаны и другие индексаторы. Там есть индекса­ тор с символьным индексом. В get-aкceccope результатом возвращает­ ся выражение th i s [ s - ' а ' ] . Поскольку значением выражения s - ' а ' является число (разность кодов символов), то выражение th i s [ s - ' а ' ] вычисляется вызовом g e t -aкceccopa для индексатора с целочисленным индексом. Аналогично, в s e t -aкceccope для индексатора с символьным индексом при выполнении команды t h i s [ s - ' а ' ] =va l u e вызывается s e t-aкceccop для индексатора с целочисленным индексом. Индексатор с двумя индексами (целое число k и текст t) в ge t-аксессо­ ре содержит команду r e t u r n t h i s [ t [ k ] ] . В выражении th i s [ t [ k ] ] 516 Сво й ства и индексаторы индекс t [ k ] является символом (символ из текста t с индексом k), поэ­ тому данное выражение вычисляется вызовом ge t-аксессора для индек­ сатора с символьным индексом. То же справедливо и для s e t-aкceccopa, в котором выполняется команда t h i s [ t [ k ] ] =va l u e . Еще одна версия индексатора с двумя индексами отличается о т описан­ ной выше порядком индексов: теперь первый индекс t текстовый и вто­ рой индекс k целочисленный. В g e t -aкceccope выполняется команда r e t u rn th i s [ k , t ] . В выражении t h i s [ k , t ] первый индекс це­ лочисленный, а второй индекс текстовый. Аналогично в s e t-aкceccope выполняется команда t h i s [ k , t ] =va l u e , содержащая такое же вы­ ражение t h i s [ k , t ] . В обоих случаях речь идет об использовании индексатора с первым целочисленным индексом и вторым текстовым индексом. В итоге мы создали ситуацию, когда порядок, в котором мы указываем целочисленный и текстовый индекс, не имеет значения. В главном методе программы создается объект o b j класса MyC l a s s . Массив в этом объекте содержит 6 (значение переменной n ) элементов, заполненных числами от 1 до 6 включительно. Далее мы запускаем опе­ ратор цикла, в котором индексная переменная k �выскакивает» за до­ пустимую верхнюю границу. Но проблемы в этом нет, поскольку при индексировании объекта o b j выполняется циклическая перестановка индексов. При выполнении команд obj [ 1 ] =7 и obj [ n + З ] =8 элементы с индек­ сами 1 и 3 массива nums в объекте obj получают новые значения 7 и 8 соответственно. При выполнении команд obj [ ' а ' ] = 9 и obj [ ' k ' ] = О новые значения ( 9 и О ) получают элементы с индексами О и 4 . � ПОДРОБН ОСТИ Есл и м ы испол ьзуем сим вол в качестве индекса , то зап рос вы пол ­ няется для элемента с и ндексом , равным разности кодов этого сим­ вола и сим вола ' а ' . Поэтому сим вол ' а ' экви валентен ч исловому индексу О , а сим вол ' k ' экви валентен числовому индексу 1 0 , что с учетом ци клической перестановки для массива из шести элемен­ тов дает значение 4 . Когда мы перебираем элементы массива, индексируя символьными зна­ чениями объект, то при выходе за допустимую верхнюю границу индек­ сы переставляются циклически - после значения последнего элемента массива отображается значение первого (начального) элемента, затем 517 Глава 9 второго и так далее. Причина в том, что при обработке выражения с сим­ вольным индексом на самом деле вычисляется выражение с целочислен­ ным индексом, а для таких случаев предусмотрена циклическая переста­ новка индексов. При выполнении команды obj [ 4 , " a lpha " ] = О нулевое значение при­ сваивается элементу массива nums с индексом, который является сим­ волом с индексом 4 в тексте " a lpha " . Это символ ' а ' , который со­ ответствует числовому индексу О в массиве. Выполнение команды o b j [ " b r avo " , О ] = 6 означает, что значение 6 присваивается элемен­ ту массива nums с индексом, который является символом с индексом О в тексте " Ь r а vo " . Это символ ' Ь ' , который соответствует числовому индексу 1 в массиве. Также в главном методе есть оператор цикла, в котором с помощью ин­ дексной переменной k перебираются символы из текстовой переменной t x t (со значением " аЬс " ). В консольном окне отображаются значения выражений вида obj [ k , txt ] и obj [ t xt , k ] , которые должны со­ впадать (и они совпадают), поскольку порядок следования индексов не имеет значения. G) Н А ЗАМ ЕТ КУ В рассмотренном при мере при оп ределении одн их и ндексаторов м ы испол ьзовал и другие индексаторы . Это не я вляется обязатель­ ным условием , но очень часто упрощает жизн ь и делает код более уни версал ьн ы м . Р ез ю ме Нечестная игра ! Ты специально мои ходы пло­ хо думал ! из к/ф -«Кин-дза-дза» Кроме полей и методов, в классе могут быть описаны и другие члены: свойства и индексаторы. • • Свойство представляет собой нечто среднее между полем и мето­ дом. По способам использования свойство похоже на поле. В об­ щем случае значение свойства можно прочитать и свойству можно 518 Сво й ства и индексаторы присвоить значение. Обращение к свойству выполняется так же, как и обращение к полю: имя свойства указывается через точку после имени объекта. • При считывании значения свойства и при присваивании значения свойству вызываются специальные методы, которые называются ак­ сессорами (ge t-aкceccop и s e t-aкceccop соответственно). Аксессо­ ры описываются при описании свойства. • При описании свойства указывается тип свойства и его название (также указывается спецификатор уровня доступа). Затем в фигур­ ных скобках описываются аксессоры. Команды, выполняемые при вызове аксессоров, указываются в фигурных скобках. Перед блоком команд для g e t -aкceccopa указывается ключевое слово ge t . Перед блоком команд для s e t -aкceccopa указывается ключевое слово s e t . • Для свойства может быть описан только один аксессор. Если для свойства описан только g e t -aкceccop, то такое свойство доступно для чтения, но недоступно для присваивания значения. Если для свойства описан только s e t -aкceccop, то такому свойству можно присвоить значение, но прочитать значение свойства нельзя. • Свойство не определяет область памяти (то есть наличие у объек­ та свойства не означает, что для этого свойства выделяется память). Свойство не может быть использовано с идентификаторами r e f и ou t . Свойство может быть статическим. • Если в классе описан индексатор, то объект такого класса можно ин­ дексировать: после имени объекта в квадратных скобках указывает­ ся индекс (или индексы). В общем случае можно считывать значение такого выражения или присваивать значение такому выражению. • При описании индексатора указывается спецификатор уровня до­ ступа, тип индексатора, ключевое слово t h i s , а также в квадратных скобках описываются индексы (указывается тип индекса и формаль­ ное название). Если индексов несколько, то их описание разделяется запятыми. В блоке, выделенном фигурными скобками, описывается g e t -aкceccop и s e t-aкceccop. В аксессорах можно использовать ин­ дексы, а в s e t-aкceccope также используют ключевое слово va l u e , обозначающее присваиваемое значение. Разрешается описать два ак­ сессора или только один аксессор. • Индексатор не определяет область памяти. Индексатор не может быть статическим, а выражения, подразумевающие использование 519 Глава 9 индексатора, не могут использоваться с ключевыми словами r e f и ou t . • Индексаторы можно перегружать: в классе может быть описано не­ сколько версий индексатора, которые должны отличаться количе­ ством и/или типом индексов. Задания для самостоятел ьной работы Своими мозгами надо играть. из к/ф -«Кин-дза-дза» 1 . Напишите программу, в которой есть класс с символьным свойством. Опишите аксессоры для свойства так, чтобы значение свойства попада­ ло в диапазон символов от ' А ' до ' z ' включительно. 2 . Напишите программу, в которой есть класс с целочисленным масси­ вом и целочисленным свойством. При считывании значения свойства оно последовательно и циклически возвращает значения элементов мас­ сива. При присваивании значения свойству изменяется значение того элемента, который в данный момент интерпретируется как значение свойства. 3. Напишите программу, в которой есть класс с целочисленным масси­ вом. Опишите в классе свойство, доступное только для считывания зна­ чения. Значением свойства является сумма элементов массива. 4. Напишите программу, в которой есть класс с закрытым неотрица­ тельным целочисленным полем. Также в классе должно быть закрытое текстовое поле, содержащее значением восьмеричный код числа из це­ лочисленного поля. Опишите в классе свойство, доступное только для присваивания значения. При присваивании неотрицательного целочис­ ленного значения свойству соответствующее число записывается в це­ лочисленное поле, а в текстовое поле заносится восьмеричный код чис­ ла. Опишите еще одно свойство, доступное только для чтения, которое результатом возвращает текст из текстового поля (восьмеричный код числа). 5. Напишите программу, в которой есть класс со статическим свойством. При считывании значения свойства возвращается нечетное число, каж­ дый раз новое: при первом считывании свойства получаем значение 1 , 520 Сво й ства и индексаторы затем 3 , затем 5 и так далее. При присваивании значения свойству опре­ деляется порядковый номер числа в последовательности нечетных чи­ сел, начиная с которого будут возвращаться числа. Например, если присвоить свойству значение 5, то при считывании значения свойства получаем число 9 (пятое по порядку нечетное число), затем число 1 1 , затем 1 3 и так далее. 6. Напишите программу, в которой есть класс с целочисленным масси­ вом и с индексатором. При считывании значения выражения с проин­ дексированным объектом результатом возвращается значение элемента массива. При присваивании значения выражению с проиндексирован­ ным объектом значение присваивается элементу массива. Необходимо описать индексатор так, чтобы при индексировании объекта первый ин­ декс отличался от нуля. Числовые значения, определяющие диапазон изменения индекса (и, соответственно, размер целочисленного масси­ ва) при индексировании объекта, передаются аргументами конструкто­ ру класса. 7 . Напишите программу с классом, в котором есть неотрицательное це­ лочисленное поле. Опишите для класса индексатор (с одним ge t-аксес­ сором) такой, что при индексировании объекта с целочисленным индек­ сом результатом возвращается значение бита в бинарном представлении числа (значение целочисленного поля). 8. Напишите программу с классом, у которого есть неотрицательное це­ лочисленное поле. В классе нужно описать индексатор с целочисленным индексом и s е t -аксессором. Присваивание значения проиндексирован­ ному объекту обрабатывается следующим образом. В фактически при­ сваиваемом значении берется только последняя цифра (остаток от де­ ления числа на 1 О ) . Индекс определяет разряд в числовом значении поля, в который записывается цифра. Нулевой разряд соответствует единицам, единичный разряд соответствует десяткам, разряд два соот­ ветствует сотням и так далее. Например, если объект проиндексирован числом 1 и присваивается значение, заканчивающееся на 5 , то это озна­ чает, что в числе, которое является значением поля, в разряд десятков (разряд 1 ) нужно записать цифру 5 . 9 . Напишите программу с классом, в котором есть двумерный числовой массив. Опишите два индексатора для класса. Двумерный индексатор с двумя целочисленными индексами позволяет прочитать и изменить значение элемента в двумерном массиве, а индексатор с одним цело­ численным индексом возвращает результатом значение наибольшего 52 1 Глава 9 элемента в строке двумерного массива. Присваивание значения вы­ ражению на основе объекта с одним индексом означает присваивание значения тому элементу в строке, который на данный момент имеет наибольшее значение. Строка определяется индексом, указанным при индексировании объекта. Если в строке несколько элементов с наиболь­ шим значением, то используется первый такой элемент. 1 0 . Напишите программу с классом, в котором есть текстовый массив. Опишите в классе одномерный и двумерный индексаторы. Одномерный индексатор позволяет прочитать элемент текстового массива и присво­ ить новое значение элементу текстового массива. Двумерный индекса­ тор позволяет прочитать символ в элементе текстового массива (первый индекс определяет элемент в текстовом массиве, а второй индекс опре­ деляет символ в тексте). Предусмотрите циклическую перестановку ин­ дексов в случае, если они выходят за верхнюю допустимую границу. Гл ава 1 0 НАСЛ ЕДОВАН И Е А как он с вами разговаривает? Вы, человек, достигший вершин лопдопского дна ! В конце концов, вы собираетесь быть принцем ? из к/ф �Формула лю бви» Данная глава посвящена наследованию одному из фундаментальных механизмов любого объектно ориентированного языка, в том числе и языка С#. Среди вопросов и тем, представляющих для нас интерес, будут такие: - • базовые и производные классы, их описание и использование; • особенности описания конструктора производного класса ; • влияние уровня доступа члена класса на его наследование; • замещение полей и методов ; • виртуальные методы и их переопределение; • возможность использовать объектные переменные базового класса при работе с объектами производного класса. Как всегда, будет много примеров, которые иллюстрируют способы при­ менения технологий, описываемых в главе. 523 Гл ава 1 0 Знакомство с наследованием - Где ваша родина? - Не знаю. Я родился па корабле, но куда он плыл и откуда, никто не помпит. из к/ф �Формула лю бви» Идея наследования достаточно проста: вместо того чтобы создавать но­ вый класс, так сказать, �на пустом месте» , мы можем создать его на ос­ нове уже существующего класса. Класс, который используется как ос­ нова для создания нового класса, называется базовым . Класс, который создается на основе базового класса, называется производным . Главный (но далеко не единственный) эффект от создания производного класса на основе базового состоит в том, что открытые (и защищенные) члены из базового класса автоматически добавляются (то есть наследуются) в производный класс. G) Н А ЗАМ ЕТ КУ Здесь есть как м и н и мум «экономия» на наборе п рограмм ного кода : нет необходи мости в теле производного класса заново вводить тот код, который наследуется из базового класса . Но имеются и другие положител ьные эффекты . Допусти м , через какое-то время понадо­ бится внести изменения в исходн ы й код, реал изован ный в базовом классе . Есл и бы испол ьзовалась технология « копирования и встав­ ки » кода , то правки пришлось бы вносить во всех местах, содер­ жащих соответствующи й код. Есл и же испол ьзуется механизм на­ следования , то п равки достаточно внести лишь в программный код базового класса . Есть и другие п реи мущества испол ьзован ия меха­ низма наследован и я . Их мы также будем обсуждать. � ПОДРОБН ОСТИ Выше упоминались защи щенные члены класса . Защищен ные чле­ ны класса описы ваются с кл ючевым словом p r o t e c t e d. Защищен­ ные члены класса , так же как и закрытые (описываются с кл ючевым словом pri va te или без спецификатора доступа) , доступ ны тол ько в п ределах п рограм много кода класса . Защи щен ные члены класса от закрытых отличаются тем , что наследуются в п роизводном клас­ се. Особенности наследован ия членов базового класса с разным уровнем доступа описы ваются в этой главе, но немного позже . 524 Н асл едование Технически наследование реализуется достаточно просто. Базовый класс каким-либо особым образом описывать не нужно. Это самый обычный класс. В описании производного класса после названия клас­ са через двоеточие указывается название базового класса. Так, если мы описываем класс MyC l a s s и хотим, чтобы он был производным от уже существующего класса Ba s e , то шаблон для описания класса MyC l a s s будет выглядеть следующим образом: c l a s s MyClas s : Base { 1 1 Код производного класса В теле класса MyC l a s s описываются только те члены, которые �добав­ ляются� в дополнение к членам, наследуемым из класса B a s e . Повтор­ но описывать члены класса B a s e в классе MyC l a s s не нужно. При этом мы можем обращаться к унаследованным членам, хотя формально они в классе MyC l a s s не описаны. G) Н А ЗАМ ЕТ КУ То , что унаследованные члены базового класса не нужно описы вать в производном классе , совсем не означает, что их нельзя там опи­ сать. Такое « повторное оп исан ие» тоже допусти мо. В этом случае имеет место замещен ие членов и переоп ределение методов . Дан­ ные воп росы мы обсудим нем ного позже . Также стоит заметить, что у производного класса может быть только один базовый класс: нельзя создать производный класс на основе сра­ зу нескольких базовых классов. Вместе с тем производный класс сам может быть базовым классом для другого класса. П роще говоря , мы можем реализовать цепочку наследования: на основе базового клас­ са создается производный класс, а на его основе создается еще один производный класс и так далее. Понятно, что один и тот же класс может быть базовым сразу для нескольких производных классов . Небольшой пример, в котором используется наследование классов, представлен в листинге 1 0. 1 . � Л истинг 1 0 . 1 . Знакомство с наследованием u s ing Sys tem; 11 Базовый класс : c l a s s Base { 525 Гл ава 1 0 1 1 Открытое целочисленное поле : puЫ ic int code ; 1 1 Открытый метод : puЫ ic vo id show ( ) { 1 1 Отображение значения поля : Console . WriteLine ( " Пoлe code : " +code ) ; 1 1 Производный класс : c l a s s MyClas s : Base { 1 1 Открытое символьное поле : puЫ ic char s ymb ; 1 1 Открытый метод : puЫ ic vo id display ( ) { 1 1 Отображение значения символьного поля : Console . WriteLine ( " Пoлe s ymb : " + s ymb ) ; 1 1 Вызов унаследованного метода : show ( ) ; 1 1 Открытое свойство : puЫ ic int numbe r { 11 Аксессор для считывания значения : get { 1 1 Обращение к унаследованному полю : return code ; 1 1 Аксессор для присваивания значения : set { 1 1 Обращение к унаследованному полю : code=value ; 526 Н асл едование 1 1 Класс с главным методом : c l a s s I nhe ritDemo { static vo id Main ( ) { 1 1 Создание объекта производного класса : MyC l a s s obj =new MyC l a s s ( ) ; 1 1 Присваивание значений полям : obj . code= l O O ; obj . symЬ= ' A ' ; 1 1 Вызов метода : obj . display ( ) ; 1 1 Использование свойства : obj . numЬer=2 0 0 ; Console . WriteLine ( " Cвoйcтвo numЬer : " +obj . numЬe r ) ; 1 1 Вызов метода : obj . show ( ) ; Результат выполнения программы такой. [1i!J Результат выполнения п рограммы (из листи нга 1 О . 1 ) Поле s ymЬ : А Поле code : 1 0 0 Свойство numЬe r : 2 0 0 Поле code : 2 0 0 Пример очень простой. Мы описали базовый класс B a s e , в котором есть целочисленное поле c o de и метод s how ( ) без аргументов, не возвраща­ ющий результат. При вызове метода в консольном окне отображается значение числового поля объекта, из которого вызывается метод. На основе класса B a s e путем наследования создается класс MyC l a s s . Непосредственно в этом классе описано символьное поле s ymb, метод d i s p l a y ( ) и целочисленное свойство numЬ e r . При этом в программ­ ном коде класса MyC l a s s мы использовали обращение к полю c o de 527 Гл ава 1 0 и методу s h o w ( ) , хотя непосредственно в теле класса они не описы­ вались. Это стало возможным благодаря тому, что поле c o de и метод s how ( ) наследуются из класса B a s e . Поэтому в классе MyC l a s s они присутствуют, и мы их можем использовать так, как если бы они были описаны в классе MyC l a s s . Если более детально, то при вызове метода di s p l a y ( ) сначала отобра­ жается значение символьного поля объекта, после чего вызывается ме­ тод s how ( ) . Метод s how ( ) , в соответствии с тем, как он описан в клас­ се B a s e , отображает в консольном окне значение целочисленного поля объекта, из которого метод вызывается (а вызывается он в данном слу­ чае из объекта производного класса). Свойство numb e r в производном классе непосредственно связано с по­ лем c o de , которое наследуется из базового класса. При считывании зна­ чения свойства numb e r возвращается значение поля code, а при присва­ ивании значения свойству numb e r значение фактически присваивается полю c o de . В главном методе программы командой МуС l а s s obj =new MyC l a s s ( ) создается объект obj производного класса MyC l a s s . Как видим, проце­ дура создания объекта осталась той же. Созданный объект o b j имеет все поля, методы и прочие члены, которые описаны в классе MyC l a s s или унаследованы в этом классе из базового класса B a s e . В частности, у объекта obj есть поля c o de и s ymb, методы d i s p l a y ( ) и s how ( ) , а также свойство n umbe r. Командами оЬ j . с ode= 1 О О и оЬ j . s ymb= ' А ' полям объекта присваиваются значения. При вызове метода di s р 1 а у ( ) (команда obj . d i s p l a y ( ) ) в консольном окне отображаются значения полей объекта obj . Командой o b j . n u mb e r = 2 О О значение присваивается свойству numb e r , а в реальности новое значение получает поле code. При про­ верке значения свойства numb e r с помощью команды C o n s o l e . W r i t e L i n e ( " С в ой с т в о n umbe r : " + o b j . numbe r ) получаем та­ кое же значение, как и при выполнении команды obj . s h o w ( ) , которой отображается значение поля c o de . G) Н А ЗАМ ЕТ КУ В рассмотренном при мере мы оп исал и класс B a s e , но объекты на основе этого класса не создавал ись. Причина в том , что в объ­ ектах класса B a s e нет н ичего особенного - обычные объекты 528 Н асл едование обычного класса . Гипотетически , есл и бы на основе класса B a s e б ы л создан объект, т о у такого объекта б ы л о бы поле code и метод show ( ) . И этот объект н и как бы не был связан с объектом , создан­ н ы м на основе класса MyC l a s s . Наследование устанавли вает связь между классам и , а не между объектам и . Н аследование и уровни доступа А этот пацак все время говорит на языках, про­ должения которых не знает ! из к/ф -«Кин-дза-дза» Как отмечалось выше, при наследовании из базового класса в производ­ ный �перекочевывают» открытые и защищенные члены класса. Пришло время более детально осветить этот вопрос. Итак, при описании члена класса могут использоваться следующие специ­ фикаторы уровня доступа: puЬ l i c, pr ivat e , p r o t e c t e d и i n t e rn a l . Есть еще вариант, что член класса будет описан вообще без специфика­ тора уровня доступа. Разберем, чем чревата каждая из ситуаций. • Если член класса описан с ключевым словом рuЫ i с (открытый член класса), то он доступен из любого места программного кода. Это наивысший уровень доступности. • Член класса, который описан с ключевым словом p r ivate (закры­ тый член класса), доступен в программном коде в пределах класса, в котором описан. Вне пределов класса напрямую обратиться к та­ кому члену нельзя. • Если член класса описан с ключевым словом p r o t e c t e d (защи­ щенный член класса), то он доступен в пределах программного кода своего класса, а также классов, которые являются производными от класса, в котором описан член. Проще говоря, отличие защищен­ ного члена класса от закрытого члена класса состоит в том, что защи­ щенный член класса наследуется, а закрытый - нет. Причем немало­ важное значение имеет, что мы вкладываем в понятие �наследуется» . Эта ситуация обсуждается далее. • Если член класса описан без спецификатора уровня доступа, то он является закрытым членом класса. 529 Гл ава 1 0 • Член класса, описанный с ключевым словом i n t e r n a l (внутрен­ ний член класса), доступен в программном коде в пределах сборки (упрощенно - это совокупность инструкций программы на проме­ жуточном языке, метаданных и ссылок на используемые в програм­ ме ресурсы). Если сравнивать в плане доступности i n t e r n a l -члeн с рuЫ i с-членом, то последний доступен не только в пределах сбор­ ки, но и в других сборках. � ПОДРОБН ОСТИ Член класса может быть описан тол ько с одн и м спецификатором уровня доступа. Искл ючение составляет комби нация p r o t e c t e d i n t e r n a l . Ч л е н класса , описан н ы й с комби нацией кл ючевых слов p r o t e c t e d i n t e rn a l , доступен в классе и его подклассах, но тол ько в пределах сборки . По умолчан и ю классы доступ ны в пределах сборки . Для того чтобы класс был доступен в других сборках, его описы вают с кл ючевым словом puЫ i c . Такой класс назы вают открыты м . Есл и класс описан с кл ючевым словом s e a l e d (указы вается перед кл ючевым словом c l a s s в оп исании класса) , то такой класс не мо­ жет быть базовым - то есть на основе s e a l ed- клacca нел ьзя со­ здать п роизводн ы й класс . Например, класс S t r i n g , на основе ко­ торого реал изуются текстовые значения , я вляется s е а l еd- классом . М ы постараемся разобраться с тем, что происходит при наследовании базового класса, в котором есть защищенные и закрытые члены класса. Для этого рассмотрим пример в листинге 1 0.2. [1i!] л истинг 1 0 . 2 . Н аследование и уровни доступа us ing Sys tem; 11 Базовый класс с закрытым и защищенным полем : c l a s s Alpha { 1 1 Закрытое целочисленное поле : private int num; 11 Защищенное символьное поле : protected char s ymЬ ; 1 1 Открытый метод для считывания значения 11 целочисленного поля : puЫ ic int getNum ( ) { 530 Н асл едование return num; 11 Открытый метод для считывания значения 11 символьного поля : puЫ ic char getSymЬ ( ) { return s ymЬ ; 1 1 Открытый метод для присваивания значения 11 целочисленному полю : puЫ ic vo id setNum ( int n ) { num=n ; 1 1 Открытый метод для присваивания значения 11 символьному полю : puЫ ic vo id setSymЬ ( char s ) { s ymb=s ; 1 1 Производный класс : c l a s s Bravo : Alpha { 1 1 Открытое символьное свойство : puЫ ic char s ymЬol { 1 1 Аксессор для считывания значения свойства : get { 1 1 Обращение к защищенному унаследованному полю : return s ymЬ ; 1 1 Аксессор для присваивания значения свойству : set { 1 1 Обращение к защищенному унаследованному полю : s ymЬ=value ; 53 1 Гл ава 1 0 1 1 Открытое целочисленное свойство : puЫ ic int numbe r { 1 1 Аксессор для считывания значения свойства : get { 1 1 Вызов открытого унаследованного метода , 1 1 возвращающего значение закрытого поля 11 из базового класса : return getNum ( ) ; 1 1 Аксессор для присваивания значения свойству : set { 1 1 Вызов открытого унаследованного метода для 11 присваивания значения закрытому полю 11 из базового класса : setNum (value ) ; 1 1 Класс с главным методом : c l a s s PrivateAndProtectedDemo { 1 1 Главный метод : static vo id Main ( ) { 11 Создание объекта базового класса : Alpha A=new Alpha ( ) ; 1 1 Вызов метода для присваивания значения 11 целочисленному полю : A . setNum ( l O O ) ; 1 1 Вызов метода для присваивания значения 11 символьному полю : A . setSymЬ ( ' A ' ) ; 1 1 Вызов методов для считывания значений полей : 532 Н асл едование Console . Wri teLine ( " Объект А: { О } и { 1 } " , А . getNum ( } , А. get S ymЬ (} } ; 1 1 Создание объекта производного класса : Bravo B=new Bravo ( } ; 1 1 Вызов метода для присваивания значения 11 целочисленному полю : B . setNum ( 2 0 0 ) ; 1 1 Вызов метода для присваивания значения 11 символьному полю : B . setSymЬ ( ' B ' ) ; 1 1 Вызов методов для считывания значений полей : Console . WriteLine ( " Объект В : { О } и { 1 } " , В . getNum ( ) , В . get S ymЬ ( ) ) ; 1 1 Присваивание значения целочисленному свойству : B . numЬer=ЗO O ; 1 1 Присваивание значения символь ному свойству : B . s ymЬol= ' C ' ; 1 1 Считывание значений свойств : Console . WriteLine ( " Oбъeкт В : { 0 } и { 1 } " , B . numЬer, B . symЬol ) ; Ниже представлен результат выполнения программы. � Результат выполнения п рограммы (из листи нга 1 0 . 2 ) Объект А: 100 и А Объект В : 2 0 0 и В Объект В : 3 0 0 и С Мы описали класс Alpha, в котором есть закрытое целочисленное поле n um, защищенное символьное поле s ymb и четыре открытых метода. С помощью методов g e t Num ( ) и ge t S ymb ( ) можно получить зна­ чение соответственно целочисленного и символьного поля, а методы s e tNum ( ) и s e t S ymb ( ) позволяют присвоить значения полям. В главном методе программы командой Аlрhа A=new Alpha ( ) создает­ ся объект А класса Alpha. Для присваивания значений полям этого объ­ екта использованы команды А . s e tNum ( 1 0 0 ) и А . s e t S ymb ( ' А ' ) . Для 533 Гл ава 1 0 считывания значений полей мы используем выражения А . ge tNum ( ) и А . get S ymb ( ) . Другого способа обращения к полям объекта нет, по­ скольку оба поля (и закрытое поле num, и защищенное поле s ymb) вне пределов кода класса Alpha недоступны. Мы не можем обратиться че­ рез объект А напрямую к этим полям. Нужны «посредники�, которыми в данном случае выступают открытые методы s e tNum ( ) , s e t S ymb ( ) , ge tNum ( ) и ge t S ymb ( ) . То есть в плане доступности поле s ymb не от­ личается от поля n um. Разница между полями проявляется при насле­ довании. Класс B r avo создается путем наследования класса Alpha. Что же «по­ лучает в наследство� класс B r avo? Во-первых, это все открытые мето­ ды. Во-вторых, в классе B r avo наследуется поле s ymb, поскольку оно не закрытое, а защищенное. Причем в классе B r avo это поле остается защищенным. То есть мы можем обратиться к полю s ymb только в теле класса B r avo. Что касается поля n um, то оно не наследуется. Причем «не наследуется� следует понимать в том смысле, что в классе B r avo мы не можем обратиться к полю num. Класс B r avo «ничего не знает� о су­ ществовании этого поля, но поле в реальности существует. В теле клас­ са B r avo мы можем обратиться к нему с помощью методов ge tNum ( ) и s e tNum ( ) , которые являются открытыми и наследуются из класса Alpha. Именно поэтому в описании целочисленного свойства numb e r в классе B r avo для обращения к полю n u m использованы методы ge tNum ( ) и s e tNum ( ) . В описании символьного свойства s ymb o l мы, по аналогии, могли бы использовать методы g e t S ymb ( ) и s e t S ymb ( ) для получения доступа к полю s ymb. Но пошли другим путем: посколь­ ку поле s ymb наследуется, то мы воспользовались возможностью непо­ средственного обращения к нему. В главном методе программы мы с помощью команды B r avo B=new B r avo ( ) создаем объект В класса B r avo. Через этот объект мы мо­ жем напрямую обращаться к четырем методам и свойствам numb e r и s ymb o l . Поле s ymb является защищенным, поэтому вне пределов кода класса B r avo оно не доступно. А поле num вообще не наследует­ ся (в указанном выше смысле). Сначала командами B . s e tNum ( 2 0 0 ) и в . s е t s ymb ( ' в ' ) полям присваиваются значения, а проверяем мы эти значения с помощью выражений В . ge tNum ( ) и В . g e t S ymЬ ( ) . Но есть и другой путь для получения доступа к полям: с помощью свойств. Так, командами в . n umbe r= 3 О О и в . s ymbo 1 = ' с ' записываем в поля новые значения, а с помощью инструкций В . numb e r и В . s ymbo l проверяем результат. 534 Н асл едование Н аследование и конструкторы Ален ноби ностра алис, что означает : ежели один человек построил , другой завсегда разо­ брать может. из к/ф �Формула лю бви» Выше мы столкнулись с ситуацией, когда в базовом классе есть закрытое поле, к которому обращается открытый метод. Метод наследуется в про­ изводном классе и при вызове из объекта производного класса обращается к полю, которое не наследуется. Это ненаследуемое поле физически нали­ чествует у объекта, но оно недоступно для прямого обращения: мы не мо­ жем даже в теле производного класса напрямую обратиться к полю только через унаследованный метод. Такая ситуация может показаться, на первый взгляд, странной, но в действительности это очень разумный механизм. Понять, как он реализуется, будет легче, если учесть, что при создании объекта производного класса сначала вызывается конструктор базового класса. В рассмотренных выше примерах мы конструкторы явно не описывали ни для базового класса, ни для производного класса. Поэто­ му использовались конструкторы по умолчанию. Упрощенная схема создания объекта производного класса в этом случае выглядит так: сначала вызывается конструктор по умолчанию базового класса, и для всех полей объекта, в том числе и закрытых, выделяется ме­ сто в памяти. Затем в игру вступает собственно конструктор по умолча­ нию для производного класса, в результате чего место выделяется для тех полей, которые описаны непосредственно в производном классе. Поэтому, даже если поле в базовом классе является закрытым, в объекте производ­ ного класса оно все равно будет присутствовать, поскольку место под это поле выделяется при вызове конструктора базового класса. � ПОДРОБН ОСТИ При создании объекта производного класса сначала выпол н я ются команды из конструктора базового класса , а потом выпол н я ются те команды , которые собственно оп исаны в конструкторе п роизвод­ ного класса . При удалении объекта п роизводного класса из памяти сначала выпол н я ются команды из деструктора производного клас­ са, а затем команды из деструктора базового класса . Таким обра­ зом , порядок вызова деструкторов обратен к порядку вызова кон ­ структоров . 535 Гл ава 1 0 Теперь представим себе, что в базовом классе несколько конструкторов и среди них нет конструктора без аргументов. Сразу возникает два во­ проса: какую версию базового класса использовать при создании объекта производного класса и как передать аргументы этой версии конструкто­ ра? Решение проблемы состоит в особом способе описания конструктора производного класса, а именно в описании конструктора производного класса после закрывающей круглой скобки через двоеточие указывает­ ся ключевое слово b a s e . После него в круглых скобках указывают ар­ гументы, передаваемые конструктору базового класса. Если аргументы конструктору базового класса передавать не нужно, то скобки оставляют пустыми. В теле конструктора производного класса размещают коман­ ды, выполняемые после выполнения команд из конструктора базового класса. Шаблон описания конструктора производного класса выглядит так (жирным шрифтом выделены основные элементы шаблона): puЫic Конструктор ( аргументы) : Ьаsе ( аргументы) { 1 1 Команды конструктора производного класса В листинге 1 0.3 представлена программа, в которой в базовом и произ­ водном классе описываются конструкторы. � Листинг 1 0 . 3 . Конструктор производного класса us ing Sys tem; 11 Базовый класс : c l a s s Alpha { 1 1 Целочисленное поле : puЫ ic int code ; 1 1 Конструктор с одним аргументом : puЫ ic Alpha ( int n ) { code=n ; Console . WriteLine ( "Alpha ( один аргумент ) : { 0 } " , code ) ; 1 1 Конструктор без аргументов : puЫ ic Alpha ( ) { code= 1 2 3 ; Console . WriteLine ( "Alpha ( без аргументов ) : { 0 } " , code ) ; 536 Н асл едование 1 1 Производный класс : c l a s s Bravo : Alpha { 1 1 Символьное поле : puЫ ic char s ymЬ ; 1 1 Конструктор с двумя аргументами : puЫ ic Bravo ( int n , char s ) : base ( n ) { s ymb=s ; Console . WriteLine ( " Bravo ( два аргумента ) : { 0 } и \ ' { 1 } \ "' , code , s ymЬ ) ; 1 1 Конструктор с целочисленным аргументом : puЫ ic Bravo ( int n ) : base ( n ) { s ymb= ' A ' ; Console . WriteLine ( " Bravo ( int - аргумент ) : { О } и \ ' { 1 } \ "' , code , s ymb ) ; 1 1 Конструктор с символьным аргументом : puЫ ic Bravo ( char s ) : base ( 32 1 ) { s ymb=s ; Console . WriteLine ( " Bravo ( сhаr - аргумент ) : { О } и \ ' { 1 } \ "' , code , s ymЬ ) ; 1 1 Конструктор без аргументов : puЫ ic Bravo ( ) : base ( ) { s ymb= ' O ' ; Console . WriteLine ( " Bravo ( без аргументов ) : { 0 } и \ ' { 1 } \ "' , code , s ymЬ ) ; 1 1 Класс с главным методом : c l a s s I nhe ritAndConstrDemo { 1 1 Главный метод : static vo id Main ( ) { 1 1 Создание объекта базового класса 537 Гл ава 1 0 1 1 ( конструктор без аргументов ) : Alpha Al=new Alpha ( ) ; Console . WriteLine ( ) ; 1 1 Создание объекта базового класса 11 ( конструктор с одним аргументом ) : Alpha A2=new Alpha ( l O O ) ; Console . WriteLine ( ) ; 1 1 Создание объекта производного класса 11 ( конструктор с двумя аргументами ) : Bravo B l =new Bravo ( 2 0 0 , ' В ' ) ; Console . WriteLine ( ) ; 1 1 Создание объекта производного класса 11 ( конструктор с целочисленным аргументом ) : Bravo B2=new Bravo ( З O O ) ; Console . WriteLine ( ) ; 1 1 Создание объекта производного класса 11 ( конструктор с символьным аргументом) : Bravo BЗ=new Bravo ( ' С ' ) ; Console . WriteLine ( ) ; 1 1 Создание объекта производного класса 11 ( конструктор без аргументов ) : Bravo B4=new Bravo ( ) ; Результат выполнения программы такой. � Результат выполнен и я про гра ммы ( и з листинга 1 0 . 3 ) Alpha ( без аргументов ) : 1 2 3 Alpha ( один аргумент ) : 1 0 0 Alpha ( один аргумент ) : 2 0 0 538 Н асл едование Bravo ( два аргумента ) : 2 0 0 и ' В ' Alpha ( один аргумент ) : 3 0 0 Bravo ( int - аргумент ) : 3 0 0 и ' А ' Alpha ( один аргумент ) : 3 2 1 Bravo ( сhаr - аргумент ) : 3 2 1 и ' С ' Alpha ( без аргументов ) : 1 2 3 Bravo ( без аргументов ) : 1 2 3 и 'О' В базовом классе Alpha есть целочисленное поле c o de и две версии конструктора: с целочисленным аргументом и без аргументов. В каж­ дой версии конструктора полю c o de присваивается значение, после чего в консольное окно выводится сообщение с названием класса Alpha, ин­ формацией о количестве аргументов конструктора и значением поля c o de . Это сделано для того, чтобы по сообщениям в консольном окне можно было проследить порядок вызова конструкторов. Пример созда­ ния объектов базового класса с использованием конструктора без аргу­ ментов и с одним аргументом есть в главном методе программы. В производном классе B r avo, кроме наследуемого поля c o d e , описа­ но еще и символьное поле s ymb. Версий конструкторов в классе B r avo четыре: с двумя аргументами, с целочисленным аргументом, с символь­ ным аргументом и без аргументов. В каждой из этих версий конструкто­ ра вызывается одна из версий конструктора базового класса. Например, в версии конструктора с двумя аргументами первый аргумент передает­ ся конструктору базового класса, а второй аргумент определяет значение символьного поля. Если конструктору производного класса передается один целочисленный аргумент, то он будет передан конструктору базо­ вого класса, а символьное поле получит значение ' А ' . Вызов конструк­ тора производного класса с символьным аргументом приведет к тому, что конструктор базового класса будут вызван с аргументом 3 2 1 , а сим­ вольный аргумент конструктора производного класса при этом опреде­ ляет значение символьного поля. Наконец, в конструкторе производ­ ного класса без аргументов вызывается конструктор базового класса без аргументов. В теле каждой версии конструктора для производного класса есть команда, которой выводится сообщение с названием клас­ са B r avo, количеством аргументов конструктора и значениями полей. 539 Гл ава 1 0 Главный метод программы содержит примеры создания объектов клас­ са B r avo с использованием разных версий конструкторов. По сообще­ ниям, которые появляются в консольном окне при создании объектов, можно проследить последовательность вызова конструкторов. Еще один пример, представленный в листинге 1 0.4, иллюстрирует по­ следовательность вызова конструкторов и деструкторов при создании и удалении объекта производного класса, когда имеет место цепочка на­ следования. G) Н А ЗАМ ЕТ КУ И меется в виду многоуровневое наслед ование , при котором про­ изводн ый класс сам я вляется базовым для другого производного класса , а этот п роизводный класс также я вляется базовым для сле­ дующего п роизводного класса и так далее . В представленной далее программе описывается базовый класс Alpha, на основе которого создается класс B r avo, а на основе этого класса соз­ дается класс Char 1 i e . Каждый из классов содержит описание конструк­ тора и деструктора. В каждом классе конструктор и деструктор выводят в консольное окно сообщение. В главном методе программы создается анонимный объект класса Cha r l i e . Интересующий нас программный код представлен ниже. � Л истинг 1 0 . 4 . Конструкторы и деструкторы при многоуровневом наследовании us ing Sys tem; 11 Базовый класс : c l a s s Alpha { 1 1 Конструктор : puЫ ic Alpha ( ) { Console . WriteLine ( " Koнcтpyктop класса Alpha " ) ; 1 1 Деструктор : �Alpha ( ) { Console . WriteLine ( " Дecтpyктop класса Alpha " ) ; 540 Н асл едование 1 1 Производный класс от класса Alpha : c l a s s Bravo : Alpha { 1 1 Конструктор : puЫ ic Bravo ( ) : base ( ) { Console . WriteLine ( " Koнcтpyктop класса Bravo " ) ; 1 1 Деструктор : �вravo ( ) { Console . WriteLine ( " Дecтpyктop класса Bravo " ) ; 1 1 Производный класс от класса Bravo : c l a s s Char l i e : Bravo { 1 1 Конструктор : puЫ ic Charlie ( ) : base ( ) { Console . WriteLine ( " Koнcтpyктop класса Cha r l ie " ) ; 1 1 Деструктор : �Charlie ( ) { Console . WriteLine ( " Дecтpyктop класса Charlie " ) ; 1 1 Класс с главным методом : c l a s s InheritConstrDest rDemo { 1 1 Главный метод : static vo id Main ( ) { 1 1 Создание анонимного объекта : new Charlie ( ) ; 541 Гл ава 1 0 При выполнении программы получим такой результат. [1i!J Результат выполнения программы (из листинга 1 0 . 4) Конструктор класса Alpha Конструктор класса Bravo Конструктор класса Charlie Деструктор класса Charlie Деструктор класса Bravo Деструктор класса Alpha При выполнении команды new Cha r l i e ( ) создается анонимный объ­ ект класса C h a r l i e . Это самый обычный объект, просто ссылка на него не записывается в объектную переменную (в данном случае в этом про­ сто нет необходимости). При вызове конструктора класса C h a r 1 ie сна­ чала выполняется код конструктора класса B r avo, который является базовым для класса Cha r l i e. Но выполнение кода конструктора клас­ са B r avo начинается с выполнения кода конструктора класса Alpha, являющегося базовым для класса B r avo. В результате получается, что конструкторы выполняются, начиная с конструктора самого первого ба­ зового класса в цепочке наследования. Сразу после создания анонимного объекта класса Cha r l i e программа завершает свое выполнение, и, следовательно, созданный объект удаля­ ется из памяти. Вызывается деструктор класса C h a r l i e . После выпол­ нения команд из тела деструктора класса C h a r 1 ie вызывается деструк­ тор класса B r av o . Когда выполнены команды из деструктора класса B r avo, вызывается деструктор класса Alpha. Как отмечалось ранее, по­ рядок вызова конструкторов и деструкторов легко проследить по сооб­ щениям, которые появляются в консольном окне. G) Н А ЗАМ ЕТ КУ Напом н и м , что для запуска приложения на выполнение в среде Visual Studio так, чтобы после выполнения п рограм м ы консол ьное окно не закры валось автоматически , следует нажать комбинацию клавиш <Ctrl >+<F5> . 542 Н асл едование Объектн ые перемен н ы е ба зовых кл ассов - Это Жазель. Француженка. Я признал ее по ноге. - Нет, это не Жазель. Жазель была брунетка, а эта вся белая. из к/ф �Формула лю бви» Ранее практически каждый раз при работе с объектами мы ссьшку на объект записывали в объектную переменную того же класса. Меха­ низм наследования вносит в это строгое правило некоторое разнообра­ зие. Дело в том, что объектная переменная базового класса может ссы­ латься на объект производного. Допустим, имеется базовый класс B a s e , н а основе которого наследованием создается класс MyC l a s s . Если мы создадим на основе класса MyC l a s s объект, то ссылку на этот объект можно записать не только в объектную переменную класса MyC l a s s , но и в объектную переменную базового класса B a s e . Правда, здесь есть одно очень важное ограничение: через объектную переменную базового класса можно получить доступ только к тем членам производного клас­ са, которые объявлены в базовом классе. В нашем случае это означает, что через объектную переменную класса B a s e можно получить доступ только к членам объекта производного класса MyC l a s s , объявленным в классе B a s e . G) Н А ЗАМ ЕТ КУ Здесь уместно вспомнить про класс Ob j e c t из п ространства имен S y s tem ( испол ьзуют также псевдоним ob j e ct для инструкци и S y s tem . Ob j e ct ) , который находится в верш ине иерархи и наследо­ вания всех классов (вкл ючая и создаваемые нам и ) . Это обстоятел ь­ ство позволяет ссылаться объектн ы м переменным класса Obj e c t на объекты самых разных классов . Даже несмотря н а упомянутое ограничение, данная особенность объ­ ектных переменных базовых классов является фундаментально важной и проявляется и используется в самых разных (часто неожиданных) ситуациях. Как небольшую иллюстрацию рассмотрим программу в ли­ стинге 10.5. 543 Гл ава 1 0 [1i!] л истинг 1 0 . 5 . Объектная переменная базового класса us ing Sys tem; 11 Базовый класс : c l a s s Base { 1 1 Целочисленное поле : puЫ ic int code ; 1 1 Открытый метод : puЫ ic vo id show ( ) { Console . WriteLine ( " Пoлe code : " +code ) ; 1 1 Конструктор с целочисленным аргументом : puЫ ic Base ( int n ) { code=n ; 1 1 Конструктор создания копии : puЫ ic Base ( Base obj ) { code=obj . code ; 1 1 Производный класс : c l a s s MyClas s : Base { 1 1 Символьное поле : puЫ ic char s ymb ; 1 1 Открытый метод : puЫ ic vo id display ( ) { Console . WriteLine ( " Пoлe s ymb : " + s ymb ) ; 1 1 Конструктор с двумя аргументами : puЫ ic MyC la s s ( int n , char s ) : base ( n ) { s ymb=s ; 1 1 Конструктор создания копии : 544 Н асл едование puЫ ic MyC l a s s (MyC l a s s obj ) : base ( obj ) { s ymb=obj . s ymЬ ; } 1 1 Класс с главным методом : c l a s s BaseObj VarDemo { 1 1 Главный метод : static vo id Main ( ) { 1 1 Создание объекта производного класса : MyC l a s s A=new MyC l a s s ( l O O , ' А ' ) ; 1 1 Объектная переменная базового класса : Base obj ; 1 1 Объектной переменной базового класса присваивается 11 ссылка на объект производного класса : obj =A; Console . WriteLine ( "Иcпoль зyeм переменную obj : " ) ; 1 1 Вызов метода через объектную переменную 1 1 базового класса : obj . show ( ) ; 1 1 Исполь зовано явное приведение типа : ( (MyClas s ) obj ) . di splay ( ) ; 1 1 Обращение к полю через объектную переменную 1 1 базового класса : obj . code=2 0 0 ; 1 1 Исполь зовано явное приведение типа : ( (MyClas s ) obj ) . s ymЬ= ' в ' ; Console . WriteLine ( "Иcпoль зyeм переменную А : " ) ; 1 1 Вызов методов через объектную переменную 1 1 производного класса : А . show ( ) ; А . display ( ) ; 1 1 Создание копии объекта : 545 Гл ава 1 0 MyC l a s s B=new MyC l a s s (A) ; 1 1 Изменение значений полей исходного объекта : A . code= O ; A . s ymЬ= ' O ' ; Console . WriteLine ( "Иcпoль зyeм переменную В : " ) ; 1 1 Проверка значений полей объекта - копии : В . show ( ) ; в . display ( ) ; Ниже представлен результат выполнения программы. � Результат выполнения программы ( из листинга 1 0 . 5) Используем переменную obj : Поле code : 1 0 0 Поле s ymЬ : А Используем переменную А : Поле code : 2 0 0 Поле s ymЬ : В Используем переменную В : Поле code : 2 0 0 Поле s ymЬ : В В этой программе мы описываем базовый класс B a s e . У него есть от­ крытое целочисленное поле code, метод s how ( ) , который при вызове отображает в консольном окне значение целочисленного поля, а также в классе есть две версии конструктора. Есть конструктор с одним цело­ численным аргументом, и еще имеется конструктор создания копии, ар­ гументом которому передается объект класса B a s e . � ПОДРОБН ОСТИ Под конструктором создания коп и и обычно подразумевают кон ­ структор , позволя ющий создавать объект класса на основе уже существующего объекта того же класса. Обычно (но не обязатель­ но) речь идет о коп и рован и и значен и й полей исходного объекта . 546 Н асл едование В нашем примере поле code создаваемого объекта получает та­ кое же значение, какое есть у поля code объекта , переданного аргу­ ментом конструктору ( команда code=obj . code в теле конструктора , а через obj обозначен его аргумент) . При этом объекты физически разные. На основе класса B a s e путем наследования создается класс MyC l a s s . В этом классе дополнительно описывается символьное поле s ymb, ме­ тод di s p l a y ( ) , отображающий в консольном окне значение символь­ ного поля, а также две версии конструктора. В соответствии с описан­ ными версиями объект класса MyC l a s s можно создавать, передав два аргумента конструктору (целое число и символ) или передав аргумен­ том уже существующий объект класса MyC l a s s (конструктор создания копии). Если при создании объекта класса MyC l a s s используются два аргумента, то первый, целочисленный, аргумент передается конструк­ тору базового класса, а второй, символьный, аргумент определяет зна­ чение символьного поля. С конструктором создания копии все намного интереснее. Аргумент конструктора obj описан как такой, что является объектной ссьткой класса MyC l a s s . И эта объектная ссылка передается конструктору базового класса (инструкция b a s e ( obj ) в описании кон­ структора класса MyC l a s s ) . Здесь имеется в виду версия конструктора базового класса, у которой аргументом является объект. Но дело в том, что в классе B a s e описан конструктор с аргументом, являющимся объ­ ектной переменной класса B a s e . А здесь мы по факту передаем аргумен­ том объектную переменную класса MyC l a s s . Тем не менее это возможно, и ошибки нет. Почему? Ответ базируется на возможности для объектных переменных базового класса ссьтаться на объекты производного клас­ са. Когда вызывается конструктор базового класса, то он �ожидает полу­ чить» ссылку на объект класса B a s e . Такая ссьтка - аргумент конструк­ тора. Чтобы ее запомнить, создается техническая объектная переменная класса B a s e , в которую будет копироваться значение аргумента. � ПОДРОБН ОСТИ Напом н и м , что аргументы в методы и конструкторы передаются по значению: на самом деле для переменной, переданной аргумен­ том , создается техн ическая коп и я , и все операции выполняются именно с этой коп ией . Другими словами, есть специальная �рабочая», автоматически создава­ емая переменная, в которую копируется аргумент конструктора. Данная 547 Гл ава 1 0 переменная относится к классу B a s e , а передается аргументом перемен­ ная класса MyC l a s s . Получается, что в объектную переменную класса B a s e копируется значение переменной класса MyC l a s s . Но это можно делать, поскольку класс B a s e является базовым для класса MyC l a s s . При вызове конструктора базового класса с аргументом, являющимся ссьшкой на объект производного класса, доступ есть только к тем по­ лям и методам объекта-аргумента, которые объявлены в базовом классе. Но больше и не нужно. Конструктор базового класса выполняет свою работу и присваивает значение полю c o de создаваемого объекта. По­ сле этого командой s ymЬ=obj . s ymЬ в теле конструктора производного класса значение присваивается символьному полю создаваемого объекта. В главном методе программы командой M y C l a s s A = n e w МуС 1 а s s ( 1 О О , ' А ' ) создается объект производного класса МуС 1 а s s . Также мы объявляем объектную переменную obj базового класса B a s e . После выполнения команды o b j = А и переменная obj , и переменная А ссы­ лаются на один и тот же объект. Но если через переменную А есть доступ к полям code и s ymЬ, а также методам s how ( ) и d i s p l ay ( ) , то через пе­ ременную obj имеется доступ только к тем полям и методам, которые объ­ явлены в классе Base: это поле code и метод s how ( ) . Поэтому команды obj . show ( ) и obj . code=2 О О являются вполне корректными. А вот если мы хотим через переменную obj получить доступ к полю s ymЬ и методу d i s p l a y ( ) , то приходится выполнять явное приведение типа - то есть фактически явно указать, что объект, на который ссьшается переменная obj , является объектом класса MyC l a s s . Пример такого подхода дают ко­ манды ( ( MyC l a s s ) obj ) . di splay ( ) и ( ( MyC l a s s ) obj ) . s ymЬ= ' в ' . После присваивания с использованием переменной obj новых значений полям c o de и s ymb, используя переменную А, убеждаемся, что перемен­ ные оЬ j и А действительно ссьшаются на один и тот же объект. Командой MyC l a s s B=new MyC l a s s ( А ) объект В создается на основе объекта А (так мы проверяем работу конструктора создания копии про­ изводного класса). На момент создания объекта В значения его полей такие же, как и у объекта А, но объекты физически разные. Чтобы убе­ диться в этом, командами А . c o de= O и А . s ymb= ' о ' изменяем значения полей исходного объекта (на основе которого создавалась копия), а с по­ мощью команд В . s h o w ( ) и В . d i s p l a y ( ) убеждаемся, что значения полей объекта В остались такими же, как бьши у объекта А на момент создания объекта в. 548 Н асл едование Замещение ч ленов п р и наследова н и и Дядя Вова, ваше пальто идет в моей шапке. из к/ф -«Кин-дза-дза» В производном классе некоторые поля и методы (и другие члены) насле­ дуются из базового класса, а часть полей и методов описывается непо­ средственно в производном классе. Может статься, что член, описанный в производном классе, имеет такое же название, как и член, наследуемый из базового класса. Такая ситуация называется замещением члена клас­ са. Она вполне законна и может использоваться. Единственное условие состоит в том, что при описании в производном классе поля или метода, название которого совпадает с названием поля или метода, наследуемо­ го из базового класса, необходимо использовать инструкцию new. G) Н А ЗАМ ЕТ КУ Есл и не испол ьзовать кл ючевое слово new , то п рограм мный код ском пилируется , но с п редуп реждением . Фактически наличие кл ю­ чевого слова new в оп исан и и члена класса при замещении свиде­ тел ьствует о том , что механизм замещения испол ьзуется осознан но. Из того, что поле или метод из базового класса замещаются в произ­ водном классе, вовсе не следует, что соответствующее поле или метод больше недоступны в производном классе. Замещаемый член класса су­ ществует одновременно с тем членом, который описан в производном классе. Просто если мы обращаемся к члену класса по его названию, то по умолчанию обращение выполняется к тому члену, который описан в производном классе. Если нам нужно получить доступ к замещенному члену класса, то перед его названием через точку указывается ключевое слово b a s e . G) Н А ЗАМ ЕТ КУ Получается , что в производном классе два поля или два метода с оди наковыми назван и я м и . По умолчанию испол ьзуется то поле ил и метод, которые оп исаны непосредственно в п роизводном клас­ се. Чтобы обратиться к члену, унаследован ному из базового класса и замещенному из-за оп исан ия члена с таки м же именем в произ­ водном классе , следует испол ьзовать кл ючевое слово b a s e . 549 Гл ава 1 0 Пример, в котором используется замещение членов класса, представлен в листинге 1 0.6. � Л истинг 1 0 . 6 . Замещение членов при наследовании us ing Sys tem; 11 Базовый класс : c l a s s Base { 1 1 Целочисленное поле : puЫ ic int code ; 1 1 Метод для отображения значения поля : puЫ ic vo id show ( ) { Console . WriteLine ( " Клacc Base : " +code ) ; 1 1 Конструктор с одним аргументом : puЫ ic Base ( int n ) { code=n ; 1 1 Производный класс : c l a s s MyClas s : Base { 1 1 Новое поле замещает одноименное поле , 1 1 унаследованное из базового класса : new puЫ ic int code ; 1 1 Новый метод замещает одноименный метод, 11 унаследованный из базового класса : new puЫ ic void show ( ) { 1 1 Вызов версии метода из базового класса : base . show ( ) ; 1 1 Обращение к полю производного класса : Console . WriteLine ( " Клacc MyCla s s : " +code ) ; 1 1 Метод для присваивания значения полю, унаследованному 550 Н асл едование 1 1 из базового класса и замещенному в производном 11 классе : puЫ ic vo id set ( int n ) { 1 1 Обращение к полю, унаследованному из базового 11 класса и замещенному в производном классе : base . code=n ; 1 1 Конструктор с двумя аргументами : puЫ ic MyCla s s ( int m, int n ) : base (m) { 1 1 Присваивание значения полю производного класса : code=n ; } 1 1 Класс с главным методом : c l a s s InheritAndHidingDemo { 1 1 Главный метод : static vo id Main ( ) { 1 1 Создание объекта производного класса : MyC l a s s obj =new MyC l a s s ( l 0 0 , 2 0 0 ) ; 1 1 Отображение значений полей объекта : obj . show ( ) ; Console . WriteLine ( ) ; 1 1 Присваивание значения замещенному полю : obj . set ( З O O ) ; 1 1 Присваивание значения замещающему полю : obj . code=4 0 0 ; 1 1 Отображение значений полей объекта : obj . show ( ) ; Результат выполнения программы такой. 551 Гл ава 1 0 [1i!J Результат выполнения программы ( из листинга 1 0 . 6 ) Класс Base : 1 0 0 Класс MyClas s : 2 0 0 Класс Base : 3 0 0 Класс MyClas s : 4 0 0 Базовый класс B a s e содержит целочисленное поле code, метод s how ( ) , отображающий в консоли сообщение с названием класса и значением поля, а также конструктор с одним аргументом, определяющим значе­ ние целочисленного поля. � ПОДРОБН ОСТИ Конструктору производного класса MyC l a s s передается два цело­ ч исленных аргумента . Первый из н их становится аргументом кон­ структора базового класса B a s e . При вызове конструктора базово­ го класса значение получает поле code , унаследованное из класса B a s e и замещен ное в классе MyC l a s s . Второй аргумент конструкто­ ра производного класса оп ределяет значение поля code , описанно­ го непосредствен но в этом классе . В производном классе MyC l a s s также описывается поле c o de и метод s how ( ) . И поле, и метод описаны с инструкцией new. Если мы в теле класса используем идентификатор c o de или вызываем метод s h o w ( ) , то имеются в виду именно те члены класса, которые описаны непосред­ ственно в классе MyC l a s s . Чтобы получить доступ к полю, унаследо­ ванному из базового класса и замещенному в производном, используем инструкцию b a s e . c o de . Чтобы вызвать версию метода s how ( ) из ба­ зового класса, используем инструкцию b a s e . s how ( ) . Инструкцию b a s e . s how ( ) мы используем в теле метода s how ( ) , ко­ торый описывается в классе MyC l a s s . Это означает, что при вызове ме­ тода s how ( ) из объекта класса MyC l a s s первой командой в теле метода вызывается версия метода s how ( ) , описанная в классе B a s e . В резуль­ тате в консольном окне отображается значение поля c o de , унаследо­ ванного из класса B a s e и замещенного в классе MyC l a s s . Затем коман­ дой C on s o l e . W r i t e L i n e ( " Кла с с MyC l a s s : " + c o de ) отображается значение поля code, описанного в классе MyC l a s s . 552 Н асл едование Инструкция b a s e . c o de использована нами в теле метода s e t ( ) , пред­ назначенного для присваивания значения полю c o de , замещенному в производном классе. В главном методе программы командой М у С 1 а s s о Ь j = n е w MyC l a s s ( 1 О О , 2 О О ) создается объект o b j , значения полей которого равны 1 0 0 (замещенное поле code) и 2 0 0 (поле code, описанное в про­ изводном классе). При выполнении команды ob j . s how ( ) из объекта obj вызывается версия метода s h ow ( ) , описанная в классе MyC l a s s . В результате в консольном окне отображаются значения обоих полей. Командой оЬ j . s е t ( 3 О О ) новое значение присваивается замещенному полю c o de, а командой obj . c o de = 4 О О новое значение получает поле c o de, описанное в производном классе. Результат этих операций прове­ ряется с помощью команды obj . s how ( ) . П ереоп редел ение ви ртуал ьных методов Ребят, как ж е это в ы без гравицапы пепелац выкатываете из гаража ? Это непорядок. из к/ф -«Кин-дза-дза» Выше мы узнали, что в производном классе можно описать метод с та­ ким же названием (и такими же аргументами и типом результата), что и у наследуемого из базового класса метода. В итоге получается, что опи­ санный в производном классе метод �перекрывает�, или замещает, ме­ тод, унаследованный из базового класса. Но есть еще один механизм, который внешне напоминает замещение метода, но в действительности имеет намного больший потенциал и который достаточно часто исполь­ зуется на практике. Речь идет о переопределении виртуальных методов. Сначала мы познакомимся с этим механизмом и научимся его исполь­ зовать, а затем (в отдельном разделе) сравним переопределение вирту­ альных методов с замещением методов. Итак, речь идет о том, что в базовом классе имеется некоторый метод, ко­ торый наследуется в производном классе. Допустим, что нам нужно, чтобы наследуемый метод в производном классе бьm, но нас, в силу определенных причин, не устраивает, как этот метод вьшолняется. Проще говоря, в про­ изводном классе мы хотим изменить способ вьшолнения унаследованного метода. В таком случае метод в производном классе переопределяют. 553 Гл ава 1 0 Переопределение метода подразумевает два этапа. Это собственно пере­ определение, когда в производном классе описывается новая версия ме­ тода. Причем такая версия описывается с ключевым словом ove r r i de . Н о кроме этого, в базовом классе данный метод должен быть объявлен как виртуальный. Для этого в описании метода в базовом классе исполь­ зуют ключевое слово v i r t u a l . Пример, иллюстрирующий, как выполняется переопределение вирту­ альных методов, представлен в листинге 1 0.7. � Л истинг 1 О . 7 . П ереопределение виртуальных методов us ing Sys tem; 11 Базовый класс : c l a s s Alpha { 1 1 Открытое поле : puЫ ic int alpha ; 1 1 Виртуальный метод : puЫ ic virtual vo id show ( ) { Console . Wri teLine ( " Класс Alpha : { О } " , alpha ) ; 1 1 Конструктор с одним аргументом : puЫ ic Alpha ( int а ) { alpha=a ; 1 1 Производный класс от класса Alpha : c l a s s Bravo : Alpha { 1 1 Открытое поле : puЫ ic int bravo ; 1 1 Переопределение виртуального метода : puЫ ic ove rride void show ( ) { Console . Wri teLine ( " Класс Bravo : { О } и { 1 } " , a lpha , bravo ) ; 1 1 Конструктор с двумя аргументами : 554 Н асл едование puЫ ic Bravo ( int а , int Ь ) : base ( a ) { bravo=b ; 1 1 Производный класс от класса Bravo : c l a s s Char l i e : Bravo { 1 1 Открытое поле : puЫ ic int cha r l i e ; 1 1 Переопределение виртуального метода : puЫ ic override void show ( ) { Console . WriteLine ( " Клacc Charlie : { 0 } , { 1 } и { 2 } " , alpha , bravo , charlie ) ; 1 1 Конструктор с тремя аргументами : puЫ ic Charlie ( int а , int Ь , int с ) : base ( a , Ь ) { charlie=c ; 1 1 Класс с главным методом : c l a s s Ove rridingDemo { 1 1 Главный метод : static vo id Main ( ) { 1 1 Создание объекта класса Alpha : Alpha obj A=new Alpha ( l O ) ; 1 1 Проверка значения поля : obj A . show ( ) ; Console . WriteLine ( ) ; 1 1 Создание объекта класса Bravo : Bravo obj B=new Bravo ( 2 0 , 3 0 ) ; 1 1 Объектной переменной базового класса присваивается 11 ссылка на объект производного класса : obj A=obj B ; 1 1 Проверка значений полей : 555 Гл ава 1 0 obj A . show ( ) ; obj B . show ( ) ; Console . WriteLine ( ) ; 1 1 Создание объекта класса Charl ie : Charlie obj C=new Charlie ( 4 0 , 5 0 , 6 0 ) ; 1 1 Объектной переменной базового класса присваивается 11 ссылка на объект производного класса : obj A=obj C ; obj B=obj C ; 1 1 Проверка значений полей : obj A . show ( ) ; obj B . show ( ) ; obj С . show ( ) ; Ниже представлен результат выполнения программы. � Результат выполнения программы (из листинга 1 О . 7) Класс Alpha : 1 0 Класс Bravo : 2 0 и 3 0 Класс Bravo : 2 0 и 3 0 Класс Char l i e : 4 0 , 5 0 и 6 0 Класс Char l i e : 4 0 , 5 0 и 6 0 Класс Char l i e : 4 0 , 5 0 и 6 0 В этой программе м ы описали три однотипных класса: Alpha, B r avo и Cha r l i e . Класс Alpha является базовым для класса B r avo, а класс B r avo является базовым для класса C h a r l i e . В классе Alpha есть це­ лочисленное поле и конструктор с одним аргументом. В классе B r avo есть два целочисленных поля и конструктор с двумя аргументами. В классе C h a r l i e есть три целочисленных поля и конструктор с тре­ мя аргументами. Но главный наш интерес связан с методом s h o w ( ) , 556 Н асл едование который описан в базовом классе Alpha и наследуется и переопределя­ ется в классах B r avo и Ch a r l i e . Метод s h o w ( ) описан как виртуальный ( с ключевым словом v i rt u a l ) . При вызове метода в консольном окне отображается значение целочис­ ленного поля. Метод переопределяется в классе B r avo. Метод в классе B r avo описан с ключевым словом ove r r i de . Метод описан так, что при вызове в консольном окне отображается значение двух целочисленных полей. Версия метода в классе Cha r l i e также описана с ключевым сло­ вом ove r r i de , а при вызове метода в консольном окне отображается значение трех целочисленных полей. В главном методе программы сначала создается объект o b j А класса Alpha. При вызове метода s h o w ( ) через переменную ob j A вызывает­ ся версия метода из класса Alpha. Далее создается объект obj в класса B r avo. Причем переменной ob j A также присваивается ссьшка на этот объект (так можно сделать, поскольку класс Alpha является базовым для класса B r avo). Метод s h ow ( ) вызывается через переменные obj A и obj В (которые ссьшаются на один и тот же объект). В обоих случаях вызывается версия метода s how ( ) , описанная в классе B r avo. Наконец, создается объект obj С класса C h a r l i e и переменным оЬj А и obj B зна­ чением присваивается ссылка на этот объект. Через каждую из трех пе­ ременных вызывается метод s h ow ( ) . Во всех трех случаях вызывается та версия метода, что описана в классе C h a r l i e . Таким образом, при вызове виртуального переопределенного метода версия метода определяется не типом переменной, через которую вы­ зывается метод, а классом объекта, на который ссьшается объектная пе­ ременная. Это очень важная особенность виртуальных методов, и она очень часто используется на практике. G) Н А ЗАМ ЕТ КУ Атрибут v i r t u a l наследуется . Это означает, что есл и метод в базо­ вом классе объя влен как ви ртуал ьн ы й , то он останется ви ртуальным при наследован ии и переоп ределен и и . 557 Гл ава 1 0 П ереоп ределение и заме щение методов А в ы опять н е переместились, з аразы. из к/ф -«Кин-дза-дза» Теперь пришел черед выяснить, чем замещение методов отличается от пе­ реопределения виртуальных методов. Для большей наглядности сра­ зу рассмотрим пример. Он будет несложным, но показательным. Идея, положенная в основу примера, простая. Имеется базовый класс Alpha, в котором есть два метода: h i ( ) и he l l o ( ) . Метод h i ( ) виртуальный, а метод he l l o ( ) обычный (не виртуальный). А еще в классе Alpha есть метод s h ow ( ) . Это обычный (не виртуальный) метод. При его вызове последовательно вызываются методы he l l o ( ) и hi ( ) . Далее, на осно­ ве класса Alpha создается производный класс B r avo. В классе B r avo замещается метод he l l o ( ) и переопределяется метод h i ( ) . При этом метод s h o w ( ) просто наследуется. Таким образом, в классе B r avo есть унаследованный метод s h ow ( ) , при вызове которого вызываются ме­ тоды he l l o ( ) и h i ( ) . Интрига связана с тем, в каких ситуациях какие версии этих методов будут вызываться. А теперь рассмотрим программ­ ный код, представленный в листинге 1 0.8. [1i!J Листинг 1 0 . 8 . Переопределение и замещение методов us ing Sys tem; 11 Базовый класс : c l a s s Alpha { 1 1 Обычный ( не виртуаль ный ) метод : puЫ ic vo id hello ( ) { Console . WriteLine ( "Метод hello ( ) из класса Alpha " ) ; 1 1 Виртуаль ный метод : puЫ ic virtual vo id hi ( ) { Console . WriteLine ( "Meтoд hi ( ) из класса Alpha " ) ; 1 1 Обычный ( не виртуаль ный ) метод : puЫ ic vo id show ( ) { 558 Н асл едование 1 1 Вызов методов : hel l o ( ) ; hi ( ) ; Console . WriteLine ( ) ; 1 1 Производный класс : c l a s s Bravo : Alpha { 1 1 Замещение метода : new puЫ ic void hel l o ( ) { Console . WriteLine ( "Метод hello ( ) из класса B ravo " ) ; 1 1 Переопределение виртуального метода : puЫ ic override void hi ( ) { Console . Wri teLine ( "Метод hi ( ) из класса Bravo " ) ; 1 1 Класс с главным методом : c l a s s HidingAndOve rridingDemo { 1 1 Главный метод : static vo id Main ( ) { 1 1 Создание объекта базового класса : Alpha A=new Alpha ( ) ; Console . WriteLine ( " Bыпoлнeниe команды A . show ( ) : " ) ; 1 1 Вызов метода : А . show ( ) ; 1 1 Создание объекта производного класса : Bravo B=new Bravo ( ) ; Console . WriteLine ( " Bыпoлнeниe команды B . he l l o ( ) : " ) ; 1 1 Вызов замещающего метода из производного класса : B . he l l o ( ) ; Console . WriteLine ( " Bыпoлнeниe команды B . hi ( ) : " ) ; 559 Гл ава 1 0 1 1 Вызов переопределенного метода : B . hi ( ) ; Console . WriteLine ( " Bыпoлнeниe команды B . show ( ) : " ) ; 1 1 Вызов унаследованного метода : В . show ( ) ; Console . WriteLine ( " Пocлe выполнения команды А=В " ) ; 1 1 Объектной переменной базового класса присваивается 11 ссылка на объект производного класса : А=В ; Console . WriteLine ( " Выполнение команды А . hello ( ) : " ) ; 1 1 Вызов замещаемого метода : A . he l l o ( ) ; Console . Wri teLine ( " Выполнение команды А . hi ( ) : " ) ; 1 1 Вызов переопределенного метода : A . hi ( ) ; Console . Wri teLine ( " Выполнение команды А . show ( ) : " ) ; 1 1 Вызов унаследованного метода : А . show ( ) ; Результат выполнения программы такой. � Результат выполнения программы (из листинга 1 0 .8) Выполнение команды A . show ( ) : Метод hel l o ( ) из класса Alpha Метод hi ( ) из класса Alpha Выполнение команды B . he l l o ( ) : Метод hel l o ( ) из класса B ravo Выполнение команды B . hi ( ) : Метод hi ( ) из класса Bravo Выполнение команды B . show ( ) : 560 Н асл едование Метод hel l o ( ) из класса Alpha Метод hi ( ) из класса Bravo После выполнения команды А=В Выполнение команды A . he l l o ( ) : Метод hel l o ( ) из класса Alpha Выполнение команды A . hi ( ) : Метод hi ( ) из класса Bravo Выполнение команды A . show ( ) : Метод hel l o ( ) из класса Alpha Метод hi ( ) из класса Bravo В главном методе сначала создается объект класса A l p h a , и ссылка на него записывается в объектную переменную А того же класса. Резуль­ тат вызова метода s h ow ( ) через эту переменную вполне ожидаем: вызы­ ваются те версии методов he l l o ( ) и hi ( ) , которые описаны в классе Alpha. На следующем этапе создается объект класса B r avo и ссылка на объект записывается в объектную переменную В класса B r avo. При выполне­ нии команд В . he l l o ( ) и В . hi ( ) убеждаемся, что вызываются версии методов he l l o ( ) и hi ( ) , описанные непосредственно в классе B r avo. Сюрприз получаем при выполнении команды В . show ( ) . Оказывается, что в этом случае для метода hi ( ) вызывается версия из класса B r avo, а для метода he l l o ( ) вызывается версия из класса Alpha. G) Н А ЗАМ ЕТ КУ Метод show ( ) вызы вается и з объекта класса Bravo , но сам метод оп исан в классе Alpha . Версия ви ртуального переоп ределен ного метода h i ( ) оп ределяется в соответствии с объектом , из которо­ го вызы вается метод show ( ) . Версия замещаемого в п роизводном классе метода h e l l o ( ) определяется по классу, в котором оп исан вызывающийся метод show ( ) . После выполнения команды А=В обе переменные А и В ссылаются на один и тот же объект класса B r avo. В частности, объектная перемен­ ная А базового класса Alpha ссьшается на объект производного клас­ са B r avo. Через эту переменную мы последовательно вызываем все три 561 Гл ава 1 0 метода (команды А . he l l o ( ) , А . h i ( ) и А . s h ow ( ) ). Вывод очевидный: версия виртуального переопределенного метода определяется по объек­ ту, из которого вызывается метод, а версия замещаемого метода опреде­ ляется типом объектной переменной. � ПОДРОБН ОСТИ Переоп ределяется тол ько ви ртуальный метод. Механизм замеще­ ния формал ьно можно применять и к ви ртуал ьн ы м методам . Но это все равно будет замещение, а не переоп ределение метода . Версия метода оп ределяется по объекту, из которого вызы вается метод, есл и метод переоп ределен . П ереоп ределение и перегрузка методов Вся операция займет н е более пятнадцати ми­ нут. Пускай отмокает, а я пока соберу б арах­ лишко. из к/ф «Бриллиантовая рука» Мы познакомились с переопределением метода, когда в производном классе описывается новая версия унаследованного метода. В данном случае речь идет о полном совпадении типа, названия и списка аргумен­ тов метода в базовом и производном классах. Но еще есть такой меха­ низм, как перегрузка методов. При перегрузке метода описывается не­ сколько версий этого метода. Для каждой версии название одно и то же, но вот списки аргументов должны отличаться. Эти два механизма (пе­ регрузка и переопределение) могут использоваться одновременно. В ли­ стинге 10.9 представлена программа, в которой на фоне наследования используется перегрузка и переопределение методов. � Л истинг 1 0 . 9 . Перегрузка и переопределение методов us ing Sys tem; 11 Базовый класс : c l a s s Alpha { 1 1 Целочисленное поле : puЫ ic int alpha ; 562 Н асл едование 1 1 Метод без аргументов : puЫ ic vo id set ( ) { 1 1 Значение поля : alpha= l O ; 1 1 Отображение значения поля : Console . WriteLine ( "Alpha ( без аргументов ) : { 0 } " , alpha ) ; 1 1 Метод ( виртуаль ный) с одним аргументом : puЫ ic virtual void set ( int n ) { 1 1 Значение поля : alpha=n ; 1 1 Отображение значения поля : Console . WriteLine ( "Alpha ( один аргумент ) : { 0 } " , alpha ) ; 1 1 Производный класс : c l a s s Bravo : Alpha { 1 1 Целочисленное поле : puЫ ic int bravo ; 1 1 Переопределение виртуального метода : puЫ ic override void set ( int n ) { 1 1 Присваивание значений полям : alpha=n ; bravo=alpha ; 1 1 Отображение значений полей : Console . WriteLine ( " Bravo ( один аргумент ) : { 0 } и { 1 } " , alpha , bravo ) ; 1 1 Метод с двумя аргументами : puЫ ic vo id set ( int m, int n ) { 1 1 Присваивание значений полям : alpha=m; bravo=n ; 563 Гл ава 1 0 1 1 Отображение значений полей : Console . WriteLine ( " Bravo (два аргумента ) : { О } и { 1 } " , alpha , bravo ) ; 1 1 Класс с главным методом : c l a s s OverloadAndOve rrideDemo { 1 1 Г лавный метод : static vo id Main ( ) { 1 1 Создание объекта базового класса : Alpha A=new Alpha ( ) ; 1 1 Вызов методов : А . set ( ) ; A . set ( 2 0 ) ; Console . WriteLine ( ) ; 1 1 Создание объекта производного класса : Bravo B=new Bravo ( ) ; 1 1 Вызов методов : в . set () ; B . set ( 3 0 ) ; B . set ( 4 0 , 5 0 ) ; Ниже показано, как выглядит результат выполнения программы. [1i!J Результат выполнения программы (из листинга 1 0 . 9 ) Alpha ( без аргументов ) : 1 0 Alpha ( один аргумент ) : 20 Alpha ( без аргументов ) : 1 0 Bravo ( один аргумент ) : 30 и 30 Bravo ( два аргумента ) : 40 и 50 564 Н асл едование У нас есть два класса: Alpha и B r avo, причем класс B r avo является производным от класса Alpha. В классе Alpha имеется открытое це­ лочисленное поле a l p h a , которое наследуется в классе B r avo. Еще в классе B r avo появляется целочисленное поле b r avo. В классе Alpha описаны две версии метода set ( ) : без аргументов и с одним аргумен­ том. Версия метода s е t ( ) с одним аргументом описана как виртуальная (использовано ключевое слово v i r t u a l ). В классе B r avo появляется еще одна версия метода s e t ( ) с двумя аргументами, а версия метода с одним аргументом переопределяется (она описана с ключевым сло­ вом ove r r i de ). Каждая версия метода описана так, что полю или полям присваиваются значения, после чего в консольное окно выводится со­ общение с указанием названия класса, количества аргументов у метода и фактического значения поля или полей объекта. Таким образом, по со­ общению, отображаемому в консольном окне при вызове метода, можно однозначно определить, какая версия метода бьша вызвана. В главном методе программы мы создаем объект А класса Alpha и объ­ ект В класса B r avo. Из каждого объекта вызываются разные версии ме­ тода s е t ( ) . Для объекта А это две версии (обе описаны в классе Al pha ): без аргументов и с одним аргументом. Для объекта В таких версий три. Это, во-первых, версия метода без аргументов, унаследованная из клас­ са Alpha. Во-вторых, переопределенная в классе B r avo версия метода с одним аргументом. И в-третьих, версия с двумя аргументами, описан­ ная в классе B r avo. Н аследование свойств и и ндексаторов Бедняга. Ребята, на его месте должен был быть я ! из к/ф «Бриллиантовая рука» Ранее при изучении вопросов, связанных с наследованием, мы в основ­ ном имели дело с полями и методами. Но наследоваться могут также индексаторы и свойства. Более того, они даже могут быть виртуальны­ ми (то есть их можно переопределять как виртуальные методы). Ничего удивительного здесь нет, особенно если вспомнить, что механизм реали­ зации свойств и индексаторов базируется на использовании методов-ак­ сессоров. Скромный пример, в котором имеет место наследование ин­ дексаторов и свойств, представлен в программе в листинге 1 0. 1 0. 565 Гл ава 1 0 [1i!J Л истинг 1 О . 1 О . Наследование индексаторов и свойств us ing Sys tem; 11 Базовый класс : c l a s s Alpha { 1 1 Защищенное целочисленное поле : protected int alpha ; 1 1 Закрытый массив : private char [ ] s ymЬ s ; 1 1 Конструктор с двумя аргументами : puЫ ic Alpha ( int а , s tring txt ) { 1 1 Присваивание значения полю : alpha= a ; 1 1 Создание массива на основе текстового аргумента : s ymbs=txt . ToCharArray ( ) ; 1 1 Виртуаль ное свойство : puЫ ic virtual int numbe r { 1 1 Аксессор для считывания значения : get { return alpha ; 1 1 Аксессор для присваивания значения : set { alpha=value ; 1 1 Открытое свойство : puЫ ic int l ength { 11 Аксессор для считывания значения : get { return s ymЬs . Length ; 566 Н асл едование 1 1 Символь ный индексатор с целочисленным индексом : puЫ ic char this [ int k] { 1 1 Аксессор для считывания значения : get { return s ymЬs [ k ] ; 1 1 Аксессор для присваивания значения : set { s ymЬs [ k ] =value ; 1 1 Виртуаль ный целочисленный индексатор 11 с символьным индексом : puЫ ic virtual int this [ char s ] { 1 1 Аксессор для считывания значения : get { 1 1 Используется индексирование объекта : return this [ s - ' a ' ] ; 1 1 Аксессор для присваивания значения : set { 1 1 Используется индексирование объекта : this [ s - ' a ' ] = ( char) value ; 1 1 Переопределение метода ToString ( ) : puЫ ic override s t ring ToString ( ) { 1 1 Текстовая переменная : s tring txt=" I " ; 1 1 Формируется значение текстовой переменной : for ( int k=O ; k<thi s . l ength ; k++ ) { 567 Гл ава 1 0 txt+=thi s [ k ] + " I " ; 1 1 Резуль тат метода : return txt ; 1 1 Производный класс : c l a s s Bravo : Alpha { 1 1 Защищенное целочисленное поле : protected int bravo ; 1 1 Конструктор с тремя аргументами : puЫ ic Bravo ( int а , int Ь , s tring txt ) : base ( a , txt ) { 1 1 Значение целочисленного поля : bravo=b ; 1 1 Переопределение свойства : puЫ ic ove rride int numЬe r { 1 1 Аксессор для считывания значения : get { return alpha+bravo ; 1 1 Переопределение индексатора с символь ным индексом : puЫ ic ove rride int this [ char s ] { 11 Аксессор для считывания значения : get { i f ( s== ' a ' ) return alpha ; e l s e return bravo ; 1 1 Аксессор для присваивания значения : set { i f ( s== ' a ' ) alpha=value ; 568 Н асл едование else bravo=value ; } 1 1 Класс с главным методом : c l a s s InheritPropindexe rDemo { 1 1 Главный метод : static vo id Main ( ) { 1 1 Целочисленная переменная : int k ; 1 1 Символьная переменная : char s ; 1 1 Создание объекта базового класса : Alpha A=new Alpha ( 1 0 0 , "ABCD " ) ; Console . WriteLine ( " Oбъeкт А : " ) ; 1 1 Содержимое символь ного массива объекта : Console . WriteLine (A) ; 1 1 Значение свойства numЬer объекта : Console . WriteLine ( "A . numЬe r=" +A . numbe r ) ; 1 1 Присваивание значения свойству numЬe r : A . numЬer=1 5 0 ; 1 1 Значение свойства numЬer объекта : Console . WriteLine ( "A . numЬe r=" +A . numbe r ) ; 1 1 Индексирование объекта : for ( k= O , s= ' a ' ; k<A . length; k++ , s + + ) { Console . WriteLine ( " Cимвoл \ ' { 0 } \ ' с кодом { 1 } " , A [ k ] , A [ s ] ) ; А[О]='Е' ; А [ ' Ь ' ] = ' A ' +l O ; 1 1 Содержимое символь ного массива объекта : Console . WriteLine (A) ; 1 1 Создание объекта производного класса : 569 Гл ава 1 0 Bravo B=new Bravo ( 2 0 0 , 3 0 0 , "EFGHI " ) ; Console . WriteLine ( " Oбъeкт В : " ) ; 1 1 Содержимое массива объекта : Console . WriteLine ( B ) ; 1 1 Значение свойства numЬer объекта : Console . WriteLine ( " B . numЬer= " +B . numbe r ) ; 1 1 Присваивание значения свойству numЬer объекта : B . numЬer=4 0 0 ; 1 1 Значение свойства numЬer объекта : Console . WriteLine ( " B . numЬer= " +B . numbe r ) ; 1 1 Индексирование объекта : B [ ' a ' ] =l O ; В [ ' d ' ] =2 0 ; Console . WriteLine ( " B [ \ ' a \ ' ] =" +B [ ' a ' ] ) ; Console . WriteLine ( " В [ \ ' Ь \ ' ] = " +В [ ' Ь ' ] ) ; 1 1 Проверка значения свойства numЬer объекта : Console . WriteLine ( " B . numЬer= " +B . numbe r ) ; 1 1 Индексирование объекта : for ( k= O ; k<B . length; k++ ) { Console . Write ( B [ k ] + " " ) ; B [ k ] = ( char) ( ' a ' + k) ; Console . WriteLine ( ) ; 1 1 Проверка содержимого массива объекта : Console . WriteLine ( B ) ; Как выглядит результат выполнения программы, показано ниже. [1iJ Результат выполнения программы (из листинга 1 О . 1 О) Объект А : IAIBICI DI 570 Н асл едование A . numЬer= l O O A . numЬer= 1 5 0 Символ ' А ' с кодом 6 5 Символ ' В ' с кодом 6 6 Символ ' С ' с кодом 6 7 Символ ' D ' с кодом 6 8 IEIКICIDI Объект В : IEIFIGIHI I I B . numЬer=S O O B . numЬer= 7 0 0 B [ ' a ' ] =l O В [ ' Ь ' ] =2 0 B . numЬer= З O Е F G Н I l alblcldle l В этой программе мы описываем базовый класс Alpha и производный от него класс B r avo. В классе Alpha есть защищенное целочисленное поле a lpha, а также закрытый символьный массив s ymb s (поле, явля­ ющееся ссылкой на массив). Конструктор класса имеет два аргумента. Первый целочисленный аргумент определяет значение целочисленного поля a lpha. Второй текстовый аргумент служит основанием для соз­ дания символьного массива. Чтобы на основе текста получить массив (состоящий из букв, формирующих текст), мы использовали библиотеч­ ный метод T o C h a rA r r a y ( ) . В классе Alpha описано виртуальное (в описании использовано клю­ чевое слово v i r t u a l ) целочисленное свойство numb e r (это свойство переопределяется в производном классе). Значением свойства возвра­ щается значение целочисленного поля a lpha, а при присваивании зна­ чения свойству значение присваивается этому полю. Еще одно свойство l e ngth доступно только для чтения и результатом возвращает размер символьного массива. В классе Alpha описано два индексатора. Есть там символьный ин­ дексатор с целочисленным индексом. Результатом выражения 571 Гл ава 1 0 с проиндексированным объектом возвращается значение элемента символьного массива s ymb s , при присваивании значения проиндекси­ рованному объекту оно присваивается элементу массива s ymb s с со­ ответствующим индексом. Еще есть целочисленный индексатор с сим­ вольным индексом. Этот индексатор виртуальный (описан с ключевым словом v i r t u a l ) и переопределяется в производном классе. В базовом классе он описан так, что для заданного символьного индекса s резуль­ татом g e t -aкceccopa возвращается значение выражения th i s [ s - ' а ' ] . Здесь ключевое слово th i s обозначает индексируемый объект. Значе­ нием выражения s - ' а ' является целое число, равное разности кодов символов: от кода символьного значения переменной s вычитается код символа ' а ' . В итоге получается ключевое слово th i s , проиндексиро­ ванное целочисленным индексом. Такое выражение обрабатывается ин­ дексатором с целочисленным индексом. Результатом является символ из символьного массива. Но поскольку все это описано в g e t -aкceccope, который должен возвращать результатом целое число, то символ автома­ тически будет преобразован в числовое значение (код символа). Таким образом, при индексировании объекта символьным индексом результа­ том будет возвращаться код символа из символьного массива. Индекс элемента в массиве определяется символьным индексом объекта: сим­ вол ' а ' соответствует индексу О, символ ' Ь ' соответствует индексу 1 и так далее. В s e t -aкceccope индексатора выполняется команда t h i s [ s - ' а ' ] = ( ch a r ) va l u e , которой присваиваемое числовое значение преобразу­ ется в символьное значение, и это символьное значение присваивает­ ся элементу символьного массива. Принцип индексации такой же, как и в get-aкceccope. В классе Alpha переопределен метод T o S t r i n g ( ) , который результа­ том возвращает текстовую строку, содержащую символы из символьно­ го массива. При этом в описании метода для определения размера мас­ сива мы использовали выражение t h i s . l e n g t h на основе свойства l e n g t h с явным указанием ссылки на объект t h i s , а для получения значения элемента массива использовалась инструкция th i s [ k ] , озна­ чающая индексирование объекта. В главном методе программы объект А класса Alpha создается коман­ дой Alpha A=n e w Alpha ( 1 О О , 11 AB C D 11 ) . Содержимое символьно­ го массива, формируемого на основе текстовой строки 11 AB C D 11 , про­ веряем командой C o n s o l e . W r i t e L i n e ( А ) . В последнем случае для 572 Н асл едование преобразования объекта А к текстовому формату будет задействован метод T o S r i n g ( ) . При считывании значения свойства numb e r (ин­ струкция А . numЬ e r ) результатом является значение поля a lpha. При присваивании значения свойству numb e r (команда А . numb e r= l 5 0 ) значение присваивается полю a lpha. Для отображения символов (из массива) и их кодов используется опе­ ратор цикла, в котором синхронно изменяются целочисленная k и сим­ вольная s индексные переменные. Выражение вида А [ k ] результатом дает символ, а выражение вида А [ s ] возвращает результатом код этого символа. При выполнении команды А [ О ] = ' Е ' элемент с индексом О в символь­ ном массиве s ymЬ s получает значение ' Е ' , а при выполнении команды А [ ' Ь ' ] = ' А ' + 1 О элемент с индексом 1 в символьном массиве s ymb s получает значением символ, код которого вычисляется выражением ' А ' + 1 О (число 7 5 , являющееся кодом символа ' К ' ). Производный класс Bravo создается наследованием класса Alpha. Класс B r avo получает такое �наследство»: защищенное поле a lpha, свойство l e ngth, индексатор с целочисленным индексом, метод T o S t r i n g ( ) все это, так сказать, �в неизменном виде» . Также в классе B r avo насле­ дуются и переопределяются: виртуальное свойство numЬ e r и виртуаль­ ный индексатор с символьным аргументом. Кроме этого, в классе Bravo описано защищенное целочисленное поле b ravo. У конструктора класса три аргумента (два целочисленных и один текстовый). При переопределении свойства numb e r мы его еще раз описываем в производном классе, причем используем ключевое слово ove r r i de . Более того, в производном классе данное свойство описано только с g е t -аксессором. Если в классе Alpha значение свойства определялось значением поля a lpha, то в классе B r avo значение свойства numb e r вычисляется как сумма значений полей a lpha и b r av o . Поскольку s e t-aкceccop не описан, то будет использоваться s e t -aкceccop свой­ ства numЬ e r из класса Alpha. Таким образом, если присвоить значение свойству numb e r объекта класса B r avo, то значение будет присвоено полю a lpha. Если прочитать значение свойства numЬ e r объекта класса B r avo, то результатом получим сумму значений полей a lpha и b r avo. Индексатор с символьным индексом в классе B r avo определяется со­ вершенно иначе по сравнению с классом Alpha. Теперь если проин­ дексировать объект символом ' а ' , то получаем доступ к полю a lpha 573 Гл ава 1 0 объекта. Если указать любой другой символьный индекс, получим до­ ступ к полю b r avo объекта. Объект В класса B r avo создается в главном методе программы коман­ дой B r avo B=new B r avo ( 2 0 0 , 3 0 0 , " E FGH I " ) . Для проверки содер­ жимого символьного массива объекта используем команду C on s o l e . Wr i t e L i n e ( В ) , при выполнении которой для преобразования объекта В к текстовому формату вызывается метод T o S t r i ng ( ) , унаследован­ ный из класса Alpha. � ПОДРОБН ОСТИ Символ ьный массив s ymb s из класса Alpha в классе Bravo не на­ следуется в том смысле, что в теле класса Bravo мы не можем на­ пря мую обратиться к массиву по имени . Тем не менее массив суще­ ствует и доступ к нему осуществляется индекси рован ием объектов , а размер массива можно узнать с помощью свойства l e ngth . При считывании значения свойства numb e r объекта В (инструкция В . numЬ e r ) значением возвращается сумма полей a lpha и b r avo объ­ екта В. При выполнении команды В . numЬe r = 4 0 0 полю a lpha присва­ ивается значение 4 О О . Командой в [ ' а ' ] = 1 0 полю a l pha присваивается значение 1 0 , а при выполнении команды в [ ' d ' ] =2 О значение 2 О присваивается полю b r avo. Соответственно, значением выражения в [ ' а ' ] возвращается значение поля a lpha, а значением выражения в [ ' Ь ' ] возвращается значение поля b ravo. � ПОДРОБН ОСТИ Есл и при индекси ровани и объекта В мы указал и сим вол ьн ы й ин­ декс ' а ' , то выполняется обращение к полю a lpha объекта В . Есл и мы указал и л юбой другой символ ьный индекс ( например, ' d ' или ' Ь ' ) , то обращение выполняется к пол ю b ravo объекта В. В операторе цикла индексная переменная k пробегает значения ин­ дексов элементов символьного массива в объекте в . Размер масси­ ва определяем выражением В . l e ngth. За каждый цикл отображается значение элемента массива (инструкция В [ k ] ), после чего командой в [ k ] = ( cha r ) ( ' а ' + k ) элементу присваивается новое значение. По за­ вершении оператора цикла командой C on s o l e . W r i t e L i ne ( В ) прове­ ряется содержимое массива объекта в. 574 Н асл едование Р ез ю ме Спокойпо, Скрипач, н е раздражай даму. из к/ф -«Кин-дза-дза» • Классы можно создавать на основе уже существующих классов пу­ тем наследования. Класс, на основе которого создается новый класс, называется базовым. Тот класс, что создается на основе базового, на­ зывается производным классом. • Для создания производного класса на основе базового в описании производного класса после его имени через двоеточие указывается имя базового класса. Все открытые и защищенные члены базово­ го класса наследуются в производном классе. Закрытые члены ба­ зового класса не наследуются в производном классе, в том смысле, что в теле производного класса к ним нельзя обратиться напрямую по имени. • При создании объекта производного класса сначала вызывается конструктор базового класса, а уже затем выполняются команды, описанные в конструкторе производного класса. В описании кон­ структора производного класса указывается инструкция вызова конструктора базового класса: это ключевое слово ba s e с круглы­ ми скобками, в которых могут передаваться аргументы конструк­ тору базового класса. Инструкция на основе ключевого слова b a s e указывается через двоеточие в описании конструктора производного класса после закрывающей круглой скобки. • При удалении объекта производного класса из памяти сначала вы­ полняются команды в теле деструктора производного класса, а затем вызывается деструктор базового класса. • Объектная переменная базового класса может ссылаться на объект производного класса. Через объектную переменную базового класса можно получить доступ только к тем членам объекта производного класса, которые объявлены в базовом классе. • Унаследованные из базового класса члены могут замещаться в про­ изводном классе. Для этого соответствующий член описывается в производном классе с ключевым словом new. Для обращения к та­ кому члену используется его имя. Для обращения к одноименному члену из базового класса, замещенному в производном классе, перед именем этого члена через точку указывают ключевое слово b a s e . 575 Гл ава 1 0 • Если в базовом классе метод объявлен как виртуальный ( в описании метода использовано ключевое слово v i r t u a l ), то в производном классе такой метод можно переопределить. Для этого в производ­ ном классе описывается новая версия метода. При этом использует­ ся ключевое слово ove r r i de . • Принципиальная разница между замещением и переопределением метода состоит в том, что при замещении метода версия метода для вызова определяется по объектной переменной, через которую вы­ зывается метод, а при переопределении метода версия метода для вызова определяется по объекту, из которого вызывается метод. На­ ряду с замещением и переопределением методов можно также ис­ пользовать и перегрузку методов. • Свойства и индексаторы могут наследоваться. В описании свойств и индексаторов в базовом классе допускается использовать ключе­ вое слово v i r t u a l , а в производном классе такие свойства и индек­ саторы могут переопределяться. Задания для самостоятел ьной работы Если есть на этом Плюке гравицапа, так доста­ нем. Не такое доставали. из к/ф -«Кин-дза-дза» 1 . Напишите программу, в которой есть базовый класс с защищенным текстовым полем, конструктором с текстовым аргументом и где перео­ пределен метод T o S t r i ng ( ) . На основе базового класса путем наследо­ вания создается производный класс. У него появляется еще одно защи­ щенное текстовое поле. Также производный класс должен иметь версии конструктора с одним и двумя текстовыми аргументами, а еще в нем должен быть переопределен метод T o S t r i n g ( ) . В обоих классах метод T o S t r i n g ( ) переопределяется так, что он возвращает строку с названи­ ем класса и значение текстового поля или текстовых полей. 2. Напишите программу, в которой есть базовый класс с защищенным текстовым полем. В базовом классе должен быть метод для присваива­ ния значения полю: без аргументов и с одним текстовым аргументом. Объект базового класса создается передачей одного текстового аргумен­ та конструктору. Доступное только для чтения свойство результатом 576 Н асл едование возвращает длину текстовой строки. Доступный только для чтения ин­ дексатор возвращает значением символ из текстовой строки. На основе базового класса создается производный класс. В производном классе по­ является дополнительное открытое целочисленное поле. В классе долж­ ны быть такие версии метода для присваивания значений полям (ис­ пользуется переопределение и перегрузка метода из базового класса) : без аргументов, с текстовым аргументом, с целочисленным аргументом, с текстовым и целочисленным аргументом. У конструктора производ­ ного класса два аргумента (целочисленный и текстовый). 3 . Напишите программу, в которой есть базовый класс с открытым по­ лем, являющимся ссылкой на целочисленный массив. Конструктору класса при создании передается ссылка на массив, в результате чего соз­ дается копия этого массива и ссылка на него записывается в поле объ­ екта. Метод T o S t r i n g ( ) переопределен так, что возвращает текстовую строку со значениями элементов массива. На основе базового класса соз­ дается производный класс. В производном классе появляется еще одно открытое поле, являющееся ссылкой на символьный массив. Конструк­ тору производного класса передаются две ссылки: на целочисленный массив и на символьный массив. В результате должны создаваться ко­ пии этих массивов, а ссылки на созданные массивы записываются в поля объекта. Метод T o S t r i ng ( ) должен возвращать текстовую строку с со­ держимым обоих массивов. 4. Напишите программу, в которой на основе базового класса создает­ ся производный класс, а на основе этого производного класса создает­ ся еще один производный класс (цепочка наследования из трех клас­ сов). В первом базовом классе есть открытое целочисленное поле, метод с одним аргументом для присваивания значения полю и конструктор с одним аргументом. Во втором классе появляется открытое символь­ ное поле, метод с двумя аргументами для присваивания значения полям (перегрузка метода из базового класса) и конструктор с двумя аргумен­ тами. В третьем классе появляется открытое текстовое поле, метод с тре­ мя аргументами для присваивания значений полям (перегрузка метода из базового класса) и конструктор с тремя аргументами. Для каждого класса определите метод T o S t r i ng ( ) так, чтобы он возвращал строку с названием класса и значениями всех полей объекта. 5. Напишите программу, в которой использована цепочка наследова­ ния из трех классов. В первом классе есть открытое символьное поле. Во втором классе появляется открытое текстовое поле. В третьем классе 577 Гл ава 1 0 появляется открытое целочисленное поле. В каждом из классов должен быть конструктор, позволяющий создавать объект на основе значений полей, переданных аргументами конструктору, а также конструктор соз­ дания копии. 6. Напишите программу, в которой есть базовый класс с защищенным текстовым полем, конструктор с текстовым аргументом и метод, при вы­ зове которого в консольном окне отображается название класса и значе­ ние поля. На основе базового класса создаются два производных класса (оба на основе одного и того же базового). В одном из классов появля­ ется защищенное целочисленное поле, там есть конструктор с двумя аргументами и переопределен метод для отображения значений полей объекта и названия класса. Во втором производном классе появляется защищенное символьное поле, конструктор с двумя аргументами и пере­ определен метод, отображающий в консоли название класса и значения полей. В главном методе создайте объекты каждого из классов. Проверь­ те работу метода, отображающего значения полей объектов, для каждого из объектов. Вызовите этот же метод через объектную переменную базо­ вого класса, которая ссылается на объект производного класса. 7. Напишите программу, в которой есть базовый класс с открытым тексто­ вым полем. На его основе создается производный класс с дополнительным открытым символьным полем. Опишите в базовом классе виртуальный ме­ тод, который при вызове создает и возвращает результатом объект произво­ дного класса. Переопределите в производном классе этот метод так, чтобы он создавал и возвращал копию объекта, из которого вызывается. 8. Напишите программу, в которой есть базовый класс с открытым цело­ численным полем. В классе описан виртуальный индексатор, позволя­ ющий считывать цифры в десятичном представлении числа (значение поля). На основе базового класса создается производный класс, в кото­ ром появляется еще одно открытое целочисленное поле. В производном классе описывается версия индексатора с двумя индексами: первый ин­ декс определяет поле, значение которого используется, а второй индекс определяет разряд, для которого считывается цифра. Индексатор с од­ ним индексом переопределяется так, что вычисления (считывание циф­ ры в десятичном представлении числа) производятся на основе значе­ ния, равного сумме значений полей индексируемого объекта. 9. Напишите программу, в которой есть базовый класс с защищенным текстовым полем. В классе имеется виртуальное текстовое свойство, 578 Н асл едование возвращающее значением текст из текстового поля. При присваивании значения свойству значение присваивается текстовому полю. В клас­ се переопределен метод T o S t r i n g ( ) : он возвращает текстовую стро­ ку с названием класса и значением текстового поля. На основе базового класса создается производный класс, у которого появляется еще одно текстовое поле. Свойство переопределяется так, что значением возвра­ щается текст, получающийся объединением (через пробел) значений текстовых полей объекта. При присваивании значения свойству при­ сваиваемая текстовая строка разбивается на две, которые присваивают­ ся полям объекта. Разделителем для разбивки строки на две подстроки является пробел (первый с начала строки). Если пробела нет, то первая подстрока совпадает с исходной строкой, а вторая подстрока пустая. Ме­ тод T o S t r i n g ( ) для производного класса нужно переопределить таким образом, чтобы он возвращал название класса и значения полей объекта. 1 0 . Напишите программу, в которой есть базовый класс с защищенным целочисленным массивом, индексатором (с целочисленным индексом), позволяющим считывать и присваивать значения элементам массива, а также свойство, возвращающее результатом размер массива. На осно­ ве базового класса создается производный класс, у которого появляется защищенный символьный массив. Опишите в производном классе вер­ сию индексатора с символьным индексом, который возвращает значение элемента символьного массива и позволяет присвоить значение элемен­ ту символьного массива. Для свойства из базового класса необходимо выполнить замещение так, чтобы результатом возвращался целочислен­ ный массив из двух элементов: первый элемент определяет размер цело­ численного массива объекта, а второй элемент определяет размер сим­ вольного массива объекта. За кл юч ен и е Ч ТО БУД ЕТ ДАЛ Ь Ш Е Прекратите панику, Фукс. И слушайте мепя внимательпо ! из м/ф <t:Приключения капитана Врунгеля» На этом первая часть книги закончена. Мы познакомились с основными, наиболее важными темами. Это основа, ядро языка С#. На данном этапе читатель уже вполне подготовлен к написанию сложных программ. Тем не менее точку в изучении языка С# мы не ставим. Впереди еще много полезного и интересного. Планы у нас большие, и их воплощению посвя­ щена вторая часть книги. Во второй части обсуждаются несколько фундаментальных концепций, которые в полной мере раскрывают красоту и мощь языка С#. Там мы познакомимся с интерфейсами и абстрактными классами. Узнаем, что такое делегаты, ссылки на методы, лямбда-выражения и анонимные ме­ тоды, познакомимся с событиями. Познакомимся с перечислениями и выясним, чем структуры отличаются от классов. Мы научимся с помо­ щью указателей выполнять операции с памятью. Узнаем, как в языке С# реализуется перехват и обработка ошибок. Для нас откроются секре­ ты создания многопоточных приложений. Мы научимся использовать обобщенные типы и сможем создавать приложения с графическим ин­ терфейсом. Еще будут операции с файлами, знакомство с коллекциями и многое другое. Так что работа предстоит большая и интересная. П р едм е т н ы й у ка з а те л ь Блок форматирования, 58 Двоичпый код, 9 1 Деструктор, 309, 329, 540, 575 Замещение, 549, 575 Индексатор, 385, 448, 475, 4 9 1 , 5 1 8, 565 , 576 двумерный, 493 многомерный, 506 перегрузка, 477 , 5 1 0 Инкапсуляция, 285 Инструкция base, 536, 549, 575 break, 1 30, 1 39, 1 43, 1 52 , 1 5 6, 1 67 case, 1 30, 1 67 class, 22 , 289, 328 const, 79, 1 1 2, 3 1 7 continue, 1 43 default, 130, 1 4 1 , 1 67 explicit, 384, 433, 444 goto, 1 5 6, 1 68 implicit, 384, 433, 444 internal, 529 new, 1 72 , 1 78, 226, 292 , 303, 329 , 549, 575 null, 469 operator, 382 , 444 out, 263 , 279, 452 , 477, 5 1 9 override, 374, 4 1 8, 554, 576 params, 272 , 279 private, 290, 299, 320, 328, 524, 529 protected, 290, 524, 529 puЬlic, 24, 278, 290, 303, 309, 328, 383, 443, 529 ref, 260, 279, 452 , 477 , 5 1 9 return, 223, 277 , 279, 280 sealed, 530 static, 23, 59 , 2 3 1 , 279, 3 1 4, 3 1 7, 329 , 383, 443, 4 7 1 this, 32 1 , 324, 329, 377, 476, 5 1 5 , 5 1 9, 572 using, 3 1 , 60 value, 45 1 , 48 1 , 5 1 9 var, 78 virtual, 554, 576 void, 23 , 232 , 279, 303 Исключепие, 1 59 Класс, 22 , 59, 282 , 286, 289 Console, 26, 52 , 60 Interaction, 46, 60, 1 2 1 , 134 Math, 320 MessageBox, 33, 42 , 60, 135, 1 65 Obj ect, 2 1 8, 227, 373, 405 , 4 1 7 , 543 Random, 1 78, 206, 245 String, 48, 60, 69, 1 36, 333, 378 базовый, 524, 575 метод, 289, 3 1 4, 328 наследование, 373, 525 поле, 289 , 3 1 4, 328 производпый, 524, 575 Код промежуточный, 1 О Комментарий, 26 многострочный, 27 однострочный, 27 Константа, 79 Конструктор, 293, 303, 328, 535, 540, 575 перегрузка, 303 по умолчапию, 309, 328 Конструкция try- catch, 1 6 1 , 1 68 Контролируемый код, 1 6 1 Литерал, 68 буквальпый, 72 Массив, 1 7 1 , 242 , 247 581 Предметны й указател ь двумерпый , 1 98 инициализация , 1 8 1 многомерный , 208 одномерный , 1 72 размер , 1 72 размерность , 1 72 строки разной длины , 2 1 3 Метка , 1 58 Метод Concat(), 350 Contains(), 364 Сору ( ) , 338 СоруТо ( ) , 372 EndsWith(), 364 Equals( ) , 35 1 , 378, 405, 4 1 7, 444 GetHashCode( ) , 405, 42 1 , 444 GetLength( ) , 1 99, 226 IndexOf( ) , 356 IndexOfAny(), 357 InputBox( ), 46, 60, 1 2 1 , 1 34, 1 64 Insert(), 364 Join(), 350 LastlndexOf( ), 356 LastlndexOfAny(), 357 Next(), 1 79 Parse( ) , 54, 60, 1 34, 1 64 ReadKey(), 32 ReadLine( ) , 3 1 , 50, 60, 127 Remove( ) , 364 Replace( ) , 364 Show( ), 33, 42, 135, 1 65 Split(), 368, 377 StartsWith( ) , 364 Substring(), 365 ToCharArray( ) , 368, 372, 57 1 ToLower( ) , 355 ToString(), 333, 373, 378, 39 1 , 40 1 , 440 ToUpper( ), 355 Trim( ) , 368 Write( ), 50, 57, 60, 145 WriteLine( ) , 26, 50, 57, 60, 7 1 , 373 аксессор, 449, 460, 476, 484, 487, 5 1 9 виртуальный , 553, 576 главный , 23, 24, 59, 225, 23 1 , 277, 280 операторный , 382, 443 перегрузка , 238, 328, 562, 576 переопределение , 373, 553, 562 , 576 статический , 23, 230 582 Наследование , 285, 523, 540, 565, 575 Объект, 282, 286 анонимпый, 3 1 3 создание, 292 Оператор, 8 1 , 1 1 2 , 383, 385, 388, 394, 404, 427, 44 1 , 443 do - while, 147, 1 67 false, 423 for, 1 50, 1 67 foreach, 1 94, 2 1 6, 227, 347 if, 1 1 6, 1 1 7, 123, 1 66 switch, 130, 1 67 true, 423 while, 142, 1 48, 1 67 больше, 86, 404 больше или равно , 86, 404 вычитапия , 82 декремента , 82, 83 деления , 82 ипкремента , 82, 83 логическое и , 87, 424, 427 логическое исключающее или , 87 логическое отрицание , 87 меньше , 86, 404 меньше или равно , 86, 404 пе равно , 86, 350, 378, 404 остаток от делепия, 82 перегрузка , 38 1 побитовая ипверсия , 94 побитовое и , 94 побитовое или , 94 побитовое исключающее или , 94 побитовый сдвиг влево , 94 побитовый сдвиг вправо , 94 приведепия типа , 384, 433 присваивания , 49, 1 00 равно , 86, 350, 378, 404 сложепия , 49, 82 тернарный , 1 04, 1 97 умпожепия , 82 Переменная , 55, 63 ипициализация , 78 локальпая , 232 массива , 1 72 , 1 98, 209, 2 1 5 область доступпости, 79 об ъектная , 292, 329, 543, 575 об ъявление, 48, 77 Перечислепие DialogResult, 45 Предметны й указател ь MessageBoxButtons, 42, 1 2 1 , 1 65 MessageBoxlcon, 43, 1 2 1 , 1 65 StringComparison, 355 Полиморфизм , 285 Приоритет операторов , 1 05 Присваивапие сокращепная форма, 1 02 , 44 1 Пространство имен Forms, 33 Microsoft, 46, 60 System, 26, 3 1 , 33, 60, 1 36, 1 64, 2 1 8 VisualВasic, 46, 60 Windows, 33 Псевдоним obj ect, 2 1 9, 543 string, 48, 60, 1 36, 334, 378 Рекурсия , 266, 279 Свойство , 448, 456, 465, 5 1 8, 565, 576 Length, 127, 1 74, 1 99, 225, 226, 344, 378 Rank, 1 99, 226 Title, 52 Символ управляющий , 7 1 , 140 Среда разработки , 1 2 , 27, 34, 223, 542 Структура Boolean, 66 Byte, 66 Char, 66 Decimal, 66 DouЫe, 66, 1 64 Int 1 6, 66 Int32, 54, 60, 66, 1 34, 1 64 Int64, 66 S Byte, 66 Single, 66 Ulnt 1 6, 66 U lnt32, 66 U lnt64, 66 Табуляция, 7 1 , 1 85 Тип , 55 bool, 66 byte, 65, 66 char, 66 decimal, 66 douЫe, 66 float, 66 int, 55, 60, 63, 66 long, 65, 66 sbyte, 65, 66 short, 65, 66 uint, 65, 66 ulong, 65, 66 ushort, 65, 66 базовый, 65, 1 1 2 преобразование , 73, 432 Х э ш-код , 4 1 7 Все права защищены. Книга или лю бая ее часть не может бы ть скопирована, воспроизведена в электронной или механической форме, в виде фотокопии, записи в память ЭВМ, репродукции или каким-ли бо иным спосо бом, а также использована в лю бой информационной системе без получения разрешения от издателя. Копирование, воспроизведение и иное использование книги или ее части без согласия издателя является незаконным и влечет уголовную, административную и гражданскую ответственность . Науч но- попул я рное изда н и е РОСС И Й С К И Й КО М П ЬЮТЕРН Ы Й Б ЕСТСЕЛЛЕР Алексей Васильев ПРО ГРАМ М И РО ВАН И Е НА С# ДЛЯ НАЧ И НАЮЩИХ Основные сведения Е. Капьёв Е. Истомина Младш и й редактор Е. Минина Художестве н н ы й редактор В. Брагина Директор редакции Ответствен н ы й редактор В оформлении обложки испол ьзована илл юстрация : pavel7tymosheпko / Sh utterstock.com Испол ьзуется по л и цензии от Sh utterstock.com ООО «Издательство «Эксмо» 123308, Москва, ул. Зорге, д. 1 . Тел . : 8 (495) 411 -68-86. Home page: www. eksmo.ru E-mail: info@eksmo.ru 6ндiрушi: •ЭКСМО• Ак,Б Басласы, 123308, Маскеу, Ресей, Зорге кешесi, 1 уй. Тел . : 8 (495) 411 -68-86. Home page: wvvw. eksmo.ru E-mail: info@eksmo.ru. Тауар белгiсi: "эксмо» 1<,азак,стан Республикасында дистрибьютор жэне енiм бойынша арыз-талаптарды к.абылдаушынын. екiлi «РДЦ-Алматы» ЖШС, Алматы к,. , Домбровский кеш . , 3"3", литер Б, офис 1 . Тел . : 8 (727) 251 -59-90/91/92; E-mail: RDC-Almaty@eksmo.kz Интернет·маrазин: wvvw. book24.kz 0нiмнiн. жарамдылык, мерэiмi шектелмеген. Сертификация туралы эк.парат сайтта: wvvw. eksmo. ru/certification Сведе н и я о подтвержден и и соответствия издания согласно законодательству РФ о техническом регул и рова н и и можно получить по адресу: http://eks m o . ru/certiftcatioп/ ендi р ген мемлекет: Ресей Сертификация к;арасты рылмаFан ISBN 978-5-04-0925 1 9-3 911�!lllJl!IJl �IJIJl1l�IJl > Новая к н и га Ал ексея В а с ил ьева , ведущего росс и й с ко го а вто ра са м оуч ител е й п о п р ограм м и ро ва н и ю , посвя ще н а я з ы ку С#. Оди н и з с а м ых попул я р н ы х я з ы ко в семе йста С , ш и роко и с п ол ьзуе м ы й во в с е м м и р е , откроется в а м во всем м н о гообра з и и в этой п е р вой кн и ге двухто м н и ка « П рогра м м и ро ва н и е н а С# дл я нач и н а ю щих» . В н е й доступ н ы м дл я н о в и ч ков я з ы ко м а вто р рассказы ва ет ч итател я м об о с н о вах это го я з ы к а : структура , о п е ратор ы , ци кл ы , м а с с и в ы и м н о г и е другие сведе н и я , нуж н ы е в а м дл я то го , чтобы н а ч ать п рогра м м и ровать н а С#. Кн и га содержит м н ожество п р и меров и п одробн ы й разбор каждо го и з н их, а та кже зада н и я дл я с а м о стоятел ь н о й ра боты . П роч ита в этот с а м оуч ител ь , вы п о я з ы ку С#, необходи м ы х дл я п р о г ра м м и ро ва н и ю н а н е м . будете то го , обл адать чтоб ы п ол н ы м п р иступ ить с п е ктро м сведен и й к са м о стоятел ь н о м у До п о л н ител ь н ы е м ате р и ал ы м о ж н о с качать п о адресу: https ://e ks m o . ru/fi Les/vas i lyev cs h a rp. zip С а м о е гл а в н о е : • О с н о в н ы е с веде н и я о я з ы ке С# - от и сто р и и до созда н и я н ебол ь ш их п рогра м м • П одроб н ы й ра збо р каждо й гл а вы с п р и м ера м и и в ы вода м и • Все п р и м е р ы а ктуал ь н ы е и м о гут п р и м е н яться в ра боте • Доступ н ы й я з ы к и зложе н и я , п о н ят н ы й н о в и ч ка м • И с п ол ь зо ва н а м етод и ка обуч е н и я , м н о го крат н о п ро ве ре н н а я ; \ н а п ра кти ке О б а вто р е Ал е кс е й п рофе ссо р Н и кол а е в и ч кафедры Васил ьев - тео р етической до кто р физики ф и з и ко - м а те м а т и ч ес к и х физического фа культета н аук, � Киев­ ско го н а ци о н а л ь н о го униве рситета и м е н и Та раса Ш е в ч е н ко . Авто р более 15 кн по п р о гра м м и р о ва н и ю на я з ы ках С + + , J ava , J avaScri pt, Python и м ате м ати ч еско му модел и ро в а н и ю . Бол ь ш и н ство к н и г по п рогра м м и р ова н и ю - с куч ное изл оже н и е спецификаций и п р и м е р о в . С это й кн игой н а ч и н а ю щи й п рогра м м и ст м ожет сразу п р и ступ ить к н а п и са н и ю кода н а я з ы ке С#, по скол ьку п р и м е р ы в ней хо рошо о п иса н ы и п роилл юстр и р ова н ы . В ы осво ите м и н и м ал ь н ы й н а б о р и н струм енто в , п р а кти ку их п ри м е н е н и я , а зада н и я дл я с а м о сто ятел ь н о й ра боты в ко н це те м ати ч еских гл а в п о м о гут п ровер ить получ е н н ы е з н а н и я . 911�lll1Шl ��IJl1 �11 ISBN 978-5-04-09251 9-3 \ Ал е ксе й Фл о р и н с ки й , з а м естител ь ди ректора S i m b l гSoft