Классы в C++: руководство для начинающих!

обложка статьи

Всем привет! Объекты очень важная вещь в программировании, которая может облегчить решения многих задач. Например нужно вести дневник пользователя: год рождения, имя, фамилия, местожительство, все это можно продолжать еще очень долго. Поэтому, как и другие языки программирования C++ обзавелся - классами.

Как создать класс

Чтобы объявить класс нужно использовать данную конструкцию:

class <имя класса> {

};

Обычно <имя класса> прописывают с заглавной буквы. Также в конце обязательно должна присутствовать точка с запятой (;).

Что такое класс

Это абстрактный тип данных. Он сочетает в себе два функционала:

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

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

класс примеры c объектами

У каждого вами созданного класса могут быть свойства и методы. Свойства - это все что может хранить информацию, которую вы потом можете заполнять (переменные, массивы и т.д.).

Так свойства класса Worker (рабочий) может иметь - имя, производительность (полезность работы) за 6 месяцев, среднюю производительность.

class Worker {
  public:  // об этом ниже
    string name; // имя
    // производительность в течении 6 месяцев
    int academic_performance[6];
    // средняя производительность
    int avarage_AP;
};

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

class Worker {
  public:  // об этом ниже
    // функция для вычисления средней производительности
    void discover_avarage_AP () {
      double answer = 0;  
      for (int i = 0; i < 6; i++) {
        answer += academic_performance[i];
      }

      avarage_AP = answer / 6; // вычисляем с.п.
    }
    string name; // имя
    // производительность в течении 6 месяцев
    int academic_performance[6];
    // средняя успеваемость
    int avarage_AP;
};

Чтобы обратится к свойствам и методам класса нужно перед названием имени свойства поставить точку ..

<имя класса>.<название свойства или метода>;

Что такое ООП

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

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

В ООП входит такие свойства:

  • Инкапсуляция - это возможность задавать разную область видимости определенной части класса .

  • Наследование - это свойство создавать новый класс на базе старого. Такие классы называют потомками, например, есть класс магазин, на базе которого можно создать потомки продуктовый_магазин, магазин_одежды (не обращайте внимание, что название на русском языке).

  • Полиморфизм - возможность создать объекты с одинаковым интерфейсом, но с разной их реализацией. Например, есть три класса треугольник, круг и квадрат. У каждого из них есть метод SquarePlis(), который вычисляет площадь фигуры. Но для каждого класса функция реализована по-разному.

    SquarePlis() {
    
      square = a * a;  // для квадрата
    
      square = 1 / 2 * h * a;  // для треугольника
    
      square = 3.14 * r * r;  // для круга
    
    }
  • Абстракция - это возможность выбирать только те свойства или функции, которые нам необходимы. Например, при создании класса про работника понадобится указать его имя, возраст, образование, но никак его цвет волос, глаз, рост и тому подобное.

Спецификаторы доступа public и private

Для разграничение содержимого класса, например которое пользователю лучше не трогать, были добавлены спецификаторы доступа public, private и protected (о нем пойдет речь в следующем уроке про наследование). Это и есть инкапсуляция, которую мы упоминали выше.

  • public - дает публичный доступ, содержимому, которое в нем указано. Так можно обратится к любой переменной или функции из любой части программы.

  • private - запрещает обращаться к свойствам вне класса. Поэтому под крылом этого доступа часто находятся именно объявления переменных, массивов, а также прототипов функций.Оперировать его содержимым можно только из методов класса. Получается все преобразования: добавление, вычитание нужно будет делать в функции (их еще называют set и get функциями).

    class Worker {
      public:
        void discover_avarage_AP () {
          // ...
          set_avarage_AP(answer);
          // вместо avarage_AP = answer / 6;
        }
    
        void set_avarage_AP (double score) {
          avarage_AP = score / 6;
        }
    
        double get_avarage_AP () {
          return avarage_AP;
        }
      private:
        string name; // имя
        // успеваемость за 6 месяцев
        int academic_performance[6];
        // средняя успеваемость
        int avarage_AP;
    };
  • В строке 5: используем функцию set_avarage_AP() для установки нового значения avarage_AP.

  • В строке 13: находится функция get_avarage_AP(), которая передает значения avarage_AP.

Если отсутствуют права доступа, то по умолчанию будет стоять доступ private.

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

Функции get и set классов

При создании класса обычно создают функции в названии которых присутствуют слова set и get.

  • set - в ней инициализируют свойства класса.

    set_number() {    // set и имя переменной
      cin >> number;  // которую инициализируют 
    }
    private:
      int number;  // наша переменная
  • get - выводит свойства конечному пользователю.

    get_number() {    // get и переменная 
      return number;  // которую возвращают
    }
    private: 
      int number;  // наша переменная

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

Давайте используем созданный класс на практике создав карточку об одном работнике, например Иване. Класс разместим в файле workers.h, который подключим к главному файлу main.cpp таким образом #include "workers.h".

// workers.h
#include <string>

using namespace std;

class Worker {
  public:
    void discover_avarage_AP () {
      double answer = 0;

      for (int i = 0; i < 6; i++) {
        answer += academic_performance[i];
      }
      set_avarage_AP(answer);
      // вместо avarage_AP = answer / 6;
    }

    void set_avarage_AP (double score) {
      avarage_AP = score / 6;
    }
    // здесь находятся set и get функции
    double get_avarage_AP () {
      return avarage_AP;
    }
    void set_name(string a) {
      // считываем имя
      name = a;
    }
    void  set_academic_performance (vector  v) {
      // заполняем 6 месячныю успеваемость
      for (int i = 0; i < 6; i++) { academic_performance[i] = v[i];
      }
    }
    string get_name () {
      // выводим имя
      return name;
    }
    // конец set и get функций

  private:
    // средняя успеваемость
    int avarage_AP;
    string name; // имя
    // успеваемость за 6 месяцев
    int academic_performance[6];
};

В строках 19-34: находятся set и get функции для инициализации наших свойств. Вот какие именно:

  • get_name() - считывает имя работника.
  • get_academic_perfomence() - считывает успеваемость на работе за шесть месяцев.

Функции set имеют такое же название, только вместо get - set.

А вот как выглядит main.cpp

// main.cpp
#include <iostream>
#include <vector>
#include "workers.h"

using namespace std;

int main() {
  Worker employee;

  string name;
  vector <int> average_balls;

  cout << "Your name: "; cin >> name;
  cout << "Your academic performance for 6 months: " << endl;

  for (int i = 0; i < 6; i++) {
    int one_score;
    cout << i + 1 << ") "; cin >> one_score;
    average_balls.push_back(one_score);
  }

  employee.set_name(name);
  employee.set_academic_performance(average_balls);

  employee.discover_avarage_AP();
  
  cout << endl << employee.get_name() << endl;
  cout << "Avarage academic performance: " << employee.get_avarage_AP() << endl;

  return 0;
}

Для создания объекта employee мы указали класс Worker.

  • В строках 14 - 21: считываем пользовательские данные.
  • В строках 23- 24: отсылаем полученные данные классу (функциями set).
  • Вычисляем среднюю успеваемость вызвав функцию discover_avarage_AP() в строке 26.
  • В строках 28 - 29: выводим все свойства: имя, фамилию, возраст, средний балл.

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

Your name: Иван

Your academic performance for 6 months:
1) 3
2) 4
3) 5
4) 5
5) 3
6) 4

Иван
Avarage academic performance: 4

Process returned 0 (0x0) execution time : 0.010 s
Press any key to continue.

Как создать массив объектов

Кроме создания единичного объекта класса можно создать целый массив объектов. Нужно всего лишь в конец создаваемого объекта добавить его количество ([n]).

Worker employee[5];

cout << "Name for second employee : "; cin >> name;

employee[2].set_name(name);

Здесь мы просим пользователя ввести имя для второго работника. Далее вводим введенное имя в объект employee[2] с помощью функции set_name().

Вынесение методов от логики

Давайте отделим реализацию всех методов отдельный файл.

// main.cpp
#include <iostream>
#include "Worker.h"
void Worker::discover_avarage_AP () {
  double answer = 0;

  for (int i = 0; i < 6; i++) {
    answer += academic_performance[i];
  }

  set_avarage_AP(answer);
}

void Worker::set_avarage_AP (double score) {
  avarage_AP = score / 6;
}
// set - get функции
double Worker::get_avarage_AP () {
  return avarage_AP;
}
void Worker::set_name(string a) {
  // считываем имя
  name = a;
}
void Worker::set_academic_performance (vector v) {
  // заполняем 6 месячныю успеваемость
  for (int i = 0; i < 6; i++) {
      academic_performance[i] = v[i];
  }
}
string Worker::get_name () {
  // выводим имя
  return name;
}

А вот файл с логикой класса.

class Worker {
  public:
    // высчитывание среднего балла
    void discover_avarage_AP ();
    void set_avarage_AP (double score);
    // вывод средней успеваемости
    double get_avarage_AP ();
    // получение и вывод имени
    void set_name(string a);
    string get_name ();
    // получение баллов за шесть месяцев
    void set_academic_performance (vector v);
  private:
    // средняя успеваемость
    int avarage_AP;
    string name; // имя
    // успеваемость за 6 месяцев
    int academic_performance[6];
};

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

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

Поэтому перед каждой функцией стоит данный префикс.

Инициализация объектов с помощью указателей

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

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

<название класса> *<имя объекта> = new <название класса>

Для освобождения памяти используется оператор delete:

delete <название объекта>;

При обращении к свойствам и методам объекта применяется такая конструкция (->):

<имя объекта>-><название свойства или метода>

Давайте используем данную реализацию в нашей программе.

// main.cpp
#include <iostream>
#include <vector>
#include workers.h

using namespace std;

int main() {
  Worker *employee = new Worker;

  string name;

  vector <int> average_balls;

  cout << "Your name: "; cin >> name;
  cout << "Your academic performance for 6 months: " << endl;

  for (int i = 0; i < 6; i++) {
    int one_score;
    cout << i + 1 << ") "; cin >> one_score;
    average_balls.push_back(one_score);
  }

  employee->set_name(name);

  employee->set_academic_performance(average_balls);
  employee->discover_avarage_AP();

  cout << endl << employee->get_name() << endl;
  cout << "Avarage academic performance: " << employee->get_avarage_AP() ;

  return 0;
}

Конструктор и деструктор класса

Конструктор - это метод, который вызывается во время создания класса. Также он имеет несколько условий для существования:

  1. Он должен называться также, как класс.
  2. У него не должно быть типа функции (bool, void …).
class Worker {
    public:
        // Конструктор для класса Worker
        Worker (string my_name_is, string my_last_name_is)
        {
            name = string my_name_is;
            last_name = my_last_name_is;
        }

    private:
        string name;
        string last_name;
};

int main()
{
    Worker employee ("Ваня", "Шарапов");
    // вот как будет выглядеть, если мы создаем через указатель
    Worker *employee_CocaCola = new Worker("Дмитрий","Талтонов");
    return 0;
}

Деструктор - тоже функция, только уже вызывается после удаления класса. Кроме условий, которые имеет конструктор, деструктор еще должен начинаться с тильды (~).

class Workers {
    public:
        // Деструктор класса Workers
        ~Workers()
        {
            std::cout << "I'm sorry, my creator(" << std::endl;
        }
};

int main()
{
    Workers *employee = new Workers;
    Worker employee_CocaCola;
    // Удаление объекта
    delete student;  // после этого сработает деструктор employee
    return 0;
}
// А вот где сработает деструктор employee_CocaCola

Обсуждение