Статические элементы класса

ВВЕДЕНИЕ

 

В С/С++ вводится понятие класса – тип данных, объединяющий структруры данных и обрабатывающие их функции.

Конкретные величины типа данных “класс” называются экземплярами класса или объектами.

Основными принципами объектно-ориентированного программирования (ООП) являются инкапсуляция, наследование и полиморфизм.

Инкапсуляция – это объединение в единое целое данных и процедур (алгоритмов) обработки данных. В рамках ООП данные называются полями объекта, а алгоритмы их обработки (процедуры) – методами объекта. Инкапсуляция позволяет в максимальной степени изолировать объект от “внешнего” окружения, т.е. остальной программы, так как локализованные в объектах алгоритмы обмениваются с программой сравнительно небольшими объемами данных. В результате замена или модификация алгоритмов и данных, инкапсулированных в объект, как правило, не ведет за собой плохо прослеживаемых последствий для программы в целом. Другим немаловажным следствием является возможность переноса объектов из одной программы в другую.

Наследование есть свойство объектов порождать своих потомков. Объект-потомок автоматически наследует от своего родителя все поля и методы, может дополнять объекты новыми полями и перегружать (перекрывать) методы родителя или дополнять их. Принцип наследования решает проблему модификации свойств и придает ООП исключительную гибкость. При работе с объектами программист обычно подбирает объект, наиболее подходящий по своим свойствам для решения данной задачи, и создает одного или нескольких потомков от него, которые “умеют” делать то, что не реализовано в их родителе.

Полиморфизм – это свойство родственных объектов, т.е. объектов, имеющих одного общего предка-родителя, решать схожие по смыслу проблемы разными способами. В рамках ООП возможности (свойства) объекта определяются набором входящих в него методов. Изменяя алгоритм того или иного метода в потомках объекта-родителя, программист может придавать этим потомкам отсутствующие у родителя специфические свойства. Для изменения метода необходимо перекрыть (перегрузить) его в потомке, т.е. объявить в потомке одноименный метод и реализовать в нем нужные действия. В результате в объекте-родителе и объекте-потомке будут действовать два одноименных метода, имеющих разные алгоритмы и, следовательно, придающих объектам разные свойства.


 

Классы

Описание класса

 

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

Данные класса называются полями, а функции класса – методами (в литературе по языку С/С++ используются чаще термины “данные-члены” и “функции-члены”, а также “компонентные данные” и “компонентные функции”). Поля и методы называются элементами класса. Описание класса имеет следующий вид:

Class имя {

[private:]

<описание скрытых элементов>

public:

<описание доступных элементов>

};

Спецификации доступа private и public управляют видимостью элементов класса. Элементы, описанные после слова private, видны только внутри класса. Этот вид доступа принят в классе по умолчанию. Интерфейс класса описывается после спецификатора public. Действие любого спецификатора распространяется до следующего спецификатора или до конца класса. Можно задавать несколько секций private и public, порядок их следования значения не имеет.

Поля класса:

- могут иметь любой тип, кроме типа этого же класса (но могут быть указателями или ссылками на этот класс);

- могут быть описаны с модификатором const, при этом они инициализируются только один раз (с помощью конструктора) и не могут изменяться;

 

- могут быть описаны с модификатором static, но не как auto, extern и register.

Инициализация полей при описании не допускается.

Классы могут быть глобальными (объявленными вне любого блока) и локальными (объявленными внутри блока).

Пример. Класс, моделирующий персонаж компьютерной игры:

class monstr {

int health, ammo;

public:

monstr (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;}

};

В этом классе два скрытых поля – health и ammo, получить значения которых можно с помощью методов int get_health ( ) и get_ammo ( ). Кроме этого, в классе содержится три определения методов и одно объявление (метод draw). Если тело метода определено внутри класса, он является встроенным (inline). Как правило, встроенными делаются короткие методы. Если внутри класса записано только объявление (заголовок) метода, сам метод должен быть определен в другом месте программы с помощью операции доступа к области видимости (: :):

void monstr: : draw (int x, int y, int scale, int position) {

/* тело метода*/

}

Метод можно определить как встроенный и вне класса с помощью директивы inline:

inline int monstr: : get_ammo( ) {

return ammo;

}

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

 

Задание

Спроектируйте класс – “калькулятор”, предназначенный для выполнения арифметических операций с двумя операндами.

 

Описание объектов

Конкретные переменные типа “класс” называются экземплярами класса или объектами.

Пример

monstr Vasia; // объект с параметрами по умолчанию

monstr Super(200,300); // объект с явной инициализацией

monstr *beavis = new monstr(10); // динамический объект

// второй параметр задается по умолчанию

monstr & butthead = Vasia; // ссылка на объект

Доступ к элементам объекта аналогичен доступу к полям структуры. Для этого используется операция . (точка) при обращении к элементу через имя объекта и операция -> при обращении через указатель:

int n = Vasia.get_ammo( );

stado [5].draw;

cout << beavis ->get_ammo( );

Обратиться таким образом можно только к элементам со спецификатором public. Получить или изменить значения элементов со спецификатором private можно только через обращение к соответствующим методам.

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

class monstr {

int get_health ( ) const {return health;}

};

const monstr Dead (0,0); //

cout << Dead.get_health ( );

 

Указатель this

 

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

Указатель this неявно используется внутри метода для ссылок на элементы объекта. В явном виде этот указатель применяется в основном для возвращения из метода указателя (return this;) или ссылки (return *this;) на вызвавший объект.

Добавим в класс monstr новый метод, возвращающий ссылку на наиболее сильного (поле health) из двух монстров, один из которых вызывает метод, а другой передается ему в качестве параметра (метод следует поместить в секцию public описания класса):

Monstr & the_best (monstr &M) {

If (health >M.get_health ( )) return *this;

Return M;

}

monstr Vasia (50), Super (200);

monstr Best = Vasia.the_best (Super); // новый объект

// Best инициализируется значениями полей Super

Указатель this можно применять также для идентификации поля класса в том случае, когда его имя совпадает с именем формального параметра. Другой способ – использовать операцию доступа к области видимости (::):

void cure (int health, int ammo) {

this -> health +=health; // используется this

monstr :: ammo +=ammo; // использование ::

}

 

Конструкторы

 

Конструктор предназначен для инициализации объекта и вызывается автоматически при его создании. Его свойства:

- конструктор не возвращает значение даже типа void. Нельзя получить указатель на конструктор;

- класс может иметь несколько конструкторов с разными параметрами для разных видов инициализации (при этом используется механизм перегрузки);

- конструктор, вызываемый без параметров, называется конструктором по умолчанию;

- параметры конструктора могут иметь любой тип, кроме типа этого же класса. Можно задавать значения параметров по умолчанию. Их содержит только один из конструкторов;

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

- конструкторы не наследуются;

- конструкторы нельзя описывать с модификаторами const, virtual и static;

конструкторы глобальных объектов создаются, как только становится активной их область действия;

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

имя_класса имя_объекта [(список параметров)];

// список параметров не должен быть пустым

имя_класса (список параметров);

// создается объект без имени

имя_класса имя_объекта = выражение;

// создается объект без имени и копируется

 

Примеры

monstr Super (200, 300), Vasia (50), Z;

monstr X = monstr (1000);

monstr Y = 500;

В первом операторе создаются три объекта.

Во втором операторе создается безымянный объект со значением параметра health = 1000; выделяется память под объект X, в которую копируется безымянный объект.

В третьем операторе создается безымянный объект со значением параметра health= 500; выделяется память под объект Y, в которую копируется безымянный объект. Такая форма создания объекта возможна в том случае, если для инициализации объекта допускается задать один параметр.

 

Пример. Усовершенствованный класс monstr с несколькими конструкторами:

Enum color {red, green, blue}; //

class monstr {

int health, ammo;

color skin; //

char *name; //

public

monstr (int he =100, int am = 10);

monstr (color, sk);

monstr (char *nam);

int get_health ( ) {return health;}

int get_ammo ( ) {return ammo;}

};

//---------------------------------------------

monstr : : monstr (int he, int am) {

health = he; ammo = am; skin = red; name = 0;

}

//----------------------------------------------

monstr : : monstr (color sk) {

switch (sk) {

case red : health = 100; ammo = 10; skin = red; name = 0;

break;

case green : health = 100; ammo = 20; skin = red; name = 0;

break;

case blue : health = 100; ammo = 40; skin = red; name = 0;

break;

}

}

//------------------------------------------------

monstr : : monstr (char *nam) {

name = new char [strlen(nam)+1];

strcpy (name, nam);

health = 100; ammo = 10; skin = red;

}

//-------------------------------------------------

monstr *m = new monstr (“Ork”);

monstr Green (green);

 

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

 

!!! Перегружать можно не только конструкторы, но и другие методы класса.

 

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

Monstr : : monstr (int he, int am) :

health (he), ammo (am), skin (sk), name (0) {};

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

 

Задание

Добавьте в разработанный вами класс-калькулятор конструктор, инициализирующий объект.

 

Конструктор копирования

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

T : : T(const &T) {/*тело конструктора*/}

Здесь T – имя класса.

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

- при описании нового объекта с инициализацией другим объектом;

- передаче объекта в функцию по значению;

- возврате объекта из функции.

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

Конструктор копирования для класса monstr:

Monstr : : (const monstr &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;

}

monstr Vasia (blue);

monstr Super = Vasia; // работает конструктор копирования

monstr *m=new monstr (“Ork”);

monstr Green = *m; // работает конструктор копирования

 

Статические элементы класса

С помощью модификатора static можно описать статические поля и методы класса. Их можно рассматривать как глобальные переменные или функции, доступные только в пределах области класса.

 

Статические поля

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

Имеются следующие особенности у статических полей.

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

class A {

public:

static int count; // объявление класса

};

int A : : count; // определение в глобальной области

// по умолчанию инициализируется нулем

// int A : : count =10; пример инициализации значением 10.

2. Статические поля доступны как через имя класса, так и через имя объекта:

A *a, b;

cout << A: :count << a->count <<b.count;

// будет выведено одно и то же

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

4. Память, занимаемая статическим полем, не учитывается при определении размера объекта с помощью операции sizeof.

Статические методы

Статические методы предназначены для обращения к статическим полям класса. Они могут обращаться непосредственно только к статическим полям и вызывать только другие статические методы класса, потому что им не передается указатель this. Обращение к статическим методам производится так же, как и к статическим полям:

class A {

static int count;

public:

static void inc_count ( ) {count ++;}

};

A: :int count;

void f ( ) {

A a;

a.inc_count ( ); // A: :inc_count ( );

Статические методы не могут быть константными и виртуальными.