Классы в C++: поля, методы, конструкторы с нуля
Разбираемся, как объявить класс, завести поля и методы, написать конструктор и не словить утечку памяти. Пошагово, с рабочим кодом под C++17 и новее.

К концу этой инструкции у вас будет собранная программа с классом BankAccount: полями для баланса и имени владельца, методами пополнения и снятия, конструктором с проверкой входных данных. На всё уйдёт минут двадцать, если компилятор уже установлен. Мы не будем углубляться в наследование, шаблоны и умные указатели — только то, что нужно, чтобы написать и запустить свой первый рабочий класс.
Что нужно до начала
Проверьте, что окружение готово. Класс — это часть языка, никаких библиотек ставить не придётся, но компилятор нужен.
- Компилятор с поддержкой C++17: g++ 9+, clang 10+ или MSVC 2019+. Проверить версию g++: команда
g++ --versionв терминале. - Любой текстовый редактор или IDE. Подойдёт VS Code, CLion, даже Блокнот.
- Умение собрать один
.cpp-файл в командной строке. Если это первый файл в жизни — команда сборки будет ниже целиком.
Весь код в статье помещается в один файл. Разбивать на .h и .cpp здесь не будем — это отдельная тема, а для первого класса лишний шаг.
Шаг 1. Объявляем класс с полями
Поле (в C++ его называют член-данное, data member) — переменная, живущая внутри объекта. Метод — функция, которая работает с этими полями. Начнём с полей.
// файл account.cpp, C++17
#include <iostream>
#include <string>
class BankAccount {
private:
std::string owner; // имя владельца
double balance; // текущий баланс
};
int main() {
BankAccount account; // создали объект
return 0;
}
Слово private означает: к owner и balance нельзя обратиться снаружи класса. Это не бюрократия — так вы защищаете баланс от случайной записи мимо проверок.
Проверка: соберите файл командой g++ -std=c++17 account.cpp -o account и запустите ./account (на Windows — account.exe). Программа ничего не выведет, но должна собраться без ошибок. Если собралась — класс объявлен верно.
Шаг 2. Добавляем методы
Теперь дадим классу поведение. Метод объявляется как обычная функция, но внутри тела класса и имеет доступ к его полям напрямую.
class BankAccount {
private:
std::string owner;
double balance;
public:
// пополнение счёта
void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
// показать текущий баланс
double getBalance() const {
return balance;
}
};
Обратите внимание на const после getBalance(). Это обещание: метод не меняет поля объекта, только читает. Компилятор поймает, если вы случайно попытаетесь что-то записать. Помечайте const все методы, которые ничего не меняют — это дешёвая страховка.
Проверка: на этом этапе программа всё ещё соберётся, но использовать методы пока нельзя — поля owner и balance не проинициализированы, и balance содержит мусор. Это чинит конструктор.
Шаг 3. Пишем конструктор
Конструктор — специальный метод, который вызывается при создании объекта. Его имя совпадает с именем класса, возвращаемого типа у него нет вообще.
class BankAccount {
private:
std::string owner;
double balance;
public:
// конструктор: список инициализации после двоеточия
BankAccount(const std::string& name, double startBalance)
: owner(name), balance(startBalance > 0 ? startBalance : 0) {
}
void deposit(double amount) {
if (amount > 0) balance += amount;
}
double getBalance() const { return balance; }
const std::string& getOwner() const { return owner; }
};
Часть после двоеточия — список инициализации. Поля заполняются до входа в тело конструктора. Это не то же самое, что присваивание внутри фигурных скобок: для std::string и других тяжёлых типов инициализация в списке экономит один лишний конструктор. Используйте её по умолчанию.
Пока не привыкли — держите правило: всё, что можно проинициализировать в списке, инициализируйте в списке. Присваивание в теле конструктора оставляйте для логики, которую нельзя выразить одним выражением.
Проверка: теперь строка BankAccount account; в main перестанет компилироваться — конструктор по умолчанию исчез, как только вы объявили свой. Это правильное поведение: объект без владельца и баланса не имеет смысла.
Шаг 4. Собираем всё вместе и запускаем
Полный рабочий файл. Скопируйте целиком.
#include <iostream>
#include <string>
class BankAccount {
private:
std::string owner;
double balance;
public:
BankAccount(const std::string& name, double startBalance)
: owner(name), balance(startBalance > 0 ? startBalance : 0) {
}
void deposit(double amount) {
if (amount > 0) balance += amount;
}
bool withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
return true; // успешно сняли
}
return false; // недостаточно средств
}
double getBalance() const { return balance; }
const std::string& getOwner() const { return owner; }
};
int main() {
BankAccount account("Ivan", 1000.0);
account.deposit(500.0);
std::cout << account.getOwner()
<< ": " << account.getBalance() << "\n"; // 1500
if (account.withdraw(2000.0)) {
std::cout << "Sняли 2000\n";
} else {
std::cout << "Nедостаточно средств\n";
}
std::cout << "Итог: " << account.getBalance() << "\n"; // 1500
return 0;
}
Соберите и запустите: g++ -std=c++17 account.cpp -o account && ./account.
Что должно вывестись:
Ivan: 1500
Nедостаточно средств
Итог: 1500
Баланс после неудачного снятия не изменился — withdraw вернул false и не тронул поле. Если видите эти три строки — класс работает.
Разница между членами и локальными переменными
Частая путаница у новичков — где хранятся данные. Короткая таблица, чтобы не смешивать понятия.
| Понятие | Где объявлено | Время жизни |
|---|---|---|
| Поле (член-данное) | внутри класса | пока жив объект |
| Метод | внутри класса | принадлежит классу, не объекту |
| Локальная переменная | внутри метода | до конца вызова метода |
| Параметр конструктора | в скобках конструктора | только на время создания |
Если не получилось
Пять ошибок, которые вы почти наверняка увидите.
no matching constructor for initialization of 'BankAccount'— пытаетесь создать объект без аргументов (BankAccount account;), а конструктор требует имя и баланс. Передайте аргументы:BankAccount account("Ivan", 1000.0);.'balance' is a private member of 'BankAccount'— обращаетесь к полю напрямую изmain. Так и задумано: используйте методыgetBalance()иdeposit().expected ';' after class definition— забыли точку с запятой после закрывающей фигурной скобки класса. В C++ после}класса она обязательна.member function 'getBalance' not viable: 'this' argument has type 'const BankAccount'— вызываете не-constметод на константном объекте. Пометьте читающий метод какconst.- Мусор вместо баланса (огромное число вроде
6.9e-310) — поле не проинициализировано. Убедитесь, что конструктор заполняетbalanceв списке инициализации.
Что дальше
Класс готов, но он ещё простой. Следующие шаги, когда освоите этот:
- Разнести объявление в
account.h, а реализацию — вaccount.cpp. Это меняет структуру проекта, но не логику класса. - Разобраться с деструктором и правилом трёх/пяти — как только в классе появится
newи ручное владение памятью. - Посмотреть, как компилятор генерирует конструктор по умолчанию и конструктор копирования, и когда их стоит объявить своими.
Компилируйте с флагами -Wall -Wextra — они предупреждают о неинициализированных полях и других ловушках ещё до запуска.
Репетитор 1—11 класс и ВУЗ: Математика и физика методом наведения на мысль, помощь с ДЗ.
Спросить за 8 ₽Источники: cppreference — Classes, cppreference — Constructors and member initializer lists
Частые вопросы
А если у меня старый компилятор без поддержки C++17?
Что будет, если не написать конструктор вообще?
Зачем список инициализации, если можно присвоить значения в теле конструктора?
В чём разница между private и public?
Что если методов и полей станет много — где их размещать?
Нужен ли деструктор для этого класса?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.