Класс может определять различное состояние, различные функции. Однако не всегда желательно, чтобы к некоторым компонента класса был прямой доступ извне. Для разграничения доступа к различным компонентам класса применяются спецификаторы доступа
Спецификатор public делает члены класса - поля и функции открытыми, доступными из любой части программы. Например, возьмем следующий класс Person:
#include <iostream>
#include <string>
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>
#include <string>
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 будет аналогичен следующему
#include <string>
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>
#include <string>
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, если оно соответствует определенным условиям.
Таким образом, состояние класса скрыто извне, к нему можно получить доступ только посредством дополнительно определенных функций, которые представляют интерфейс класса.