C++ подробное объяснение ООП и как вы должны использовать его в своем коде

Давайте перейдем к делу: что такое ООП?

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

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

Большинство популярных языков программирования поддерживают ООП, включая C++. Это также основная особенность языка C++. И вы будете использовать ООП большую часть времени, чтобы написать отличную программу на C++.

Определение ООП в программировании на C++

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

Сорт

Чтобы создать модульный объект, нам нужен многоразовый проект под названием класс. Класс — это определяемый пользователем тип данных в C++. Он отличается от других примитивных типов данных (int, bool, char и т. д.), поскольку его можно настраивать.

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

Атрибуты. Допустим, мы хотим создать класс с именем Fruit, который содержит такой атрибут, как price . Мы можем просто объявить переменную с типом данных float внутри файла Fruit.

Методы — если мы хотим иметь метод, который устанавливает цену на фрукты. Мы можем определить метод с именем setPrice внутри класса.

Как выглядит наш чертеж:

class Fruit{
public:
   void setPrice(float new_price){price = new_price;}
private:
   float price;
};

Пока что мы создали только план (класс) под названием Fruit. Чтобы создать объект, нам нужно вызвать класс Fruit с именем объекта, как и другие примитивные типы данных.

int main(){
   Fruit banana;
   Fruit apple;
}

В приведенном выше примере создаются только экземпляры объекта путем вызова конструктора по умолчанию, созданного компилятором C++. Потому что мы никогда не указываем конструктор.

Конструктор

Так что же такое конструктор? Конструктор — это метод, вызывающий создание объекта.

Как правило, мы хотим выполнить некоторые методы класса или определить члены переменных класса при создании нового объекта. Вот тут и пригодится конструктор. Вы можете проверить эту страницу, чтобы узнать больше о конструкторах.

Мы можем объявить конструктор следующим образом:

class Fruit{
public:
   Fruit(float _price, std::string _color){
      price = _price;
      color = _color;
   }  
   void setPrice(float new_price){price = new_price;}
private:
   float price;
   std::string color;  
};

Как видите, конструктор выше (Fruit()) требует два аргумента: _price и _color. Конструктор помогает нам создавать значимые данные внутри объектов во время их создания:

int main(){
   Fruit banana(1.25, "yellow");
   Fruit apple(1.60, "red");
}

4 основные концепции ООП

Четыре основных понятия делают C++ чрезмерно мощным (ООП):

1. Инкапсуляция

Инкапсуляция — это обёртывание важной информации внутри объекта. Другими словами, переменные и методы сохраняются внутри объекта при его создании.

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

Чтобы реализовать это ограничение, нам нужно что-то, что позволяет указать «уровень безопасности». И здесь в дело вступают модификаторы доступа.

Модификаторы доступа

Модификаторы доступа public и private, как вы видите в примере выше. (Есть еще один под названием protected , об этом позже)

Мы можем указать члены класса как public или private. По умолчанию всем членам класса private, если они специально не помечены как public.

К общедоступным членам может обращаться любой пользователь объекта, а к закрытым членам может обращаться только сам объект.

Взяв приведенный выше пример, мы объявили price закрытым членом, потому что не хотим, чтобы пользователи имели к нему прямой доступ. Мы определили methodsetPrice как общедоступный член, чтобы позволить другим пользователям устанавливать price только путем явного вызова метода.

Пример:

int main(){
   Fruit banana(1.25, "yellow");
   
   // error   
   std::cout << banana.color << std::endl;
   }

Объект banana не может напрямую обращаться к переменной класса color в main().

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

2. Абстракция

Абстракция данных в ООП означает отображение только важной информации и сокрытие деталей от конечного пользователя. Эта концепция является расширением инкапсуляции.

В классе абстракция относится к отделению интерфейса класса от деталей реализации.

Если вы все еще не можете понять это, вы можете понять это так: «Вы видите только то, что я хочу показать вам».

Абстракция позволяет нам использовать простые классы для выражения сложных деталей.

Возьмем пример из вождения автомобиля:

Вам не нужно знать, как работает двигатель, чтобы управлять автомобилем. Вам не нужно разбираться в технике: как устроена трансмиссия, как работает система охлаждения или как устроено шасси, чтобы выдерживать вес автомобиля, бла-бла-бла…

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

Это похоже на ООП C++. Давайте посмотрим, как мы реализуем абстракцию в C++:

фрукты.h

class Fruit{
public:
   void increasePrice(float percentage);
private:
   float price;
};

fruit.cpp

void Fruit::increasePrice(float percentage)
{
   price *= 1.0 + (percentage / 100);
}

Обычно пользователи видят только файл заголовка, fruit.h, но не исходный файл, fruit.cpp.

Пользователи могут взаимодействовать с классом Fruit через функцию increasePrice(). Но им не нужно знать детали реализации increasePrice() (который скрывается внутри fruit.cpp).

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

Наследование позволяет классам получать свойства и характеристики от другого класса.

Это одна из самых важных особенностей ООП, поскольку она поддерживает повторное использование. Вам не нужно переопределять другой класс, имеющий характеристики, аналогичные классу, который вы определили ранее.

Например, давайте воспользуемся Fruit, который мы определили ранее:

class Fruit{
public:
   void setPrice(float new_price){price = new_price;}
protected:
   float price;
   std::string color;  
};

Мы можем вывести другие классы из Fruit, такие как Durian и Strawberry. Таким образом, нам не нужно снова определять переменные-члены — price и color.

Еще одним преимуществом наследования является то, что производные классы могут иметь разные переменные-члены, такие как thorns и is_berry:

class Durian : public Fruit {
private:
   bool thorns = true;
};
class Strawberry : public Fruit {
private: 
   bool is_berry = false;
};

Durianи Strawberry известны как производные классы. Напротив, Fruit, унаследованный другими классами, называется базовым классом.

Некоторые из вас уже могли заметить другой спецификатор доступа — protected в Fruit . protected предназначен для использования в наследовании классов.

Производный класс не наследует доступ к закрытым элементам данных своего базового класса. Чтобы предотвратить прямой доступ других пользователей к price и color, позволяя Durian и Strawberry наследовать обе переменные-члены, используется protected.

Спецификаторы доступа также используются в наследовании при определении производных классов. В этом примере мы используем открытое наследование.

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

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

Полиморфизм в переводе с греческого означает иметь несколько форм. В ООП это означает способность функции работать по-разному в зависимости от того, как она вызывается.

Перегрузка

В C++ полиморфизм обычно достигается за счет перегрузки, которая также известна как полиморфизм времени компиляции. И есть два типа перегрузки: перегрузка оператора и перегрузка функции. Перегрузку операторов понять сложнее, поэтому мы не будем обсуждать ее здесь для простоты этой статьи.

Перегрузка функций очень проста. Вы поймете, что я имею в виду, прочитав пример ниже:

class Fruit{
public:
   Fruit(float _price){price = _price;}
   void increasePrice(double percentage)
   {
      price *= 1.0 + (percentage / 100);
   }
   void increasePrice(int cents)
   {
      price += cents/100.0;
   }
private:
   float price;
};
int main()
{
   Fruit orange;
   Fruit apple;
   Orange.setPrice(0.60);
   Apple.setPrice(0.80);
   orange.increasePrice(0.20);
   apple.increasePrice(10);

Результат:

Price of orange: 0.72
Price of apple: 0.90

Мы определили две функции с одинаковыми именами, но с разными типами данных аргументов: одна — double, а другая — int. Программа будет выполнять любую функцию в зависимости от входных данных.

Итак, когда вы передаете параметр double, increasePrice(0.20) увеличивает цену апельсина на 20%. Но если вы передадите параметр int, increasePrice(10) увеличит цену яблока на 10 центов.

Переопределение

Полиморфизм можно осуществить и другим способом: Переопределение. Этот подход называется полиморфизмом времени выполнения.

Чтобы реализовать переопределение, вы должны объявить функцию virtual в базовом классе, одновременно определяя другую функцию в производном классе с тем же именем функции и типами аргументов.

class Fruit{
public:
   virtual void printSkinColor() 
   { 
      std::cout << "The skin colour of fruit is " << color << "\n";
   }
protected:
   std::string color;  
};
class Durian : public Fruit {
public:
   void printSkinColor()
   {
      std::cout << "The skin colour of durian is " << color << "\n";
   }
};

В конечном итоге вы получите разные результаты, вызывая разные классы printSkinColor() .

Полиморфизм с использованием перегрузки и переопределения поддерживает согласованность и улучшает читаемость кода. Это также способствует повторному использованию кода.

Заключение

На этом мы заканчиваем знакомство с объектно-ориентированным программированием C++.

Изучать C++, как правило, сложно, но ООП всегда отличный выбор для начала. Вы получите больше удовольствия, изучая показатели (ну, десятки людей бросили, но я думаю, вам будет весело!).

Если вам нравится этот блог, поддержите его, похлопав в ладоши. Помните, что вы можете хлопать до 50 раз.

Повышение уровня кодирования

Спасибо, что являетесь частью нашего сообщества! Перед тем, как ты уйдешь:

  • 👏 Хлопайте за историю и подписывайтесь на автора 👉
  • 📰 Смотрите больше контента в публикации Level Up Coding
  • 🔔 Подписывайтесь на нас: Twitter | ЛинкедИн | "Новостная рассылка"

🚀👉 Размещение таких разработчиков, как вы, в топовых стартапах и технологических компаниях