Объектно-ориентированное программирование (ООП) ООП развивает идеи структурного и модульного программирования и позволяет успешно создавать крупные программные системы. Принципы ООП проще всего понять на примере программ моделирования. В реальном мире каждый предмет или процесс обладает свойствами и поведением. Поведение объекта зависит от его состояния и внешних воздействий. Например, объект «автомобиль» не поедет, если в баке нет бензина, а если повернуть руль, то изменится положение колес. Понятие объекта в программе совпадает с обыденным смыслом этого слова: объект представляется как совокупность данных, характеризующих его состояние, и функций их обработки, моделирующих его поведение. Вызов функции на выполнение часто называют посылкой сообщения объекту. При создании объектно-ориентированной программы предметная область представляется в виде совокупности объектов. Выполнение программы состоит в том, что объекты обмениваются сообщениями. Это позволяет использовать при программировании понятия, более адекватно отражающие предметную область. С помощью ООП легко реализуется событийно-управляемая модель, когда данные активны и управляют вызовом того или иного программного кода. При представлении реального объекта с помощью программного необходимо выделить существенные особенности реального объекта. Их список зависит от цели моделирования. Выделение существенных с конкретной точки зрения свойств называется абстрагированием. Таким образом, программный объект – это абстракция. Важным свойством объекта является его обособленность. Детали реализации объекта, то есть внутренние структуры данных и алгоритмы их обработки, логически объединены, скрыты от пользователя объекта и недоступны для непреднамеренных изменений, то есть инкапсулированы. Инкапсуляцией называется объединение данных и функций их обработки со скрытием деталей реализации. Объект используется через его интерфейс — совокупность правил доступа. 1 Таким образом объект является «черным ящиком», замкнутым по отношения к внешнему миру. Это позволяет представить программу в укрупненном виде — на уровне объектов и их взаимосвязей, а следовательно, управлять большим объемом информации и успешно отлаживать сложные программы. Можно более строго и кратко сформулировать : объект – это инкапсулированная абстракция с четко определенным интерфейсом. Инкапсуляция позволяет изменить реализацию объекта без модификации остальной программы, если его интерфейс остается прежним. Кроме того, инкапсуляция позволяет использовать объект в другом окружении, зная, что он не испортит чужие области памяти, а также создавать библиотеки объектов для применения во многих программах. Для многократного использования кода в ООП используется механизм наследования. Наследование — это возможность создания иерархии объектов, когда потомки наследуют все свойства своих предков, могут их изменять и добавлять новые. Свойства при этом повторно не описываются, что сокращает объем программы. Иерархия представляется в виде дерева, в котором более общие объекты располагаются ближе к корню, а более специализированные — на ветвях и листьях. Выделение общих черт различных классов в один класс –предок является мощным механизмом абстракции. Кроме того с помощью механизма наследования можно вносить изменения в объекты, исходный код которых недоступен. ООП позволяет писать гибкие, расширяемые и ситабельные программы. Во-многом это обеспечивается благодаря полиморфизму, под которым понимается возможность во время выполнения программы с помощью одного и того же имени выполнять разные действия или обращаться к объектам разного типа. Пример использования полиморфизма – перегрузка функций, когда из нескольких одноименных вариантов выбирается наиболее подходящая функция по соответствию ее прототипа передаваемым аргументам. Другой пример – использование шаблонов функций, когда один и тот же код видоизменяется в соответствии с параметром-типом. Основные принципы ООП были разработаны еще в языках Simula-67 и Smalltalk, но не получили в то время широкого применения из-за трудностей освоения и громоздкой 2 реализации. В С++ эти концепции реализованы достаточно эффективно и это обусловило его широкое распространение и внедрение подобных средств в другие языки программирования. Достоинства ООП; использование при программировании понятий близких к предметной области; возможность успешно управлять большими объемами исходного кода благодаря инкапсуляции; возможность многократного использования кода за счет наследования; сравнительно простая возможность модификации программ; возможность создания и использования библиотек классов. Классы Для представления объектов в языках С++, С#, Java и подобных используется понятие класс. Класс является обобщенным понятием, определяющим характеристики и поведение некоторого множества конкретных объектов этого класса, часто называемых экземплярами класса. Класс содержит данные, задающие свойства объектов класса, и функции, определяющих их поведение. Описание класса Класс является типом данных, определяемым пользователем. Данные класса называются полями, а функции методами. Поля и методы называются элементами класса. В С++ есть классы трех видов: собственно классы, структуры и объединения. Описание класса имеет вид: вид_класса имя_класса ( элементы_класса ); вид_класса — одно из ключевых слов (class, struct, union); имя_класса — идентификатор; элементы_класса — поля и методы класса. Класс может не содержать полей и/или методов. Пустые классы часто используются при обработке исключений. 3 Видимостью элементов класса можно управлять с помощью спецификаторов доступа, которые обозначаются ключевыми словами: public — объявляет элемент видимым вне класса; private — закрывает доступ к элементу извне; protected — используется при наследовании. После ключевого слова ставится двоеточие, за которым следует описание одного или более элементов класса. Элементы класса (class) по умолчанию видимы только внутри класса, то есть закрыты (private), а элементы структуры (struct) открыты (public). Поля класса: могут иметь любой тип, кроме типа их класса, но могут быть указателями или ссылками на этот класс; могут быть описаны с модификатором const, при этом они инициализируются один раз и не могут изменяться; могут быть описаны с модификатором static, но не как extern и register. Классы могут быть глобальными (объявленными вне любого блока), вложенными (объявленными внутри другого класса) и локальными (объявленными внутри функции). Чаще всего используют глобальные классы. Пример. Рассмотрим класс, моделирующий персонаж компьютерной игры. Для этого требуется задать его свойства (например, количество щупалец. Силу или наличие гранатомета) и поведение. class monster { int health, ammo; public: monster (int he = 100, int am = 10){health = he; ammo = am;} void draw(); int get_healh() { return health;} int get_ammo () {return ammo;} }; В этом классе два скрытых поля ─ health и ammo. Получить их значение извне можно с помощью методов get_healh и get_ammo. Все методы класса имеют непосредственный доступ к его скрытым полям, то есть тела функций класса входят в область видимости private элементов класса. В классе monster три определения методов и одно объявление (метод draw). Если внутри класса записано только объявление (заголовок) метода, сам метод должен быть 4 определен в другом месте программы с помощью операции доступа к области видимости (::): void monster :: draw(){ /* тело метода */ } В каждом классе явно или неявно есть метод, имя которого совпадает с именем класса. Он называется конструктором и вызывается автоматически при создании объекта класса. Описание объектов Конкретные переменные типа класса называются экземплярами класса, или объектами. В С++ время жизни и видимость объектов зависят от вида и места их описания и подчиняются общим правилам: Monster Vasia; // Monster Super (200, 300); // Monster stado [100]; // Monster *beavis = new monster (10); // // Monster &butthead = Vasia // При создании каждого объекта выделяется память, достаточная для хранения всех его полей, кроме статических, и автоматически вызывается конструктор, выполняющий их инициализацию. При выходе объекта из области действия он уничтожается, при этом автоматически вызывается деструктор. Методы места в классе не занимают и не дублируются для каждого объекта: единственный экземпляр метода используется всеми объектами совместно. Доступ к элементам объекта аналогичен доступу к полям структуры. Используются операция . (точка) при обращении к объекту через имя объекта и операция -> при обращении через указатель. Например: int n = Vasia.get_anmo(); stado[5].draw(); cout << beavis -> get_health() Обратиться таким образом можно только к элементам со спецификатором public. Получить или изменить значение элементов со спецификатором private можно только через обращение к соответствующим методам Можно создать константный объект, значения полей которого изменять запрещается. К нему должны применяться константные методы. 5 class monster { ... int get_healh() const{ return health;} }; const monster Dead (0, 0); // Константный объект cout << Dead.get_health(); Константный метод: объявляется с ключевым словом const после списка параметров; не может изменять значения полей класса(кроме mutable); может вызывать только константные методы; может вызываться для любых (не только константных) объектов. Рекомендуется описывать как константные те методы, которые предназначены для получения значения полей. Иногда требуется иметь возможность изменить значение поля константного объекта. Например, это может быть какое-либо вспомогательное скрытое поле. На этот случай в С++ есть квалификатор хранения mutable, который указывает, что должна быть возможность изменить поле, даже если оно является элементом константного объекта. Указатель this Поля каждого объекта занимают свое отдельное место в памяти. Единственный экземпляр метода используется всеми объектами совместно, поэтому нестатический метод, помимо явно объявленных параметров, получает еще один скрытый параметр, обозначенный ключевым словом this и являющийся константным указателем на объект, для которого вызван метод. Указатель this можно использовать в методе и явным образом; для обращения к полю и для возврата из метода ссылки на свой объект. Выражение *this представляет собой разыменование указателя и имеет тип определяемого класса. Доступ к полю с помощью this используется, если имя параметра метода совпадает с именем поля класса. Можно это сделать и по другому ─ с помощью операции доступа к области видимости: void cure (int health, int ammo){ this ->health += health; // monster :: ammo +=ammo; // } 6 Пример возврата из метода ссылки на свой объект. Добавим в класс monster метод, возвращающий ссылку на наиболее здорового (поле health) из двух монстров, один из которых вызывает метод, а другой передается ему в качестве параметра. Метод нужно поместить в секцию public описания класса: monster & the_best (monster &M){ if (health > M.get_health() ) return *this; return M; } … monster Vasia (50), Super (200); // Новый объект Best инициализируется значениями полей Super: monster Best = Vasia.the_best(Super); Конструкторы Конструктор предназначен для инициализации объекта и вызывается автоматически при его создании. Основные свойства конструктора: Конструктор не возвращает значение, даже типа void. Нельзя получить указатель на конструктор. Класс может иметь несколько конструкторов с разными параметрами для разных видов инициализации (при этом используется механизм перезагрузки). Конструктор, который можно вызвать без параметров, называется конструктором по умолчанию. Параметры конструктора могут иметь любой тип, кроме типа того же класса. Можно задавать значения параметров по умолчанию. Если программист не указал ни одного конструктора, компилятор создает его автоматически. Такой конструктор вызывает конструкторы по умолчанию для полей класса и конструкторы по умолчанию базовых классов. Конструкторы не наследуются. Конструкторы нельзя описывать с модификаторами const, virtual, ststic. Конструкторы глобальных объектов вызываются до вызова функции main. Локальные объекты создаются, как только становится активной область их действия. Конструктор запускается и при создании временного объекта (например, при передаче объекта из функции). 7 Имеются несколько способов инициализации полей: присваивание значения при описании поля; присваивание полю значения параметра в теле конструктора; с помощью списка инициализаторов. Список инициализаторов располагается после двоеточия между заголовком и телом конструктора: monster:: monster (int the, int am) : health (he), ammo (am) {} Поля перечисляются через запятую. Для каждого поля в скобках указывается инициализирующее выражение. Если поле является объектом, будет вызван конструктор, соответствующий указанным в скобках аргументам. Инициализация выполняется более эффективно, чем присваивание в теле конструктора. Поля инициализируются в порядке их объявления, а не в порядке их появления в списке инициализации. Поэтому для уменьшения числа возможных ошибок порядок указания полей в списке инициализации конструктора должен соответствовать порядку их объявления в классе. Конструкторы копирования и перемещения Конструктор копирования – это специальный вид конструктора, получающий в качестве единственного параметра ссылку на объект того же класса: T::T (const T&) {… /* Тело конструктора */} Здесь T ─ имя класса. Этот конструктор вызывается, когда новый объект создается путем копирования существующего: при описании нового объекта с инициализацией другим объектом; при передаче объекта в функцию по значению; при возврате объекта из функции; при обработке исключений. Если программист не описал конструктор копирования, компилятор создает его автоматически. Такой конструктор выполняет поэлементное копирование полей. Если класс содержит указатели или ссылки, такое копирование, скорее всего, окажется неправильным, поскольку копия и оригинал будут указывать на одну и ту же область памяти. Конструктор перемещения используется для классов, содержащих, например, указатели на большой объем данных в динамической памяти, с целью избежать затрат на копирование данных. 8 Конструкторы копирования и перемещения должны использовать списки инициализации, поскольку иначе для базовых классов и вложенных объектов будут вызваны конструкторы по умолчанию: class X { public: X(void); X(const X & r ); // Конструктор копирования }; class Y: public X { // Класс Y, наследник класса X string s; // Вложенный объект public : Y(const Y & r): X (r) , s(r, s) {}// Конструктор копирования }; 9