Добавил:
sergeevpavel0406@mail.ru СОВА Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Информатика в техническом университете / Информатика в техническом университете. Объектно ориентированное программирование

.pdf
Скачиваний:
105
Добавлен:
06.03.2018
Размер:
9.48 Mб
Скачать

3.Средства ООП в Borland C++ 3.1

3.2.Конструкторы и деструкторы

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

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

Пример 3.6. Использование конструктора для инициализации полей класса. Рассмотрим использова1ше конструктора для инициализации полей класса, описанного в примере 3.2, изменив доступность поля strl. Конструктор инициализирует поля класса sstr и осуществляет действия, связанные с проверкой длины вводимой строки и коррекцией строки, в случае несоответствия.

^include <string.h>

 

 

 

^include <iostream.h>

 

 

 

^include <conio.h>

 

 

 

class sstr

char

strl[40];

 

 

{private:

 

 

public:

int x,y;

 

 

 

 

void print(void)

 

 

 

 

 

{cout«"

содерэюимое общих полей: " «

endl;

 

cout«"x=

"«x«"y=

"«y«endl;

 

 

cout«'*

содержимое скрытых полей: " «

endl;

 

cout«"

strl^"

«strl«endl;}

 

sstrfint vxyint vy,char *vs) //конструктор класса sstr

f int len=strlen(vs);

II проверка правильности задания длины

if(len>=^0) {strncpy(strl,vs,40);strlf40J=W;}

elsestrcpy(strl,vs);

x=vx;y=vy;

}

 

 

 

} : voidmainQ { clrscrQ;

//создание объекта класса sstr при наличии конструктора sstr аа(200у150,'' пример использования конструктора '');

aa.printQ;

getchQ;

}

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

110

3.2, Конструкторы и деструкторы

ПОЗВОЛЯЮЩИХ использовать разные способы иншщализащш объектов.

Пример 3.7. Использование переопределяемых конструкторов.

Mnclude <string,h> ^include <iostream.h> Mnclude <conio.h>

class sstr

 

 

 

{private:

char

strl[40];

 

public:

int x,y;

 

void print(void)

 

{cout«"

содерэ/саиие полей:

" « endl;

cout«"x=

"«x«"y=

"«y«endl;

cout< < " strl="< <strl < <endl;}

sstrfvoid) II конструктор, определяющий поля по умолчанию

fstrcpyfstrl/^KOHcmpyKmop по умолчанию"); х=0;у=Ф; } ; sstr(char *vs) II конструктор, получающий значение поля strl

{int len-strlen(vs);

if(len>=40) {strncpy(strJ,vs,40);strlf40J=W;} else strcpy(strl,vs); x=0; y-0; } //значения x и у задаются no умолчанию

sstr(char *vsyint vx) II конструктор, получающий значения strl и x

{int len-strlen(vs);

if(len>=40) {strncpy(strl,vs,40);strlf40J-=W;} else strcpy(strl,vs); x=vx; y=0; } //значение у задается no умолчанию

sstr(char *vs,int vx,int vy) II конструктор, получающий все значения

{int len-strlen(vs);

if(len>=40) {strncpy(strl,vs,40);strlf40J=W;} else strcpy(strl,vs); x=vx;y==vy; }

}: voidmainQ { clrscrQ;

sstr aO, al ("пример конструктора 1 схиу no умолчанию"); sstr a2("пример конструктора 2 су по умолчанию"J00); sstr аЗ("пример использования конструктора 3",200,150); aO.printQ;

al.printQ;

a2.print();

a3.print();

getchO;

}

Кроме того, в самом конструкторе предусматривается возможность использования аргументов, заданных по умолчанию. В этом случае вместо нескольких переопределяемых конструкторов можно использовать один. Вернемся к предыдущему примеру и заменим все переопределяемые конструкторы одним.

111

3. Средства ООП в Borland C++ 3.1

Пример 3.8. Использование конструктора с аргументами по умолчанию.

^include <string.h>

 

 

include <iostream.h>

 

 

#mclude <conio.h>

 

 

class sstr

 

 

 

 

{private:

char strl[40];

 

public:

 

 

 

 

int x,y;

 

 

 

 

void print(void)

 

 

{ cout«''

 

содержимое полей :"«

endl;

cout«"x=

" «x«"

y= "

«y«endl;

cout«"

 

strl="

«strl«endl;}

sstr(char

 

*vs,int vx,int vy); /* прототип конструктора с аргументами по

 

 

 

умолчанию */

};

sstr::sstr(char vs=''cmpoKa no умолчанию '\int vx=^80,int vy-90) /* тело конструктора с аргументами по умолчанию */

{int len-strlen(vs);

 

 

if(len>=40){strncpy(strl,vs,40);

strl[40]='\0';}

else

strcpy(strl,vs);

 

 

x=vx;y=vy;

}

 

 

voidmainQ

 

 

 

{ clrscrQ;

 

 

 

sstr aO,alC' X и у no умолчанию");

 

sstr a2C'y no умолчанию ", 100);

 

sstr аЗ("определяет все поля",200,150);

аО.printО; II выводит: х=80

у=90

strl= строка по умолчанию

al.printO; II выводит: х=80

у=90

strl= х и у по умолчанию

a2.print(); II выводит: х=100 у=90

strl= у по умолчанию

a3.print(); II

выводит: х=200 у=150 strl= определяет все поля

getchO;

}

 

 

 

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

<имя> (<список вьфажений>),

где в качестве имени могут фигурировать:

-имя поля данного класса;

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

-имя базового класса.

112

3.2. Конструкторы и деструкторы

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

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

Иногда возникает необходимость создать объект, не инициализируя его поля. Такой объект называется неинициализированным и под него только резервируется память. Для создания подобного объекта следует предусмотреть иеииициализирующий конструктор. У такого конструктора нет списка параметров и отсутствует тело («пустой» конструктор). В этом случае при объявлении объекта с использованием такого конструктора значение полей объекта не определено.

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

Пример 3.9. Использование конструктора со списком инициали­ зации и неинициализирующего конструктора.

^include <iostream,h>

 

 

Mnclude <conio.h>

 

 

class integ

 

 

{ int пит;

 

 

public:

 

 

voidprint(void)

{ cout«'' num=

"«num«endl;}

integ(int v):num(v){} II конструктор со списком инициализации

integfvoid){}

II неинициализирующий конструктор

}:

class obinteg

{ integ опит; II поле объектного типа public:

const intx,y,c; II поля фиксированного типа

voidprint(void)

{ onum.printQ;

 

cout«"x="

«x«"y=

"«y«"

color="«c«endl;}

obinteg(mt va,int vx,int vy,int vc):onum(va),x(vx),y(vy),c(vc){} I*

конструктор инициализирует поле объектного типа списком инициализации*/

obinteg(void) {} II неинициализирующий конструктор

} : voidmainQ

{clrscrQ;

integ аа{10); II инициализируемый объект

113

 

3. Средства ООП в Borland C++ 3.1

integsl;

lls\ неинициализируемый объект

obinteg bb(20,40,100,13); II инициализируемый объект obinteg s2; II неинициализируемый объект

соШ«"данные неинициализируемого объекта mtegsl"« endl; sLprintQ; II выводит случайные числа

соШ«"данные неинициализируемого объекта obintegs2*'«endl;

s2.printQ;

II выводит случайные числа

cout«

"данные объекта класса integ: "«endl;

aa.printQ;

II выводит заданные значения

cout«

"данные объекта с объектным полем integ:" «endl;

bb.printQ;

II выводит заданные значения

getchO;}

 

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

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

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

integ А(20М50,б), С=А;

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

Classl (const Classl&);

или

Classl(const Classl&,mt=0);

114

3.2. Конструкторы и деструкторы

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

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

<имя класса> (const <имя класса>&),

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

Примечание. Если для какого-либо поля или базового класса явно определен копирующий конструктор без const, то и конструктор класса в целом неявно определяется без const.

Пример ЗЛО. Использование предполагаемого копирующего конструктора.

Mnclude <string.h> #include <iostream.h> ^include <conio.h> class child

{private: char *name; int age; public:

void print(void)

{cout«" имя : "«name; cout«"возраст: "«age« endl;} child(char *Name, int Age): name(Name), age(Age) {}

}; voidmainO {clrscrO;

сЫШаа("Марш'\6),ЬЬ("Евгеиий'\5);

cout « "результаты работы программы: "«endl; aa,printQ; II вьшодит: Мария и 6

bb.printQ; II выводит: Евгений и 5

child dd-aa; I* предполагается использование копирующего конструктора */ dd.printQ; II вьгоодит: Мария и 6

getchO;

}

115

3.Средства ООП в Borland С+ + 3.1

Впримере 3.10 по умолчанию предполагается конструктор:

child(const child & obj):name(obj.name),age(obj,age) {}

В результате поля объекта аа без изменения копируются в поля объекта

dd.

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

Пример 3.11. Явное определение копирующего конструктора.

Mnclude <string,h> #include <iostream.h> Mnclude <conio.h>

class intpole

 

{ intcif;

 

public:

 

voidprint(void)

{ cout< < "cif="«cif< < " "< < endl;}

intpole(intva)

{cif=va;}

intpole(const intpole& obi) II копирующий конструктор

{cifrobl.ciTS;}

};

class obpole { int пит;

intpole intob; II объектное поле

public:

 

void print(void)

 

{intob.printQ;

 

cout«" num=

"«num«endl;}

obpole(int vn, int va):num(vn), intob(va) {}

}; voidmainQ

{clrscrQ;

obpole aa(10,40);

cout« "результаты работы программы: "«endl; aa.printQ; II выводится 40 и 10

obpole bb-aa; II активизируется копирующий конструктор bb.printQ; II выводится 120 и 10

getchO; }

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

obpole(const obpole &obj):num(obj.num) Jntob(obj.intob){}

116

3.2. Конструкторы и деструкторы

При инициализации объекта intob вызывается копирующий конструктор класса intpole, что приводит к инициализации соответствующего поля cif утроенным значением.

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

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

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

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

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

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

117

3. Средства ООП в Borland C++ 3.]

Up имечание. Если объект coдq)жaл указатель на некоторую динамически выделенную область памяти, то по умолчанию эта память не освобождается.

Пример 3.12. Определение дестру1стора в классе.

^include <iostream,h> #include <string.h> class din_string

{ char *str; II поле класса - строка

unsigned size;

 

 

public:

 

 

dinjstring(char

*s,unsigned SIZE=100); II прототип конструктора

^dinjstringO;

II прототип деструктора

void displayQ{cout «str

«endl;}

};

dinstring: :din_string(char'^s,unsigned SIZE) II конструктор {str = new char [size = SIZE]; strcpy(str,s);}

din_string::^dinjstringO

II описание деструктора вне класса

{delete []str;

}

void mainQ

 

 

^{dinjstringA

(''my example ");

char ss [] - "ve cjvhuter is very good";

dinjstring */7 = new din_string(ss,sizeof(ss));

P'>display():

 

 

delete p;

II уничтожить объект - неявный вызов деструктора

A.displayO;}

II деструктор А будет вызван после окончания программы

3.3. Наследование

C++ поддерживает механизм наследования, являющийся одной из фундаментальных концепций ООП. Этот механизм обеспечивает создание производных классов. Производные классы - это эффективное средство расширения функциональных возможностей существующих классов без перепрограммирования этих классов и повторной компиляции существующих программ. В смысле функционирования производные классы или потомки являются более мощными по отношению к исходным {базовым) классам или родителям^ так как, имея доступ ко многим функциям и полям базового класса, они обладают еще и своими компонентами.

По определению компонентами производного класса являются все компоненты базового класса, за исключением конструктора, деструктора и оператора равно (=), а также все компоненты, перечисленные в теле производного класса. Первое поле производного класса расположено после всех полей базового класса.

118

3.3. Наследование

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

Определение производного класса (потомка) вьшолняется следующим образом:

class <имя производного класса >:

<вид наследованияхимя базового класса>{<тело класса>};

где вид наследования определяется ключевыми словами: private, protected, public.

Видимость полей и функций базового класса из производного определяется видом наследования и представлена в табл. 3.1.

Т а б л и ц а 3.1. Видимость компонент базового класса в производном

1

Вид

Объявление компонентов в

Видимость компонентов в

 

наследования

базовом классе

производном классе

 

 

private

не доступны

 

private

protected

private

 

 

public

private

 

 

private

не доступны

 

protected

protected

protected

 

 

public

protected

 

 

private

не доступны

 

public

protected

protected

 

 

public

public

Если вид наследования явно не указан, то по умолчанию принимается private. Однако хороший стиль программирования требует, чтобы в любом случае вид наследования был задан явно.

Пример 3.13. Описание производного класса с типом доступа public.

^include <iostream.h> Mnclude <conio.h> class A

{private: //защищенные (закрыгые) компоненты класса int numza;

119