Реферат: Концепция класса Ключевым понятием объектно-ориентированного проектирования и программирования является понятие класса
Концепция класса
Ключевым понятием объектно-ориентированного проектирования и программирования является понятие класса.
Класс представляет собой обобщение однородных объектов реального мира. Другими словами, класс описывает общие свойства и поведение некоторой совокупности объектов. Свойства класса представлены в виде переменных класса, а его поведение – в виде методов (функций). Например, в виде класса может быть представлена концепция человека (персоны). Переменными такого класса будут: ФИО, текущие паспортные данные, номер свидетельства о рождении и т.п. В основу методов может быть положено следующее поведение: поступление на работу, получение заработной платы и т.д.
Класс описывает свойства и поведение объектов статическим способом. Это значит, что содержание класса не может быть изменено без изменения и повторной сборки программного обеспечения. Экземпляры классов (объекты) в ходе работы программы создаются и удаляются в вычислительной среде динамически, в соответствии с кодом программы. Если класс – это описание свойств объектов, то экземпляр класса отражает конкретный объект реального мира. Например, класс «человек» описывает свойства всех людей, а отдельный экземпляр этого класса – соответствует конкретному человеку.
^ Переменные класса
Класс содержит описание двух аспектов – структурного аспекта и аспекта поведения. Остановимся на первом. Структура данных класса представляет собой последовательность переменных. Порядок следования переменных важен и определяет их расположение в памяти при создании объектов.
Как и другие переменные, переменная класса относится к некоторому типу и имеет имя (уникальное в рамках класса). Тип переменной характеризуется размером, то есть количеством байт, занимаемых каждым значением. В качестве типа переменной могут быть использованы другие классы объектов. В этом случае нижестоящий объект (являющийся переменной класса) входит как часть в вышестоящий объект. Приведем пример класса на языке C++:
class Person {
int number; -- Индивидуальный номер человека
char name[10]; -- Имя
int age; -- Возраст
float height; -- Рост
};
В соответствии с размером переменных на стадии компиляции для каждого класса формируется описание расположения переменных в памяти относительно начала объектов, например, следующего вида:
^ Имя класса
№ переменной в классе
Тип переменной
Название переменной
Смещение от начала объекта
Размер переменной
Person
1
int
Number
0
4
Person
2
char[]
Name
4
10
Person
3
int
Age
14
4
Person
4
float
Height
18
4
Иллюстративно структуру объектов класса в памяти можно представить следующим образом:
Все объекты, принадлежащие одному классу, имеют одинаковый размер, равный сумме размеров всех входящих переменных. Этот размер определяется на стадии компиляции и остается постоянным до тех пор, пока в структуру данных класса не будут внесены изменения.
Описанный аспект класса очень близок к понятию «структура» структурного программирования. Но есть и отличия, которые будут изложены при обсуждении управления доступом к переменным класса.
^ Методы класса
Второй аспект, описание которого содержит класс – это аспект поведения. Поведение класса (точнее всех объектов, принадлежащих классу) описывается совокупностью методов. Каждый метод представляет собой функцию, действующую в контексте класса. Этот контекст используется компилятором в ходе поиска переменной по ее имени. Когда компилятор встречает имя некоторой переменной в методе,
сначала он ищет ей соответствие среди переменных, объявленных в рамках метода,
затем среди переменных, объявленных в рамках того класса, к которому относится данный метод,
и только затем среди глобальных переменных:
^ Рисунок 1 Последовательность поиска переменных в рамках метода по их имени
Как и функция структурного языка, метод объектно-ориентированного языка может принимать ряд переменных (являющихся параметрами), и может возвращать одну переменную (являющуюся результатом). Совокупность всех этих переменных представляет собой сигнатуру метода.
В объектно-ориентированном программировании методы классифицируются по различным критериям. Начнем рассмотрение с классификации по принципу статичности. Так, все нестатические методы принимают в качестве неявного параметра тот объект, в контексте которого они работают. Для таких методов использование переменной класса подразумевает обращение к переменной именно того объекта, который передан в качестве неявного параметра. Эти методы могут быть вызваны только в контексте некоторого объекта. Невозможно вызвать нестатический метод «вообще», не указав конкретный объект. Статические методы, наоборот, вызываются только вне контекста какого-либо объекта. Они не имеют неявных параметров.
Приведем пример объявления нестатического метода в C++:
class Person {
float aMethod (int param1, float param2, char * param 3);
};
Такой метод будет обязательно иметь неявный аргумент – указатель на тот объект, для которого он вызывается:
В случае, если метод является статическим, к его объявлению добавляется специальное ключевое слово static:
class Person {
static float aMethod (int param1, float param2, char * param 3);
};
Такой метод не будет иметь неявного аргумента:
Переменные класса также классифицируются по критерию статичности. Статической переменной считается переменная, объявленная в рамках класса, но не включаемая в каждый объект этого класса. Статические переменные существуют в единственном экземпляре для всех объектов класса. Даже если в памяти нет ни одного экземпляра класса, статическая переменная все равно уже существует. Статические переменные создаются в начале работы всей программы в целом и удаляются в конце ее работы. Так как статические методы работают вне контекста конкретного объекта, то они могут обращаться только к статическим переменным класса.
Так, если мы в любое место класса «персона» добавим статическую переменную «количество персон»:
class Person {
int number; -- Индивидуальный номер человека
static int personCnt; -- ^ Количество персон
char name[10]; -- Имя
int age; -- Возраст
float height; -- Рост
};
то представление каждого объекта в памяти не изменится и останется тем же, что и без статических переменных:
Добавленная статическая переменная будет создана в единственном экземпляре для всех объектов в сегменте данных программы во время ее загрузки.
Следующим критерием классификации методов является их постоянство. Метод считается постоянным, если в нем не происходит изменения значений переменных того объекта, для которого он вызван. Приведем пример объявления постоянного метода в C++:
class Person {
float aMethod (int param1, float param2, char * param 3) const;
};
Другие виды методов мы опишем после изложения принципа наследования классов в объектно-ориентированном программировании.
Наследование
Механизм наследования является ключевым в концепции объектно-ориентированного программирования и сводится к следующему тезису: произвольный класс (класс-потомок, класс-наследник) может быть объявлен как наследник другого класса (класса-предка). При этом, потомок наследует все переменные и методы предка. Таким образом, если мы объявим класс «водитель», как наследника класса «персона»,
class Driver: public Person {
char drivingLicence[10]; -- Номер водительского удостоверения
};
то в памяти каждый объект класса «водитель» будет представлять собой расширение класса «персона» по составу переменных:
То же самое происходит с методами этих классов: методы класса «персона» будут входить в состав методов всех его наследников, в том числе класса «водитель».
В принципе, класс-потомок может быть наследован от нескольких классов-предков одновременно. Хотя в языках Java и C# на этот счет существуют ограничения, которые мы обсудим позднее.
^ Динамические методы
Еще одним критерием классификации методов класса является свойство динамичности. При обсуждении проблемы динамических методов, затронем тесно связанный с этим вопрос динамического связывания.
Существует два вида связывания (вызова) методов класса: динамический и статический. При статическом связывании компилятор указывает (в объектном коде программы) непосредственно адрес вызываемого метода. Например, следующим образом:
^ Рисунок 2 Вызов методов при статическом связывании
где стрелки – вызов соответствующего метода по фиксированному адресу.
При динамическом связывании – адрес вызываемого метода вычисляется в зависимости от того, к какому классу принадлежит тот объект, для которого он вызывается. В этом случае на стадии компиляции в объектном коде фиксируется лишь индекс (номер) динамического метода в рамках так называемой таблицы виртуальных методов. Таблица виртуальных методов создается компилятором для каждого класса, содержащего динамические методы, и содержит указатели на все динамические методы этого класса (каждый по своему индексу). Таблица виртуальных методов загружается в момент инициализации программы из ее объектного кода.
Для того, чтобы определить ту таблицу виртуальных методов, которая должна использоваться при вызове методов конкретного объекта, объекты всех классов, содержащих динамические методы, включают дополнительную переменную, указывающую на таблицу виртуальных методов. Например, если класс «персона» имеет динамические методы, то все его объекты будут представлены в памяти с использованием дополнительной переменной vmtPtr (указатель на таблицу виртуальных методов), не объявленную в явном виде среди переменных класса:
Эта дополнительная переменная заполняется каждый раз при создании в памяти очередного объекта. Код по ее заполнению встраивается в программу на стадии компиляции автоматически, прозрачно для программиста. Эта функция компилятора основана на том, что зная, к какому классу относится конкретный создаваемый объект, компилятор может определить адрес соответствующей таблицы виртуальных методов, создаваемой им самим же. В результате, каков бы ни был тип указателя на объект, переменная vmtPtr будет содержать указатель на таблицу виртуальных методов именного того класса, объект которого был создан. Например, мы можем создать класс «водитель» и присвоить указатель на него типу «указатель на персону»:
Person * p = new Driver;
Несмотря на то, что на объект указывает переменная p типа «указатель на персону», скрытая переменная vmtPtr этого объекта будет содержать указатель на таблицу виртуальных методов класса «водитель».
При динамическом связывании компилятор автоматически встраивает в код программы следующий способ вызова динамического метода на основании его индекса:
по указателю на объект, в контексте которого вызывается метод, берется указатель на таблицу виртуальных методов (с помощью дополнительной переменной, описанной выше);
по индексу динамического метода берется указатель на вызываемый динамический метод;
вызывается метод по полученному указателю.
Выше мы изложили понятие динамического связывания, теперь дадим определение динамическому методу. Динамический метод – это метод класса, к которому применяется метод динамического связывания, если явно не указано обратное – необходимость использовать статическое связывание. Все динамические методы класса присутствуют в его таблице виртуальных методов. Конечный метод является противоположностью динамического метода в том смысле, что к нему динамическое связывание никогда не применяется. Конечные методы отсутствуют в таблице виртуальных методов. Разницу между динамическим и конечным методам можно проиллюстрировать на следующем примере на языке C++:
Person * p = new Driver;
p->aMethod();
Допустим, что в классе «персона» и классе «водитель» объявлено два метода aMethod с одинаковой сигнатурой. В том случае, если методы aMethod являются динамическими в обоих классах, будет вызван метод класса «водитель» в соответствии с правилом динамического связывания. Если же методы являются конечными, то будет вызван метод класса «персона» в соответствии с правилом статического связывания (так как переменная p указывает на объект класса «персона»).
Возможность использования индекса метода в таблице виртуальных методов в качестве ссылки на него основана на том, что если динамические методы в базовом и производном классах имеют одинаковую сигнатуру, то в таблицах виртуальных методов для обоих классов эти методы будут иметь одинаковый индекс. Например, для классов «персона» и «водитель» таблицы виртуальных методов будут содержать методы aMethod (если они динамические) с одинаковыми индексами:
В нашем примере одновременно используется динамическое и статическое связывание методов. M1 и M2 связываются статически. M1 и Person::aMethod, Driver::aMethod связываются динамически. На стадии компиляции в объектный код M1 встраивается вызов динамического метода по индексу 23. Так как все такие методы имеют одинаковую сигнатуру, то на процессе передачи параметров динамическое связывание никак не сказывается. На стадии выполнения программы, в зависимости от того, на какую таблицу виртуальных методов ссылается конкретный объект, вызывается либо динамически метод Person::aMethod, либо Driver::aMethod.
При этом, для динамического метода может быть указана необходимость использования статического связывания в явном виде. Например, на языке C++ для вышеприведенного примера это будет выглядеть так:
Person * p = new Driver;
p->Person::aMethod();
В этом случае, какими бы не были методы aMethod – динамическими или конечными, – будет вызван метод класса «персона». Например, даже если методы aMethod являются динамическими, будет использовано статическое связывание:
Существует частный случай динамического метода – абстрактный метод. Абстрактный метод отличается тем, что он не имеет реализации (тела), для него задается только объявление (заголовок). Класс, содержащий абстрактные методы, также является абстрактным. В программе нельзя создать объекты абстрактного класса – будет выдана ошибка на стадии компиляции, – но можно объявлять указатели на абстрактные классы. Заметим, что существование указателя на абстрактный класс никак не подразумевает создание объекта абстрактного класса, так как указатель на базовый класс может указывать на объект производного класса. Например, если класс «персона» является абстрактным, то
Person * p = new Driver;
допустимо, а
Person * p = new Person;
недопустимо.
Абстрактные классы используются для описания обобщенных концепций, для содержательного использования которых необходимо программировать реализацию конкретных динамических методов. Если на стадии написания программы ясно, что создавать объекты некоторого класса не имеет смысла, то такой класс целесообразно объявить абстрактным. Но такой шаг не запрещает создавать массивы указателей на абстрактные классы и обрабатывать их унифицированным образом, вне зависимости от того, объект какого именно класса располагается по каждому из указателей массива.
Например, допустим класс «персона» является абстрактным и у него есть два производных класса: «водитель» и «спортсмен». Тогда мы можем объявить массив указателей на объекты класса «персона»,
Person * p[];
каждому указателю которого может быть присвоен как адрес объекта класса «водитель», так и адрес объекта класса «спортсмен»:
В результате, массив персон может обрабатываться унифицированным образом вне зависимости от истинного типа объекта, расположенного в памяти. Объекты классов «водитель» и «спортсмен» умышленно изображены на рисунке разного размера, чтобы подчеркнуть тот факт, что в памяти они могут занимать разное количество байт.
^ Конструктор и деструктор
Конструктор представляет собой нестатический конечный метод, вызываемый в момент создания объекта для его инициализации. Особенностью конструктора является то, что он не может иметь возвращаемой переменной. Код вызова конструктора вставляется в объектный код программы компилятором автоматически, прозрачно для программиста, каждый раз, когда выполняется команда создания нового объекта (будь то в куче или стеке, см. ниже про виды памяти).
Класс может иметь несколько конструкторов, отличающихся сигнатурой. Если в ходе создания объекта в его конструктор не передаются параметры, то вызывается конструктор без параметров, называемый конструктором по умолчанию. Если для класса не определен конструктор по умолчанию, то объект создается в памяти без выполнения инициализации, что приводит к случайным значениям его переменных (кроме неявного указателя на таблицу виртуальных функций – он заполняется верным значением в любом случае). Например, в следующем случае будет вызван конструктор по умолчанию, если он объявлен в классе «водитель»:
Person * p = new Driver;
Если в ходе создания объекта параметры конструктора указаны явно, например,
Person * p = new Driver (10);
то будет вызван конструктор, сигнатура которого соответствует передаваемым параметрам. Если такой конструктор не будет найден, или возникнет неоднозначность, будет выдана соответствующая ошибка компилятора.
В случае наследования, до вызова конструктора производного класса обязательно вызывается конструктор базового класса. При этом, если конструкторов базового класса несколько, то в объектно-ориентированном языке существует возможность указать какой именно конструктор вызывать. Например, на языке C++ это можно сделать следующим образом:
class Driver: public Person {
Driver ():Person(0){}
};
если для класса «персона» объявлен конструктор, принимающий целое число в качестве параметра.
Если же не указан ни один из конструкторов, то вызывается конструктор по умолчанию. Даже если в производном классе не объявлен конструктор, конструктор по умолчанию базового класса все равно будет вызван. Например, в случае
class Driver: public Person {
};
при создании объекта класса «водитель» всегда будет вызываться конструктор по умолчанию для класса «персона», если он объявлен.
Деструктор представляет собой нестатический метод, вызываемый в момент уничтожения объекта. Деструктор, как и конструктор, не имеет возвращаемой переменной. К тому же, деструктор не имеет параметров, и класс может иметь только один деструктор. Код вызова деструктора вставляется в объектный код программы компилятором прозрачно для программиста каждый раз, когда некоторый объект прекращает свое существование (будь то в стеке или куче). Если для класса деструктор не задан, то память, выделенная под объект, освобождается без каких-либо дополнительных действий. В случае, если объект захватил некоторые ресурсы (например, память или файловые дескрипторы), отсутствие деструктора приведет к тому, что они будут потеряны для дальнейшего использования в программе. Поэтому, для корректной работы программы, классы, захватывающие ресурсы, необходимо дополнять деструктором, освобождающим эти ресурсы.
В случае наследования, компилятор для уничтожения объекта помещает в объектный код программы вызов как деструктора производного класса, так и деструктора базового. При этом, сначала выполняется вызов деструктора производного класса. Только после вызова деструкторов всех предков, вызывается деструктор самого класса, после чего память, выделенная под объект, освобождается.
Деструктор может быть динамическим. В этом случае, в ходе удаления объекта, будет выполнена последовательность вызовов деструкторов именно для того объекта, который располагается в памяти, а не того объекта, на который показывает указатель. Эту особенность можно проиллюстрировать на следующем примере:
Person * p = new Driver;
delete p;
В этом примере, если деструктор класса «персона» виртуальный, то в ходе уничтожения объекта, в соответствии с правилом динамического связывания и правилом вызова деструкторов, будет вызван сначала деструктор класса «персона», затем деструктор класса «водитель». Если деструктор класса «персона» не является виртуальным, то в ходе уничтожения объекта, в соответствии с правилом статического связывания и правилом вызова деструкторов, будет вызван только деструктор класса «персона», так как удаляемый указатель показывает на объект класса «персона».
^ Виды памяти и создание объектов
Во время работы программы переменные (в том числе объекты) могут создаваться в различных местах оперативной памяти: в сегменте данных, в стеке и в куче. Эти виды памяти отличаются принципом создания переменных в них.
В сегменте данных создаются переменные, существующие все время от начала работы программы до завершения. В эту категорию попадают все статические переменные (в том числе глобальные). С точки зрения программиста эти переменные всегда существуют и могут быть доступны.
Стек используется для передачи параметров в методы и для создания внутренних переменных методов, существующих только во время выполнения метода или его части. Создание переменных в сетке встраивается в объектный код программы компилятором и происходит прозрачно для программиста. Обращение программиста к переменным метода компилятор преобразует в обращение к стеку по фиксированному смещению от вершины (вглубь стека).
При вызове метода ее параметры помещаются на вершину стека. Если в методе объявлены другие переменные, то они также помещаются на вершину стека. В момент компиляции команды с обращением к той или иной переменной компилятор вычисляет по какому смещению располагается эта переменная относительно вершины стека и встраивает в объектный код программы ссылку на позицию в стеке по фиксированному смещению от вершины. При завершении работы метода или вложенного блока операторов, все переменные, объявленные в этом методе (или блоке), снимаются с вершины стека. Например, если в программе объявлены следующие функции:
void methodA (int x, float y)
{
int c;
methodB (x, y, c);
… -- Точка X
}
void methodB (int x, float y, int d)
{
int c;
… -- Точка Y
}
то в точке Y в стеке будут существовать следующие переменные:
А в точке X следующие:
Использование кучи зависит от языка программирования. Например, в C++ создание переменных в куче полностью управляется программистом. После создания переменной программисту доступен указатель на ее начало. За удаление переменной в конце ее жизненного цикла отвечает сам программист, что служит причиной большого количества ошибок, приводящих к утечке памяти. В Java, наоборот, использование кучи прозрачно для программиста: если переменная представляет собой сложный объект, то она автоматически размещается в куче. Когда последняя ссылка на такой объект удаляется, он помечается неиспользуемым. В Java-машине существует регулярный процесс, называемый сборщиком мусора, который и отвечает за освобождение памяти из-под неиспользуемых объектов.
Если переменная представляет собой сложный объект, то во время ее создания вызывается конструктор, а во время уничтожения – деструктор, где бы она не находилась: в сегменте данных, в стеке или в куче.
^ Управление доступом к переменным и методам класса
Для переменных и методов класса может быть задан уровень доступа, регламентирующий доступ к переменным и методам одного класса из методов другого класса. Наиболее жестким является личный уровень доступа, дающий разрешение на доступ к переменным и методам только для методов самого класса. Наименее жестким – общий уровень доступа, дающий разрешение на доступ из любой точки программы. Средним вариантом является личный защищенный уровень доступа, когда доступ разрешен только для методов самого класса и его наследников. Уровень доступа в классе-потомке к переменной или методу класса-предка может быть изменен в сторону ужесточения. В зависимости от языка программирования промежуточные уровни доступа могут различаться. Так, например, в Java существует два дополнительных уровня доступа. В случае нарушения программистом регламента доступа к переменным во время компиляции программы будет выдано сообщение об ошибке. Приведем пример задания уровней доступа для переменных класса на языке C++:
class Person {
private: -- Личный уровень доступа
int number; -- Индивидуальный номер человека
public: -- Общий уровень доступа
char name[10]; -- Имя
protected: -- Личный защищенный уровень доступа
int age; -- Возраст
float height; -- Рост
};
В объектно-ориентированных языках программирования существуют механизмы описания исключений из перечисленных правил доступа. Первый механизм заключается в объявлении дружественности класса. Если некоторый класс A объявлен как дружественный некоторому классу B, то все методы A могут обращаться ко всем методами и переменным класса B, независимо от их уровня доступа. Например,
class B {
…
friend class A; -- Объявление дружественного класса.
};
Второй механизм позволяет объявлять лишь некоторые методы класса A как дружественные классу B. Это приводит к возможности обращаться ко всем переменным и методам класса B, независимо от заданного для них уровня доступа, только в рамках этих методов. Например,
class B {
…
friend A::methodX; -- Объявление дружественного метода
};
Интерфейс
Интерфейс представляет собой частный случай абстрактного класса, все методы которого являются абстрактными, и который не имеет переменных класса. Синтаксис объявления интерфейсов различается в разных языках программирования. Так, в C++ вообще нет специального синтаксиса для интерфейсов и они объявляются подобно другим классам. А в Java, наоборот, существует специальное ключевое слово interface для объявления интерфейсов. При этом в Java и C# налагается ограничение, что у класса может быть только один предок, не являющийся интерфейсом, и произвольное количество предков-интерфейсов. Также в этих языках существует ограничение, что предками интерфейса могут быть только интерфейсы (предков может быть несколько). В C++ подобных ограничений нет. Приведем простой пример интерфейса на языке C++:
class Interchanger {
virtual int get ()=0;
virtual void put ()=0;
};
Исключения
В объектно-ориентированном языке существует два механизма сообщения об ошибке, возникшей в методе. Первый, традиционно используемый в структурном программировании, заключается в кодировании всех возможных ошибок и возврате кода результата в качестве выходной переменной метода. Недостатками этого подхода являются:
Необходимость поддержания списка констант со всеми возможными кодами результата.
Необходимость при вызове каждого метода проверять возвращаемое им значение. При этом, программа превращается в наслоение множества вложенных if или switch-операторов, теряется ее читабельность.
Этот подход можно проиллюстрировать на таком примере:
if (methodA()<>ERROR) {
if (methodB()<>ERROR) {
…}
else {}
else {}
От перечисленных недостатков свободен второй механизм сообщения о критических ошибках – механизм исключений. Исключение представляет собой механизм обработки ошибочных ситуаций без использования кодов возврата из метода и включает в себя два аспекта: аспект описания обработки исключений и аспект генерации исключений.
Для описания обработки исключений программист определяет в рамках метода три типа блоков операторов: блок испытания, блок обработки исключений, финальный блок. Блок испытания объявляется с использованием ключевого слова try. Непосредственно за блоком испытаний следует один или несколько блоков обработки, объявляемых ключевым словом catch и, при необходимости, один финальный блок, объявляемый ключевым словом finally. Блок обработки может иметь одну переменную в качестве параметра. При генерации исключения обязательно указывается переменная, значение которой является параметром исключения. Генерация исключения осуществляется при помощи ключевого слова throw. Приведем пример описания обработки исключений на языке С++:
try {
methodA();
methodB();
}
catch (Error e) {…}
finally {…}
Если сравнить этот пример с предыдущим, то наглядно видно, что второй механизм обработки исключений приводит к более читабельному коду программы. Особенно, если блок try достаточно большой (при использовании первого механизма мы получили бы операторы ветвления очень большой вложенности).
Выполнение совокупности блоков исключений происходит следующим образом. Начинается выполнение с блока испытания: операторы исполняются стандартным образом до момента генерации исключения при исполнении одного из операторов (этим оператором может быть явная генерация исключения throw, вызов некоторого метода или математическая ошибка, к примеру, деление на ноль). В момент генерации исключения по типу переменной-параметра определяется блок обработки, которому и передается управление. После выполнения блока обработки в обязательном порядке выполняется финальный блок, если он задан; затем выполнение метода продолжится с оператора, расположенного после совокупности блоков исключений. Проиллюстрируем эту последовательность переходов между блоками на диаграмме:
В случае, если блок обработки с типом переменной сгенерированного исключения не найден, выполнение текущего блока испытания будет прервано, выполнится финальный блок, после чего необработанное исключение будет передано для обработки в вышестоящем блоке испытаний, в рамках которого произошел вызов данного блока.
Пунктирная линия показывает логический переход к вышестоящему блоку испытания в результате генерации исключения.
Если некоторый блок является корневым, то есть для него не существует вышестоящих, то будет выполнен обработчик исключений по умолчанию, который выдаст содержимое параметра исключения и завершит работу программы.
Поиск блока обработки по типу параметра исключения происходит последовательно, в порядке их описания. Поэтому блоки обработки, входные переменные которых имеют тип классов-потомков, должны идти раньше блоков обработки классов-предков. Иначе они никогда не будут выполняться. Последним блоком обработки может идти блок без входной переменной. Такой блок обрабатывает все исключения с любыми параметрами.
Во всех объектно-ориентированных языках существует специальный синтаксис оператора throw, позволяющий в рамках любого обработчика исключений повторить генерацию обрабатываемого исключения. В этом случае не требуется явно указывать переменную исключения в качестве параметра – автоматически будет использована текущая переменная. При этом перед тем, как сгенерированное исключение будет передано вышестоящему блоку испытания, обязательно будет выполнен финальный блок для текущего блока испытания. Проиллюстрируем это.
Если в ходе исполнения блока испытания ни один из операторов не привел к генерации исключения – будет выполнен финальный блок из совокупности блоков исключений, а затем выполнение метода продолжится с того оператора, который следует после совокупности блоков исключений.
Программный код, реализующий описанную логику, встраивается в объектный код программы компилятором прозрачно для программиста. При этом используются такие понятия, как фрейм исключения и стек фреймов. Фрейм исключения существует для каждого блока испытания и представляет собой совокупность переменных стека, которые были созданы в рамках данного блока испытания.
В момент начала выполнения вложенного блока испытания срабатывает встраиваемый код, отмечающий в стеке начало следующего фрейма исключения. В момент завершения выполнения – эта метка снимается. В результате образуется стек фреймов путем логического разделения стека переменных на части. Например, для следующего программного кода на языке C++:
try { -- Начало фрейма X
int x, y, z;
try { -- Начало фрейма Y
int a, b, c;
… -- Точка P
} catch (…) {…}
} catch (…) {…}
в точке P стек переменных и фреймов будет представлять собой следующее:
Когда выполнение некоторого блока испытания прерывается исключением, все переменные входящие в его фрейм удаляются из стека, что приводит к автоматическому откату всех методов, вызванных в этом блоке (любой степени вложенности). Далее на вершину стека помещается параметр исключения и по специальным структурам осуществляется поиск необходимого блока обработки (соответствующего текущему блоку испытания, данные о котором хранятся в метке фрейма исключения, снятого с вершины стека). Если блок обработки найден – передается управление ему, если не найден – с вершины стека снимается очередной фрейм. В конце каждого обработчика встраивается код, выполняющий поиск и выполнение финального блока для текущего блока испытания, а также переход к оператору метода, следующему за совокупностью блоков исключений. Одновременно, в конце каждого блока испытания встраивается вызов финального блока и переход на оператор метода, следующий после блоков исключений. Это приводит к тому, что при выполнении блока испытания без генерации исключений исполняется финальный блок и работа метода продолжается.
Шаблоны
В объектно-ориентированном программировании существует два вида шаблонов: шаблон класса и шаблон функции. Начнем рассмотрение с шаблона класса. Шаблон класса представляет собой описание структуры класса (как структуры данных, так и методов), используемое компилятором для автоматического конструирования новых классов. Шаблон класса определяется одним или несколькими параметрами шаблона и имеет структуру, полностью совпадающую со структурой класса. Параметры шаблона используются в шаблоне тем же образом, что и типы переменных в классе. Это позволяет компилятору в процессе конструирования нового класса подставлять вместо параметров шаблона конкретные типы, получая работоспособный класс, принципиально не отличающийся от классов, явно объявленных программистом. В качестве примера шаблона класса приведем следующий шаблон:
template
class Entity {
A var1;
B var2;
};
Шаблоны классов используются так же, как и классы, с тем отличием, что для них обязательно указываются типы переменных, которые необходимо использовать в качестве параметров шаблона. Например, для того, чтобы объявить переменную e класса Entity с параметрами типа int и float необходим следующий программный код:
Entity e;
Новый класс конструируется в ходе компиляции в тот момент, когда использование шаблона с заданным сочетанием параметров встречается в первый раз. Для каждого сочетания параметров шаблона будет создан свой собственный класс. Далее, когда данный шаблон с тем же сочетанием параметров будет встречен компилятором повторно, будет использован ранее сгенерированный класс. Таким образом, в процессе компиляции для шаблона Entity может быть создано большое количество классов:
Далее рассмотрим второй вид шаблонов: шаблон функции. Шаблон функции, как и шаблон класса, имеет один или несколько параметров, которые могут использоваться в теле функции подобно типам. В ходе компиляции из шаблона функции конструируются функции – по одной для каждого сочетания параметров шаблона, встреченного в исходном тексте программы. При этом параметры шаблона заменяются на конкретные типы по всему телу функции. Например, шаблоном функции на языке C++ является:
template
T Mid (T x, T y) { return (x+y)/2; }
Данный шаблон может быть использован посредством указания конкретного типа в качестве его параметра:
float x=Mid (a, b);
В ходе компиляции для этого шаблона могут быть созданы, например, следующие функции:
^ Ср
еще рефераты
Еще работы по разное
Реферат по разное
Советы родителям пятиклассников
17 Сентября 2013
Реферат по разное
План работы школьных методических объединений. 2007-2008 учебный год шмо учителей гуманитарных дисциплин шмо учителей естественно-математического цикла
17 Сентября 2013
Реферат по разное
В. В. Бочаров политическая антропология и общественная практика
17 Сентября 2013
Реферат по разное
Введение Термин «Паблик Рилейшнз»
17 Сентября 2013