Что такое класс
Класс - это чертёж для пользовательского типа, который объединяет данные (переменные-члены) и поведение (функции-члены) в одну сущность. Если встроенные типы вроде 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 текущего объекта.