Перейти к основному содержимому

Продолжаем изучать классы

Инкапсулированный объект датчика между сигналом и управляющей программой

Класс полезен не только тем, что объединяет поля и методы. Главная польза — контроль состояния. Если поле закрыто, объект может сам проверять, какие значения допустимы.

Коротко

Закрытые поля защищают состояние. Открытые методы задают разрешённые действия. Конструктор с параметрами помогает создать объект сразу в правильном состоянии.

Проверяющий метод

#include <iostream>
using namespace std;

class Sensor {
public:
bool set(int value) {
if (value < 0) {
return false;
}
if (value > 400) {
return false;
}

dist = value;
return true;
}

int get() {
return dist;
}

private:
int dist = 0;
};

int main() {
Sensor front;

if (front.set(250) == true) {
cout << front.get() << endl;
}

return 0;
}

Метод set не позволяет записать отрицательное расстояние или слишком большое значение.

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

#include <iostream>
using namespace std;

class Sensor {
public:
Sensor(int value) {
if (value < 0) {
value = 0;
}
if (value > 400) {
value = 400;
}
dist = value;
}

int get() {
return dist;
}

private:
int dist;
};

int main() {
Sensor front(150);
cout << front.get() << endl;
return 0;
}

Объект front создаётся сразу со значением 150.

Интерфейс и реализация

Интерфейс — это то, чем пользуется внешний код. Реализация — то, как класс устроен внутри.

ЧастьПримерКто должен знать
интерфейсset, getпользователь класса
реализацияполе dist, проверки внутри setавтор класса

Если внешний код не трогает поле напрямую, внутреннее устройство класса можно изменить спокойнее.

Интерфейс класса скрывает внутренние поля

Разделение на файлы

В большом проекте класс обычно делят на заголовочный файл и файл реализации.

Sensor.h

#ifndef SENSOR_H
#define SENSOR_H

class Sensor {
public:
Sensor(int value);
bool set(int value);
int get();

private:
int dist;
};

#endif

Sensor.cpp

#include "Sensor.h"

Sensor::Sensor(int value) {
if (value < 0) {
value = 0;
}
if (value > 400) {
value = 400;
}
dist = value;
}

bool Sensor::set(int value) {
if (value < 0) {
return false;
}
if (value > 400) {
return false;
}
dist = value;
return true;
}

int Sensor::get() {
return dist;
}

main.cpp

#include <iostream>
#include "Sensor.h"
using namespace std;

int main() {
Sensor front(100);
cout << front.get() << endl;
return 0;
}

Компиляция из консоли:

g++ main.cpp Sensor.cpp -o main

Защитные строки в .h

#ifndef SENSOR_H
#define SENSOR_H
...
#endif

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

Практика

  1. Создайте класс Sensor с закрытым полем dist.
  2. Добавьте метод set, который принимает только значения от 0 до 400.
  3. Добавьте метод get.
  4. Добавьте конструктор с начальным значением.
  5. Разделите класс на Sensor.h, Sensor.cpp и main.cpp.

Проверьте себя

  1. Почему поля лучше делать закрытыми?
  2. Что возвращает метод set, если значение неверное?
  3. Чем интерфейс отличается от реализации?
  4. Зачем класс делят на .h и .cpp?
  5. Для чего нужны защитные строки в заголовке?
Ориентиры для самопроверки

Сначала ответьте без подсказки. Ответ можно считать полным, если вы:

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

Если один из пунктов объяснить не получается, найдите соответствующую главу статьи, перечитайте её и повторите ответ.

Словарь статьи

ТерминЗначение
интерфейсдоступные снаружи методы класса
реализациявнутренний код и поля класса
заголовочный файлфайл с объявлением класса
файл реализациифайл с кодом методов
защита заголовкаприём против повторного подключения
состояние объектазначения его полей

Связанные темы

Источники