Классы, структуры и инкапсуляция
Поймите разницу между структурой и классом, спецификаторами доступа, инкапсуляцией, функциями членов const, статичными членами и современной агрегированной инициализацией.
Что такое класс?
класс в C++ представляет собой тип, определяемый пользователем, который объединяет данные (переменные члены) и операции с этими данными (функции членов) в единое целое. Это объединение называется инкапсуляция — один из четырех столпов ООП.
Ключевое понимание заключается в том, что класс определяет инвариант: набор правил, которые всегда должны быть верными о его внутреннем состоянии. Например, для класса Date может потребоваться, чтобы месяц всегда был между 1 и 12. Инкапсуляция позволяет применять эти инварианты, контролируя доступ к внутреннему состоянию.
Класс vs класс
В C++, struct и class почти идентичны**. Единственным отличием является спецификатор доступа по умолчанию: struct по умолчанию public, а class по умолчанию private. Конвенция: используйте struct для простых агрегатов данных, class, когда у вас есть инварианты для обслуживания.
#include <iostream>
#include <string>
// struct: members are public by default
struct Point {
double x; // public
double y; // public
};
// class: members are private by default
class BankAccount {
std::string owner; // private
double balance; // private
public:
BankAccount(const std::string& name, double initial)
: owner{name}, balance{initial} {}
double get_balance() const { return balance; }
void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
};
int main() {
Point p{3.0, 4.0}; // aggregate initialization
std::cout << p.x << ", " << p.y << '\n';
BankAccount acct{"Alice", 1000.0};
acct.deposit(500.0);
std::cout << acct.get_balance() << '\n'; // 1500
// acct.balance = -999; // ERROR: private member
return 0;
}Указатели доступа и этот указатель
C++ обеспечивает три уровня доступа: public (каждый может получить доступ), private (только класс и его друзья) и protected (класс, его друзья и производные классы). Указатель this является неявным указателем на текущий объект, доступный внутри каждой нестатической функции-члена.
#include <iostream>
#include <string>
class Employee {
private:
std::string name_;
int id_;
protected:
double salary_; // accessible to derived classes
public:
Employee(const std::string& name, int id, double salary)
: name_{name}, id_{id}, salary_{salary} {}
// const member function: promises not to modify the object
const std::string& get_name() const { return name_; }
int get_id() const { return id_; }
// 'this' pointer usage — return *this for method chaining
Employee& set_name(const std::string& name) {
this->name_ = name; // 'this->' disambiguates
return *this;
}
Employee& set_id(int id) {
this->id_ = id;
return *this;
}
void print() const {
std::cout << "Employee{" << name_ << ", #" << id_
<< ", $" << salary_ << "}\n";
}
};
int main() {
Employee e{"Bob", 42, 75000.0};
e.set_name("Robert").set_id(43); // method chaining via *this
e.print(); // Employee{Robert, #43, $75000}
return 0;
}Статические члены и друзья
Статические члены принадлежат к классу, а не к какому-либо случаю. Используйте их для общих функций состояния или полезности. Ключевое слово friend предоставляет нечлену функцию или другой класс доступа к частным членам - используйте экономно, поскольку это нарушает инкапсуляцию.
#include <iostream>
class Widget {
int value_;
// C++17: inline static — define in header, no .cpp needed
inline static int count_ = 0;
public:
explicit Widget(int v) : value_{v} { ++count_; }
~Widget() { --count_; }
static int get_count() { return count_; }
// friend function can access private members
friend std::ostream& operator<<(std::ostream& os, const Widget& w);
};
std::ostream& operator<<(std::ostream& os, const Widget& w) {
return os << "Widget(" << w.value_ << ')'; // accesses private value_
}
int main() {
Widget a{10}, b{20};
std::cout << Widget::get_count() << '\n'; // 2
std::cout << a << '\n'; // Widget(10)
{
Widget c{30};
std::cout << Widget::get_count() << '\n'; // 3
}
std::cout << Widget::get_count() << '\n'; // 2
return 0;
}Агрегированная инициализация и назначенные инициализаторы (C++20)
aggregate — это класс или структура, в которой нет объявленных пользователем конструкторов, нет частных/защищенных нестатических элементов данных, нет виртуальных функций и нет виртуальных базовых классов. C++20 добавляет обозначенные инициализаторы , позволяя вам назвать, какие члены вы инициализируете.
#include <iostream>
#include <string>
struct Config {
std::string host = "localhost";
int port = 8080;
bool use_tls = false;
int max_connections = 100;
};
int main() {
// Traditional aggregate initialization
Config c1{"example.com", 443, true, 200};
// C++20 designated initializers — much more readable
Config c2{
.host = "api.example.com",
.port = 443,
.use_tls = true
// .max_connections uses default value 100
};
std::cout << c2.host << ':' << c2.port << '\n';
std::cout << "TLS: " << std::boolalpha << c2.use_tls << '\n';
std::cout << "Max: " << c2.max_connections << '\n'; // 100
return 0;
}Сделайте членов данных приватными по умолчанию и обнародуйте только то, что необходимо через минимальный публичный интерфейс. Если в классе нет инварианта для поддержания (это просто набор данных), используйте вместо него struct. Не добавляйте геттеров и сеттеров для каждого поля, что нарушает цель инкапсуляции. Вместо этого предоставьте значимые операции, такие как deposit() и withdraw(), а не set_balance().
Если функция-член не изменяет состояние объекта, пометьте его const. Без const вы не можете вызвать функцию на объекте const или через ссылку const, что происходит все время при прохождении объектов const&. Отсутствующий const является распространенным источником ошибок компиляции в больших кодовых базах.
structиclassотличаются только доступом по умолчанию:structявляется общедоступным,classявляется частным.- Инкапсуляция защищает классовые инварианты — используйте данные
privateс значимым публичным интерфейсом - Немутирующие функции
constдля работы с ссылкамиconst - Использовать
inline static(C++17) для статических элементов, определенных в заголовках - Назначенные инициализаторы C++20 делают агрегированную инициализацию читаемой и самодокументирующейся.
friendдолжен использоваться экономно — как правило, для перегрузок операторов, которые требуют частного доступа.
Тест — проверьте свои знания
(15 XP)1. В чем разница между `struct` и `class` в C++?
2. Почему вы должны пометить функции участника доступа как `const`?
3. Что делает `inline static int count_ = 0;` в C++17?