Заглавная страница Избранные статьи Случайная статья Познавательные статьи Новые добавления Обратная связь FAQ Написать работу КАТЕГОРИИ: АрхеологияБиология Генетика География Информатика История Логика Маркетинг Математика Менеджмент Механика Педагогика Религия Социология Технологии Физика Философия Финансы Химия Экология ТОП 10 на сайте Приготовление дезинфицирующих растворов различной концентрацииТехника нижней прямой подачи мяча. Франко-прусская война (причины и последствия) Организация работы процедурного кабинета Смысловое и механическое запоминание, их место и роль в усвоении знаний Коммуникативные барьеры и пути их преодоления Обработка изделий медицинского назначения многократного применения Образцы текста публицистического стиля Четыре типа изменения баланса Задачи с ответами для Всероссийской олимпиады по праву Мы поможем в написании ваших работ! ЗНАЕТЕ ЛИ ВЫ?
Влияние общества на человека
Приготовление дезинфицирующих растворов различной концентрации Практические работы по географии для 6 класса Организация работы процедурного кабинета Изменения в неживой природе осенью Уборка процедурного кабинета Сольфеджио. Все правила по сольфеджио Балочные системы. Определение реакций опор и моментов защемления |
Объектно-ориентированный подход↑ Стр 1 из 4Следующая ⇒ Содержание книги
Поиск на нашем сайте
Литература 1. Б.Страуструп. Язык программирования С++, 2-е изд./Пер. с англ. Часть первая. – Киев.: “ДиаСофт”, 1993. – 264 с. Часть вторая. – Киев.: “ДиаСофт”, 1993. – 296 с. 2. М.Эллис, Б.Страуструп. Справочное руководство по языку программирования С++ с комментариями./Пер. с англ. – М.: Мир, 1992. 3. Бабз Б. Просто и ясно о Borland C++./Пер. с англ. – М.: Бином, 1995. - 400 с. 4. Дьюхарст С., Старк К. Программирование на С++/Пер. с англ. – Киев, ДиаСофт, 1993. – 272 с. 5. Романов В.Ю. Программирование на языке С++: Практический подход. – М.: Компьютер, 1993. – 160 с. 6. С++. Язык программирования. – М.: ИВК СОФТ, 1991. – 315 с. 7. Г.Шилдт. Самоучитель С++, 3-е изд./Пер. с англ. – СПб.: БХВ-Петербург, 2001. – 688 с. 8. Б.Страуструп. Язык программирования С++, 3-е изд./Пер. с англ. – СПб.: М.: «Невский Диалект» – «Издательство БИНОМ», 1999. – 991 с., ил. 9. Том Сван. Программирование для Windows в Borland C++./Пер. с англ. – М.: БИНОМ, 1995. – 480 с. (Только особенности программирования в среде Windows, использование библиотеки OWL. Предполагает знание уже C++). 10. Г.Шилдт. Теория и практика С++ (серия Мастер, руководство для профессионалов). /Пер. с англ. – СПб.:BHV – Санкт-Петербург, 1996. – 416 с. 11. Г.Буч. Объектно-ориентированный анализ и проектирование с примерами приложений на С++, 2-е изд./Пер. с англ. – М.: «Издательство Бином», СПб.: «Невский диалект», 1999 г. – 560 с., ил. Цель курса Цель курса – освоение принципов объектно-ориентированного проектирования и методов объектно-ориентированного программирования с использованием языка С++. “…Если на компьютере установлена ОС Windows – в курсе рассматриваются примеры программ, которые не предназначены специально для работы в этой ОС. Причина: программы для Windows по самой своей сути большие и сложные. При написании каждой такой программы для демонстрации возможностей языка С++ потребовалось бы написать тысячи строк исходного кода. Конечно, программирование на С++ под Windows позволяет пользоваться библиотеками классов, что существенно упрощает разработку приложений. Кроме этого, интерфейс любого приложения под Windows достаточно просто создать с помощью таких средств визуального программирования, как Visual C++ 5 или Borland C++ 5. Сердцевиной же любого профессионального приложения является программная реализация его идеи, а отнюдь не интерфейс, пусть даже самый что ни на есть дружественный” [7, стр. 6-7]. Поэтому будем рассматривать в курсе не создание пользовательского интерфейса в стиле Windows, а собственно язык программирования С++. Процесс разработки ПО В основе деятельности по созданию и использованию программного обеспечения (ПО) лежит понятие его жизненного цикла (ЖЦ). ЖЦ является моделью создания и использования ПО, отражающей ее различные состояния, начиная с момента возникновения необходимости в данном программном изделии и заканчивая моментом его полного выхода из употребления у всех без исключения пользователей. Традиционно выделяют следующие основные этапы ЖЦ ПО: - анализ требований, - проектирование, - кодирование (программирование), - тестирование и отладка, - эксплуатация и сопровождение. Существующие модели ЖЦ определяют порядок выполнения этапов в ходе разработки, а также критерии перехода от этапа к этапу. Главная особенность индустрии ПО состоит в концентрации сложности на начальных этапах ЖЦ (анализ, проектирование) при относительно невысокой сложности и трудоемкости последующих этапов. Более того, нерешенные вопросы и ошибки, допущенные на этапах анализа и проектирования, порождают на последующих этапах трудные, часто неразрешимые проблемы и, в конечном счете, приводят к неуспеху всего проекта. [Калянов Г.Н. CASE-технологии. Консалтинг при автоматизации бизнес процессов. 2-е изд. – М.: Горячая линия – Телеком, 2000. – 320 с., ил. Стр. 20 - 22] Первые четыре этапа определяют ЖЦ разработки ПО и (более или менее подробно) рассматриваются в данном курсе. В [11, стр. 36-37] дается разграничение понятий метода и методологии. Метод – это последовательный процесс создания моделей, которые описывают вполне определенными средствами различные стороны разрабатываемой программной системы. Методология – это совокупность методов, применяемых в ЖЦ разработки ПО и объединенных одним общим философским подходом. Методы важны, так как они упорядочивают процесс создания сложных программных систем. Методы появились как ответ на растущую сложность программных систем. В 60-70-е годы было разработано много методов, помогающих справиться с растущей сложностью программ. В настоящее время все методы можно разделить на три группы: - метод структурного проектирования сверху вниз, - метод потоков данных, - метод объектно-ориентированного проектирования. Наибольшее распространение получило структурное проектирование по методу сверху вниз. Структурное проектирование использует следующий подход, определяемый топологией традиционных языков высокого уровня: применяется алгоритмическая декомпозиция для разбиения большой задачи на более мелкие. И сейчас значение структурного подхода осталось прежним, но, как замечает Стейн, «оказалось, что структурный подход не работает, если объем программы превышает приблизительно 100 000 строк» (цит. по [11, стр. 36]). Объектно-ориентированный подход включает в себя объектно-ориентированные анализ, проектирование и программирование. ОО Анализ – на этом этапе определяются абстракции и их общие свойства. В процессе анализа предметной области выделяются абстракции, наличие которых существенно упрощает разработку прикладной задачи. Рассмотрим, например, понятие (абстракцию) рациональная дробь. В С++ абстракции определяются с помощью классов (класс – абстракция, экземпляр класса – конкретный объект). Определим общие свойства для класса Рациональная дробь: должны иметь возможность хранить корректные значения дроби со знаком, должны иметь возможность выполнять все арифметические операции и операции ввода-вывода – или, другими словами, должны иметь возможность использовать их в программе так же, как и базовые числовые типы языка. ОО Проектирование – на этом этапе необходимо сначала точно сформулировать все особенности поведения объектов выделенных классов, а затем четко определить интерфейс выделенных классов на конкретном языке программирования. После этого разработка приложения может быть разделена на два направления, работа по которым может выполняться относительно независимо друг от друга. Одно направление предполагает разработку и реализацию классов – или, другими словами, специальных инструментальных средств, ориентированных на решение конкретной задачи. Другое направление связано с разработкой и реализацией самого приложения (прикладной программы – ПП) в терминах новых классов (как бы с использованием новой версии языка программирования, включающей в себя новые типы данных, необходимые для конкретной ПП). Реализация класса скрыта от его использования и не должна мешать применению класса в ПП. Если требуется изменить реализацию – это не должно приводить к изменению самого приложения. Например, для упомянутого ранее класса Рациональная дробь на данном этапе можно ввести следующие уточнения: состояние класса – числитель и знаменатель дроби, знаменатель обязательно отличен от нуля и всегда положителен (знак дроби определяет ее числитель); числитель и знаменатель дроби всегда представлены в виде взаимно простых чисел. Поведение класса – набор необходимых операций из постановки задачи (математические операции, ввод-вывод, сравнения и т.п.), представленных в виде привычных знаков арифметических операций (+, - и т.п.; это возможно при использовании языка программирования С++). Теперь можно независимо разрабатывать класс Рациональная дробь и ПП. ОО Программирование и отладка – используются конкретные инструментальные средства; в нашем распоряжении – Borland С++ или Visual C++ (в зависимости от лабораторного класса). Конечно, на этом этапе при отладке ПП должна быть доступна какая-то реализованная версия класса Рациональная дробь. Изменения и дополнения, которые могут быть внесены в класс Рациональная дробь, не должны требовать перепрограммирования ПП. Модификация ПП и изменения в предметной области могут привести к появлению дополнительных требований к классу; опять же новые возможности класса не должны оказать серьезное влияние на старые уже существующие версии ПП. Определение объектно-ориентированного языка программирования (ООЯП) ООЯП – это язык программирования, обладающий набором определенных свойств. Главными свойствами ООЯП являются абстракция, инкапсуляция, наследование, полиморфизм. Абстракция – это свойство, позволяющее создавать новые типы данных, выделяя общие абстрактные свойства этих типов. Инкапсуляция – скрытие информации; механизм, который объединяет данные и код, манипулирующий с этими данными, а также защищает и то, и другое от внешнего вмешательства или неправильного использования. Наследование – это процесс, посредством которого один объект может приобретать свойства другого (точнее, объект может наследовать свойства другого объекта и добавлять к ним черты, характерные только для него). Например, роза или ромашка – это разные разновидности некоторого общего (родительского) класса, называемого цветком. В свою очередь, цветок – это разновидность еще более общего класса растение, и т.д. В каждом случае порожденный класс наследует все связанные с родителем качества и добавляет к ним свои собственные определяющие характеристики. Полиморфизм – это свойство, которое позволяет одно и то же имя использовать для решения двух или более схожих, но технически разных задач. Например, в не ОО языках программирования нахождение абсолютной величины числа требует нескольких различных функций – в зависимости от типов аргумента и результата (например, в С – это abs() для данных типа int, labs() для данных типа long и fabs() для данных типа double). В ООЯП каждая из этих функций может быть названа abs(). Тип данных, который используется при вызове функции, определяет, какая конкретно версия функции действительно выполняется. Пример использования ООП (объектно-ориентированного проектирования) Постановка задачи – решить систему линейных алгебраических уравнений, коэффициентами которой являются целые числа, с максимально возможной точностью. Решение: для уменьшения погрешности решения следует использовать точные методы (не обладающие методической погрешностью, например, правило Крамера); исходные данные, в соответствии с постановкой задачи, заданы точно; основной источник погрешности – вычисления в ограниченной разрядной сетке. Для устранения этой погрешности – вычисления в рациональных дробях. Отсюда – этапы разработки: спроектировать и реализовать класс Rational – рациональная дробь; спроектировать и реализовать основную задачу; провести исследования решения; в случае необходимости – модифицировать класс Rational. Разработка класса в ООП Определение класса (синтаксис); уровни видимости; методы класса, их классификация по разным критериям; конструкторы и деструктор; члены и друзья класса; реализация методов; неявный параметр this; создание экземпляров класса; пример использования класса; что происходит при посылке сообщения экземпляру класса. Пример Рассмотрим класс “рациональная дробь” – Rational [‘рэшенел]. Состояние класса: два поля типа “целое”, с именами num (от numerator [‘нью:мерэйте] – числитель) и den (от denominator [ди’номенэйте] – знаменатель. Пока ограничиваемся диапазоном представления в стандартных типах. Дополнительные требования: знаменатель не должен быть равен нулю, ни при каких условиях; знаменатель всегда положителен, знак дроби определяется знаком числителя; поля класса не должны быть доступны извне класса непредусмотренными классом способами. Методы класса: традиционные арифметические операции (сложение, умножение и т.п.), ввод/вывод; кроме того, потребуются вспомогательные операции для выполнения арифметических операций – типа сокращения дроби и т.п. Определение класса Представление класса на языке программирования С++. Для определения класса предусмотрено специальное ключевое слово class, но можно использовать и традиционное struct. Синтаксис определения класса приведен на рис. 2-1.
Рис. 2-1. Определение класса Уровень_видимости задается одним из трех ключевых слов: - private [‘прайвит] – определяет закрытую часть класса, не доступную извне класса; - protected [прэ’тэктид] – пока для нас аналогичен private; различия между ними проявляются при использовании наследования; - public [‘паблик] – определяет открытую часть класса, видимую и доступную извне класса. Определение класса можно проиллюстрировать следующим образом (рис. 2-2): Рис. 2-2. Уровни видимости класса Методы класса определены для класса, предназначены для обработки информации, определяющей состояние класса, поэтому всегда видят его (состояние). Описания_полей_класса и прототипы_функций определяются в соответствии с обычными правилами С++ (рис. 2-3).
Рис. 2-3. Пример определения класса Объявляем экземпляр нового типа данных X – в соответствии с обычными правилами (независимо от того, определен класс с помощью struct или class): X obj; Тогда обращения: obj.a1, obj.a2, obj.f1() – вызовут сообщения об ошибке (члены класса a1, a2 и f1() не видны (не доступны) извне класса; obj.a3, obj.f3()– корректны. Внутри функций-методов класса f1()и f3()можно без опасений использовать все имена: a1, a2, a3, f1()и f3(). Порядок следования ключевых слов, определяющих уровень видимости, произволен; они могут появляться неоднократно или отсутствовать в определении класса. Если в начале определения класса отсутствует уровень видимости, тогда для class предполагается private, а для struct – public (рис. 2-4).
Рис. 2-4. Правила умолчания для class (a) и struct (b) В дальнейшем для определения класса будем использовать ключевое слово class. Рекомендации по поводу использования уровней видимости при определении класса Члены-данные класса, определяющие его состояние, как правило, помещаются в private- или protected- область класса – они не должны быть непосредственно доступны извне класса. Доступ к состоянию класса должен определяться только интерфейсом класса. Методы класса могут размещаться в любой области видимости класса. Это определяется особенностями функционирования и использования класса с позиций прикладной задачи. Методы класса Методы класса можно классифицировать по двум независимым критериям – по функциональному назначению и по их отношению к классу. По функциональному назначению методы класса делятся на следующие категории: - конструкторы – предназначены для инициализации состояния экземпляров класса при их создании; - деструкторы – предназначены для выполнения каких-то дополнительных действий в момент уничтожения экземпляров класса; - селекторы – предназначены для обработки состояния класса без его изменения; - модификаторы – предназначены для изменения состояния класса; - итераторы – предназначены для организации последовательного доступа к элементам данных, определяющих состояние некоторого (одного) экземпляра класса. По отношению к классу методы делятся на следующие две категории: - функция-член класса – функция, принадлежащая самому классу и не существующая вне класса; прототипы функций-членов класса включены в определение класса; - функция-друг класса – внешняя по отношению к классу функция, которая может существовать вне класса, но имеет доступ к закрытой (и защищенной) части класса. Прототип функции-друга класса также включается в определение класса, но начинается специальным ключевым словом friend. Конструкторы и деструктор класса могут быть реализованы только функциями-членами класса и имеют специальный синтаксис. Другие методы класса имеют обычный синтаксис функций языка С++ и могут быть реализованы и функциями-членами, и функциями-друзьями класса. Мы пока ограничимся рассмотрением только функций-членов класса. Конструкторы и деструктор Как упоминалось выше, конструкторы служат для инициализации экземпляров класса в момент их создания. Часто конструкторы определяют как методы, предназначенные для создания экземпляров класса. Это не совсем корректно, так как экземпляр класса непосредственно создается (т.е. под него выделяется память) не конструктором, а соответствующими программными средствами, в соответствии с определением языка. Так, например, при определении локальных объектов память под них выделяется в момент вызова функции, в которой эти объекты определяются; при использовании динамических объектов память выделяется при выполнении оператора new. Конструктор непосредственно память не выделяет, но вызывается для инициализации выделенной памяти – т.е. в момент создания экземпляров класса. Однако при выполнении каких-либо вычислений может потребоваться создание временных экземпляров класса, которые уничтожаются по окончании этих вычислений; для создания таких временных экземпляров класса также используется конструктор. При определении класса можно определить несколько конструкторов, которые можно классифицировать опять же по двум независимым критериям: каким образом конструктор инициализирует состояние класса и кем определен конструктор. По тому, каким образом конструктор инициализирует состояние класса, конструкторы определяются как инициализирующие и копирующий. Инициализирующие конструкторы содержат отдельные значения, используемые для инициализации состояния полей экземпляра класса. В списке параметров может быть указан нуль, один или более параметров любых типов. Один из инициализирующих конструкторов, имеющий пустой список параметров, имеет специальное наименование – пустой конструктор. Пустой конструктор также инициализирует состояние экземпляра класса, используя для этого предопределенные значения (в соответствии с требованиями задачи). Копирующий конструктор инициализирует состояние класса значением другого экземпляра этого класса (создает копию существующего экземпляра класса). В списке параметров указывается единственный параметр, имеющий тип «ссылка на экземпляр класса». По тому, кто определяет конструкторы, последние делятся на конструкторы по умолчанию (не требуют какого-либо упоминания в определении класса) и явно определенные программистом. Конструкторы по умолчанию: только пустой и копирующий. Пустой конструктор по умолчанию не инициализирует состояние экземпляра класса. Копирующий конструктор по умолчанию при инициализации осуществляет побайтное копирование состояния указанного существующего экземпляра класса. Конструкторы, определенные программистом: любые. Они осуществляют инициализацию состояния экземпляра класса в соответствии с логикой, определенной в конструкторе. Если программист определяет свои конструкторы, будут использоваться именно они. Если есть хотя бы один инициализирующий конструктор, определенный программистом, пустой конструктор по умолчанию не используется (даже если программист не определил собственный пустой конструктор). В определении конструкторов отсутствует тип возвращаемого значения (конструктор ничего не возвращает); имя конструктора совпадает с именем класса; в классе может быть определено несколько конструкторов. Деструктор служит для разрушения экземпляра класса. Опять же, память, занятая экземплярами класса, освобождается в соответствии с используемыми средствами языка; локальный объект уничтожается, когда осуществляется выход за пределы области видимости для этого объекта. Динамический объект уничтожается при выполнении оператора delete. Временный объект уничтожается по окончании вычислений, в которых он используется. В момент уничтожения объектов для них вызывается деструктор. В классе может быть определен только один деструктор. Также существует деструктор по умолчанию или явно определенный программистом. Деструктор по умолчанию не выполняет никаких действий. Деструктор, определенный программистом, выполняет действия, указанные в его определении. В определении деструктора также отсутствует тип возвращаемого значения; имя деструктора также совпадает с именем класса, но начинается символом ~. Правила записи прототипов конструкторов разных типов и деструктора приведены на рис. 2-5.
Рис. 2-5. Правила записи прототипов конструкторов и деструктора Пример определения класса Рациональная дробь (Rational) приведен ниже (рис. 2-6). class Rational{ private: int num, den; // состояние класса – числитель и знаменатель дроби int gcd() const; // метод класса – нахождение наибольшего общего делителя void reduce(); // метод класса – сокращение дроби void correct(); // метод класса – коррекция дроби protected: /* отсутствует: можно совсем не включать данную часть класса, вместе с ключевым public: /* Конструкторы класса */ Rational(); // пустой конструктор Rational(int num); // инициализирующий конструктор Rational(int num, int den); // инициализирующий конструктор с 2 аргументами /* Деструктор класса */ ~Rational(); /* Методы класса: селекторы */ void print()const; // вывод значения дроби в поток Rational add(const Rational &opd)const; // сложение дробей /* Модификатор */ void assign(int x, int y); // присваивание дроби нового значения }; Рис. 2-6. Пример определения класса Рациональная дробь Использование класса В соответствии с определением языка С++, новый класс представляет собой новый тип данных, определенный пользователем. Использование этого типа, с одной стороны, определяется обычными правилами языка (действующими и для стандартных типов языка). С другой стороны, использование классов предполагает использование и новых возможностей. Так, в соответствии с обычными правилами языка, любой объект, используемый в программе, должен быть предварительно определен с помощью предложений описания типа. Для созданного класса (нового типа) это определение выглядит следующим образом: имя_класса имя_объекта; // элементный объект имя_класса имя_объекта [ количество ]; // массив объектов имя_класса * имя_объекта; // указатель на объект Например: Rational a; Rational b[4]; Rational *ptr; При определении объектов они могут быть инициализированы. При этом, если конструкторы в определении класса отсутствуют, правила инициализации объектов определяются стандартными правилами инициализации структуры. Если же в определении класса включены конструкторы, тогда инициализация экземпляров класса осуществляется соответствующим конструктором в соответствии со следующими правилами: а) элементные объекты имя_класса имя_объекта, // пустой конструктор имя_объекта (значение), // одноаргументный инициализирующий // конструктор имя_объекта (знач1, знач2), // 2-х аргументный инициализирующий // конструктор имя_объекта1 (имя_объекта2); // копирующий конструктор Например, для класса Rational это будет выглядеть так: Rational a1, // пустой конструктор a2(2), // одноаргументный инициализирующий конструктор a3(2, 5); // 2-х аргументный инициализирующий конструктор Rational b(a1); // копирующий конструктор; эквивалентная запись – // Rational b = a1; При определении массива объектов для каждого элемента массива будет вызван пустой конструктор. Помимо этого, можно инициализировать значения элементов массива – в соответствии с обычными правилами языка: имя_класса имя_объекта [ количество ] = { знач1, знач2,...}; знач1, знач2,... – константные значения соответствующего класса. Так как в языке не определены константы для создаваемого нового класса, в качестве таких значений используется явный вызов какого-либо конструктора. Это приведет к созданию временного объекта нового типа (класса), который после использования будет уничтожен. Например, для класса Rational это выглядит так: Rational mas[4] = {Rational(), // пустой конструктор Rational(2), // одноаргументный конструктор Rational(3, 8), // 2-х аргументный конструктор Rational(a1) // копирующий конструктор }; Если определен указатель на новый класс, объект может быть построен динамически с помощью оператора new. Использование оператора традиционное: имя_класса *имя_объекта; имя_объекта = new имя_класса; – выделяется память под один экземпляр класса; будет вызван пустой конструктор имя_объекта = new имя_класса(арг,...); – также выделяется память под один экземпляр; для его инициализации будет вызван указанный конструктор имя_объекта = new имя_класса [количество]; – выделяется память под массив экземпляров; для каждого экземпляра массива будет вызван пустой конструктор Примеры для класса Rational: Rational *p1, *p2, *p3; p1 = new Rational; // инициализация пустым конструктором p2 = new Rational(2, 3); // инициализация 2-х аргументным конструктором p3 = new Rational[5]; // массив из 5 элементов; для каждого элемента вызывается // пустой конструктор Реализация класса Реализация класса предполагает разработку и написание на языке всех функций – методов класса. Каждая функция должна быть определена (и только один раз). Определение функции имеет обычный вид: тип_результата имя_функции ( тип пар1, … ) // заголовок функции { тело_функции } Функцию можно определить со спецификатором inline. Такие функции называются встроенными: inline тип_результата имя_функции ( тип пар1, … ) { тело_функции } Спецификатор inline указывает компилятору, что он должен пытаться каждый раз генерировать в месте вызова код, соответствующий указанной функции, а не создавать отдельно (один раз) код функции и затем вызывать его, используя обычный механизм вызова. Отличия в использовании обычных и встроенных функций приведены на рис. 2-7. Рис. 2-7. Использование обычных и встроенных функций Пока мы рассматриваем реализацию функций-членов класса. Как указывалось ранее, функции-члены класса являются неотъемлемой частью класса и не существуют вне класса. Так как различные классы могут иметь функции-члены с одинаковыми именами, при определении функции-члена класса необходимо указать имя этого класса (здесь используется специальный оператор языка – оператор разрешения видимости ::); в результате определение функции-члена класса будет иметь вид: тип_результата имя_класса :: имя_функции ( тип пар1, … ) { тело_функции } Определения функций могут быть размещены вне класса или включены в определение класса; в последнем случае получаем inline-функции. При реализации функций-членов класса необходимо обращаться к членам класса – данным и функциям. Такое обращение осуществляется в соответствии с правилами использования таких функций. В теле функции-члена класса имена членов этого же класса можно использовать без явного указания объекта. В данном случае имя относится к члену того объекта, для которого вызвана функция. Если возникают какие-либо неоднозначности (например, имя параметра функции совпадает с именем какого-либо члена класса), можно использовать полное квалификационное имя членов класса: имя_класса:: имя_члена_класса (здесь может быть указано имя и данного – члена класса, и функции-члена класса). Внутри функций-членов класса определен специальный неявный параметр this – он имеет тип “указатель на данный класс”; его значение определяет адрес конкретного объекта (экземпляра класса), которому посылается соответствующее сообщение (или для которого вызывается соответствующая функция-член класса). Возможен доступ к членам класса по этому указателю: this-> имя_члена Если нет никаких неясностей и неопределенностей, имя_класса и/или this-> могут быть опущены. Пример: реализация класса Rational Рассмотрим реализацию класса Rational, определенного выше. class Rational{ private: int num, den; // состояние класса – числитель и знаменатель дроби int gcd() const; // метод класса – нахождение наибольшего общего делителя void reduce(); // метод класса – сокращение дроби void correct(); // метод класса – коррекция дроби protected: public: /* Конструкторы класса: пустой; инициализирует дробь значением 0 */ Rational(){num = 0; den = 1; } /* Инициализирующий с 1 аргументом; инициализирует дробь целым значением */ Rational(int num){Rational::num = num; den = 1; } /* Инициализирующий с 2 аргументами; инициализирует дробь заданным значением */ Rational(int num, int den) {num = n; den = d; correct(); } /* Деструктор класса */ ~Rational(){} /* Методы класса: селекторы */ void print() const; Rational add(const Rational &opd) const; /* Модификатор */ void assign(int x, int y); }; Инициализирующие конструкторы обычно небольшие по размерам, поэтому их определение обычно включается в определение класса. Определение методов класса также можно включить в определение класса, если оно не велико. Но лучше вынести за пределы класса, используя при необходимости указание inline. Реализация методов класса inline void Rational::correct() { if(!den) den = 1; if(den < 0) num = -num, den = -den; } inline void Rational::assign(int x, int y) { num = x; den = y; correct(); } Видно, что две функции – двух аргументный конструктор и assign – имеют одинаковые коды; но это функционально разные функции: конструктор будет вызываться при объявлении и инициализации данных типа Rational, тогда как assign можно вызывать неоднократно – каждый раз, когда с помощью присваивания нужно изменить значение уже существующего экземпляра класса. Отличие такое же, как и в случае использования базовых типов: int x = 1;... x = 1;... // Нахождение наибольшего общего делителя для числителя и знаменателя дроби. // Известно, что знаменатель дроби всегда > 0 int Rational::gcd() const { int n = abs(num), d = den, r; while(r = n % d) // вычисляется остаток от деления и сравнивается с 0 n = d, d = r; // переопределяются делимое и делитель return r; } // Сокращение дроби void Rational::reduce() { int div = gcd(); num /= div; den /= div; } // Сложение дробей Rational Rational::add(const Rational &opd) const { Rational temp; temp.num = num * opd.den + den * opd.num; temp.den = den * opd.den; temp.reduce(); return temp; } // Вывод значения дроби в выходной поток void Rational::print() const { cout << num; if(den > 1) cout << ’/’<< den; } Использование класса Конкретные объекты (экземпляры класса) создаются при локальном и внешнем объявлении данных в соответствии с обычными правилами языка. Они могут быть объявлены как простые переменные, массивы, указатели (для создания экземпляров класса в свободной памяти), члены-данные других классов, могут создаваться как временные переменные в процессе вычислений. Объявления экземпляров класса имеют специфический синтаксис только при инициализации объявляемых данных. · Простые переменные: Rational a, /* пустой конструктор; конструкция Rational a() определяет d(5), /* одно аргументный инициализирующий конструктор */ b(3,8); /* двух аргументный инициализирующий конструктор */ Возможна и традиционная инициализация экземпляров класса: Rational c = 8, /* В результате будет создана дробь со значением 8/1 */ p = Rational(3,8); /* Так как при классической инициализации требуются значения соответствующего типа, а в языке не определены константы типа Rational, нужно построить такую константу, явно вызвав конструктор класса */ · Массивы: Rational x[3], /* Используется пустой конструктор для создания каждого элемента y[] = {2, 1, Rational(3,8)}; /* Обычный синтаксис при инициализации массива, обязательно используются значения соответствующего типа */ · Использование свободной памяти Rational *ptr1, *ptr2; ptr1 = new Rational(1,3); /* Классическое использование операции new, в которой указывается имя нового типа; при этом возможна сразу и инициализация выделенной области памяти за счет работы соответствующего конструктора */ ptr2 = new Rational[4]; /* Если выделяется память под массив, работает только пустой конструктор; инициализация памяти не выполняется */ Важное правило: если создается тем или иным способом массив экземпляров класса, класс должен иметь пустой конструктор. Пример использования класса main() { Rational a(2), b[3], x, y; const Rational c(5,8); // Вывод значения дроби a a.print(); cout << endl; // Вывод значения элемента массива b b[1].print(); cout << endl; // Сложение значений дробей a и c x = a.add(c); // Вывод результата сложения x.print(); cout << endl; // Сложение дроби x с дробью 3/5 и вывод результата x.add(Rational(3,5)).print(); cout << endl; /* Для свободной памяти */ Rational *ptr; ptr = new Rational(3,8); (*ptr).print(); cout << endl; /* Возможна и запись ptr->print(); */ } Ошибки: a.gcd() a.reduce() и т.п. Еще пример – решение основной задачи (система двух уравнений с двумя неизвестными). Предполагается, что для класса Rational определены все арифметические операции: сложения (add), вычитания (sub), умножения (mul) и деления (div). Решить систему вида: Значения коэффициентов системы приведены в таблице:
Решение имеет вид: определитель системы det = a * e - d * b; x = (c * e - b * f) / det; y = (a * f - d * c) / det; Чтобы умножить a на e, нужно экземпляру a послать сообщение: “умножь себя (свое значение) на e”: a.mul(e);
main() { Rational a(2), b(3), c(-1), d(5), e(2), f(3), x, y; Rational det; det = (a.mul(e)).sub(d.mul(b)); x = (c.mul(e)).sub(b.mul(f)).div(det); y = (a.mul(f)).sub(d.mul(c)).div(det); x.print(); cout << ’,’; y.print(); cout << endl; } Перегрузка Перегрузка функций Перегруженные функции – это функции с одним и тем же именем, но имеющие разные списки параметров. Параметры могут отличаться типами и/или количеством. Тип возвращаемого функцией значения во внимание не принимается. Примеры: void f(int); void f(char);
|
|||||||||||||||||||||||||||||||||||||
Последнее изменение этой страницы: 2017-02-07; просмотров: 169; Нарушение авторского права страницы; Мы поможем в написании вашей работы! infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.137.189.236 (0.011 с.) |