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

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

.pdf
Скачиваний:
0
Добавлен:
06.09.2026
Размер:
2 Мб
Скачать
51
Далее приведен образец синтаксиса (неработающая программа!):
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
52
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
53
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;
}
//--------------------------------------------------------------
54
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
55
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){
56
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{
57
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 уровни наследования, но они практически не
используются.
58
Внутри производного класса можно переопределить методы базового, при
этом останется возможность получить к ним доступ, используя имя базового
класса и оператор доступа “::”.
Конструктор и деструктор, в отличие от обычных методов, не наследуются,
но до выполнения конструктора производного класса автоматически будет
вызван конструктор базового. Его также можно вызвать самостоятельно, указав
имя
класса и параметры. При вызове деструктора после работы деструктора
производного класса автоматически будет вызван деструктор базового.
Виртуальные
функции
Статический и динамический тип
Предположим, у нас есть базовый и производный классы:
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;
}
59
Вопрос: что выведется на экран? Ответ: «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 и в производных классах.
60
Вернемся к примеру без виртуальной функции и добавим деструкторы и
очистку памяти.
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, что неверно.
Вывод: при наследовании деструктор обязательно должен быть виртуальным.
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]