Сравнение: процедурный и объектный подходы
Поможем в ✍️ написании учебной работы
Поможем с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой

Нельзя сказать, что объектный подход всегда лучше процедурного.

Как и любой образ мыслей, объектно-ориентированный подход может быть удобен или не удобен, в зависимости от ситуации.

Бывают задачи, в которых сложно выделить сущность. Например, «алгоритм нахождения наибольшего общего делителя». В нём есть два числа и численные операции над ними. Или задача «разложить число на простые».

Для них замечательно подойдёт процедурный подход.

В эту же группу попадает большинство математических алгоритмов, в которых есть сложные вычисления без выделения дополнительных сущностей. Именно поэтому древние языки программирования, предназначенные для расчетов (Фортран, Алгол) не содержали объектных возможностей.

С другой стороны, при разработке интерфейсов мы имеем дело с ярко выраженными сущностями.

Посетитель открывает окно браузера, затем нажимает кнопку, при этом раскрывается меню. Выборпункта меню приводит к запросу на сервер — все выделенные слова здесь являются сущностями.

Поэтому ООП отлично подходит для веб-разработки и виджетов. Какие-то сущности можно оформлять в объект, а какие-то — нет, это уже решает разработчик.

 

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, просмотров: 217.