Объектно-ориентированное программирование
Парадигмы программирования
Элементы объектной модели
Преимущества объектной модели
Ограничения доступа
Пример использования класса
Скрытый указатель *this
Если функция возвращает *this, можно объединить вызовы в цепочку.
Конструктор – это особый вид функции-члена класса, которая автоматически вызывается при создании экземпляра объекта этого
Конструктор с параметрами
Замечания о конструкторах
Деструкторы
Области видимости для классов
Спецификатор памяти statiс
Спецификатор const
Дружественные функции
Основные свойства и правила использования спецификатора friend:
Наследование
Основные правила использования базовых и производных классов:
Наследование атрибутов компонентов базового класса:
Множественное наследование
Использование виртуального класса
Полиморфизм
Делегирующие конструкторы – конструкторы, которым разрешено вызывать другие конструкторы
Конструктор копирования
Конструктор перемещения
Отношения между классами
Пять принципов объектно-ориентированного программирования и проектирования, (SOLID)
5.63M
Категория: ПрограммированиеПрограммирование

Объектно-ориентированное программирование

1. Объектно-ориентированное программирование

Введение

2. Парадигмы программирования

Структу́рное программи́рование
Функциона́льное программи́рование
Логи́ческое программи́рование
Автома́тное программи́рование
Объе́ктно-ориенти́рованное
программи́рование
Событи́йно-ориенти́рованное
программи́рование
Агентно-ориентированное программи́рование

3.

Объекто-ориентированное
программирование
Использует в качестве
основных логических
конструктивных
элементов
объекты,
а
не
алгоритмы
2. Каждый объект является экземпляром (instance)
определенного класса (class);
3. Классы образуют иерархии
1.

4. Элементы объектной модели

• Абстракция
• Инкапсуляция
• Модульность
• Иерархия
• Контроль типов
• Параллелизм
• Персистентность

5. Преимущества объектной модели

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

6.

Плюсы ООП:
Визуально код становится проще, и его легче читать. Когда всё разбито на
объекты и у них есть понятный набор правил, можно сразу понять, за что
отвечает каждый объект и из чего он состоит.
Меньше одинакового кода решается наследованием.
Сложные программы пишутся проще. Каждую большую программу
можно разложить на несколько блоков, сделать им минимальное
наполнение, а потом раз за разом подробно наполнить каждый блок.
Увеличивается скорость написания. На старте можно быстро создать
нужные компоненты внутри программы, чтобы получить минимально
работающий прототип.
Минусы:
Сложно понять и начать работать. Подход ООП намного сложнее
обычного процедурного программирования
Требует больше памяти. Объекты в ООП состоят из данных, интерфейсов,
методов и много другого, а это занимает намного больше памяти, чем
простая переменная.
Иногда производительность кода будет ниже. Из-за особенностей подхода
часть вещей может быть реализована сложнее, чем могла бы быть.

7.

Состояние
Статические
свойства
Динамические
значения
ОБЪЕКТ
Поведение
Изменение
состояния
Передача
сообщения
Индивидуальность

8.

Класс - это множество объектов, имеющих общую
структуру и общее поведение.

9.

struct Person
{char *Fam_name[25];
int age;
void set_name(char*);
void set_age(int);
} person1, person2;
class PERSON
{ private:
char *Fam_name[25];
int age;
pubIic:
void set_name(char*);
void set_age(int);
};
Class A
public
член-данные
protected
член-данные
private
член-данные
ЧЛЕН-ФУНКЦИИ
ГЛОБАЛЬНЫЕ ФУНКЦИИ
Производный Class B
Член функции

10. Ограничения доступа

Private
Public
Protected
Член-данные и член- Член-данные и член- Член-данные и членфункции
доступны функции
только через член- из
функции
класса.
доступны функции
любого
данного программы,
места только через член-
где функции
имеется
класса
представитель
потомков.
класса.
доступны
данного
и
его

11. Пример использования класса

class Date
{ public:
int m_year{};
int m_month{};
int m_day{};
void print() //открытый интерфейс
{ std::cout << m_year << '/' << m_month << '/' << m_day; }
void setDate(int month, int day, int year)
{m_month = month; m_day = day; m_year = year; }
};
int main()
{ Date today { 2023, 3, 7 }; // инициализация списком возможна так как доступ public
today.print(); // компилятор интерпретирует m_day, как today.m_day ,связанный объект, неявно
передается функции-члену
today. setDate(3,8,2024);
return 0; }

12.

class Date
// члены по умолчанию закрыты
{ int m_month;
int m_day;
int m_year;
public:
void setDate(int month, int day, int year)
{m_month = month; m_day = day; m_year = year; }
void print()
{ std::cout << m_month << '/' << m_day << '/' << m_year; }
void copyFrom(const Date &d)
{m_month = d.m_month; m_day = d.m_day; m_year = d.m_year; }
};
int main()
{ Date date;
date.setDate(3, 8, 2024);
date.print();
Date copy;
copy.copyFrom(date);
copy.print();
copyFrom() является членом Date, может напрямую обращаться не только к
return 0; }
закрытым членам неявного объекта, с которым она работает, но также означает,
что она имеет прямой доступ к закрытым членам Date параметра d! Если бы
параметр d был другого типа, этого не было бы.

13.

Функция доступа – это короткая открытая функция, задачей которой
является получение или изменение значения закрытой переменной-члена
Геттеры (аксессоры) – это функции, возвращающие значение закрытой
переменной-члена.
Сеттеры (мутаторы) – это функции, которые устанавливают значение
закрытой переменной-члена

14. Скрытый указатель *this

class Simple
{ private:
int m_id;
public:
Simple(int id) : m_id{ id } { }
void setID(int id) { m_id = id; }
int getID() { return m_id; }
};
int main()
{ Simple simple{1};
simple.setID(2);
std::cout << simple.getID() << '\n';
return 0; }
Указатель this – это скрытый
константный указатель, который содержит
адрес объекта, для которого была вызвана
функция-член.
При компиляции компилятор
преобразует simple.setID(2);
• simple из объектного префикса ->в аргумент функции!
setID(&simple, 2);
void setID(int id) { m_id = id; }
преобразуется в:
void setID(Simple* const this, int id) { this->m_id = id; }

15. Если функция возвращает *this, можно объединить вызовы в цепочку.

Если функция возвращает *this, можно
объединить
вызовы в цепочку.
class Calc
{ private:
int m_value {0};
public:
Calc& add(int value) { m_value += value; return *this; }
Calc& sub(int value) { m_value -= value; return *this; }
Calc& mult(int value) { m_value *= value; return *this; }
int getValue() { return m_value; }
}
int main()
{
Calc calc;
calc.add(5).sub(3).mult(4);
std::cout << calc.getValue() << '\n';
return 0; }

16.

Основные свойства и правила
использования указателя this:
каждый новый объект имеет свой скрытый
указатель this;
this указывает на начало своего объекта в памяти;
this не надо дополнительно объявлять;
this передается как скрытый параметр во все
нестатические член-функции своего объекта;
this - это локальная переменная, которая
недоступна за пределами объекта.

17. Конструктор – это особый вид функции-члена класса, которая автоматически вызывается при создании экземпляра объекта этого

Конструктор – это особый вид функции-члена класса, которая
автоматически вызывается при создании экземпляра объекта этого
класса
Конструктор, который не принимает параметров (или все параметры имеют значения
по умолчанию), называется конструктором по умолчанию
class Date
{ int m_month;
int m_day;
int m_year;
public:
Date() // конструктор по умолчанию
{m_month=1; m_day=1; m_year =2000; }
void setDate(int month, int day, int year)
{m_month = month; m_day = day; m_year = year; }
void print()
{ std::cout << m_month << '/' << m_day << '/' << m_year; }
void copyFrom(const DateClass &d)
{m_month = d.m_month; m_day = d.m_day; m_year = d.m_year; }
};
int main()
{ Date date;
date.print();
return 0; }

18. Конструктор с параметрами

class Date
{ int m_month;
int m_day;
int m_year;
public:
Date() {m_month=1; m_day=1; m_year =2000; }
Date()=default;
// явно заданный конструктор по умолчанию все
данные инициируются 0.
Date(int month, int day=1, int day=2000)
{m_month= month; m_day= day; m_year = day; }
Date(int month, int day=1, int day=2000):
m_month(month), m_day(day), m_year(day) { }
void setDate(int month, int day, int year)
{m_month = month; m_day = day; m_year = year; }
void print()
{ std::cout << m_month << '/' << m_day << '/' << m_year; }
void copyFrom(const DateClass &d)
{m_month = d.m_month; m_day = d.m_day; m_year = d.m_year; }
};
int main()
{ Date date {3,20,2024}; // инициализация списком вызывает Date(3,20,2024)
Date date1 (3,20,2024 ); // прямая инициализация вызывает Date(3,20,2024)
Date date1 (4 ); // прямая инициализация вызывает Date(4,4,2000)
return 0; }

19.

Основные свойства и правила использования
конструкторов:
конструктор имеет то же имя, что и класс, в котором он объявляется;
конструктор не возвращает значения (даже типа void);
если у класса нет конструктора, компилятор сгенерирует конструктор,
без аргументов автоматически.
если в вашем классе есть какие-либо другие конструкторы, неявно
сгенерированный конструктор предоставлен не будет.
использование Date()= default - добавление конструктора по умолчанию с
пустым телом
конструктор не наследуется в производных классах.
конструктор может иметь параметры, заданные по умолчанию;
конструктор - это функция, но его нельзя объявить с ключевым словом
virtual;
невозможно получить в программе адрес конструктора;
конструктор вызывается автоматически только при описании объекта;
объект, содержащий конструктор, нельзя включить в виде компонента в
объединение;
конструктор класса Х не может иметь параметр типа Х, может иметь
параметр ссылку на объект типа Х (&X), в этом случае он называется
конструктором для копирования (сору constructor) класса Х.

20. Замечания о конструкторах

Компилятор выполняет выделение памяти для объекта до вызова
конструктора.
Конструкторы определяют, кому разрешено создавать объект. То есть
объект класса может быть создан только в том случае, если может быть
найден соответствующий конструктор.
Конструкторы можно использовать для инициализации объектов.
Всегда инициализируйте все переменные-члены в ваших объектах.
Не следует пытаться вызвать конструктор для повторной инициализации
существующего объекта. Хотя это может компилироваться, результаты
будут не такими, как вы планировали (вместо этого компилятор создаст
временный объект, а затем отбросит его).

21. Деструкторы

особый вид функции-члена класса, которая выполняется при уничтожении объекта
этого класса
когда объект выходит за пределы области видимости, или динамически
выделенный объект явно удаляется с delete, автоматически вызывается деструктор
класса для выполнения очистки, необходимой перед удалением объекта из памяти.
для простых классов деструктор не нужен потому, что C++ автоматически очистит
память
объект не уничтожится до тех пор, пока не выполнится деструктор

22.

class IntArray
{
private:
int *m_array{};
int m_length{};
public:
IntArray(int length) // конструктор
{
m_array = new int[static_cast<std::size_t>(length)]{};
m_length = length;
}
~IntArray() // деструктор
{
// удаляем динамически выделенный ранее массив
delete[] m_array;
}
void setValue(int index, int value) { m_array[index] = value; }
int getValue(int index) { return m_array[index]; }
int getLength() { return m_length; }
};
int main()
{
IntArray ar(10); // выделяем 10 чисел int
for (int count=0 ; count < ar.getLength(); ++count)
ar.setValue(count, count+1);
std::cout << "The value of element 5 is: " << ar.getValue(5) << '\n';
return 0;
}

23.

Основные свойства и правила использования
деструкторов:
деструктор имеет то же самое имя, что и класс, в котором он
объявляется, с префиксом ~ (тильдой);
деструктор не возвращает значения ;
деструктор не наследуется в производных классах;
производный класс может вызвать деструкторы для его базовых
классов;
деструктор не имеет параметров;
класс может иметь только один деструктор;
деструктор - это функция, и он может быть виртуальным;
невозможно получить в программе адрес деструктор);
если деструктор не задан в программе, то он будет автоматически
сгенерирован компилятором;
деструктор можно вызвать так же, как обычную функцию,
например:
date *my_day;
my_day->date::~date().
деструктор вызывается автоматически при разрушении объекта.

24.

Время выполнения конструктора и деструктора
class Simple
{
private:
int m_nID{};
public:
Simple(int nID): m_nID{ nID }
{std::cout << "Constructing Simple " << nID << '\n';}
~Simple()
{std::cout << "Destructing Simple" << m_nID << '\n';}
int getID() { return m_nID; }
};
int main()
{
// Размещаем Simple в стеке
Simple simple( 1 );
std::cout << simple.getID() << '\n';
// Размещаем Simple динамически
Simple *pSimple=new Simple( 2 ) ;
std::cout << pSimple->getID() << '\n';
// Мы разместили pSimple динамически, поэтому должны удалить его.
delete pSimple;
return 0;
} // здесь simple выходит из области видимости

25.

Объявление и разрушение глобальных объектов:
class А
{ int i;
pubIic:
A(int I) : i(I)
{ cout « "class А" « i « " constructor\n";}
~A()
{ cout « "class А" « i « "destructor\n"; }
};
А а1(1),а2(2);
void main(void)
{ getch(); }
Результаты выполнения этой программы:
class А 1 constructor
class А2 constructor
< здесь можно нажать любую клавишу>
class А2 destructor
class А 1 destructor

26.

Объявление и разрушение локальных объектов.
class А
{ int i;
pubIic:
A(int I) : i(I) { cout « "class А" « i « " constructor\n"; }
~A()
{ cout « "class А" « i « "destructor\n"; }
};
void fuпction(void)
{ cout« "begin\n";
А а1(1),а2(2);
cout «"end\n"; }
void main(void)
{ cout« "before\n";
function();
cout « "after\n"; }
}
Результаты:
before
begin
class А 1 constructor
class А2 constructor
end
class А2 destructor
class А 1 destructor
after

27.

Создание объектов в динамически выделяемой
памяти
class А
{ int i;
pubIic:
A(int I) : i(I) { cout « "class А" « i « " constructor\n"; }
~A () { cout « "class А" « i « "destructor\n"; }
};
void main(void)
{ А *р1 = new А(1);
А *р2 = new А(2);
delete р1;
delete р2; }
Результаты :
class А 1 constructor
class А2 constructor
class А 1 destructor
class А2 destructor

28.

Объявление объектов в виде компонентов в друrих классах.
class а
{int j;
pubIic
a(int J) : j(J) { cout « "class а" « j « " constructor\n"; }
~a() { cout « "class а" « j « "destructor\n"; }
};
class b
{int i;
а а1;
pubIic:
b(int i, int j} : а1 (j}, i(i) { cout « "class b" « i « "constructor\n"; }
~b() { cout « "class b" « i « "destructor\n"; }
};
void main(void)
{ b а1(1, 1};
b a2(2,1);}
}
Результаты:
class а1 constructor
class b1 constructor
class а1 constructor
class b2 constructor
class b2 destructor
class а1 destructor
class b1 destructor
class а1 destructor

29. Области видимости для классов

int x = 2;
class Example
{
void f () {x = 0;}
short x
};
void f () {::x = 0;}

30. Спецификатор памяти statiс

статические члены существуют, даже если объекты класса не были созданы! Как и
глобальные переменные, они создаются при запуске программы и уничтожаются при
завершении программы.
class Something
{public:
static int s_value;
};
int Something::s_value( 1 );
int main()
{ Something first;
Something second;
first.s_value = 2;
std::cout << first.s_value << '\n'; //2
std::cout << second.s_value << '\n'; //2
return 0;
}
int main()
{ // мы не создаем какие-либо
экземпляры объектов типа Something
Something::s_value = 2;
std::cout << Something::s_value << '\n';
return 0; }

31.

class Something
{private:
static int s_idGenerator;
int m_id;
public:
// получаем следующее значение из генератора id
Something() { m_id = s_idGenerator++; }
int getID() const { return m_id; }
};
int Something::s_idGenerator ( 1 ); // запускаем наш генератор id
со значения 1
int main()
{ Something first;
Something second;
Something third;
std::cout << first.getID() << '\n';
std::cout << second.getID() << '\n';
std::cout << third.getID() << '\n';
return 0;
}

32. Спецификатор const

После того, как константный объект класса был инициализирован с помощью
конструктора, любая попытка изменить переменные-члены объекта запрещается,
так как это нарушит константность объекта. Это включает в себя как изменение
переменных-членов напрямую (если они являются открытыми), так и вызов
функций-членов, которые устанавливают значения переменных-членов.
class Stack {
char s[MaxSize];
int top;
public:
Stack () {top = 0;}
void Look_Top() const {cout << s[top];}
void push() {top++;}
};
const Stack s1;
Stack s2;
s2.Look_Top();
s1.push();
s1.Look_Top();

33. Дружественные функции

функция, которая может получить доступ к закрытым членам класса, как если бы она
была членом этого класса. Не имеет значения, объявляете ли вы дружественную
функцию в закрытом или открытом разделе класса
class Accumulator
{
private:
int m_value;
public:
Accumulator() { m_value = 0; }
void add(int value) { m_value += value; }
friend void reset(Accumulator &accumulator);
};
void reset(Accumulator &accumulator)
{accumulator.m_value = 0;}
int main()
{
Accumulator acc;
acc.add(5); // накапливающий сумматор
reset(acc); // сбрасываем сумматор в 0
return 0;
}
class Value
{
private:
int m_value;
public:
Value(int value) { m_value = value; }
friend bool isEqual(const Value &value1,
const Value &value2);
};
bool isEqual(const Value &value1, const Value &value2)
{
return (value1.m_value == value2.m_value);
}

34.

Сравнить компоненты объектов разных классов,
имеющие атрибут private
class my_class2;
class my_ class 1
{ int а;
friend void fun(my_class1&,my_class2&);
pubIic:
my_class1(int А) : а(А) {};
};
class my_class2
{ int а;
friend void fun(my_class1&,my_class2&);
pubIic:
my_class2(int А) : а(А) {};
};
void fun(my_class1& M1,my_class2& М2)
{ if (М1.а == М2.а) cout « "equal\n";
else cout « "not equal\n";
void main(void)
{ my_class1 mс1(100);
my_class2 mc2( 100);
fun(mc1,mc2);}

35.

Функция одного класса со спецификатором friend для
другого класса.
class Х;
class У
{
int а;
void Y(int c): a(c){};
pubIic:
void display(X* рХ);
};
class Х
{
int а;
void X(int C): a(C){};
pubIic:
friend void Y::display(X*);
};
void Y::display(X* рХ)
{ cout« pX->a « '\t' « а «endl; }
void main(void)
{ Х my_X(100);
У my_У(200);
my_Y.display(&my_X); / / Результат: 100 200
}

36.

class Storage
{
private:
int m_nValue;
double m_dValue;
public:
Storage(int nValue, double dValue)
{m_nValue = nValue;
m_dValue = dValue; }
// Сделаем класс Display другом Storage
friend class Display;
};
class Display
{private:
bool m_displayIntFirst;
public:
Display(bool displayIntFirst) { m_displayIntFirst = displayIntFirst; }
void displayItem(const Storage &storage)
{std::cout << storage.m_nValue << ' ' << storage.m_dValue << '\n'; }
};
int main()
{
Storage storage(5, 6.7);
Display display(false);
display.displayItem(storage);
return 0;
}

37. Основные свойства и правила использования спецификатора friend:

Основные свойства и правила использования
спецификатора friend
:
friend функции не являются компонентами класса,
но получают доступ ко всем его компонентам;
если friend функции одного класса не являются
компонентами другого класса, то они вызываются
так же, как и обычные rлобальные функции (без
операторов . и ->);
если friend функции одного класса не являются
компонентами другого класса, то они не имеют
указателя this;
friend функции не наследуются в производных
классах;
отношение friend не является транзитивным.

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

Организация связи между абстрактными типами
данных, при которой имеется возможность на
основании
существующих
типов
данных
порождать новые типы
Супер класс
Базовый класс
Производный
класс
Производный
класс
Простое наследование
Базовый класс
Базовый класс
Производный
класс
Множественное наследование

39.

class employee
{ std::string & name; // имя
int income;
// доход
employee * next; // следующий служащий
public:
employee (const std::string & n= “ “, int i=0); // конструктор
const std::string& getName() const { return name; }
int getincome() const { return income; }
};
employee::employee(const std::string& n , int i) : name(n), income(i)
{ next = 0;}
class manager : public employee
{ int level;
// уровень
employee * group; // подчиненные
public:
manager(const std::string &, int, int, employee *);
void print() const;
};
void manager::print() const
{ employee::print();
cout << "руководит : ";
group->getName();}
manager::manager(char* n, int i,int l, employee * g):
employee(n,i), level(l), group(g)

40. Основные правила использования базовых и производных классов:

Пусть функция F принадлежит базовому классу Б. Тогда в
производном классе П можно:
1) полностью заменить функцию F (старая Б::F и новая П::F);
2) доопределить (частично изменить) функцию F;
3) использовать функцию Б::F без изменения.
- Если объявить указатель рБ на базовый класс, то ему можно
присвоить значение указателя на объект производного класса;
- указателю рП на производный класс нельзя присвоить значение
указателя на объект базовоrо класса;
- регулирование доступа к компонентам базового и производного
классов осуществляется с помощью атрибутов private, public и
protected;
- производный класс может быть в свою очередь базовым.
Множество классов, связанных отношением наследования
базовый - производный, называется иерархией классов.

41. Наследование атрибутов компонентов базового класса:

наследник
PRIVATE
PROTECTED
PUBLIC
PRIVATE
PROTECTED
PUBLIC
:PROTECTED
:PUBLIC
:PRIVATE родитель

42.

class base
{protected:
int x;
public:
char * str;
void f(int, char*);
};
class generate : private base
{
protected:
int base::x;
public:
char * base::str;
base::f;
…}

43. Множественное наследование

class base1
{public:
Часть унаследованная от класса
int field;
base1
char * str;
};
Часть унаследованная от класса
class base2
base2
{public:
Собственная
часть
класса
int field;
int data;
generate
};
class generate :public base1, public base2
{…};
Generate ex;
ex.field = 4;
ex.base1::filed = 4;

44.

base
class base
{public:
int field;
gen1
char * str;
};
class gen1 : public base
{public:
float s;
};
class gen2 : public base
{public:
char * name;
};
class global : public gen1, public gen2
{…} ex;
base
gen2
global
Часть унаследованная от класса
base
Часть унаследованная от класса
gen1
Часть унаследованная от класса
base
Часть унаследованная от класса
gen2
Собственная часть класса global

45. Использование виртуального класса

class gen1 : virtual public base{...};
class gen2 : virtual public base{...};
class global : public gen1, public gen2
{…} ex;
base
gen1
gen2
global
Формально конструктор класса global будет иметь
вид:
global::global() : base(), gen1(), gen2() {...}

46. Полиморфизм

class Base
{ public:
virtual int f(const int &d)
{return d;}
int CallFunction(const int &d)
{ return f(d)+1; }
}
};
class Derived: public Base
{ public:
virtual int f(const int &d)
{ return d*d; }
};
int main()
{ Base a;
cout << a.CallFunction(5)<< endl;
Derived b;
cout << b.CallFunction(5)<< endl;
return();
}

47.

class Clock
{ public:
virtual void print() const { cout << "Clock!" << endl; }
};
class Alarm: public Clock
{ public:
virtual void print() const { cout << "Alarm!" << endl; }
};
void settime(Clock &d)
{ d.print(); }
Clock W;
settime(W);
Alarm U;
settime(U);
Clock *c1 = &W;
c1->print();
c1 = &U;
c1->print();
}
((Alarm *)c1)->print();

48.

Правила описания и использования виртуальных функций:
Виртуальная функция может быть только методом класса.
Любую перегружаемую операцию-метод класса можно
сделать виртуальной.
3. Виртуальная функция, как и сама виртуальность,
наследуется.
4. Виртуальная функция может быть константной.
5. Если в базовом классе впервые объявлена виртуальная, то
функция должна быть либо чистой (virtual int f(void) = 0;),
либо для нее должно быть задано определение.
6. Если в базовом классе определена виртуальная функция,
то метод производного класса с такими же именем и
прототипом автоматически является виртуальным.
7. Конструкторы не могут быть виртуальными.
8. Статические методы не могут быть виртуальными.
9. Деструкторы могут (чаще — должны) быть.
10. Если некоторая функция вызывается с использованием ее
полного имени, то виртуальный механизм игнорируется.
1.
2.

49.

Вызов виртуальной функции может не
являться виртуальным в некоторых случаях:
Вызывается не через указатель или ссылку:
global object;
object.f();
Вызывается через указатель или ссылку, но с
уточнением имени класса:
base * p;
global object;
p = &object;
p->f();
// виртуальный вызов
p->global::f();
// не виртуальный вызов
вызывается в конструкторе или деструкторе базового
класса.

50.

Раннее и позднее связывание
Связывание относится к процессу, который используется для
преобразования идентификаторов в адреса.
Раннее связывание - компилятор может напрямую связать имя
идентификатора с машинным адресом, заменяет вызов функции на адрес
Один из способов получить позднее связывание – использовать указатель
на функцию
int add(int x, int y)
{ return x + y;}
int main()
{int (*pF)(int, int) = add;
std::cout << pF(2, 1) << '\n';
return 0;
}

51.

Таблица виртуальных методов (vtable, VTM) - это таблица поиска функций,
используемая для разрешения вызовов функций в режиме позднего
связывания
class Base
{public:
virtual void function1() {};
virtual void function2() {};};
class D1: public Base
{public:
virtual void function1() {};};
class Base
{public:
FunctionPointer* __vptr;
virtual void function1() {};
virtual void function2() {};};
class D1: public Base
{public:
virtual void function1() {};};

52.

Чистые виртуальные функции
class Base
{public:
// обычная виртуальная функция
virtual const char* getName() const { return "Base"; }
// чистая виртуальная функция
virtual int getValue() = 0;
// ошибка компиляции: невозможно установить невиртуальной функции
значение 0
int doSomething() = 0;
};
Интерфейсный класс – это класс, не имеющий данных, а все
функции являются чистыми виртуальными! Этот класс имеет только
определение и не имеет реальной реализации. class IErrorLog

53.

//интерфейсный класс ошибки
class IErrorLog
{public:
virtual bool openLog(const char *filename) = 0;
virtual bool closeLog() = 0;
virtual bool writeError(const char *errorMessage) = 0;
virtual ~IErrorLog() {} };
double mySqrt(double value, IErrorLog &log)
{if (value < 0.0)
{log.writeError(«аргумент меньше 0");
return 0.0; }
else
{return std::sqrt(value);}

54. Делегирующие конструкторы – конструкторы, которым разрешено вызывать другие конструкторы

Пусть необходимо:
class Foo
{ public:
Foo()
{ // код для выполнения A }
Foo(int value)
{ // код для выполнения A
// код для выполнения B }
};
Реализуем:
class Foo
{ public:
class Foo
{
public:
Foo()
{
// код для выполнения A
}
Foo(int value)
{
Foo(); // не работает, т.к. создается новый объект Foo,
//который немедленно отбрасывается, т.к. он не
//хранится в переменной
// код для выполнения B
}
};
Foo() { // код для выполнения A }
Foo(int value): Foo{} // использовать конструктор по умолчанию Foo() для выполнения A
{ // код для выполнения B }
};
Конструктору, который делегирует выполнение другому конструктору, не
разрешается выполнять инициализацию каких-либо членов самостоятельно.
Делегировать нельзя косвенно рекурсивно.

55. Конструктор копирования

– это особый тип конструктора, используемый для создания нового объекта как копии существующего объекта.
Созданный по умолчанию конструктор копирования использует метод инициализации, называемый
поэлементной инициализацией(поверхностное копирование).
Поэлементная инициализация означает, что каждый член копии инициализируется напрямую членом
копируемого класса.
class String
{
char* str = 0;
int size = 0;
public:
String(char* arg = (char*)"") // конструктор по умолчанию
{set(arg);}
char* out() { return str; };
void set(char* arg) // установка нового значения строки
{if (str != 0) // освобождаем память если в строке что-то было
delete[] str;
size = strlen(arg) + 1;
str = new char[size];
strcpy(str, arg);}
};
int main()
{
cout << "До изменения" << endl;
String s = (char*)"abc";
String p = s;
cout << "s: " << s.out() << endl;
cout << "p: " << p.out() << endl;
s.set((char*)"rty");
cout << "После изменения" << endl;
cout << "s: " << s.out() << endl;
cout << "p: " << p.out() << endl;
}
Конструктор копирования по умолчанию и операторы присваивания по умолчанию подходит для
классов, не содержащих динамически размещаемых переменных.

56.

Классы с динамически размещаемыми переменными должны иметь
конструктор копирования и оператор присваивания, выполняющие глубокое
копирование.
int main()
class String
{
char* str = 0;
int size = 0;
public:
String(char* arg);
String(String& right) // Конструктор копии
{ set(right.str);}
char* out() { return str; };
void set(char* arg)
{if (str != 0)
delete[] str;
size = strlen(arg) + 1;
str = new char[size];
strcpy(str, arg);}
};
{
cout << "До изменения" << endl;
String s = (char*)"abc";
String p = s;
cout << "s: " << s.out() << endl;
cout << "p: " << p.out() << endl;
s.set((char*)"rty");
cout << "После изменения" << endl;
cout << "s: " << s.out() << endl;
cout << "p: " << p.out() << endl;
}

57.

Конструктор копирования и оператор присваивания почти эквивалентны – > один объект в
другой.
Конструктор копирования инициализирует новые объекты
Оператор присваивания заменяет содержимое существующих объектов
Если новый объект должен быть создан до того, как может произойти копирование,
используется конструктор копирования (передача или возврат объектов по значению).
Если новый объект не нужно создавать до того, как может произойти копирование, используется
оператор присваивания.

58.

class Fraction //Класс для хранения дробного числа в виде целочисленных числителя и знаменателя
{private:
int m_numerator;
int m_denominator;
public:
Fraction(int numerator=0, int denominator=1) :
m_numerator(numerator), m_denominator(denominator) {}
Fraction(const Fraction &copy) :
m_numerator(copy.m_numerator), m_denominator(copy.m_denominator)
{ std::cout << "Copy constructor called\n"; }
Fraction& operator= (const Fraction &fraction) // Перегруженное присваивание
{ // делаем копию
m_numerator = fraction.m_numerator;
m_denominator = fraction.m_denominator;
// возвращаем существующий объект
return *this;}
};
int main()
{ Fraction f1(5,3);
Fraction f2(7,2);
Fraction f3(9,5);
f1 = f2 = f3; // цепочка присваиваний
return 0;}

59. Конструктор перемещения

Цель– передать владение ресурсами от одного объекта к другому (что обычно намного
дешевле, чем создание копии).
В то время как копирующие конструкторы принимают в качестве параметра константную
lvalue-ссылку, перемещающие используют в качестве параметра неконстантные rvalue-ссылки.
Чаще всего это r-значение будет литералом или временным значением
template<class T>
class Auto_ptr4
{
T* m_ptr;
public:
Auto_ptr4(T* ptr = nullptr) :m_ptr(ptr)
{}
~Auto_ptr4()
{delete m_ptr;}
// Конструктор копирования
Auto_ptr4(const Auto_ptr4& a)
{
m_ptr = new T;
*m_ptr = *a.m_ptr;
}
// Конструктор перемещения
Auto_ptr4(Auto_ptr4&& a)
: m_ptr(a.m_ptr)
{a.m_ptr = nullptr; }
};

60. Отношения между классами

Ассоциация – композиция часть –целое:
компонент является частью класса;
компонент может принадлежать только одному классу одновременно;
компонент существует под управлением объекта класса;
компонент не знает о существовании объекта класса.
class Point2D
{int m_x;
int m_y;
public:
Point2D(): m_x{ 0 }, m_y{ 0 };
Point2D(int x, int y) : m_x{ x }, m_y{ y };
friend std::ostream& operator<<(std::ostream& out, const Point2D &point)
{
out << '(' << point.m_x << ", " << point.m_y << ')';
return out;}
void setPoint(int x, int y)
{
m_x = x;
m_y = y;}
};

61.

class Creature
{ std::string m_name;
Point2D m_location;
public:
Creature(const std::string &name, const Point2D &location)
: m_name{ name }, m_location{ location };
friend std::ostream& operator<<(std::ostream& out, const Creature &creature)
{
out << creature.m_name << " is at " << creature.m_location;
return out;}
void moveTo(int x, int y)
{
m_location.setPoint(x, y);
}

62.

Агрегация
часть является частью объекта класса;
часть может принадлежать более чем одному классу одновременно;
существование части не управляется классом;
часть не знает о существовании объекта.
class Teacher
{std::string m_name{};
public:
Teacher(const std::string& name : m_name{ name }
const std::string& getName() const { return m_name; }};
class Department
{const Teacher& m_teacher;
public:
Department(const Teacher& teacher): m_teacher{ teacher }};
int main()
{Teacher bob{ "Bob" };
{ Department department{ bob };}
std::cout << bob.getName() << " still exists!\n";
return 0;}

63. Пять принципов объектно-ориентированного программирования и проектирования, (SOLID)

S: Single Responsibility Principle (Принцип единственной
ответственности Каждый класс должен решать лишь одну задачу).
class Robot {
public:
void move() { /*Метод для передвижения*/ }
void speak() { /*Метод: сказать фразу*/ }};
class Movement { public: void move() { }};
class Speaker {public: void speak() { }};
class Robot {
public:
void move() { /* использование movement*/ }
void speak() { /* использование speaker*/ }
private:
Movement movement; // Логика передвижения
Speaker speaker; // Логика произнесения фразы
};

64.

O: Open-Closed Principle (Принцип открытости-закрытости. Программные
сущности (классы, модули, функции) должны быть открыты для
расширения, но не для модификации).
struct Character {
void displayInfo(const std::string& type) {
if (type == "Knight") std::cout << "Я
Рыцарь";
if (type == "Wizard") std::cout << "Я
Маг";
}
};
struct Character { virtual void displayInfo() = 0; };
struct Knight : public Character {
void displayInfo() override {
std::cout << "Я Рыцарь"; }
};
struct Wizard : public Character {
void displayInfo() override {
std::cout << "Я Маг";}
};

65.

L: Liskov Substitution Principle (Принцип подстановки Барбары Лисков.
Классы-наследники могут использоваться вместо родительских классов, не
нарушая работу программы)

66.

@Override
class Robot implements Movable, Speakable {
public
@Override
void move() { /*Сложная логика движения*/ }
@Override
public void move() { /*Сложная логика движения*/ }
public
@Override
void speak() { /*Сложная логика произнесения фразы*/ }
} public void speak() { /*Сложная логика произнесения фразы*/ }
}
// Реализуем только необходимые интерфейсы
class
// Реализуем
Drone implements
только необходимые
Flyable {
интерфейсы
@Override
class Drone implements Flyable {
public
@Override
void fly() { /*Сложная логика полета*/ }
} public void fly() { /*Сложная логика полета*/ }
}
I: Interface Segregation Principle (Принцип разделения интерфейса. Узкая
специализация интерфейсов: клиенты не должны зависеть от интерфейсов,
которые они не используют).
class Movable { void move(); }
class Speakable { void speak(); }
class Flyable { void fly(); }
class Robot : public Movable,
Speakable {
public void move() { }
public void speak() {}
}
class Drone: public Flyable {
public void fly() { }
}

67.

D: Dependency Inversion Principle (Принцип инверсии зависимостей.
1)Модули верхних уровней не должны зависеть от модулей нижних
уровней. Оба типа модулей должны зависеть от абстракций.
2)Абстракции не должны зависеть от деталей. Детали должны зависеть
от абстракций.).
struct Database {
void saveData(User user) { }};
class UserService {
private:
Database database;
public:
void addUser(User user) {
database.saveData(user);}};
class IDatabase {
public:
virtual void saveData(User user) = 0;};
class Database : public IDatabase { public: void saveData(User user) override { }};
class UserService {
private:
IDatabase& database;
public:
UserService(IDatabase& db): database(db) {}
void addUser(User user) {database.saveData(user);}}
English     Русский Правила