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

 

Объявление классов

Класс в языке C++ объявляется с помощью ключевого слова class.

class Person
{
    int id;
    short age;
public:
    std::string name;
    void setAge(short _age) { this->age = _age; }
    short getAge() { return age; }
};

 

Обращение к членам класса

Доступ к членам класса осуществляется через точку, если переменная класса - экземпляр класса (объект).

Person person;
person.name = "Jane";
person.setAge(20);
std::cout << person.name.c_str() << std::endl;
std::cout << person.getAge() << std::endl;

Если переменная - указатель на класс, то доступ к членам класса осуществляется через "->".

Person *person = new Person();
person->name = "Jane";
person->setAge(20);
std::cout << person->name.c_str() << std::endl;
std::cout << person->getAge() << std::endl;

 

Спецификаторы доступа

В отличие от структур, в классе все члены по-умолчанию объявляются как закрытые. Для изменения типа объявления используются спецификаторы доступа private, public и protected:

  • private - закрытые (приватные) члены класса. К ним можно обращаться только из методов этого класса.
  • protected - защищённые члены класса. К ним можно обращаться из методов этого класса, а также наследников этого класса.
  • public - открытые члены класса. К ним можно обращаться через экземпляр класса, так же как к членам структур.
class Person
{
    int id;
protected:
    std::string name;
public:
    short age;
};

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

class Person
{
    std::string name;
public:
    void copyFrom(Person &p)
    {
        this->name = p.name;
    }
};

 

Инициализация классов

При создании экземпляра класса его члены изначально создаются неинициализированными, с неопределенными значениями.

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

class Person
{
public:
    int id;
    std::string name;
};

// Инициализация каждого члена класса по-отдельности
Person person1;
person1.id = 1;
person1.name = "Person #1 name";

// Инициализация списком значений
Person person2 = { 2, "Person #2 name" };

// Универсальная инициализация
Person person3 { 3, "Person #3 name" };

Начиная со стандарта C++11 при объявлении класса для еге членов можно указать значения по-умолчанию.

class Person
{
    int id = 0;
    std::string name = "Default name";
};

В остальных случаях инициализация производится только через конструктор класса.

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

class Person
{
    short age;
    std::string name;
public:
    Person(short _age)  // Конструктор с простым присваиванием
    {
        age = _age;
        name = "Default name";
    }
    Person()
        : name("Default name"), age(0) {}  // Конструктор по-умолчанию со списком инициализации
    Person(short _age = 0, std::string _name = "Default name")
        : age(_age), name(_name) {}  // Конструктор со списком инициализации
};

Начиная со стандарта C++11 в списке инициализации лучше использовать универсальную инициализацию, когда значения переменных указываются не в круглых, а в фигурных скобках.

class Person
{
    short age;
    std::string name;
public:
    Person()
        : name{ "Default name" }, age{ 0 } {}  // Конструктор по-умолчанию со списком инициализации
    Person(short _age, std::string _name = "Default name")
        : age{ _age }, name{ _name } {}  // Конструктор со списком инициализации
};

Переменные указанные в списке инициализации инициализируются в том порядке, в котором они объявлены в классе, а не в том, в котором они были указаны в списке.

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

class A
{
public:
    A(int _a) { std::cout << "A=" << _a << "\n"; }
};

class B
{
    A a;
public:
    B(int _b) : a{ _b - 1 } { std::cout << "B=" << _b << "\n"; }
};

B b(5);    // Выведет A=4, а затем B=5

 

Конструктор

Конструктор - это особый метод класса, который вызывается при создании (конструировании) объекта класса.

В языке C++ конструктор всегда должен иметь такое же имя, как и сам класс. Также конструктор не должен иметь возвращаемого типа (даже void).

В классе может быть несколько перегруженных конструкторов (принимающих разные типы переменных).

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

class Person
{
    short age;
public:
    std::string name;
public:
    Person() { age = 0; name = "Default"; }	    // Конструктор по-умолчанию
    Person(short _age, const char* _name) { age = _age; name = _name; }  // Перегруженный конструктор
    auto getAge() { return age; }
};

Person person1;     // Инициализация с помощью конструктора по-умолчанию
Person person2(20, "Jane");

Если в классе есть переменные других классов, то их конструктор по-умолчанию будет вызван до вызова конструктора самого класса-контейнера.

class Address
{
public:
    Address() { std::cout << "Address constructor" << std::endl; }
};

class Person
{
    Address address;
public:
    Person() { std::cout << "Person constructor" << std::endl; }
};

Person person;    // Выведется сначала "Address constructor", а затем "Person constructor"

Начиная с C++11, конструкторам разрешено вызывать другие конструкторы. Этот процесс называется делегированием конструкторов.

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

class A
{
    int v;
public:
    A(int _v) : v{ _v } {}
    A() : A(10) {}
};

 

Деструктор

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

Назначение деструктора - выполнить освобождение динамических ресурсов, выделенных при работе класса. Если этого не требуется, то деструктор создавать необязательно.

В языке C++ деструктор всегда должен иметь такое же имя, как и сам класс, но с символом ~ (тильда) перед ним. Также как и конструктор, деструктор не должен иметь возвращаемого типа. Кроме того, деструктор не может иметь никакие параметры.

В классе может быть только один деструктор.

class Person
{
    short age;
    std::string name;
    Address *pAddr;
public:
    Person() { pAddr = new Address(); }	    // Конструктор
    ~Person() { delete pAddr; }    // Деструктор
};

 

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

Язык C++ позволяет отделить объявление класса от реализации его методов.

Объявление класса делается в заголовочных файлах с расширением .h (header).

// Person.h
class Person
{
    short age;
    std::string name;
public:
    short getAge();
};

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

Реализация методов класса делается в файлах с расширением .cpp.

// Person.cpp
#include "Person.h"

short Person::getAge()
{
    return this->age;
}

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

Также использование заголовочных файлов позволяет использовать сторонние предварительно скомпилированные библиотеки на этапе линковки программы.

 

Константные объекты и методы классов

Если объект класса объявляется с помощью ключевого слова const, то этот экземпляр класса можно инициализировать с помощью конструктора, но нельзя изменить его содержимое. Запрещается как изменение переменных-членов напрямую (если они являются открытыми), так и вызов методов, с помощью которых можно установить значения переменным-членам. Позволяется только вызов константных методов класса.

class Person
{
    int id;
    short age;
public:
    std::string name;
    void setAge(short _age) { this->age = _age; }
    short getAge() const;
};

short Person::getAge() const
{
    return this->age;
}

const Person person(20, "Jane");
person.name = "John";    // Ошибка компиляции
person.setAge(30);       // Ошибка компиляции
std::cout << person.getAge() << std::endl;

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

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

Константные методы можно перегружать их неконстантными версиями. При этом константная версия функции будет вызываться для константных классов, а неконстантная - для неконстантных.

 

Статические члены класса

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

Объявление статической переменной

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

class Person
{
    int id;
public:
    static std::string s_type;  // Объявление об использовании статической переменной в классе
};

// Объявление и инициализация статической переменной класса Person
// Без ключевого слова static
std::string Person::s_type = "Default type";

Инициализировать таким образом статические переменные можно, даже если они закрытые и защищенные. Если статическая переменная объявлена без инициализации, то она автоматически инициализируется значением 0.

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

Константную статическую переменную целочисленного типа (int, char, bool, enum) можно инициализировать внутри класса.

class Person
{
    int id;
public:
    static const enum {Male, Female} sex = Person::Female;  // Инициализация статической переменной в классе
};

Начиная со стандарта C++11, позволяется в классе инициализировать статические члены constexpr любого типа, поддерживающего инициализацию constexpr.

class Person
{
    int id;
public:
    static constexpr double val = 1.25;  // Инициализация статической переменной constexpr в классе
};

Использование статической переменной

К статической переменной можно обращаться как через объекты класса, так и через сам класс, используя оператор разрешения области видимости :: (два двоеточия).

Person person1, person2;

// К статической переменной можно обращаться как через объект
std::cout << person1.s_type.c_str() << std::endl;    // Default type
std::cout << person2.s_type.c_str() << std::endl;    // Default type

// Так и через сам класс
std::cout << Person::s_type.c_str() << std::endl;    // Default type

Для доступа к закрытой статической переменной класса используются статические методы класса.

Особенностью статических методов является то, что у них нет указателя this на свой класс, а также то, что они не могут обращаться к нестатическим членам класса.

 

Дружественные функции, методы и классы

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

class Person
{
    int id;
    short age;
    friend bool checkAge(Person& person);  // Функция checkAge является дружественной для класса Person
};

bool checkAge(Person& person)
{
    return person.age > 18;
}

Функция может быть объявлена дружественной одновременно для нескольких классов.

Можно объявить дружественным другой класс, чтобы все его методы могли свободно обращаться к переменным данного класса. Для этого используется выражение friend class.

class Person
{
    int id;
    short age;
    friend class Checker;  // Класс Checker является дружественным для класса Person
};

class Checker
{
public:
    bool checkAge(Person& person) { return person.age > 18; }
};

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

class Person;

class Checker
{
public:
    bool checkAge(Person& person);
};

class Person
{
    int id;
    short age;
    friend bool Checker::checkAge(Person& person);  // Класс Checker является дружественным для класса Person
};

bool Checker::checkAge(Person& person)
{
    return person.age > 18;
}

Это объявление делается проще, если использовать для него заголовочные файлы.