Menu
Coddy logo textTech

Члены экземпляра против статических

Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 27 из 104.

До сих пор каждая переменная-член, которую вы создавали, принадлежала конкретному объекту. Когда вы создаете два объекта Player, у каждого из них есть свои собственные name и health. Они называются членами экземпляра — они существуют отдельно для каждого экземпляра класса.

Но что, если вам нужны данные, общие для всех объектов? Например, для отслеживания общего количества существующих игроков.

Вот здесь и появляются статические члены. Статический член принадлежит самому классу, а не какому-либо конкретному объекту.

class Player {
    std::string name;           // Член экземпляра - у каждого игрока свой собственный
    int health;                 // Член экземпляра
    
    static int playerCount;     // Статический член - общий для ВСЕХ игроков
    
public:
    Player(std::string n) : name(n), health(100) {
        playerCount++;          // Увеличивается при создании любого игрока
    }
    
    ~Player() {
        playerCount--;          // Уменьшается при уничтожении любого игрока
    }
};

Ключевое отличие: члены экземпляра доступны через объект, в то время как статические члены доступны через само имя класса. Представьте члены экземпляра как «один на объект», а статические члены — как «один на класс».

Члены экземпляраСтатические члены
Уникальны для каждого объектаОбщие для всех объектов
Создаются при создании объектаСуществуют до создания любого объекта
Доступ через объект: player.healthДоступ через класс: Player::playerCount

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

challenge icon

Задание

Легко

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

Вы создадите два файла для организации вашего кода:

  • Product.h: Определите класс Product, который управляет отдельными продуктами и одновременно отслеживает данные по всему инвентарю. Ваш класс должен иметь:
    • Члены экземпляра: name (string) и price (double) — уникальные для каждого продукта
    • Статический член totalProducts, который подсчитывает, сколько продуктов существует в данный момент
    • Статический член totalValue, который отслеживает общую стоимость всех продуктов
    • Конструктор, который принимает имя и цену, инициализирует члены экземпляра и обновляет оба статических счетчика (увеличивает количество, добавляет цену к общей стоимости)
    • Деструктор, который обновляет статические счетчики при удалении продукта (уменьшает количество, вычитает цену из общей стоимости)
    • Метод getInfo(), который возвращает строку в формате: "<name>: "<name>: $<price>"lt;price>"
    • Статический метод getInventoryStats(), который возвращает строку: "Products: <count>, Total Value: "Products: <count>, Total Value: $<value>"lt;value>"
  • main.cpp: Продемонстрируйте, как статические члены разделяются между всеми объектами, в то время как члены экземпляра остаются уникальными. Считайте два названия продуктов и их цены из входных данных (name1, price1, name2, price2 — каждое на отдельной строке), затем:
    • Выведите начальную статистику инвентаря, используя имя класса: Product::getInventoryStats()
    • Создайте блок области видимости с помощью фигурных скобок и внутри него создайте объект Product с первым именем и ценой. Выведите информацию о нем и текущую статистику инвентаря.
    • После завершения блока (первый продукт уничтожен), выведите "After first scope:", а затем статистику инвентаря
    • Создайте еще один Product со вторым именем и ценой. Выведите информацию о нем и статистику инвентаря.
    • Создайте третий Product с именем "Bonus" и ценой 5.00. Выведите финальную статистику инвентаря.

Не забудьте определить статические члены вне класса в заголовочном файле (после определения класса). Форматируйте цены с двумя знаками после запятой, используя std::fixed и std::setprecision(2) из библиотеки <iomanip>.

Подключите ваш заголовочный файл в main.cpp с помощью #include "Product.h".

Шпаргалка

C++ поддерживает два типа членов класса: члены экземпляра и статические члены.

Члены экземпляра уникальны для каждого объекта — у каждого экземпляра есть своя собственная копия:

class Player {
    std::string name;  // У каждого игрока есть свое имя
    int health;        // У каждого игрока есть свое здоровье
};

Статические члены являются общими для всех объектов класса — существует только одна копия, которая принадлежит самому классу:

class Player {
    static int playerCount;  // Общий для ВСЕХ игроков
    
public:
    Player(std::string n) : name(n), health(100) {
        playerCount++;  // Увеличиваем общий счетчик
    }
    
    ~Player() {
        playerCount--;  // Уменьшаем при уничтожении
    }
};

Ключевые различия:

Члены экземпляраСтатические члены
Уникальны для каждого объектаОбщие для всех объектов
Создаются при создании объектаСуществуют до создания любого объекта
Доступ через объект: player.healthДоступ через класс: Player::playerCount

Статические методы можно вызывать, используя имя класса, и они могут обращаться только к статическим членам:

class Player {
    static int playerCount;
    
public:
    static int getPlayerCount() {
        return playerCount;  // Могут обращаться только к статическим членам
    }
};

// Вызов с использованием имени класса
int count = Player::getPlayerCount();

Статические члены должны быть определены вне класса (обычно в файле .cpp или после определения класса в заголовочном файле):

int Player::playerCount = 0;

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

Попробуйте сами

#include <iostream>
#include <string>
#include "Product.h"

using namespace std;

int main() {
    // Чтение входных данных
    string name1;
    double price1;
    string name2;
    double price2;
    
    getline(cin, name1);
    cin >> price1;
    cin.ignore();
    getline(cin, name2);
    cin >> price2;
    
    // TODO: Выведите начальную статистику инвентаря, используя Product::getInventoryStats()
    
    // TODO: Создайте блок области видимости с фигурными скобками
    // Внутри блока:
    //   - Создайте Product с name1 и price1
    //   - Выведите информацию о нем, используя getInfo()
    //   - Выведите текущую статистику инвентаря
    
    // TODO: После завершения блока выведите "After first scope:"
    // Затем выведите статистику инвентаря
    
    // TODO: Создайте еще один Product с name2 и price2
    // Выведите информацию о нем и статистику инвентаря
    
    // TODO: Создайте третий Product с именем "Bonus" и ценой 5.00
    // Выведите итоговую статистику инвентаря
    
    return 0;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование