Menu

Классы в C++: определяем объекты, члены и методы

Узнайте, как классы C++ объединяют данные и поведение в переиспользуемые типы: объявление переменных-членов и методов, создание объектов, разделение public/private и подводные камни вроде неинициализированных членов и указателя this.

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

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

Класс - это чертёж для пользовательского типа, который объединяет данные (переменные-члены) и поведение (функции-члены) в одну сущность. Если встроенные типы вроде int и double описывают отдельные значения, то класс позволяет смоделировать целую концепцию - банковский счёт, точку в 2D, игрока - как единое значение, которое можно передавать.

Вы уже пользовались классами, не определяя их: std::string и std::vector - это классы из стандартной библиотеки. Вызов name.length() или v.push_back(3) - это вызов функции-члена у объекта. Теперь вы создадите собственные типы тем же способом, а затем перейдёте к конструкторам для их инициализации.

Определение класса и создание объектов

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

rex и luna - два отдельных объекта. Изменение rex.name не затрагивает luna.name - каждый объект хранит свои данные. К члену обращаются через оператор точки . у объекта (или через ->, когда у вас есть указатель на него).

public и private

По умолчанию всё в class является private: к этому могут обращаться только функции-члены самого класса. Метка public: открывает члены для внешнего кода. Это разделение - суть инкапсуляции: вы предоставляете безопасный интерфейс и скрываете внутренние данные, чтобы их нельзя было перевести в некорректное состояние.

Поскольку count является private, ни один вызывающий код не сможет подсунуть отрицательное или бессмысленное значение - он обязан идти через increment(). const после списка параметров value() обещает, что функция не изменит объект, что позволяет вызывать её для const-объектов и сообщает читателю о намерении. Полный рассказ о public, private и protected смотрите в спецификаторах доступа.

Объявление и определение методов

Для небольших классов определять методы внутри тела (как выше) - нормально. Для крупных классов принято объявлять методы внутри класса и определять их тела снаружи, используя синтаксис области видимости ClassName::method. Это сохраняет определение класса читаемым - как сводку интерфейса.

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

Указатель this

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

Без this-> запись x = x; внутри setX присвоила бы параметр самому себе и оставила член нетронутым - незаметная ошибка. this->x делает всё однозначным. Многие кодовые базы вообще избегают проблемы, называя параметры иначе (например, setX(int newX)), но в реальном коде вы будете постоянно встречать this->.

Типичные ошибки

Несколько ловушек с классами раз за разом ловят программистов:

  • Пропуск закрывающей точки с запятой. Определение класса заканчивается на };. Уберите ; - и получите поток непонятных ошибок, указывающих на то, что идёт после класса, а не на сам класс.
  • Забыли инициализировать члены. Члены встроенных типов вроде int и double не обнуляются автоматически. Чтение неинициализированного члена - это неопределённое поведение. Задавайте членам значения по умолчанию (int count = 0;) или инициализируйте их в конструкторе.
  • Доступ к private-членам извне. c.count = 5; для private-члена - это ошибка компиляции; пользуйтесь публичным методом. Так и должна работать инкапсуляция.
  • Путать класс с объектом. Dog.bark(); неверно - Dog это тип. Методы вызывают для объектов: rex.bark();.
// Неинициализированный член - чтение 'age' это неопределённое поведение:
class Cat {
public:
    int age;            // нет значения по умолчанию
};

Cat c;
std::cout << c.age;     // мусорное значение, а не 0

Далее: Конструкторы

Задавать каждый член вручную после создания объекта - rex.name = ...; rex.age = ...; - утомительно и легко забыть, и именно так появляются неинициализированные члены. Следующая страница посвящена конструкторам: специальным функциям, которые запускаются автоматически при создании объекта и позволяют гарантировать, что каждый объект начинает существование в корректном состоянии, с помощью чистого однострочного синтаксиса Dog rex("Rex", 4);.

Часто задаваемые вопросы

В чём разница между классом и объектом в C++?

Класс - это чертёж: он описывает, какие данные (переменные-члены) и какое поведение (функции-члены) есть у типа. Объект - это конкретный экземпляр, созданный по этому чертежу. class Dog { ... }; определяет тип один раз; Dog rex; создаёт реальный Dog, который можно использовать. Один класс может породить множество независимых объектов.

В чём разница между class и struct в C++?

Технически только в уровне доступа по умолчанию: члены class по умолчанию private, а члены struct - public. У обоих могут быть функции-члены, конструкторы и наследование. По соглашению struct используют для простых наборов данных, а class - для типов с поведением и инвариантами, которые нужно защищать.

Что делает указатель this в классе C++?

Внутри функции-члена this - это указатель на объект, для которого вызвана функция. Используйте его, чтобы отличить параметр от члена (this->x = x;) или вернуть текущий объект. Для обычного доступа к членам он редко нужен - написав x, вы уже обращаетесь к x текущего объекта.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ