ООП c++
ООП c++
https://www.youtube.com/playlist?list=PLQOaTSbfxUtBm7DxblJZShqBQnBAVzlXX
Шаблоны классов с++ примеры. Обобщенные классы.
https://www.youtube.com/watch?v=SDRMUgJwro0
Шаблоны классов с++ примеры. Обобщенные классы.
https://www.youtube.com/watch?v=SDRMUgJwro0
Базовые понятия ООП. Стэк данных: инкапсуляция
Базовые понятия ООП
https://www.youtube.com/watch?v=0uCwMnZVb9o
Исходники
https://github.com/TheEntityCircle/oop-2nd-term/tree/master/2022/lection01
Стэк данных: инкапсуляция - упаковка кода на внешний интерфейс и внутреннюю реализацию
Создание типа stack
Реализация: конструктор, инициализирует поля класса при его создании
Деструктор
Добавляет новый элемент в стек
Создание объекта main
valgrind ./a.out -g - позволяет смотреть ошибки и утечки памяти
https://www.youtube.com/watch?v=0uCwMnZVb9o
Исходники
https://github.com/TheEntityCircle/oop-2nd-term/tree/master/2022/lection01
Стэк данных: инкапсуляция - упаковка кода на внешний интерфейс и внутреннюю реализацию
Создание типа stack
Код: Выделить всё
class stack
{
public:
stack(int size); // Конструктор
~stack(); // Деструктор Очищает память
void push(int a); // <-| Добавляет элемент в стэк
int pop(); // <-|- Методы
void clear(); // <-| Сбрасывает память в 0
private:
int size; // Размер стэка
int* data; // Указатель на данные
int top; // Верхний элемент
};
Код: Выделить всё
stack::stack(int stack_size)
{
this->top = 0;
this->size = stack_size;
this->data = new int[stack_size];
}
Код: Выделить всё
stack::~stack()
{
delete[] this->data;
}
Код: Выделить всё
void stack::push(int a)
{
if(this->top == this->size)
{
cout << "Стэк заполнен" << endl;
return;
}
this->data[this->top] = a;
this->top++;
}
Код: Выделить всё
{
stack s1(10); // Инициализация стэка на 10 элементов
s1.push(1); // Добавление элемента в стэк
si.push(2);
cout << s1.pop << endl;
cout << s1.pop << endl;
}
Базовые понятия ООП. Наследование
Базовые понятия ООП. Наследование
https://www.youtube.com/watch?v=oDcZY31YZns
Исходники
https://github.com/TheEntityCircle/oop-2nd-term/tree/master/2022/lection02
Базовый класс, реализацию класса можно писать как снаружи (см. выше пост), так и внутри (как в данном примере)
Дочерний класс базового класса
Дочерний класс базового класса
Дочерний класс базового класса
Дочерний класс наследуется от дочернего класса базового класса
Создание объекта
https://www.youtube.com/watch?v=oDcZY31YZns
Исходники
https://github.com/TheEntityCircle/oop-2nd-term/tree/master/2022/lection02
Базовый класс, реализацию класса можно писать как снаружи (см. выше пост), так и внутри (как в данном примере)
Код: Выделить всё
class Animal
{
protected: // Закрывает доступ всем снаружи и открывает доступ только своим потомкам (промежуточный вариант между private и public)
bool brain;
public:
Animal() { this->brain = true; }
void say() { cout << " (silence) " << endl; }
bool hasBrain() { return brain; }
};
Код: Выделить всё
class Turtle: public Animal
{
public:
Turtle() { }
};
Код: Выделить всё
class Crow: public Animal
{
public:
Crow() { }
void say() // Перекрыли метод предка и добавили своё поведение
{
cout << "Moo" << endl;
}
};
Код: Выделить всё
class Dog: public Animal
{
public:
Dog() { }
void say() // Перекрыли метод предка и добавили своё поведение
{
cout << "Woof" << endl;
}
};
Код: Выделить всё
class CrazyDog: public Dog
{
public:
CrazyDog() { this->brain = false; }
void say() // Перекрыли метод предка и добавили своё поведение
{
cout << "Woof,Woof,Woof" << endl;
}
};
Код: Выделить всё
int main
{
Turtle t;
return 0;
}
Реализация виртуального (абстрактного)
Реализация виртуального (абстрактного) класса подразумевает обязательное наличие метода в неабстрактном классе
Реализация виртуального метода появляется в потомках
реализация main
Код: Выделить всё
class Figure // Данный класс является абстрактным, потому что содержит в себе метод без реализации
{
protected: // К этому свойству имеют доступ все потомки этого класса
float wight;
public:
float getWeight() // Позволяет получить доступ на чтение снаружи: геттер
{
return weight;
}
// Синтаксис равно нулю - это сигнал компилятору, что реализацию дальше не ожидать
virtual float square() = 0; // Виртуальный метод обязан быть реализован в потомке
};
Код: Выделить всё
class Triangle: public Figure
{
// ...
float square()
{
float p = (a+b+c)/2
return sqrtf(p*(p-a)*(p-b)*(p-c));
}
};
class Rectangle: public Figure
{
//...
float square() { return a*b; }
};
Код: Выделить всё
{
Figure* figs[15]; // Массив указателей на объекты базового типа Figure
//...
// По каждому указателю кладёте объекты производных типов
// т.е. указатель на тип предка, а реализация тип потомка
//...
// Для каждого метода будет вызвана своя реализация
for(int i=0; i<15; i++)
cout << figs[i]->square() << endl;
}
Перегрузка при наследовании
Перегрузка при наследовании:
Указатель на объект класса предка, который указывает на объект класса потомка
Виртуальность включается в том случае, если есть указать на класс предка, а объект класса потомка. Он указывает на объект класса потомка, и вызываемый метод у вас помечен как virtual.
Если есть указать на класс потомка, то указывать на предка он не может.
Указатель на объект класса предка, который указывает на объект класса потомка
Код: Выделить всё
Animal* animal = new CrazyDog();
cout << animal->hasBrain() << endl;
animal->say();
Если есть указать на класс потомка, то указывать на предка он не может.
Интерфейс
Интерфейс - абстрактный класс, у которого все методы виртуальные
Реализация - класс, унаследованный от интерфейса и реализующий все его виртуальные методы
Реализация - класс, унаследованный от интерфейса и реализующий все его виртуальные методы
Пространства имён
https://github.com/TheEntityCircle/oop-2nd-term/tree/master/2022/lection03
Глобальное пространство имён
Переменные, функции, классы в пространстве имён A
Вызываем функции из разных пространств имён
Можно построить иерархию из вложенных пространств имён (как матрёшки)
Вызов функций с явным указанием пути по пространствам имён
Включение содержимого A в глобальное пространство имён
теперь вызывать можно так
Включение одной функции в глобальное пространство именён
Глобальное пространство имён
Код: Выделить всё
int doSomeWork();
Код: Выделить всё
namespace A
{
int doSomeWork();
}
Код: Выделить всё
{
doSomeWork();
A::doSomeWork();
}
Код: Выделить всё
namespace A
{
void hello();
namespace B
{
void hello();
}
}
Код: Выделить всё
{
A::hello();
A::B::hello();
}
Код: Выделить всё
using namespace A;
Код: Выделить всё
{
hello();
B::hello();
}
Код: Выделить всё
using A::B::hello;
Модификаторы доступа: friend (костыльная конструкция)
Модификаторы доступа: friend (костыльная конструкция и по возможности не использовать)
Класс A позволяет получить доступ к приватным переменным для B
Эта дружба только в одну сторону
Если класс B пометит класс A как friend, тогда эта дружба будет взаимной
Класс A позволяет получить доступ к приватным переменным для B
Эта дружба только в одну сторону
Если класс B пометит класс A как friend, тогда эта дружба будет взаимной
Код: Выделить всё
class A
{
// Теперь B - друг A, и ему всё можно в классе А
friend class B;
};
class B
{
public:
void run(A* a)
count << a->secret << endl;
a->secret = -1;
}
Виртуальный деструктор
Виртуальный деструктор
Код: Выделить всё
class IBade
{
virtual ~IBase() = default;
}
class TestClass: public IBase
{
public:
~TestClass();
};
// Теперь деструктор будет корректно вызван
IBase* obj2 = new TestClass(100);
delete obj2;
override - Позволяет отловить ошибку на этапе компиляции
override (перекрытие) - Позволяет отловить ошибку на этапе компиляции
override - говорит о взаимодействии с предками о том, что он что-то перекрывает
final - говорит о том, что дальше перекрывать нельзя
Код: Выделить всё
class IBase
{
public:
virtual void describe(bool foo) = 0;
};
class ClassA : public IBase
{
public:
// Перекрытие метода с аргументами и без аргументов - это разные методы, соответственно компилятор выдаст ошибку
void desctibe() override final;
}
class ClassC final: public ClassA // тут final указывает, что дальше от этого класса наследоваться нельзя
{
public:
// тоже ошибку выдаст, потому что final в родительском классе
void describe() override;
}
final - говорит о том, что дальше перекрывать нельзя
Множественное наследование
Множественное наследование
Обращение к объекту позволяет обращаться по указателю в двух вариантах:
В случае наличия одинаковых методов в предках, обращение к нему должно быть явным
Код: Выделить всё
class Animal // Интерфейс для движка
{
virtual void move() = 0;
};
class Drawing //Интерфейс для визуализатора
{
virtual void show() = 0;
};
// Реализация интерфейсов
class Snake : public Animal , public Drawing
{
public:
void move() override;
void show() override;
};
Код: Выделить всё
// Первый вариант позволяет наследовать интерфейс от Animal
Animal* a = new Snake();
a->move();
//Второй вариант позволяет наследовать интерфейс от Drawing
Drawing* d = new Snake();
d->show();
Код: Выделить всё
class TestClass : public A, public B { ... };
...
{
TestClass t;
// t.describe(); - неявное обращение к методу
t.A::describe(); // явное обращение к методу
t.B::describe(); // явное обращение к методу
}
Множественное наследование: наличие общего предка
Множественное наследование: наличие общего предка
решается виртуальным наследованием (костыль), и если строится на этом, стоит пересмотреть архитектуру
решается виртуальным наследованием (костыль), и если строится на этом, стоит пересмотреть архитектуру
Код: Выделить всё
class Person { ... };
class Faculty : virtual public Prson { ... };
class Student : virtual public Prson { ... };
class Mentor: public Faculty, public Student { ... };
Полиморфизм
Полиморфизм - вызов одной и той же функции, которая ведёт себя по разному
Потому как на самом деле ранее объявлено несколько функций с одинаковыми именами, но с разными параметрами
Ещё один пример полиморфизма:
Если есть массив указателей на тип класс предка и соответственно по этим указателям вы кладёте объекты дочерних классов, то тогда если используемый метод виртуальный - будут вызываться реализации для тех объектов тех классов, которые на само деле там лежат
Если метод square() помечен в классе Figure как виртуальный, то при обращении к square() компилятор будет смотреть какого он типа, и соответственно будет вызывать нужную реализацию.
Реальный метод будет вызываться только на этапе запуска.
Потому как на самом деле ранее объявлено несколько функций с одинаковыми именами, но с разными параметрами
Ещё один пример полиморфизма:
Если есть массив указателей на тип класс предка и соответственно по этим указателям вы кладёте объекты дочерних классов, то тогда если используемый метод виртуальный - будут вызываться реализации для тех объектов тех классов, которые на само деле там лежат
Код: Выделить всё
{
Figure * figs[15];
for (int i=0; i>15; i++)
cout << figs[i]->square() << endl;
}
Реальный метод будет вызываться только на этапе запуска.