
- •Учебное пособие
- •Введение
- •Объектно-ориентированный подход
- •Объектно-ориентированное программирование Абстрактные типы данных
- •Базовые принципы объектно-ориентированного программирования
- •Простейший ввод и вывод
- •Объект cout
- •Манипуляторы hex и oct
- •Другие манипуляторы
- •Объект cin
- •Операторы для динамического выделения и освобождения памяти (new и delete)
- •Базовые конструкции объектно-ориентированных программ Объекты
- •Понятие класса
- •Конструктор копирования
- •Конструктор explicit
- •Указатели на компоненты класса
- •Встроенные функции (спецификатор inline)
- •Организация внешнего доступа к локальным компонентам класса (спецификатор friend)
- •Вложенные классы
- •Static-члены (данные) класса
- •Указатель this
- •Компоненты-функции static и const
- •Proxi-классы
- •Параметры ссылки
- •Независимые ссылки
- •Практические приемы ограничения числа объектов класса
- •Наследование (производные классы)
- •Конструкторы и деструкторы при наследовании
- •Виртуальные функции
- •Абстрактные классы
- •Виртуальные деструкторы
- •Множественное наследование
- •Виртуальное наследование
- •Перегрузка функций
- •Перегрузка операторов
- •Перегрузка бинарного оператора
- •Перегрузка унарного оператора
- •Дружественная функция operator
- •Перегрузка оператора []
- •Перегрузка оператора ()
- •Перегрузка операторов new и delete
- •Преобразование типа
- •Явные преобразования типов
- •Преобразования типов, определенных в программе
- •Шаблоны Параметризированные классы
- •Передача в шаблон класса дополнительных параметров
- •Шаблоны функций
- •Совместное использование шаблонов и наследования
- •Шаблоны класса и friend
- •Некоторые примеры использования шаблона класса Реализация smart-указателя
- •Классы поддерживающие транзакции
- •Задание значений параметров класса по умолчанию
- •Пространства имен
- •Ключевое слово using как директива
- •Ключевое слово using как объявление
- •Псевдоним пространства имен
- •Организация ввода-вывода
- •Состояние потока
- •Строковые потоки
- •Организация работы с файлами
- •Организация файла последовательного доступа
- •Создание файла произвольного доступа
- •Основные функции классов ios, istream, ostream
- •Основы обработки исключительных ситуаций
- •Перенаправление исключительных ситуаций
- •Исключительная ситуация, генерируемая оператором new
- •Генерация исключений в конструкторах
- •Задание собственной функции завершения
- •Спецификации исключительных ситуаций
- •Задание собственного неожиданного обработчика
- •Иерархия исключений стандартной библиотеки
- •Стандартная библиотека шаблонов (stl) Общее понятие о контейнере
- •Общее понятие об итераторе
- •Категории итераторов
- •Основные итераторы
- •Вспомогательные итераторы
- •Операции с итераторами
- •Контейнерные классы Контейнеры последовательностей
- •Контейнер последовательностей vector
- •Контейнер последовательностей list
- •Контейнер последовательностей deque
- •Ассоциативные контейнеры
- •Ассоциативный контейнер multiset
- •Ассоциативный контейнер set
- •Ассоциативный контейнер multimap
- •Ассоциативный контейнер map
- •Адаптеры контейнеров
- •Адаптеры stack
- •Адаптеры queue
- •Адаптеры priority_queue
- •Пассивные и активные итераторы
- •Алгоритмы
- •Алгоритмы сортировки sort, partial_sort, sort_heap
- •Алгоритмы поиска find, find_if, find_end, binary_search
- •Алгоритмы fill, fill_n, generate и generate_n
- •Алгоритмы equal, mismatch и lexicographical_compare
- •Математические алгоритмы
- •Алгоритмы работы с множествами
- •Алгоритмы swap, iter_swap и swap_ranges
- •Алгоритмы copy, copy_backward, merge, unique и reverse
- •Примеры реализации контейнерных классов Связанные списки
- •Реализация односвязного списка
- •Реализация двусвязного списка
- •Реализация двоичного дерева
- •Литература
- •Вопросы по курсу ооп
- •220013, Минск, п.Бровки, 6.
Абстрактные классы
Базовый класс иерархии типа обычно содержит ряд виртуальных функций, обеспечивающих динамическую типизацию. Часто в базовом классе эти виртуальные функции фиктивны и имеют пустое тело. Эти функции существуют как некоторая абстракция, конкретное значение им придается в производных классах. Такие функции называются чисто виртуальными функциями, то есть такими, тело которых, как правило, не определено. Общая форма записи чисто виртуальной функции имеет вид:
virtual прототип функции = 0;
Чисто виртуальная функция используется для того, чтобы отложить решение о реализации функции. То, что функция объявлена чисто виртуальной, требует, чтобы эта функция была определена во всех производных классах от класса, содержащего эту функцию. Если класс имеет хотя бы одну чисто виртуальную функцию, то он называется абстрактным. Для абстрактного класса нельзя создать объекты и он используется только как базовый класс для других классов. Если base – абстрактный класс, то для инструкций
base a;
base *p= new base;
компилятор выдаст сообщение об ошибке. В то же время вполне можно использовать инструкции вида
rect b;
base *p=&b;
base &p=b;
Чисто виртуальную функцию, как и просто виртуальную функцию, не обязательно переопределять в производных классах. При этом если в производном классе она не переопределена, то этот класс тоже будет абстрактным, и при попытке создать объект этого класса компилятор выдаст ошибку. Таким образом, забыть переопределить чисто виртуальную функцию невозможно. Абстрактный базовый класс навязывает определенный интерфейс всем производным от него классам. Главное назначение абстрактных классов – в определении интерфейса для некоторой иерархии классов.
Класс можно сделать абстрактным, даже если все его функции определены. Это можно сделать, например, чтобы быть уверенным, что объект этого класса создан не будет. Обычно для этих целей выбирается деструктор.
class base
{ компоненты-данные
public:
virtual ~base() = 0;
компоненты-функции
}
base ::~base()
{реализация деструктора}
Объект класса base создать невозможно, в то же время деструктор его определен и будет вызван при разрушении объектов производных классов.
Для иерархии типа полезно иметь базовый абстрактный класс. Он содержит общие свойства порожденных объектов и используется для объявления указателей, которые могут обращаться к объектам классов, порожденным от базового. Рассмотрим это на примере программы экологического моделирования. В примере мир будет иметь различные формы взаимодействия жизни с использованием абстрактного базового класса living. Его интерфейс унаследован различными формами жизни. Создадим fox (лис) – хищника, rabbit (кролик) – жертву и grass - (траву).
#include "iostream.h"
#include "conio.h"
// моделирование хищник - жертва с использованием
// иерархии классов
const int N=6, // размер квадратной площади (мира)
STATES=4, // количество видов жизни
DRAB=5,DFOX=5, // количество циклов жизни кролика и лиса
CYCLES=10; // общее число циклов моделирования мира
enum state{EMPTY,GRASS,RABBIT,FOX};
class living; // forvard объявление
typedef living *world[N][N]; // world- модель мира
void init(world);
void gener(world);
void update(world,world);
void dele(world);
class living
{protected:
int row,col; // местоположение в модели
void sums(world w,int sm[]); //
public:
living(int r,int c):row(r),col(c){}
virtual state who() = 0; // идентификация состояний
virtual living *next(world w)=0; // расчет next
virtual void print()=0; // вывод содержимого поля модели
};
void living::sums(world w,int sm[])
{ int i,j;
sm[EMPTY]=sm[GRASS]=sm[RABBIT]=sm[FOX]=0;
int i1=-1,i2=1,j1=-1,j2=1;
if(row==0) i1=0; // координаты внешних клеток модели
if(row==N-1) i2=0;
if(col==0) j1=0;
if(col==N-1) j2=0;
for(i=i1;i<=i2;++i)
for(j=j1;j<=j2;++j)
sm[w[row+i][col+j]->who()]++;
}
В базовом классе living объявлены две чисто виртуальные функции - who() и next() и одна обычная функция sums(). Моделирование имеет правила для решения о том, кто продолжает жить в следующем цикле. Они основаны на соседствующих популяциях в некотором квадрате. Глубина иерархии наследования – один уровень.
// текущий класс - только хищники
class fox:public living
{ protected:
int age; // используется для принятия решения о смерти лиса
public:
fox(int r,int c,int a=0):living(r,c),age(a){}
state who() {return FOX;} // отложенный метод для foxes
living *next(world w); // отложенный метод для foxes
void print(){cout << " ли ";}
};
// текущий класс - только жертвы
class rabbit:public living
{ protected:
int age; // используется для принятия решения о смерти кролика
public:
rabbit(int r,int c,int a=0):living(r,c),age(a){}
state who() {return RABBIT;} // отложенный метод для rabbit
living *next(world w); // отложенный метод для rabbit
void print(){cout << " кр ";}
};
// текущий класс - только растения
class grass:public living
{ public:
grass(int r,int c):living(r,c){}
state who() {return GRASS;} // отложенный метод для grass
living *next(world w); // отложенный метод для grass
void print(){cout << " тр ";}
};
// жизнь отсутствует
class empty : public living
{ public:
empty(int r,int c):living(r,c){}
state who() {return EMPTY;} // отложенный метод для empty
living *next(world w); // отложенный метод для empty
void print(){cout << " ";}
};
Характеристика поведения каждой формы жизни фиксируется в версии next(). Если в окрестности имеется больше grass, чем rabbit, grass остается, иначе grass будет съедена.
living *grass::next(world w)
{ int sum[STATES];
sums(w,sum);
if(sum[GRASS]>sum[RABBIT]) // кролик ест траву
return (new grass(row,col));
else
return(new empty(row,col));
}
Если возраст rabbit превышает определенное значение DRAB, он умирает либо, если поблизости много лис, он может быть съеден.
living *rabbit::next(world w)
{ int sum[STATES];
sums(w,sum);
if(sum[FOX]>=sum[RABBIT]) // лис ест кролика
return (new empty(row,col));
else if(age>DRAB) // кролик слишком старый
return(new empty(row,col));
else
return(new rabbit(row,col,age+1)); // кролик постарел
}
Fox тоже умирает от старости.
living *fox::next(world w)
{ int sum[STATES];
sums(w,sum);
if(sum[FOX]>5) // слишком много лис
return (new empty(row,col));
else if(age>DFOX) // лис слишком старый
return(new empty(row,col));
else
return(new fox(row,col,age+1)); // лис постарел
}
// заполнение пустой площади
living *empty::next(world w)
{ int sum[STATES];
sums(w,sum);
if(sum[FOX]>1) // первыми добавляются лисы
return (new fox(row,col));
else if(sum[RABBIT]>1) // вторыми добавляются кролики
return (new rabbit(row,col));
else if(sum[GRASS]) // третьими добавляются растения
return (new grass(row,col));
else return (new empty(row,col));// иначе пусто
}
Массив world представляет собой контейнер для жизненных форм. Он должен иметь в собственности объекты living, чтобы распределять новые и удалять старые.
// world полностью пуст
void init(world w)
{ int i,j;
for(i=0;i<N;++i)
for(j=0;j<N;++j)
w[i][j]=new empty(i,j);
}
// генерация исходной модели мира
void gener(world w)
{ int i,j;
for(i=0;i<N;++i)
for(j=0;j<N;++j)
{ if(i%2==0 && j%3==0) w[i][j]=new fox(i,j);
else if(i%3==0 && j%2==0) w[i][j]=new rabbit(i,j);
else if(i%5==0) w[i][j]=new grass(i,j);
else w[i][j]=new empty(i,j);
}
}
// вывод содержимого модели мира на экран
void pr_state(world w)
{ int i,j;
for(i=0;i<N;++i)
{ cout<<endl;
for(j=0;j<N;++j)
w[i][j]->print();
}
cout << endl;
}
// новый world w_new рассчитывается из старого world w_old
void update(world w_new, world w_old)
{ int i,j;
for(i=0;i<N;++i)
for(j=0;j<N;++j)
w_new[i][j]=w_old[i][j]->next(w_old);
}
// очистка мира
void dele(world w)
{ int i,j;
for(i=1;i<N-1;++i)
for(j=1;j<N-1;++j) delete(w[i][j]);
}
Модель имеет odd и even мир. Их смена является основой для расчета последующего цикла.
int main()
{ world odd,even;
int i;
init(odd);
init(even);
gener(even); // генерация начального мира
cout<<"1 цикл жизни модели"<<endl;
pr_state(even); // вывод сгенерированной модели
for(i=0;i<CYCLES-1;++i) //цикл моделирования
{ getch();
cout<<i+2<<" цикл жизни модели"<<endl;
if(i%2)
{ update(even,odd); // создание even модели из odd модели
pr_state(even); // вывод сгенерированной модели even
dele(odd); // удаление модели odd
}
else
{ update(odd,even); // создание odd модели из even модели
pr_state(odd); // вывод сгенерированной модели odd
dele(even); // удаление модели even
}
}
return 1;
}