Uploaded by FlarseeK

C++ OOP

advertisement
Более серьезное определение ООП:
Объектно-ориентированное программирование (сокр. ООП) — методология
программирования, основанная на представлении программы в виде совокупности
взаимодействующих объектов, каждый из которых является экземпляром
определенного класса, а классы образуют иерархию наследования.
мем жиза капец)
Если говорить простым языком, все что есть в нашей программе это объекты, которые
взаимодействуют между собой.
Например: есть объект человек, у которого есть руки, ноги, рот.
- Объект руки выполняет действие «открыть дверь»
- Объект ноги отвечает за передвижение человека
- Объект рот отвечает за принятие данных «еда» и вывод данных «голос»
ООП применяют для создания больших и самое главное структурированных проектов. В
первую очередь в ООП каждый объект отвечает только за свою задачу и руками
человек не должен ходить, а ртом открывать дверь))
Еще одним преимуществом ООП является удобство чтения кода, когда мы видим класс
Человек, Рука, Рот, мы примерно понимаем какие действия выполняют объекты этого
класса даже не читая код.
Это происходит потому, что в реальной жизни мы тоже мыслим объектноориентированно. Птица летит в небе, Лягушка квакает.
Мы не привыкли к тому, что массив строк летит в небе, а буква L квакает.
Привычно говорить, что ООП — это «способ моделирования реального мира»
Для того, чтобы ООП эффективно работало в проекте, необходимо чтобы выполнялись
«принципы ООП». В языке С++ чаще выделяют «трёх китов», на которых стоит ООП, но
для более глубокого понимания разберем «четырех»:
1) инкапсуляция в первую очередь — это сокрытие сложной реализации от
пользователя и предоставление ему удобного интерфейса для использования
методов объекта.
Например: когда мы говорим объекту «человек» открыть дверь, мы не говорим
ему «вытяни руку», «положи кисть на ручку», «поверни ручку», «потяни на себя».
Задача программиста обеспечить простоту использования методов объекта
пользователем, а все мелкие детали скрыть от его глаз.
Таким образом обеспечивается безопасность и надежность метода. Если мы
сделаем действия при «открытии двери» в другом порядке, то в лучшем случае
ничего не произойдет, а в худшем мы можем сломать логику, дверь или руку.
2) наследование – это способ легко расширить существующий класс, дополнив его
функциональностью.
Например: Класс человек у нас мало чего умел, так как у него не было профессии,
например, как в играх: Алхимик, Лучник или Бард.
Благодаря тому, что мы наследовали эти классы от «человека», у них сохранились
методы ходить, есть, открывать дверь. Но при этом они теперь также умеют что-то
особенное:
- Лучник теперь умеет стрелять из лука
- Алхимик варить зелья
- Бард умеет играть на музыкальных инструментах
Классы, которые мы получили таким способ называются «дочерними». Механизм
наследования позволяет нам избавиться от лишнего повторяющегося кода в
программе
.
3) полиморфизм – это возможность обработки разных типов данных, т. е.
принадлежащих к разным классам, с помощью "одной и той же" функции, или
метода.
Например: Классы кошка, собака и тигр умеют рычать, но все делают это поразному. Мы можем для каждого из этих объектов написать метод «рычать»,
который просто обрабатывает данные своим способом. По сути, мы
перегружаем метод «рычать».
4) абстракция (тот самый четвертый принцип, который обычно не упоминают) – это
принцип согласно, которому не следует выделять те данные, которые мы не будем
использовать в классе.
Например: в программе есть кошки и собаки, которые должны только издавать
голос и ходить. В реальном мире эти милахи еще могут много чего, например
спать, есть, вилять хвостом и т.д. Но мы программируем только то, что важно
нам в конкретных задачах.
Для закрепления информации о принципах ООП советую также почитать в других
источниках в интернете, иначе мем:
Поговорим о модификаторах доступа, которые помогают нам обеспечить
инкапсуляцию объектов. Всего их существует три: public, private и protected.
public (публичный, открытый) позволяет нам обращаться к полям и методам из любого
места где есть доступ к классу. Это ключевое слово, по сути, делает данные объекта
общедоступными, что может привести к неправильному использованию, а также их
случайному удалению/изменению.
Но при этом, в ряде случаев, без этого модификатора просто не обойтись, главное не
злоупотреблять.
почему-то показалось
что подходит по смыслу хых
private (приватный, закрытый) позволяет обращаться к полям и методам только внутри
класса. Важно помнить, что мы можем создать публичные методы для доступа к
приватным полям, обезопасив при этом объект от изменения или неправильного
использования.
В лучшем случае все поля и большая часть методов у класса должны быть приватными.
Так мы обеспечиваем сокрытие данных от пользователя, а взаимодействие будет
осуществляться только с помощью «интерфейсов»
Пользовательский интерфейс класса — это публичные методы, которые выполняют
задачи объекта именно так как задумывал программист.
Например, функция «открыть дверь», как мы ранее писали, может включать себя
множество действий, которые будут работать только в правильном порядке.
Пользователь может не знать этот порядок и случайно сломать логику объекта,
сделав из него кирпич.
protected (защищенный) работает также как и private модификатор, но при
наследовании (о котором мы тоже поговорим обязательно) объект наследник может
обращаться к protected полям родителя, а вот private так не умеет.
Пока не пройдем наследование, можете не использовать protected :)
Отличия класса от структуры:
Классы по умолчанию всё поля и методы защитили модификатором private: кроме
«конструктора по умолчанию»
Структуры по умолчанию всё открыли модификатором public:
Перейдем к примерам с кодом.
В данном примере мы создали класс «Человек» и важными для себя выделили его
возраст и имя. Такая программа может только изменять возраст и выводить надпись «С
днем рождения, <Имя>»
Но важно заметить отсутствие модификатора доступа public: без которого мы не сможем
никак взаимодействовать с полями и методами данного класса.
#include <iostream>
using namespace std;
class Human // Класс - пользовательский тип данных, состоящий из полей и методов
{
int age = 30;
// Поле класса - это какие-либо данные, некая
string name = "Ivan"; // информация, характеризующая данный класс
void birthday() { // Метод класса - функция, которая совершает
age++;
// те или иные действия над полями своего класса
cout << "Happy birthday, " << name << "!\n";
}
};
int main()
{
Human h; // Объект - конкретный экземпляр класса, по сути
// являющийся переменной пользовательского типа данных (класса)
}
В коде, представленном ниже, мы видим, что конструкторов может быть написано
неограниченное количество для разных целей. А также конструктор копирования,
который вызывается, когда мы делаем так:
Human a;
Human b = a;
Когда мы создаем объект, основанный на данных другого объекта, мы копируем все
поля старого объекта в новый объект. Этот конструктор будет работать так даже
если вы его не будете прописывать как в коде ниже.
Но ведь бывают ситуации, когда просто скопировать данные будет неправильно, для
таких случаев «конструктор копирования» пишут вручную (логика написания ложится на
ваши плечи)
// Конструктор - специальный метод класса, который используется
// при создании объекта для корректной инициализации его полей
class Human
{
public:
Human() {
// Конструктор по умолчанию, который
age = 0;
// не принимает параметры
name = "default";
}
Human(int age_, string name_) { // Конструктор от двух параметров
age = age_; // Полям класса присваиваются значения параметров,
name = name_; // передаваемых при вызове конструктора
}
Human(const Human& h) { // Копирующий конструктор позволяет создать
age = h.age;
// полностью идентичный объект, получая входным
name = h.name; // параметром константную ссылку (&) на объект
}
// того же класса. Копирующий конструктор нужен
private:
// если в классе динамически выделяется память
int age;
string name;
};
Если в классе происходит выделение динамической памяти с помощью указателей и
ключевого слова new, необходимо писать «деструктор», это метод, который
автоматически запускается после удаления объекта и очищает данные в памяти (но, к
сожалению, динамическую память сам очищать не умеет, пишем это ручками как в
примере ниже)
// Деструктор - метод, который вызывается при удалении объекта
class Human
{
public:
~Human() { // Деструктор обязательно прописывать вручную
delete age; // когда при удалении объекта нужно освобождать
}
// ресурсы, например, если динамически
private:
// выделялась память, или велась работа с файлами
int* age = new int(0);
};
Сеттеры и Геттеры нам нужны чтобы правильно работать с приватными полями вне
класса. Не нарушать логику их инициализации, а также получать только
значения(копию) поля, а не само поле.
Эти методы совершенно не обязательно прописывать каждый раз и для каждого поля.
Они нужны только там, где без них не обойтись
Если поле не должно изменяться пользователем никогда, то можем не писать
сеттер,
а если пользователю данные из этого поля не нужны, то не пишем и геттер тоже
class Human
{
public:
void set_age(int age_) { // Сеттеры - функции, которые позволяют присваивать
if (age_ > 0) { // значения закрытым переменным класса
age = age_;
}
}
int get_age() { // Геттеры - функции, которые возвращают значения
return age; // закрытых переменных классов
}
private:
int age;
};
// Безопаснее использовать сеттеры и геттеры, чем предоставлять доступ
// напрямую к полям класса, рискуя дать возможность повредить данные
Ключевое слово this это как обращение к объекту внутри объекта. Примерно, как наш
мозг, читая этот текст, подумал о себе)))
Для полей, которые внутри методов класса, запись:
age++;
Равносильна записи:
this->age++;
Но в коде ниже показано одно из преимуществ this.
// this - указатель на объект, владеющий функцией
class Human
{
public:
Human() { cout << "!\n"; }
Human& birthday() { // Через this-> можно обращаться
this->age++; // к полям и методам класса
cout << this->age << " years old\n";
return *this; // С помощью this можно возвращать
}
// текущий объект класса
~Human() { cout << "~\n"; }
private:
int age = 0;
};
Дружественные классы. В данном примере мы видим два класса Собака и Человек, но
мы видим, что у собаки метод «голос» приватный (мы не сможем его вызвать нигде,
кроме как в классе Собака).
Но код, который представлен ниже работает. Это обеспечивает строка friend class
Human; которая открывает доступ для всех приватных полей и методов классу Human,
но использовать эти поля и методы можно только внутри класса Human.
class Dog {
friend class Human; // Теперь класс Human имеет доступ
private:
// к private-членам класса Dog
void speak() {
cout << "Woof!\n";
}
};
class Human {
public:
void dog_speak(Dog& d) {
cout << "Speak!\n";
d.speak();
// Вызов private-метода класса Dog
cout << "Good boy!\n"; // внутри описания класса Human
}
};
int main() {
Human h;
Dog d;
h.dog_speak(d); // Вызов private-метода из экземпляра дружественного класса
}
Статические поля и методы не принадлежат конкретным объектам, они общие для всех
объектов класса (даже если еще нет ни одного объекта в программе).
Такие поля и методы можно вызывать с помощью «::»
Чаще всего их используют для подсчета объектов данного класса в проекте
// Статические члены класса относятся ко всему классу
// сразу, а не к отдельным объектам этого класса
class Human {
public:
static int population; // Объявление статической переменной
Human() {
population++;
}
static void new_born() { // Статический метод может использовать
population++;
// только статические переменные и не
}
// может использовать указатель this
};
int Human::population = 0; // Статические переменные должны быть
// дополнительно определены вне определения класса
int main() {
Human h1, h2, h3;
h1.new_born();
cout << Human::population; // Вывод значения статической переменной
}
Создаём класс Human, применяя все полученные знания:
#include <iostream>
using namespace std;
class Dog { // Объявляем класс Dog
public:
friend class Human; // Делаем класс Dog дружественным классу Human
Dog() {
// Прописываем конструктор по умолчанию
cout << "Dog default Constructor\n";
}
~Dog() {
// Прописываем деструктор
cout << "Dog Destructor\n";
}
private:
void speak() { // Прописываем приватный метод
cout << "Woof!\n";
}
};
class Human { // Объявляем класс Human
public:
Human() { // Конструктор по умолчанию
age = 0;
name = "default";
population++;
cout << "Human default Constructor\n";
}
Human(int age_, string name_) { // Конструктор от двух параметров
age = age_;
name = name_;
population++;
cout << "Human age and name Constructor\n";
}
Human(const Human& h) { // Конструктор копирования
age = h.age;
name = h.name;
population++;
cout << "Human copy Constructor\n";
}
~Human() { // Деструктор
cout << "Human " << name << " Destructor\n";
population--;
}
void set_age(int age_) { // Сеттер для возраста
if (age_ > 0) {
age = age_;
}
}
void set_name(string name_) { // Сеттер для имени
name = name_;
}
int get_age() { // Геттер для возраста
return age;
}
string get_name() { // Геттер для имени
return name;
}
static int get_population() { // Статический метод доступа к статической переменной
return population;
}
void birthday() { // Прописываем методы класса
this->age++;
cout << "Happy birthday, " << this->name << "!\n";
}
void dog_speak(Dog& d) {
cout << "Speak!\n";
d.speak();
cout << "Good boy!\n";
}
private:
static int population; // Статическая переменная класса
int age;
string name;
};
int Human::population = 0; // Обязательное объявление статической переменной вне класса
int main()
{
Human h1;
// Создаём объекты - экземпляры класса,
Human h2(30, "Ivan"); // используя разные конструкторы
Human h3 = h2;
cout << "Total population is " << Human::get_population() << endl; // Вызываем статический метод
h1.birthday();
h2.birthday();
// Вызываем методы для объектов
h1.set_age(35);
h1.set_name("Alexey");
h1.birthday();
cout << h1.get_name() << " is " << h1.get_age() << " years old!\n";
cout << h2.get_name() << " is " << h2.get_age() << " years old!\n";
cout << h3.get_name() << " is " << h3.get_age() << " years old!\n";
Dog d;
// Создаём объект класса Dog
h1.dog_speak(d);
// Вызываем метод, имеющий доступ к приватным полям класса Dog
}
Если дошли до сюда, то вы умница! Если что-то осталось непонятным то можем
разобраться с этим в общем чате
Download