Краткие теоретические сведения

Понятие класса.

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

Классы С++ предполагают расширение системных типов. Каждый класс представляет уникальное множество объектов и операции над ними (методов). Класс предоставляет средства для создания, манипулирования и уничтожения таких объектов.

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

С точки зрения синтаксиса, класс в С++ - это структурированный тип, образованный на основе уже существующих типов.

Класс представляется в следующей синтаксической форме:

 

ключ_класса имя: <базовый список>

{

список членов;

};

 

Ключ_класса – это одно из ключевых слов class, struct или union (т.е. собственно класс, а также структуры и объединения).

Имя – есть уникальный идентификатор данного блока. Базовый список определяет классы, из которых данный класс наследует объекты и методы. Здесь также могут определяться идентификаторы доступа к наследуемым элементам. Список членов класса есть последовательность элементов любых типов, включая перечисление, битовые поля и другие классы, а также определения функций.

В С++ структура, класс и объединение рассматриваются как типы классов. Структура и класс походят друг на друга, за исключением доступа по умолчанию: в структуре элементы имеют по умолчанию доступ public, в то время как в классе – private (рассмотрим ниже). Аналогично структуре, объединение по умолчанию представляет доступ public; аналогично объединениям в С, его элементы данных размещаются начиная с одного и того же места в памяти.

В следующей таблице перечислены эти отличия:

 

Таблица 1 Различие между классом структурой и объединением

 

  Классы Структуры Объединения
Ключевое слово: class struct union
Доступ по умолчанию private public public
Перекрытие данных Нет Нет Да

 

 

Примеры.

struct date // дата

{int month,day,year; // поля: месяц, день, год

void set(int,int,int); // метод – установить дату

void get(int*,int*,int*); // метод – получить дату

void next(); // метод – установить следующую дату

void print(); // метод – вывести дату

};

class complex // комплексное число

{double re,im;

double real(){return(re);}

double imag(){return(im);}

void set(double x,double y){re = x; im = y;}

void print(){cout<<“re = “<<re; cout<<“im = “<<im;}

};

Для описания объекта класса (экземпляра класса) используется конструкция

имя_класса имя_объекта;

date today,my_birthday;

date *point = &today; // Указатель на объект типа date

date clim[30]; // Массив объектов

date &name = my_birthday; // Ссылка на объект

complex x1 ,x2, D; // Три объекта типа complex

complex *paint = &D; // Указатель на объект типа complex

complex dim[8]; // Массив объектов типа complex

complex &Name = x2; // Ссылка на объект типа complex

 

В определяемые объекты входят данные, соответствующие членам - данным класса. Функции - члены класса позволяют обрабатывать данные конкретных объектов класса. Обращаться к данным объекта и вызывать функции для объекта можно двумя способами:

1. С использованием квалифицированных имен:

 

имя_объекта.имя_класса::имя_компонента;

или

имя_объекта. имя_элемента;

При этом возможности те же, что и при работе с элементами структур:

 

x1.re = dim[3].re =1.24; // Явное присвоение значения

элементам объекта

x1.im = 2.3;

dim[3].im = 0.0; // То же для массива

Уточненное имя компонентной функции:

 

имя_объекта.обращение к функции

 

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

 

x2.set(5.1,7);//Комплексное число 5.1+i*7

x1.print();// вывод комплексного числа на экран

 

2. С использованием указателя на объект класса:

 

указатель_на_объект_класса -> имя элемента;

 

Например, определив указатель pоint, адресующий объект D класса complex, можно присваивать значения элементам класса D следующим образом:

 

complex *point = &D; // или point = new complex;

point -> re = 2.3;//присвоение значения элементу объекта D

point -> im = 6.1; // то же

point –>print();

Доступность компонентов класса.

В рассмотренных ранее примерах классов компоненты классов являются общедоступными. В любом месте программы, где “видно” определение класса, можно получить доступ к компонентам объекта класса. Тем самым не выполняется основной принцип абстракции данных – инкапсуляция (сокрытие) данных внутри объекта. Для изменения видимости компонент в определении класса можно использовать спецификаторы доступа: public, private, protected.

Общий вид объявления класса с применением спецификаторов доступа имеет вид:

 

#include<iostream.h>

 

class имя

{

private: //может быть опущен, т.к. поддерживается по умолчанию

приватные данные и функции

protected:

защищенные данные и функции

public:

общие данные и функции

} список объектов;

 

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

имя_объекта.имя_члена_класса

ссылка_на_объект.имя_члена_класса

указатель_на_объект->имя_члена_класса

 

Собственные (private) компоненты локализованы в классе и не доступны извне. Они могут использоваться функциями – членами данного класса и функциями – “друзьями” того класса, в котором они описаны.

Защищенные (protected) компоненты доступны внутри класса и в производных классах.

Изменить статус доступа к компонентам класса можно и с помощью использования в определении класса ключевого слова class. В этом случае все компоненты класса по умолчанию являются собственными (private).

Пример.

class complex

{

double re, im; // private по умолчанию

public:

double real(){return re;}

double imag(){return im;}

void set(double x,double y){re = x; im = y;}

};

Указатель this

 

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

имя_класса *const this = адрес_объекта

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

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

Примером широко распространенного явного использования this являются операции со связанными списками.

Конструктор

 

Недостатком рассмотренных ранее классов является отсутствие автоматической инициализации создаваемых объектов. Для каждого вновь создаваемого объекта необходимо было вызвать функцию типа set (как для класса complex) либо явным образом присваивать значения данным объекта.

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

Формат определения конструктора следующий:

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

{операторы_тела_конструктора}

 

Имя этой компонентной функции по правилам языка С++ должно совпадать с именем класса. Такая функция автоматически вызывается при определении или размещении в памяти с помощью оператора new каждого объекта класса.

Пример.

сomplex(double re1 = 0.0,double im1 = 0.0){re = re1; im = im1;}

 

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

Конструктор имеет ряд особенностей:

Для конструктора не определяется тип возвращаемого значения. Даже тип void не допустим.

Указатель на конструктор не может быть определен, и соответственно нельзя получить адрес конструктора.

Конструкторы не наследуются.

Конструктор всегда существует для любого класса, причем, если он не определен явно, он создается автоматически. По умолчанию создается конструктор без параметров и конструктор копирования. Если конструктор описан явно, то конструктор по умолчанию не создается. По умолчанию конструкторы создаются общедоступными (public).

Параметром конструктора не может быть его собственный класс, но может быть ссылка на него (T&). Без явного указания программиста конструктор всегда автоматически вызывается при определении (создании) объекта. В этом случае вызывается конструктор без параметров. Для явного вызова конструктора используются две формы:

имя_класса имя_объекта (фактические_параметры);

имя_класса (фактические_параметры);

Первая форма допускается только при не пустом списке фактических параметров. Она предусматривает вызов конструктора при определении нового объекта данного класса:

complex ss (5.9,0.15);

Вторая форма вызова приводит к созданию объекта без имени:

complex (5.9,0.15);

Существуют два способа инициализации данных объекта с помощью конструктора. Ранее мы рассматривали первый способ, а именно, передача значений параметров в тело конструктора. Второй способ предусматривает применение списка инициализаторов данного класса. Этот список помещается между списком параметров и телом конструктора. Каждый инициализатор списка относится к конкретному компоненту и имеет вид:

имя_данного (выражение)

Примеры.

class CLASS_A

{

int i; float e; char c;

public:

CLASS_A(int ii,float ee,char cc) : i(8),e( i * ee + ii ),с(сс){}

. . .

};

Класс “символьная строка”.

#include <string.h>

#include <iostream.h>

class string

{

char *ch; // указатель на текстовую строку

int len; // длина текстовой строки

public:

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

// создает объект – пустая строка

string(int N = 80): len(0){ch = new char[N+1]; ch[0] = ‘\0’;}

// создает объект по заданной строке

string(const char *arch){len = strlen(arch);

ch = new char[len+1];

strcpy(ch,arch);}

// компоненты-функции

// возвращает ссылку на длину строки

int& len_str(void){return len;}

// возвращает указатель на строку

char *str(void){return ch;}

. . .};

 

Здесь у класса string два конструктора – перегружаемые функции.

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

Конструктор копирования является конструктором специального вида: он воспринимает в качестве аргумента константную ссылку на объект класса (const тип_класса&) или просто ссылку на объект (тип_класса&). Использование первого предпочтительнее, так как второй не позволяет копировать константные объекты.

Если механизм работы конструктора копирования не определен в программе, то он создается по умолчанию вида T::T(const T&), где Т – имя класса и производит побитовое копирование. Такой конструктор создает буквальную копию объекта что скорее всего будет непригодным для объектов, содержащих указатели и ссылки.

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

а) когда объект передается функции по значению;

б) при построении временного объекта как возвращаемого значения функции;

в) при использовании объекта для инициализации другого объекта.

Если класс не содержит явным образом определенного конструктора копирования, то при возникновении одной из этих трех ситуаций производится побитовое копирование объекта. Побитовое копирование не во всех случаях является адекватным. Именно для таких случаев и необходимо определить собственный конструктор копирования. Например, в классе string:

string(const string& st)

{len=strlen(st.len);

ch=new char[len+1];

strcpy(ch,st.ch); }

 

Можно создавать массив объектов, однако при этом соответствующий класс должен иметь конструктор по умолчанию (без параметров).

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

class complex{

double re;

double im;

 

public:

complex(){re=0.0;im=0.0;}

complex (double real, double image){re=real; im=image;}

};

void main(){

class complex a[20]; //вызов конструктора без параметров(по умолчанию)

class complex[2]={complex(10.2,2.4),complex(1.5,3.4)};//явное присваивание

 

Деструктор

 

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

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

~имя_класса(){операторы_тела_деструктора}

Имя деструктора совпадает с именем его класса, но предваряется символом “~” (тильда).

Деструктор не имеет параметров и возвращаемого значения. Вызов деструктора выполняется не явно (автоматически), как только объект класса уничтожается.

Например, при выходе за область определения или при вызове оператора delete для указателя на объект.

string *p=new string (“строка”);

delete p;

Если в классе деструктор не определен явно, то компилятор генерирует деструктор по умолчанию, который просто освобождает память, занятую данными объекта. В тех случаях, когда требуется выполнить освобождение и других объектов памяти, например область, на которую указывает ch в объекте string, необходимо определить деструктор явно: ~string(){delete []ch;}

Так же, как и для конструктора, не может быть определен указатель на деструктор.