Объектноориентированное программирование
Скачать 1.73 Mb.
|
3.2 Спецификаторы public, private, protected Спецификаторы доступа private и public управляют видимостью эле- ментов класса. Элементы, описанные после служебного слова private , ви- димы только внутри класса. Этот вид доступа принят в классе по умолча- нию. Интерфейс класса описывается после спецификатора public . Дей- ствие любого спецификатора распространяется до следующего специфика- тора или до конца класса. Можно задавать несколько секций private и public , порядок их следования значения не имеет. Поля класса: 26 могут быть простыми переменными любого типа, указателями, мас- сивами и ссылками (т.е. могут иметь практически любой тип, кроме типа этого же класса, но могут быть указателями или ссылками на этот класс); могут быть константами (описаны с модификатором const ), при этом они инициализируются только один раз (с помощью конструктора) и не мо- гут изменяться; могут быть описаны с модификатором static , но не как auto , extern и register Инициализация полей при описании не допускается. 3.3 Глобальные и локальные классы Классы могут быть глобальными (объявленными вне любого блока) и локальными (объявленными внутри блока, например, внутри функции или внутри другого класса). Обычно классы определяются глобально. Локальные классы имеют некоторые особенности: локальный класс не может иметь статических элементов; внутри локального класса можно использовать из охватывающей его области типы, статические ( static ) и внешние ( extern ) переменные, внеш- ние функции и элементы перечислений; запрещается использовать автоматические переменные из охватыва- ющей класс области; методы локальных классов могут быть только встроенными ( inline ); если один класс вложен в другой класс, они не имеют каких-либо осо- бых прав доступа к элементам друг друга и могут обращаться к ним только по общим правилам. 3.4 Примеры классов В качестве примера создадим класс, моделирующий персонаж компью- терной игры. Для этого требуется задать его свойства (например, количе- ство щупалец, силу или наличие гранатомета) и поведение. Естественно, пример будет схематичен, поскольку приводится лишь для демонстрации синтаксиса. class monster { int health, ammo; public: monster(int he = 100, int am = 10) { health = he; ammo = am;} void draw(int x, int y, int scale, int position); int get_health(){return health;} int get_ammo(){return ammo;} }; 27 В этом классе два скрытых поля – health и ammo , получить значения которых извне можно с помощью методов get_health() и get_ammo() . До- ступ к полям с помощью методов в данном случае кажется искусственным усложнением, но надо учитывать, что полями реальных классов могут быть сложные динамические структуры, и получение значений их элементов не так тривиально. Кроме того, очень важной является возможность вносить в эти структуры изменения, не затрагивая интерфейс класса. Методы класса имеют неограниченный непосредственный доступ к его полям. Внутри метода можно объявлять объекты, указатели и ссылки как своего, так и других классов. В приведенном классе содержится три определения методов и одно объ- явление (метод draw ). Если тело метода определено внутри класса, он явля- ется встроенным ( inline ). Как правило, встроенными делают короткие ме- тоды. Если внутри класса записано только объявление (заголовок) метода, сам метод должен быть определен в другом месте программы с помощью операции доступа к области видимости: void monster::draw(int x, int y, int scale, int position) { /* тело метода */} Встроенные методы можно определить и вне класса с помощью дирек- тивы inline (как и для обычных функций, она носит рекомендательный ха- рактер): inline int monster::get_ammo() { return ammo; } Методы можно перегружать (это одно из проявлений полиморфизма), а также объявлять либо константными, либо статическими (но не одновре- менно). В каждом классе есть метод, имя которого совпадает с именем класса. Он называется конструктором и вызывается автоматически при создании объекта класса. Конструктор предназначен для инициализации объекта. Ав- томатический вызов конструктора позволяет избежать ошибок, связанных с использованием неинициализированных переменных. Подробнее конструк- торы описываются далее в разделе «Конструкторы». Типы данных struct и union являются специальными видами класса. Конкретные переменные типа данных «класс» называются экземпля- рами класса, или объектами. Время жизни и видимость объектов зависит от вида и места описания и подчиняется общим правилам С++: monster Vasia; // Объект класса monster с параметрами по умолчанию 28 monster Super(200, 300);// Объект с явной инициализацией monster stado[100]; // Массив объектов с параметрами по умолчанию /* Динамический объект (второй параметр задается по умолчанию) */ monster *beavis = new monster (10); monster &butthead = Vasia;// Ссылка на объект При создании каждого объекта выделяется память, достаточная для хра- нения всех его полей, и автоматически вызывается конструктор, выполняю- щий их инициализацию. Методы класса не тиражируются. При выходе объ- екта из области действия он уничтожается, при этом автоматически вызы- вается деструктор (деструкторы описаны далее). Доступ к открытым ( public ) элементам объекта аналогичен доступу к полям структуры. Для этого используются операция . (точка) при обраще- нии к элементу через имя объекта и операция -> при обращении через ука- затель: объект.поле указатель -> поле (*указатель).поле объект.метод( параметры ) указатель -> метод( параметры ) (*указатель).метод( параметры ) Обращение к открытому полю и вызов метода для массива объектов: имя_массива[ индекс ].поле имя_массива[ индекс ].метод( параметры ) Например: int n = Vasia.get_ammo(); stado[5].draw; cout << beavis->get_health(); Получить или изменить значения private элементов можно только че- рез обращение к соответствующим методам. Можно создать константный объект, значения полей которого изменять запрещается. К нему должны применяться только константные методы: class monster { int get_health() const { return health; } }; const monster Dead (0,0); // Константный объект cout << Dead.get_health(); Константный метод: объявляется с ключевым словом const после списка параметров; не может изменять значения полей класса; может вызывать только константные методы; 29 может вызываться для любых (не только константных) объектов. Рекомендуется описывать как константные те методы, которые предна- значены для получения значений полей. 3.5 Inline функции Встроенная функция – это функция, код которой прямо вставляется в том месте, где она вызвана. Как и макросы, определенные через #define , встроенные функции улучшают производительность за счет стоимости вы- зова и (особенно!) за счет возможности дополнительной оптимизации («процедурная интеграция»). В обычном С вы можете получить «инкапсулированные структуры», по- мещая в них указатель на void , и заставляя его указывать на настоящие дан- ные, тип которых неизвестен пользователям структуры. Таким образом, пользователи не знают, как интерпретировать эти данные, а функции до- ступа преобразуют указатель на void к нужному скрытому типу. Так дости- гается некоторый уровень инкапсуляции. К сожалению, этот метод идет вразрез с безопасностью типов, а также требует вызова функции для доступа к любым полям структуры (если вы позволили бы прямой доступ, то его мог бы получить кто угодно, поскольку будет известно, как интерпретировать данные, на которые указывает void* Такое поведение со стороны пользователя приведет к сложностям при по- следующем изменении структуры подлежащих данных). Стоимость вызова функции невелика, но дает некоторую прибавку. Классы С++ позволяют встраивание функций, что дает вам безопасность ин- капсуляции вместе со скоростью прямого доступа. Более того, типы пара- метры встраиваемых функций проверяются компилятором, что является преимуществом по сравнению с #define макросами. В отличие от #define макросов, встроенные ( inline ) функции не под- вержены известным ошибкам двойного вычисления, поскольку каждый ар- гумент встроенной функции вычисляется только один раз. Другими сло- вами, вызов встроенной функции – это то же самое что и вызов обычной функции, только быстрее. Также, в отличие от макросов, типы аргументов встроенных функций проверяются, и выполняются все необходимые преобразования. 3.6 Указатель this Каждый объект содержит свой экземпляр полей класса. Методы места в классе не занимают и не дублируются для каждого объекта. Единственный 30 экземпляр метода используется всеми объектами совместно, поэтому каж- дый нестатический метод класса должен «знать», для какого объекта он вы- зван. Для этого, при вызове каждого нестатического метода класса, ему не- явно передается указатель на объект, вызвавший его T * const this Выражение *this представляет собой разыменование указателя и имеет тип определяемого класса. Обычно это выражение возвращается в качестве результата, если метод возвращает ссылку на свой класс ( return *this; ). Для иллюстрации использования указателя this добавим в приведен- ный выше класс 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); 31 4. КОНСТРУКТОРЫ КЛАССОВ 4.1 Конструкторы и их свойства Конструктор предназначен для инициализации объекта и вызывается ав- томатически при его создании. Ниже перечислены основные свойства кон- структоров. 1. Конструктор не возвращает значения, даже типа void . Нельзя полу- чить указатель на конструктор. 2. Класс может иметь несколько конструкторов с разными параметрами для разных видов инициализации (при этом используется механизм пере- грузки). 3. Конструктор, который можно вызвать без параметров, называется конструктором по умолчанию. 4. Параметры конструктора могут иметь любой тип, кроме этого же класса. Можно задавать значения параметров по умолчанию. Их может со- держать только один из конструкторов. 5. Если программист не указал ни одного конструктора, компилятор со- здает его автоматически (кроме случая, когда класс содержит константы и ссылки, поскольку их необходимо инициализировать). Такой конструктор вызывает конструкторы по умолчанию для полей класса и конструкторы ба- зовых классов. 6. Конструкторы не наследуются. 7. Конструктор не может быть константным, статическим и виртуаль- ным (нельзя использовать модификаторы const , virtual и static ). 8. Конструкторы глобальных объектов вызываются до вызова функции main . Локальные объекты создаются, как только становится активной об- ласть их действия. Конструктор запускается и при создании временного объекта (например, при передаче объекта из функции). При объявлении объектов вызывается один из конструкторов. При от- сутствии инициализирующего выражения в объявлении объекта вызывается конструктор по умолчанию, при инициализации другим объектом того же типа – конструктор копирования (см. далее), при инициализации полей – один из явно определенных конструкторов инициализации (т.е. конструкто- ров, которым передаются параметры для инициализации полей объекта). 4.2 Конструкторы по умолчанию Конструкторы часто вызываются неявно для создания временных объек- тов. Обычно это происходит в следующих случаях: 32 при инициализации; при выполнении операции присваивания; для задания значений параметров по умолчанию; при создании и инициализации массива; при создании динамических объектов; при передаче параметров в функцию и возврате результатов по значе- нию. Примеры конструкторов: monster Super(200, 300), Vasia(50); monster X = monster(1000); В последнем операторе создается объект Х , которому присваивается безымянный объект со значением параметра health = 1000 (значения остальных параметров устанавливаются по умолчанию). При создании динамического массива вызывается конструктор без аргу- ментов. В качестве примера класса с несколькими конструкторами усовершен- ствуем описанный ранее класс monster , добавив в него поля, задающие цвет ( skin ) и имя ( name ): enum color {red, green, blue}; // Возможные значения цвета class monster { int health, ammo; color skin; char *name; public: monster(int he = 100, int am = 10); monster(color sk); monster(char * nam); }; //-------------------------------- monster::monster(int he, int am) { health = he; ammo = am; skin = red; name = 0;} //-------------------------------- monster::monster(color sk) { switch (sk) { case red: health = 100; ammo = 10; skin = red; name = 0; break; case green: health = 100;ammo = 20;skin = green;name = 0;break; case blue: health = 100; ammo = 40; skin = blue;name = 0;break; } } //-------------------------------- monster::monster(char * nam) { /* К длине строки добавляется 1 для хранения нуль-символа */ name = new char [strlen(nam) + 1]; 33 strcpy(name, nam); health = 100; ammo = 10; skin = red; } //-------------------------------- monster * m = new monster ("Ork"); monster Green (green); Первый из приведенных выше конструкторов является конструктором по умолчанию, поскольку его можно вызвать без параметров. Объекты класса monster теперь можно инициализировать различными способами, требуемый конструктор будет вызван в соответствии со списком инициали- зации. При задании нескольких конструкторов следует соблюдать те же пра- вила, что и при написании перегруженных функций – у компилятора должна быть возможность распознать нужный вариант. Существует еще один способ инициализации полей в конструкторе (кроме уже описанного присваивания полям значений параметров) – с по- мощью списка инициализаторов, расположенным после двоеточия между заголовком и телом конструктора: monster::monster(int he, int am): health (he), ammo (am), skin (red), name (0){} Поля перечисляются через запятую. Для каждого поля в скобках указы- вается инициализирующее значение, которое может быть выражением. Без этого способа не обойтись при инициализации полей-констант, полей-ссы- лок и полей-объектов. В последнем случае будет вызван конструктор, соот- ветствующий указанным в скобках параметрам. 4.3 Конструктор копирования Конструктор копирования – это специальный вид конструктора, получа- ющий в качестве единственного параметра указатель на объект этого же класса: T::T(const T&) { ... /* Тело конструктора */ } Здесь T – имя класса. Этот конструктор вызывается в тех случаях, когда новый объект создается путем копирования существующего: при описании нового объекта с инициализацией другим объектом; при передаче объекта в функцию по значению; при возврате объекта из функции. Если программист не указал ни одного конструктора копирования, ком- пилятор создает его автоматически. Такой конструктор выполняет поэле- ментное копирование полей. Если класс содержит указатели или ссылки, 34 это, скорее всего, будет неправильным, поскольку и копия, и оригинал бу- дут указывать на одну и ту же область памяти. Запишем конструктор копирования для класса monster . Поскольку в нем есть поле name , содержащее указатель на строку символов, конструктор ко- пирования должен выделять память под новую строку и копировать в нее исходную: monster::monster(const monster &M) { if (M.name) { name = new char [strlen(M.name) + 1]; strcpy(name, M.name); } else name = 0; health = M.health; ammo = M.ammo; skin = M.skin; } monster Vasia (blue); monster Super = Vasia; // Работает конструктор копирования monster *m = new monster ("Ork"); monster Green = *m; // Работает конструктор копирования 4.4 Статические элементы класса С помощью модификатора static можно описать статические поля и методы класса. 4.4.1 Статические поля Статические поля применяются для хранения данных, общих для всех объектов класса, например, количества объектов или ссылки на разделяе- мый всеми объектами ресурс. Эти поля существуют для всех объектов класса в единственном экземпляре, то есть не дублируются. Память под статическое поле выделяется один раз при его инициализа- ции независимо от числа созданных объектов (и даже при их отсутствии) и инициализируется с помощью операции доступа к области действия, а не операции выбора: class A { public: static int count; } A::count = 0; Статические поля доступны как через имя класса, так и через имя объ- екта: 35 /* будет выведено одно и то же */ A *a, b; * cout << A::count << a->count << b.count; На статические поля распространяется действие спецификаторов до- ступа, поэтому статические поля, описанные как private , нельзя инициали- зировать с помощью операции доступа к области действия, как описано выше. Им можно присвоить значения только с помощью статических мето- дов, как описано ниже. Память, занимаемая статическим полем, не учитывается при определе- нии размера объекта операцией sizeof . Статические поля нельзя инициа- лизировать в конструкторе, так как они создаются до создания любого объ- екта. Классическое применение статических полей – подсчет объектов. Для этого в классе объявляется целочисленное поле, которое увеличивается в конструкторе и уменьшается в деструкторе. |