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

книги / Объектно-ориентированное программирование

..pdf
Скачиваний:
11
Добавлен:
12.11.2023
Размер:
16.61 Mб
Скачать

3.1. Определение класса

разрешается объявлять встраиваемыми:

-функции, содержащие циклы, ассемблерные вставки или переклю­

чатели;

-рекурсивные функции;

-виртуальные функции.

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

class Y

{int х,у;

public: int k; char l;

11прототипы компонентных функций voidprint(void);

void set_ Y(char al,int ax,int ay,int ak);

>; // описание встраиваемой компонентной функции вне описания класса

inline void Y::set_Y(char al,int ax=40,int ay=l5,int ak=l5) { x=ax; У~пу; k=ak; l=al; }

/* описание компонентной функции print() содержит цикл, значит

использовать режим inline нельзя *!

 

void Y:.printО

 

{ clrscrQ;

 

gotoxy(x,y);

 

for(int i=0;i<k;i++) printf(" %c",l);

}

Рассмотрим пример определения класса, обеспечивающего работу со строками.

П ример 3.1. Определение класса (класс Строка). Пусть требуется описать класс, который обеспечит хранение и вывод строки:

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

class String

Иначало описания класса

{

private:

char str[25J; 11поле класса - строка из 25 символов

 

public:

 

 

 

И прототипы компонентных функций (методов)

 

void set_str (char *);

// инициализация строки

 

void display_str(void);

IIвывод строки на экран

 

char *return_str(void); Иполучение содержимого строки

};

описание компонентных функций вне класса

//

void String::set_str(char * s)

{ strcpy(str,s) ;}

void String::display_str(void)

{ cout« str « endl; }

char * String::return_str(void)

{ return str; }

101

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

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

-если файл находится в текущем каталоге - #include «имя файла»;

-если файл находится в каталогах автоматического поиска - ^include <имя файла>.

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

<имя класса> <список объектов или указателей на объект>

Например:

String а, *Ь, с[6\;

Приведенные переменные определяют три вида объекта класса string - объект а, указатель на объект в и массив из шести объектов с.

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

<имя объекта>.<имя класса>: :<имя поля или функции>.

Например:

a.String::str; b->String::set_str(stl); c[i].String:.display_str().

Однако чаще доступ к компонентам объекта обеспечивается с помощью укороченного имени, в котором имя класса и двоеточие опускаются. В этом случае доступ к полям и методам осуществляется по аналогии с обращением к полям структур:

<имя объекта>.<имя поля или функции>; <имя указателя на объект>-хим я поля или функции>;

<имя объекта>[индекс]. <имя поля или функции>.

Например:

 

 

a.str

b->str

c[ij.str;

a.display_str()

b->display_strQ

c[ij.display_str()■

102

3.1. Определение класса

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

П р и м е ч а н и е . Обращение из программы возможно только к общедоступным компонентам класса. Доступ к внутренним и защищенным полям и функциям возможен только из компонентных и «дружественных» функций.

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

- глобальные и локальные статические объекты создаются до вызова функции m ain и уничтожаются по завершении программы;

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

-объект, память под который выделяется динамически, создается функцией new и уничтожается функцией delete.

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

Значение может заноситься в поле объекта во время выполнения программы несколькими способами:

1)непосредственным присваиванием значения полю объекта;

2)внутри любой компонентной функции используемого класса;

3)согласно общим правилам C++ с использованием оператора инициализации.

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

Инициализация полей, описанных в секцияхprivate иprotected, возможна

только с помощью компонентной функции.

Пример 3.2. Различны е способы инициализации полей объекта

#,include <string.h> #,include <iostream.h> #,include <conio.h> class sstro

{ public:

char strl[40]; int x,y;

void set_str{char *vs) // функция инициализации полей

103

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

{strcpy(strl,vs); х=0; у=0;}

voidprintivoid) { c o u t« ' '< < х « ' '< < у « ' '« s tr l« e n d l;}

};

void mainQ

{ sstro aa={"пример 1", 200,400}; /* использование оператора инициализации при создании объекта */

sstro bb,cc; Исоздание объектов с неинициализированными полями bb.x=200; bb.y=150; //инициализация общедоступных компонент strcpy(bb.strl, "пример 2"); // при прямом обращении из программы cc.set_str{"npuMep 3"); /* инициализация с помощью специальной

компонентной функции */ aa.print{); bb.printQ; cc.printQ;

}

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

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

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

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

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

this->pole this->str this->fun().

Причем, при объявлении некоторого объекта А выполняется операция this=&A, а при объявлении указателя на объект b - операция this=b.

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

104

3.1. Определение класса

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

Пример 3.3. Использование параметра this.

#include <iostream.h>

 

#include <conio.h>

 

class ТА

 

 

{

intx.y;

 

 

public:

 

 

 

void set (int ax,int ay){x=ax;y=ay; }

 

voidprint(void) { c o u t« x « ' \t' <<y«endl;}

 

ТА *funl(){ x=y=100; return this; } /*функция возвращает указатель на

 

 

 

объект, для которого вызывается */

 

ТАfun2(TA М) {х+=М.х; у+=М.у; return *this;} /* функция возвращает

 

 

объект, для которого она вызывается */

};

 

 

 

void main()

 

 

{

clrscrO;

ТА aa,bb;

 

aa.set(10,20);

 

 

 

bb.JunlQ->print();

Ивыводит: 100 100

 

aa.printi);

 

II выводит: 10 20

 

aa.Jun2(bb).print{);

11 выводит: 110 120

 

getchQ; }

 

 

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

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

Инициализация статических полей класса осуществляется только вне определения класса, но с указанием описателя видимости <имя класса>::. Например:

classpoint

{intx,y;

105

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

static int obj_count; /* статическое поле - (счетчик обращений) инициализация в этом месте невозможна */

public:

point (){x=0;y-0; obj_count++;} // обращение к статическому полю

};

intpoint::obj_count=0; // инициализация статического поля

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

компонентных функций, объявленных со спецификатором static. Статические функции не ассоциируются с каким-либо объектом и не

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

<имя объекта>.<имя нестатического поля класса>.

При обращ ении к статическим полям класса такой проблемы не возникает:

class point

{int х,у,color; / / нестатические поля

static int obj_count; // статическое поле - счетчик обращений public:

point () {x=0;y:=0;color=5; }

static void draw_pount(point <£/?); // статическая функция

};

intpoint::obj_count=0; // инициализация статического поля

void point: -.draw_point(point &p) /* имя объекта передано в списке параметров */

{ putpixel( pjc, р.у, p.color );// обращение к нестатическому полю obj_count++;} // обращение к статическому полю

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

<класс>: :<компонент>.

106

3.1.Определение класса

Прим ер 3.4. К ласс со статическим и ком понентам и. В качестве примера, иллюстрирующего полезные свойства статических компонент, рассмотрим класс, использующий статические компоненты для построения списка своих объектов (рис. 3.1). В поле first хранится адрес первого элемента списка, в поле last - адрес последнего элемента списка. Нестатическое поле next хранит адрес следующего объекта. Полученный список затем используется для распечатки всех экземпляров класса с помощью статической функции drawAll(). Статическая функция drawAll() обращается к нестатическому полю next с указанием конкретного объекта

#,include <iostream.h> #include <string.h> #include <conio.h> #,include <alloc.h> #include <stdio.h> class String

{ public: char str[40\,

static String first; // статическое поле - указатель на начало списка static String *last; // статическое поле - указатель на конец списка

String *next; String(char *s)

{strcpy(str,s);

next—NULL;

if (first==NULL)first=this; else last->next=this; last=this; }

voiddisplayO { cout << str«endl; }

static void displayАО’, // объявление статической функции

};

String *String::first=NULL; 11 инициализация статических компонент

String *String::last=NULL;

void String::displayAll 0 // описание статической функции { String *p=first;

if (p— NULL) return;

Рис. 3.1. Организация списка объектов для примера 3.4

107

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

do {p->display(); p-p->next; } while(pl=NULL);

}

int main(void)

{ String a(“Это пример"),

//

объявление - создание трех объектов класса

Ъ(“использования статических ”),

с(“комронент

 

 

if (String::first!=NULL)

//

обращение к общему статическому полю

String::displayAll();

II

обращение к статической функции

getchO;

 

 

}

Использование статических компонент класса позволяет снизить потребность в глобальных переменных, которые могли бы быть использованы

саналогичной целью.

Локальны е и вложенные классы . Класс может быть объявлен внутри некоторой функции. Такой класс в C++ принято называть локальным. Функция,

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

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

Иногда возникает необходимость объявления одного класса внутри другого. Такой класс называется вложенным. Вложенный класс находится в

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

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

#include <iostream.h> #,include <conio.h> #include <graphics.h> class Figura {

class Point Ивложенный вспомогательный класс Точка { intx,y,cv;

public:

int getxO{return x;} II метод получения координаты x int getyO{return у;} II метод получения координаты у int getc(){return cv;} II метод получения цвета

void setpointfint ncv) Иметод задания координат точки и ее цвета

108

3.1.Определение класса

{c o u t« " введите координаты точки:" « en d l;

c in » x » y ;

cv=ncv;}

};

class Line // вложенный вспомогательный класс Линия { Point Tn,Tk; / / начало и конец линии

public:

void draw(void) // метод рисования линии

{ setcolor(Tn.getcO);

line(Tn.getx(),Tn.getyO,Tk.getx(),Tk.getyO);}

void setline(int ncv) II метод задания координат точек линии

{ cout<<" введите координаты точек линии:" « en d l;

Tn.setpoint(ncv); Tk.setpoint(ncv);} voidprintO // метод вывода полей класса

{ cout«Tn.getxQ «'

'«T n .getyO «'

cout«T k.getxO «,

' « Tk.getyO«endl; }

}; // поля и методы класса Фигура

int kolline;

И количество отрезков Фигуры

Line *mas;

Идинамический массив объектов типа Линия

public:

 

void setjigura(int n,int ncv); 11прототип метода инициализации объекта

void draw(void);

//прототип методаотрисовкилиний

void delfiguraO {delete [] mas;} Иметод уничтожения объекта

voidprintQ;

Ипрототип метода вывода координат точек

};

void Figura::setfigura(int п,int ncv) { kolline =n;

c o u t« " введите координаты" < < kolline«"линий:" « e n d l; mas=new Line [kolline];

for(int i=0;i<kolline;i++){mas[i].setline(ncv);} } void Figura:.draw(void)

{for(int i=0;i<kolline;i++) mas[i].drawQ;} void Figura::print(void)

{for(int i=0;i<kolline;i++) mas[i].printQ;}

void mainQ

{ int dr=DETECT, mod; Figura Treangle; initgraph(&dr,&mod," c. WborlandcWbgi" );

c o u t« " результаты работы: "«endl; setbkcolor(lS); setcolor(O); Treangle.setfigura(3,6); II инициализировать поля объекта Треугольник Treangle.printQ; // вывести значения координат точек

getchQ; setbkcolor(3); cleardeviceQ;

Treangle.drawQ;

II нарисовать фигуру

Treangle.delfigura();

// освободить динамическую память

getchQ; }

 

109

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

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

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

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

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

#include <string.h>

#,include <iostream.h>

#,include <conio.h>

class sstr

char strl[40];

{private:

public:

intx.y;

voidprint(void)

 

{ c o u t« " содержимое общих полей: " « endl;

 

cout«"x= "< < x«" y= "<<y«endl;

 

c o u t« " содержимое скрытых полей: " « endl;

c o u t« " strl=" « s tr l« e n d l;}

sstr(int vx,int vy,char *vs) //конструктор класса sstr

{ int len=strlen(vs); // проверка правильности задания длины

if (leri>=40) {strncpy(strl,vs,40);strl[40]=,\0';} else strcpy(strl,vs); x=vx;y=vy; }

};

void main() { clrscrO;

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

getchO;

}

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

110