Нельзя сказать, что объектный подход всегда лучше процедурного.
Как и любой образ мыслей, объектно-ориентированный подход может быть удобен или не удобен, в зависимости от ситуации.
Бывают задачи, в которых сложно выделить сущность. Например, «алгоритм нахождения наибольшего общего делителя». В нём есть два числа и численные операции над ними. Или задача «разложить число на простые».
Для них замечательно подойдёт процедурный подход.
В эту же группу попадает большинство математических алгоритмов, в которых есть сложные вычисления без выделения дополнительных сущностей. Именно поэтому древние языки программирования, предназначенные для расчетов (Фортран, Алгол) не содержали объектных возможностей.
С другой стороны, при разработке интерфейсов мы имеем дело с ярко выраженными сущностями.
Посетитель открывает окно браузера, затем нажимает кнопку, при этом раскрывается меню. Выборпункта меню приводит к запросу на сервер — все выделенные слова здесь являются сущностями.
Поэтому ООП отлично подходит для веб-разработки и виджетов. Какие-то сущности можно оформлять в объект, а какие-то — нет, это уже решает разработчик.
3 Наследование
Модели отношений is-a и has-a.
отношение is-a - "является объектом типа" - при public наследовании объект производного класса является также и объектом базового класса и т.д.
отношение has-a - "имеет объект" - приватное наследование; создание класса, элемент которого являются объектами другого класса и т.п.
Отношение типа has-a (содержит), так же называется композицией, и подразумевает,
что один объект содержит в себе другой.
Отношение типа is-a (является), это и есть наследование, которое подразумевает, что один объект может иметь набор свойств схожих с другими объектами, подходящих для объединения в группу.
Механизм наследование в классах. Понятия базового и производного классов. Передача
Наследование (англ. inheritance) представляет собой механизм, благодаря которому один объект может наследовать, приобретать свойства от другого объекта. Это свойство поддерживает концепцию классификации, чем и обуславливается его важность. Эта концепция лежит в основе классификации знаний.
Класс, свойства которого наследуются, называется базовым, а новый класс - производным. Все множество базовых и производных классов образует иерархию наследования.
Наследование классов позволяет создавать производные классы (классы наследники), взяв за основу все методы и элементы базового класса (класса родителя). Таким образом экономится масса времени на написание и отладку кода новой программы. Объекты производного класса свободно могут использовать всё, что создано и отлажено в базовом классе. При этом, мы можем в производный класс, дописать необходимый код для усовершенствования программы: добавить новые элементы, методы и т.д.. Базовый класс останется нетронутым.
Общий вид наследования классов
class имя_базового_класса
{
данные_базового_класса;
};
class имя_класса_потомка: спецификатор_доступа имя_базового_класса
{
данные_класса_потомка;
};
где, спецификатор_доступа – это один из спецификаторов public, private, protected.
Когда базовый класс имеет конструктор с аргументами, производные классы должны явным образом обрабатывать эту ситуацию путём передачи базовому классу необходимых аргументов.
Механизм включения.
class имя_класса : список_базовых_классов
{список_компонентов_класса};
class имя_базового_класса
{ данные_базового_класса;};
class имя_класса_потомка: спецификатор_доступа имя_базового_класса
{данные_класса_потомка;};
4 Полиморфизм
1. Перегрузка операций.
Кратко смысл полиморфизма можно выразить фразой: «Один интерфейс, множество методов». В Си++ полиморфизм реализован ввиде перегрузки функций, перегрузки операций и полиморфного наследования.
Перегрузка операций
Для повышения возможностей моделирования объектов реального мира в классе добавлена возможность перегрузки операций. Перегрузка операций позволяет определить (переопределить) операции, что даёт возможность их использовать с объектом класса.
На самом деле в Си++ некоторые операции уже перегружены. Например, операция (*) при использовании с двумя операндами перемножает их, а при применении её к адресу даёт доступ к значению, расположенному по этому адресу.
Чтобы понять, зачем нужна перегрузка операций сравните два следующих оператора присваивания:
strcpy(string_1, string_2);
и
string_1 = string_2;
Сама перегрузка операций синтаксически выглядит, как функция.
Общий вид прототипа перегрузки операции:
operatorop(список_аргументов);
где, op — это символ, перегружаемой операции. Например: operator+().
Допускается перегружать только определённые в Си++ операции. Допустим нельзя перегрузить $ или @, так как в языке их нет(то есть нельзя создавать новые операции). Также Си++ налагает дополнительные ограничения на перегрузку, перечисленные далее.
●Перегружаемые операции должны иметь, как минимум один операнд пользовательского типа.
●Нельзя перегружать операцию с нарушением её исходного синтаксиса. Например, если операция определена для работы с двумя операндами, то и перегруженную операцию нужно использовать с двумя операндами, а не со одним.
●Нельзя перегружать следующие операции:
sizeof() - операция получения размера;
. - операция принадлежности(членства);
.*- операция принадлежности к указателю;
:: - операция разрешения контекста;
:? - условная операция.
●Для следующих операций допустимо использовать только функции-члены:
= - операция присваивания;
() - операция вызова функции;
[] - операция индексации;
-> - операция доступа к членам класса через указатель.
При перегрузке операций, допустим, если требуется сложить два объекта, то это можно записать так:
n = k + t;
Компилятор, в свою очередь, определив операцию класса, объектами которого являются операнды, заменит эту запись на следующую:
n = k.operator+(t);
Пример класса с использованием функции sum() для сложения объектов за место перегрузки операций:
class A
{
int x;
Public:
A() : x(0) {} // тоже самое x = 0;
A(int n) : x(n){} // тоже самое x = n;
~A(){}
void Set_A(int const n){x = n;}
int Get_A() const {return x;}
void Show() const {cout << x << endl;}
A sum(const A & a){return x + a.x;}
};
int main()
{
A n = 3, m = 4, k; // создание и инициализация переменных класса A
cout << n << " " << m << endl; // вывод их содержимого
k = n.sum(m); // сложение переменных n и m
cout << k; // печать результата
return 0;
}
Пример класса с реализованной перегрузкой операции сложения:
class A
{
int x;
Public:
A() : x(0) {} // тоже самое x = 0;
A(int n) : x(n){} // тоже самое x = n;
~A(){}
void Set_A(int const n){x = n;}
int Get_A() const {return x;}
void Show() const {cout << x << endl;}
A operator+(const A &a){return x + a.x;}
};
int main()
{
A n = 3, m = 4, k; // создание и инициализация переменных класса A
cout << n << " " << m << endl; // вывод их содержимого
k = n + m; // сложение переменных n и m
cout << k; // печать результата
return 0;
}
Существует ещё одна особенность перегрузки. Предыдущий пример показал, как перегрузить операцию, если левый операнд является объектом, а что делать, если он будет цифровой константой? Например:
k = 4 + m;
С логической точки зрения это абсолютно нормальная операция, но если вспомнить, что компилятор преобразует её к следующему виду:
k = 4.operator+(m);
то появляется проблема, налагающая некоторые особенности по работе с перегрузкой операций. Решить её помогут функции друзья. Подробнее про друзей читайте в теме 6.5.1.
Вывод данных из класса на экран можно организовать за счёт перегрузки операции << ― поместить в поток. Если добавить эту операцию и операцию присвоения, то наш пример станет выглядеть так:
class A
{
int x;
Public:
A() : x(0) {} // тоже самое x = 0;
A(int n) : x(n){} // тоже самое x = n;
~A(){}
void Set_A(int const n){x = n;}
int Get_A() const {return x;}
void Show() const {cout << x << endl;}
A operator+(const A &a){return x + a.x;}
A& operator=(const A& rhs); // перегрузка операции присвоения
A operator<<(ostream & os) {os << x << endl;} // перегрузка операции поместить в поток
};
Однако, возникает проблема связанная с тем, что воспользоваться перегруженной операцией поместить в поток, необходимо использовать следующий код:
k << cout;
который является довольно запутанным. По этому, в данном случае, также придётся воспользоваться функциями друзьями.
Перегрузка операции присваивания почти ничем не отличается от перегрузки других операций, за исключением того, что требуется вставить проверку: не присваивает ли объект сам себя. Пример такого присваивания:
k = k;
Код этой проверки вместе с кодом перегруженной операции присваивания выглядит так:
A& A::operator=(const A& rhs)
if (this == &rhs) return *this; // проверка — текущий объект и объект rhs
// не являются ли, одним и тем же объектом
x = rhs.x; // присваиваем данные из объекта rhs в текущий
return *this;
}
2.Полиморфное наследование. (динамический полиморфизм)
При использовании механизма наследования мы можем использовать методы из базового класса в классах потомках. То есть, если в базовом классе определена функция, то мы можем её использовать в классе наследнике. Но иногда возникают ситуации, в которых требуется использовать унаследованный метод, который вёл бы себя иначе, чем метод определённый в базовом классе. Такой способ поведения называется полиморфным. При этом ценность полиморфного наследования заключается в том, что можно использовать один метод для разных объектов. При этом модель поведения метода будет выбираться в зависимости от контекста.
Бывают следующие виды полиморфного наследования:
●переопределение методов базового класса в производном классе;
●использование виртуальных функций.
3.Виртуальные функции понятия и реализация.
Виртуальная функция — это функция, которая может быть переопределена классом-наследником, для того чтобы тот имел свою, отличающуюся, реализацию. В языке C++ используется такой механизм, как таблица виртуальных функций
(кратко vtable) для того, чтобы поддерживать связывание на этапе выполнения программы. Виртуальная таблица — статический массив, который хранит для каждой виртуальной функции указатель на ближайшую в иерархии наследования реализацию этой функции. Ближайшая в иерархии реализация определяется во время выполнения посредством извлечения адреса функции из таблицы методов объекта.
Теперь давайте подумаем, как реализовать концепцию виртуальных функций на C. Зная, что виртуальные функции представлены в виде указателей и хранятся в vtable, а vtable — статический массив, мы должны создать структуру, имитирующую сам класс ClassA, таблицу виртуальных функций для ClassA, а также реализацию методов ClassA.
Дата: 2019-03-05, просмотров: 244.