Классы в языке C++
- Объявление классов
- Обращение к членам класса
- Спецификаторы доступа
- Инициализация классов
- Конструктор
- Деструктор
- Отделение объявления класса от реализации
- Константные объекты и методы классов
- Статические члены класса
- Дружественные функции, методы и классы
Класс в 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; }
Это объявление делается проще, если использовать для него заголовочные файлы.