МетодРисование_квадрата интерфейс 2 страница

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

Рис. 1.31. Шаблон «Список»

Пример 1.14 Шаблон классов (шаблон классов Список).Шаблон (рис. 1.31) практически полностью повторяет описание класса, но везде, где должен указываться тип элемента, вместо него следует указать параметр (в нашем случае Элемент):

Шаблон классов Список (Элемент)

реализация

поляПервый, Текущий: ^Элемент

интерфейс

метод Добавить_перед_первым (аЭлемент: ^Элемент)

методУдалить_последний: ^Элемент

методОпределить_первый: ^Элемент

методОпределить_следующий: ^Элемент

методКонец_списка

Конец описания.

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

Список (Запись1) или Список (Записъ2)

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

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

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

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

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

• генерация информации о возникновении исключительной ситуации - генерация исключения;

обработка этой информации - перехват исключения.

При перехвате исключений используется стек вызовов, в котором в момент передачи управления очередной подпрограмме фиксируется адрес возврата. Например, если метод A вызывает метод B, а тот в свою очередь вызывает подпрограмму (или метод) C, то в стеке последовательно записываются: адрес возврата в A и адрес возврата в B (рис. 1.32).

Рис. 1.32. Организация стека вызовов

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

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

Механизм исключений предполагает использование двух конструкций. Первая - обеспечивает описанный выше алгоритм поиска обработчика исключения. Она может быть названа обрабатывающей конструкцией (try...except - в Delphi Pascal, try... catch - в стандарте C++, _try ... _except -в стандарте C). Операторы обработки исключения выполняются только, если при выполнении заданного фрагмента программы обнаруживается исключение указанного типа.

Вторая - используется в тех случаях, когда необходимо обеспечить правильное освобождение ресурсов метода даже при обнаружении исключительных ситуаций. Она называется завершающей конструкцией обработки исключения (try... finally - в Delphi Pascal, try... finally - в стандарте C, try... finally - в стандарте C++Builder). Завершающая обработка исключения отличается от обычной тем, что блок операторов завершающей обработки выполняется в любом случае: как при обнаружении исключения, так и при его отсутствии. Эта конструкция обычно используется для обеспечения освобождения памяти, закрытия файлов и т. д.

В целом при возникновении исключений возможны три варианта действий системы:

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

• обнаружен фрагмент, включающий обрабатывающую конструкцию - исключение корректируется, и выполнение программы продолжается;

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

Исключение может инициироваться:

• операционной системой при получении сигналов от контролирующих схем, например, исключение «деление на ноль»;

• операционной системой при обнаружении некорректных ситуаций в работе программ самой операционной системы;

• библиотечными программами среды разработки;

• непосредственно разрабатываемой программой.

В последнем случае в месте возможного возникновения исключительной ситуации программист организует генерацию исключения (raise - в Delphi Pascal, throw - в C++, вызывает функцию RaiseException - в C). Физически при генерации исключения создается некоторый объект, содержащий информацию об обнаруженной исключительной ситуации. В простейшем случае таким объектом может служить скалярная переменная одного из стандартных типов, а в более сложных - объект описанного ранее класса. В качестве информации может использоваться номер исключения, строка сообщения, значения операндов невыполненной операции, адрес некорректных данных и т. д.

Большое внимание при программировании обработки исключений уделяется работе с типами исключений. Наибольший интерес представляет объявление иерархии классов исключений, что позволяет перехватывать сразу исключения всех типов иерархии обработчиком типа указатель (ссылка) на базовый класс. В процессе обработки таких иерархий исключений обычно используют динамические полиморфные методы, вызов которых происходит в соответствии с реальным типом (классом) исключения, определяемым на этапе выполнения программы.

2.

3. СРЕДСТВА ОБЪЕКТНО-ОРИЕНТИРОВАННОГО ПРОГРАММИРОВАНИЯ В BORLAND C++ 3.1

Объектная модель C++, используемая Borland C++ 3.1, предоставляет программисту несколько большие возможности по сравнению с Borland Pascal 7.0. Так, в языке реализованы более надежные средства ограничения доступа к внутренним компонентам класса, перегрузка операций, возможность создания шаблонов функций и классов.

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

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

class<имя класса>{

private: <внутренние (недоступные) компоненты класса>

protected: <защищенные компоненты класса>

public: <общие (доступные) компоненты класса>

};

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

Компоненты класса, объявленные в секции private, называются внутренними. Они доступны только компонентным функциям того же класса и функциям, объявленным дружественными (см. § 3.5) описываемому классу.

Компоненты класса, объявленные в секции protected, называются защищенными. Они доступны компонентным функциям не только данного класса, но и его потомков. При отсутствии наследования — интерпретируются как внутренние.

Компоненты класса, объявленные в секции public, называются общими. Они доступны за пределами класса в любом месте программы. Именно в этой секции осуществляется объявление полей и методов интерфейсной части класса.

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

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

<тип функции>.<имя класса>::<имя функции>(<список параметров>)
{<тело компонентной функции>}

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

#include <stdio.h>

#include <conio.h>

class X {

private: char c;

public: int x,y;

/* встраиваемые компонентные функции, определенные внутри класса */

void print (void)

{ clrscr(); gotoxy(x,y); printf ("%c", c);

x=x+10; y=y+5; gotoxy(x,y); printf ("%c", c); }

void set_X(char ach,int ax,int ay) { c=ach; x=ax; y=ay; }

};

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

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

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

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

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

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

class Y{

int х,у;

public: int k; char l;

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

void set_Y(char al,int ax,int ay,int ak);

};

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

inline void Y::set_Y(char al,int ax=40,int ay=15,int ak=15)

{ x=ax; y=ay; k=ak;l=al;}

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

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

void Y::print() {clrscr(); 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[25]; // поле класса - строка из 25 символов

public :

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

void set_str (char *); // инициализация строки

void display_str(void); // вывод строки на экран

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

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

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

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

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

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

Например:

String a, *b, c[6]; /*определяет: объект a класса String, указатель b на объект класса String и массив c из шести объектов класса String*/

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

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

Например:

a.String::str;

b-> String::set_str(st1);

с[i].String::display_str();

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

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

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

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

Например:

a.str b->str c[i].str
a.display_str() b-> display_str() c[i].display_str()

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

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

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

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

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

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

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

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

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

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

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

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

Инициализация полей, описанных в секциях private и protected, возможна только с помощью компонентной функции.

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

#include <string.h>

#include <iostream.h>

#include <conio.h>

class sstro {

public: char str1[40]; int x,y;

void set_str(char *vs) // метод инициализации полей

{strcpy(strl,vs); x=0; y=0; }

void print(void) // метод вывода содержимого полей

{ cout<<" "<<х<<" "<<у<<" "<<str1<<endl;}

void main() {

sstro aa={" пример 1 ", 200, 400 };

/* использование оператора инициализации при создании объекта */

sstro bb,cc; // создание объектов с неинициализированными полями

bb.x=200; bb.y=150; // инициализация общедоступных компонентов

strcpy(bb.str1, "пример 2"); // при прямом обращении из программы

cc.set_str(" пример 3"); // использование компонентной функции

aa.print(); bb.print(); cc.print();

}

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

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

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

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

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

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

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

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

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

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

#include <iostream.h>

#include <conio.h>

class ТА {

int x,y; public:

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

void print(void) { cout<<x<< "\t"<<y<<endl;}

ТА *fun1() // возвращает указатель на объект, для которого вызывается

{ х=у=100; return this;}

TA fun2(TA M) // возвращает объект, для которого вызывается

{ х+=М.х; у+=М.у; return *this;}

};

void main(){

clrscr();

ТА aa,bb;

aa.set( 10,20);

bb.fun1()->print(); // выводит: 100 100

aa.print(); // выводит: 1020

aa.fun2(bb).print(); // выводит: 110 120

getch();

}

Кроме того, указатель this явно используют для формирования результата при переопределении операций (см. § 3.6), так как операция переопределяется для конкретного, вызывающего ее объекта.

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

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

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

class point {

int x,y;

static int obj_count; /* статическое поле (счетчик обращений),

инициализация в этом месте не возможна */

public:

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

};

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

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

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

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

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

class point

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

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

public:

point (){ х=0; у=0; со1оr=5; }

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

};

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

void point::draw point(point &p) // имя объекта передано через параметр

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

obj_count++; } // обращение к статическому полю

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

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

Пример 3.4 Класс со статическими компонентами.В качестве примера, иллюстрирующего полезные свойства статических компонентов, рассмотрим класс, использующий статические компоненты для построения списка своих объектов (рис. 3.1).

Рис. 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; }

void display() { cout <<str<<endl; }

static void displayAll(); // объявление статической функции

};

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

String *String::last=NULL;

void String::displayAll() // описание статической функции

{String *p=first;

if(p==NULL) return;

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

void main(void) {

String а("Это пример"), b(''использования статических"), c(''компонентов");

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

if (String::first !=NULL) // обращение к общему статическому полю

String:: displayAll(); // обращение к статической функции

getch();}

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

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

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

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

#include <iostream.h>

#include <conio.h>

#include <graphics. h>

class Figura { // основной класс Фигура

class Point { // вложенный вспомогательный класс Точка

int x,y,cv;

public:

int getx() { return x;} // метод получения координаты

х int gety() {return y;} // метод получения координаты у

int getc() { return cv;} // метод получения цвета

void setpoint(int ncv) // метод задания координат точки и ее цвета

{ cout<<" введите координаты точки:"<<endl;

cin>>x>>y; cv=ncv;}

class Line { // вложенный вспомогательный класс Линия

Point Tn,Tk; // начало и конец линии

public:

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

{ setcolor(Tn.getc());

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

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

{ cout«" введите координаты точек линии: "«endl;

Tn.setpoint(ncv); Tk.setpoint(ncv);}

void print() // метод вывода полей класса

{ cout«Tn.getx()«" "<<Tn.gety()<<" ";

cout<<Tk.getx()<<" "<<Tk.gety()<<endl; }

};

// продолжение описания класса Фигура

int kolline; // количество отрезков Фигуры

Line *mas; // динамический массив объектов типа Линия

public:

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

void draw(void); // прототип метода отрисовки линий

void delfigura() {delete [] mas;} // метод уничтожения полей объекта

void print(); // прототип метода вывода координат точек

};

void Figura::setfigura(int n, int ncv)

{ kolline=n;

cout<< " введите координаты "<<kolline« "линий: "<<endl;

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].draw();}

void Figura::print(void)

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

void main() { int dr=DETECT, mod; Figura Treangle;

initgraph(&dr, &mod, "с: \ \borlandc\\bgi");

cout << "результаты работы: "<<endl;

setbkcolor(15); setcolor(0);

Treangle.setfigura(3,6); // инициализировать поля объекта Треугольник

Treangle.print(); // вывести значения координат точек

getch();

setbkcolor(3); cleardevice();

Treangle.draw(); // нарисовать фигуру

Treangle.delfigura(); // освободить динамическую память

getch();}


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

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

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