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

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

.pdf
Скачиваний:
0
Добавлен:
12.08.2026
Размер:
997 Кб
Скачать

Далее приведен образец синтаксиса (неработающая программа!):

class AClass{

// a class

 

public:

 

 

class AnError

// exception class

{

 

 

};

 

 

void Func(){

// a member function

if( /* error condition */ ){

 

 

throw AnError();

// throw exception

}

 

 

}

 

 

};

 

 

//========================================================

======

 

 

 

int main(){

// application

try{

 

// try block

 

AClass obj1;

 

// interact with AClass objects

 

obj1.Func();

 

// may cause error

}

 

 

 

catch(AClass::AnError)

// exception handler

{

// (catch block)

}

 

 

 

return 0;

}

Рассмотрим в качестве примера класс Stack с обработкой ошибок: превышение максимального количества элементов и попытка чтения из пустого стека.

class Stack{ private:

enum {MAX = 3};

//stack holds 3 ints

51

int st[MAX];

//stack: array of integers

int top;

//index of top of stack

public:

 

 

 

class Range

//exception class for Stack

{

//note: empty class body

};

 

 

 

//--------------------------------------------------------------

 

 

 

Stack(){

 

 

 

top = -1;

 

 

}

 

 

 

//--------------------------------------------------------------

 

 

 

void push(int var){

 

 

if(top >= MAX-1){

//if stack full,

 

throw Range();

//throw exception

}

 

 

 

st[++top] = var;

//put number on stack

}

 

 

 

//--------------------------------------------------------------

 

 

 

int pop(){

 

 

 

if(top < 0){

//if stack empty,

 

throw Range();

//throw exception

}

 

 

 

return st[top--];

//take number off stack

}

 

 

 

};

 

 

 

//========================================================

=======

 

int main(){

 

Stack s1;

 

try{

 

s1.push(11);

 

s1.push(22);

 

s1.push(33);

 

// s1.push(44);

//oops: stack full

52

cout << "1: " << s1.pop() << endl; cout << "2: " << s1.pop() << endl; cout << "3: " << s1.pop() << endl;

cout << "4: " << s1.pop() << endl; //oops: stack empty

}

catch(Stack::Range){ //exception handler

cout << "Exception: Stack Full or Empty" << endl;

}

cout << "Arrive here after catch (or normal exit)" << endl; return 0;

}

Разделим обработку различных исключений:

class Stack{

 

 

 

private:

 

 

 

enum {MAX = 3};

 

//stack holds 3 ints

int st[MAX];

 

//stack: array of integers

int top;

//index of top of stack

public:

 

 

 

class Full { };

 

//exception class

class Empty { };

 

//exception class

//--------------------------------------------------------------

 

 

 

Stack(){

 

//constructor

top = -1;

 

 

 

}

 

 

 

//--------------------------------------------------------------

 

 

 

void push(int var){

//put number on stack

if(top >= MAX-1){

//if stack full,

throw Full();

//throw Full exception

}

 

 

 

st[++top] = var;

 

}

//--------------------------------------------------------------

53

int pop()

//take number off stack

{

 

if(top < 0){

//if stack empty,

throw Empty(); //throw Empty exception

}

return st[top--];

}

};

//========================================================

=====

int main(){ Stack s1; try{

s1.push(11);

s1.push(22);

s1.push(33);

// s1.push(44); //oops: stack full cout << "1: " << s1.pop() << endl;

cout << "2: " << s1.pop() << endl; cout << "3: " << s1.pop() << endl;

cout << "4: " << s1.pop() << endl; //oops: stack empty

}

catch (Stack :: Full){

cout << "Exception: Stack Full" << endl;

}

catch (Stack :: Empty){

cout << "Exception: Stack Empty" << endl;

}

return 0;

}

Классы для ошибок не обязаны быть пустыми – рассмотрим пример, в котором класс ошибки хранит информцию об ошибке.

class Distance{

//English Distance class

54

private:

 

 

 

int feet;

 

 

float inches;

 

 

public:

 

 

 

//--------------------------------------------------------------

 

 

 

class InchesEx{

//exception class

public:

 

 

 

 

string origin;

//for name of routine

 

float iValue;

//for faulty inches value

 

InchesEx(string or, float in){

 

origin = or;

//store string

 

iValue = in;

//store inches

 

}

 

 

};

//end of exception class

//--------------------------------------------------------------

 

 

 

Distance(){

 

 

 

feet = 0;

 

 

 

inches = 0.0;

 

 

}

 

 

 

//--------------------------------------------------------------

 

 

 

Distance(int ft, float in){

 

 

if(in >= 12.0){

 

 

 

throw InchesEx("2-arg constructor", in);

 

}

 

 

 

feet = ft;

 

 

 

inches = in;

 

 

}

 

//--------------------------------------------------------------

 

void getdist(){

//get length from user

cout << "\nEnter feet: "; cin >> feet;

cout << "Enter inches: "; cin >> inches;

if (inches >= 12.0){

55

throw InchesEx("getdist() function", inches);

}

 

}

 

//--------------------------------------------------------------

 

void showdist() {

//display distance

cout << feet << "\'-" << inches << '\"';

}

};

//========================================================

=

int main(){ try{

Distance dist1(17, 3.5); //2-arg constructor

Distance dist2;

//no-arg constructor

dist2.getdist();

//get value

//display distances

 

cout << "\ndist1 = ";

dist1.showdist();

cout << "\ndist2 = ";

dist2.showdist();

}

catch(Distance::InchesEx ix){ //exception handler cout << "\nInitialization error in " << ix.origin

<<".\n Inches value of " << ix.iValue

<<" is too large.";

}

return 0;

}

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

int main(){

const unsigned long SIZE = 10000; //memory size

char* ptr;

//pointer to memory

try{

 

56

ptr = new char[SIZE];

//allocate SIZE bytes

}

catch(bad_alloc){ //exception handler

cout << "\nbad_alloc exception: can't allocate memory.\n"; return(1);

}

delete[] ptr; //deallocate memory cout << "\nMemory use is successful.\n"; return 0;

}

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

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

Основные цели использования наследования: моделирование отношения «Является» между объектами предметной области (иерархия объектов), выделение общей части нескольких классов (чтобы избежать дублирования), отделение интерфейса от реализации (выделение интерфейса).

Для описания того, что класс B является базовым, а класс D – производным, используется следующий синтаксис:

class B{};

class D : public B{};

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

57

Внутри производного класса можно переопределить методы базового, при этом останется возможность получить к ним доступ, используя имя базового класса и оператор доступа “::”.

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

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

Статический и динамический тип

Предположим, у нас есть базовый и производный классы: class B{

public:

void Iam(){

cout << "I am B";

}

};

class D : public B{ public:

void Iam(){

cout << "I am D";

}

};

Определим указатель на базовый тип и присвоим адрес переменной производного типа (это допустимо в силу принципа подстановки):

int main(){

B *Ptr = new D; Ptr->Iam(); return 0;

}

58

Вопрос: что выведется на экран? Ответ: «I am B». Дело в том, что у переменной Ptr получилось два типа – статический (B*) и динамический (D*). На этапе компиляции метод выбирается по статическому типу. Говорят, что происходит раннее связывание – метод, который будет вызван, определяется на этапе компиляции. Существует также позднее связывание, когда вызываемый метод определяется на этапе выполнения программы. Для этого метод должен быть описан как виртуальная функция. Здесь имеет место терминологическая путаница – мы говорим функция, несмотря на то, что речь идет о методе. Исправленный код выглядит так:

class B{ public:

virtual void Iam(){

cout << "I am B";

}

};

class D : public B{ public:

virtual void Iam(){

cout << "I am D";

}

};

int main(){

B *Ptr = new D; Ptr->Iam(); return 0;

}

Вывод на экран будет следующим: “I am D”. Нужно отметить, что не обязательно в производном классе указывать ключевое слово virtual. Метод Iam будет виртуальным, даже если это указано только в базовом классе. Но для удобства чтения кода программы рекомендуется добавлять ключевое слово virtual и в производных классах.

59

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

class B{ public:

void Iam(){

cout << "I am B";

}

~B(){

cout << "Goodbye, B!";

}

};

class D : public B{ public:

void Iam(){

cout << "I am D";

}

~D(){

cout << "Goodbye, D!";

}

};

int main(){

B *Ptr = new D; Ptr->Iam(); delete Ptr; return 0;

}

Аналогично обычным методам, деструкторы могут быть виртуальными и невиртуальными. В данном примере в строчке delete Ptr произойдет раннее связывание, соответственно вызовется деструктор класса B, что неверно. Вывод: при наследовании деструктор обязательно должен быть виртуальным.

60

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]