Класс может определять различное состояние, различные функции. Однако не всегда желательно, чтобы к некоторым компонента класса был прямой доступ извне. Для разграничения доступа к различным компонентам класса применяются спецификаторы доступа
Спецификатор public делает члены класса - поля и функции открытыми, доступными из любой части программы. Например, возьмем следующий класс Person:
#include <iostream> class Person { public: std::string name; unsigned age; void print() { std::cout << "Name: " << name << "\tAge: " << age << std::endl; } Person(std::string p_name, unsigned p_age) { name = p_name; age = p_age; } }; int main() { Person tom{"Tom", 38}; // поля name, age и функция print общедоступные tom.name = "Tomas"; tom.age = 22; tom.print(); // Name: Tomas Age: 22 }
То есть в данном случае поля name и age и функция print являются открытыми, общедоступными, и мы можем обращаться к ним во внешнем коде. Однако это имеет некоторые недостатки. ТаК, мы можем обратиться к полям класса и присвоить им любые значения, даже если они будут не совсем корректными, исходя из логики прогаммы:
Person tom("Tom", 22); tom.name = ""; tom.age = 1001;
В том числе можно присвоить какие-то недопустимые значения. Например, полю age можно передать чересчур большой нереальвй возраст. Или мы не хотим, чтобы имени можно было присвоить пустую строку. Естественно это не очень хорошая ситуация.
Однако с помощью другого спецификатора private мы можем скрыть реализацию членов класса, то есть сделать их закрытыми, инкапсулировать внутри класса. Перепишем класс Person с применением спецификатора private:
#include <iostream> class Person { private: std::string name; unsigned age; public: void print() { std::cout << "Name: " << name << "\tAge: " << age << std::endl; } Person(std::string p_name, unsigned p_age) { name = p_name; age = p_age; } }; int main() { Person tom{"Tom", 38}; // функция print общедоступная tom.print(); // Name: Tom Age: 22 // поля name и age вне класса недоступны // tom.name = ""; // tom.age = 1001; }
Все компоненты, которые определяются после спецификатора private и идут до спецификатора public, являются закрытыми, приватными. Теперь теперь мы не можем обратиться к переменным name и age вне класса Person. Мы можем к ним обращаться только внутри класса Person. А функция print и конструктор по прежнему общедоступные, поэтому мы можем обращаться к ним в любом месте программы.
Стоит отметить, что в данном случае мы все равно можем передать некорректные значения - через конструктор. В этом случае можно проверять входные данные и использовать различные стратегии, например, не создавать объект или передавать ему данные по умолчанию, но в целях упрощения я опущу подобную проверку.
Если для каких-то компонентов отсутствует спецификатор доступа, то по умолчанию применяется спецификатор private
. Так,
предыдущий класс Person будет аналогичен следующему
class Person { std::string name; unsigned age; public: void print() { std::cout << "Name: " << name << "\tAge: " << age << std::endl; } Person(std::string p_name, unsigned p_age) { name = p_name; age = p_age; } };
Хотя в примере выше мы избегаем установки некорректных значений, тем не менее иногда может потребоваться доступ к подобным полям. Например, человек стал старше на год - надо изменить возраст. Или мы хотим отдельно получить имя. В этом случае мы можем определить специальные функции, через которые будем контроллировать доступ к состоянию класса:
#include <iostream> class Person { private: std::string name; unsigned age; public: Person(std::string p_name, unsigned p_age) { name = p_name; if (p_age > 0 && p_age < 110) age = p_age; else age = 18; // если значение некорректное, устанавливаем значение по умолчанию } void print() { std::cout << "Name: " << name << "\tAge: " << age << std::endl; } void setAge(unsigned p_age) { if (p_age > 0 && p_age < 110) age = p_age; } std::string getName() { return name; } unsigned getAge() { return age; } }; int main() { Person tom{"Tom", 38}; // изменяем возраст tom.setAge(22); tom.setAge(123); tom.print(); // Name: Tom Age: 22 //отдельно получаем имя std::cout << "Person name: " << tom.getName() << std::endl; }
Чтобы можно было получить извне значения переменных name и age, определены дополнительные функции getAge и getName. Установить значение переменной name напрямую можно только через конструктор, а значение переменной age - через конструктор или через функцию setAge. При этом функция setAge устанавливает значение для переменной age, если оно соответствует определенным условиям.
Таким образом, состояние класса скрыто извне, к нему можно получить доступ только посредством дополнительно определенных функций, которые представляют интерфейс класса.