Инкапсуляция данных как один из принципов ООП. Модификаторы доступа public, private и protected.

Инкапсуляция (encapsulation) - это механизм, который объединяет данные и код, манипулирующий зтими данными, а также защищает и то, и другое от внешнего вмешательства или неправильного использования. В объектно-ориентированном программировании код и данные могут быть объединены вместе; в этом случае говорят, что создаётся так называемый "чёрный ящик". Когда коды и данные объединяются таким способом, создаётся объект (object). Другими словами, объект - это то, что поддерживает инкапсуляцию.

Внутри объекта коды и данные могут быть закрытыми (private). Закрытые коды или данные доступны только для других частей этого объекта. Таким образом, закрытые коды и данные недоступны для тех частей программы, которые существуют вне объекта. Если коды и данные являются открытыми, то, несмотря на то, что они заданы внутри объекта, они доступны и для других частей программы. Характерной является ситуация, когда открытая часть объекта используется для того, чтобы обеспечить контролируемый интерфейс закрытых элементов объекта.

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

Public, private и protected – это модификаторы доступа, а не видимости, как ошибочно думают некоторые. Private члены видны снаружи класса, но не доступны.

· Public – доступ открыт всем, кто видит определение данного класса.

· Private – доступ открыт самому классу (т.е. функциям-членам данного класса) и друзьям (friend) данного класса, как функциям, так и классам.

· Protected – доступ открыт классам, производным от данного.

Далее приведены примеры доступа с указанием какие поля в каких местах программы доступны.

1 class some {

2 friend void f(some&);

3 public:

4 int a_;

5 protected:

6 int b_;

7 private:

8 int c_;

9 };

11 void f(some& obj) {

12 obj.a_ = 0; // ok

13 obj.b_ = 0; // ok

14 obj.c_ = 0; // ok

15 }

17 void g(some& obj) {

18 obj.a_ = 0; // ok

19 obj.b_ = 0; // CT error

20 obj.c_ = 0; // CT error

21 }

23 class derived : public some {

24 derived() {

25 a_ = 0; // ok

26 b_ = 0; // ok

27 c_ = 0; // CT error

28 }

29 };

В C++ существует public-наследование, private-наследование и protected-наследование. В зависимости от того, какой тип используется, изменяется доступ к членам базового класса для клиентов производного. В таблице сведена информация об этом изменении.

  Исходный модификатор доступа
  public private protected
public-наследование public private protected
private-наследование private private private
protected-наследование protected private protected


Статические компоненты классов

Компоненты класса также могут объявляться с описателем static, такие компоненты - данные являются общими для всех экземпляров объектов этого класса и размещаются в памяти отдельно от данных объектов класса. Доступ к static - компонентам класса возможен по имени, уточненному именем класса (именем типа) или именем объекта этого класса, причем к static - компонентам класса можно обращаться до создания экземпляров объектов этого класса. Статическое данное - член класса должно быть обязательно инициализировано вне описания класса:

classTBase //базовый класс для массивов всех типов

{ staticintnw;

int size, //размерэлемента

count, //текущее число элементов

maxCount, //размер выделенной памяти

delta; //приращение памяти

/* Другие компоненты класса TBase */

}

intTBase::nw =1; /* Инициализация статической компоненты класса */

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

class X

{ staticint sx1,sx2;

static void fsx ( int k);

int x1,x2;

/* Другие компоненты класса X */

}

int X::sx1 = 1;

int X::sx2 = 2;

intmain ()

{ ..........

X:: fsx( 3 );

..............

}

Перегрузка операторов

Перегрузка операторов – это возможность назначать новый смысл операторам при использовании их с определенным классом.

Во всей литературе для обучения перегрузке операторов скорее всего используется один и тот же пример. Самое стандартное – это перегрузка операторов для работы со строкой. Легко понять, что знак плюс или минус для чисел и знак плюс или минус для строки несут различные смыслы. В первом случае математические вычисления, во втором добавление или удаление символа (символов) или части строки.

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

Код С++ Перегрузка операторов

#include <stdlib.h> #include <iostream.h> #include<string.h> //для работы со строковыми функциями   /*СОЗДАЕМ СОБСТВЕННЫЙ КЛАСС*/ classstring { public: string (char*); //конструктор класса принимающий один параметр voidoperator +(char*); //определение оператора + voidoperator -(char); //определение оператора - voidshow_string(void); //метод класса для отображения строки private: chardata[256]; //символьный массив, доступный только классу };   string::string(char *str) //Транзитом через конструктор { strcpy(data,str); //копируем в символьный массив класса данные из принимаемой извне строки }   void string::operator +(char *str) //Определяемоператор + { strcat(data,str); //как функцию сложения двух строк }   void string::operator -(char letter)//Определяемоператор - { chartemp[256]; // будем создавать новую строку inti,j; //счетчики циклов //Проходим по всей строке класса с помощью цикла и если символ строки не равен принятому символу (параметру), то копируем его в новую строку for (i=0,j=0;data[i];i++) if (data[i]!=letter) temp[j++]=data[i]; temp[j]=NULL;   strcpy(data,temp); //Копируем новую строку в символьный массив класса }   void string::show_string(void) { cout<<data<<endl; //Показываем символьный массив класса }   voidmain() { system("cls"); //Очистка экрана   char *stroka,*stroka2; //Объявление двух указателей для строк cin.getline(stroka,256); //Считывание первой строки с клавиатуры cin.getline(stroka2,256); //Считывание второй строки с клавиатуры   stringtitle(stroka); //Объявление переменной типа нашего класса и передача вконструктор первой строки title+" "; //С помощью перегрузки операторов добавили к строке пробел title+stroka2; //C помощью перегрузки операторов добавили к строке вторую строку title.show_string(); //Отобразили результирующую строку на экране title-'в'; //При помощи перегрузки операторов пытаемся удалить символ в title.show_string(); //Отобразили результирующую строку cin.get(); }

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

Нельзя перегружать:

. (точка)

* (звездочка)

:: (два двоеточия подряд)

?:(вопрос с двоеточием)

sizeof (тоже нельзя)

Нужно знать:

Чтобы перегрузить оператор, нужно определить класс, к которому оператор будет назначен

Когда перегружается оператор, перегрузка действует только для класса в котором он определяется. Если оператор использует неклассовые переменные, то используется стандартное определение оператора

Для перегрузки оператора используется ключевое слово С++ operator. Это слово определяет метод класса, который С++ вызывает каждый раз, когда переменная класса вызывает оператор

С++ позволяет перегружать все операторы кроме вышеуказанных

Кроме уже описанных ограничений на перегрузку операторов имеют место другие ограничения.

Старшинство операций не может быть изменено перегрузкой

Ассоциативность операций не может быть изменена перегрузкой

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

Каждая из операций & * + - может иметь и унарный и бинарный варианты. Эти унарные и бинарные варианты могут перегружаться отдельно

Создавать новые операции невозможно. Возможно только использовать существующие.

Нельзя изменить операцию для объекта встроенного типа (уже упоминалось чуть другими словами)

Неявной перегрузки не существует (например object1=object1+object2 не равноobject1+=object2 если явно перегрузки не прописано)

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

Дружественные функции

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

Дружественная функция объявляется внутри класса с модификатором friend

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

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

Код C++ Дружественная функция и обычная функция Класса

#include <conio.h> #include <iostream.h>   class A { int x; //Приватный элемент из класса A friendvoidget_x(int, A &); //Прототип дружественной функции для занесение в приватный x значения public: voidget_x(int);//Прототип обычного метода для занесения значения в приватный x voidshow(); //Прототип функции для отображения x из приватного поля }; /*Прототипы функций определены внутри класса. Сами функции описаны вне*/   voidget_x(int N, A &obj_A) //Функция не является частью класса, но работает словно является { obj_A.x=N; //в элемент x класса A передается принимаемый параметр N }   void A::get_x(int N) //функция является частью класса A { x=N; //в приватный элемент x класса A заносится принимаемый в N параметр }   void A::show() //Функция является частью класса A и играет роль посредника { cout<<x<<endl; //Отображаем приватный элемент x из класса A }   voidmain() { clrscr(); intvalue=100; //value будет передаваться как параметр вовнутрь класса в приватный x A obj_A;   get_x(value, obj_A); //Работаем как с обычной функцией. obj_A.show(); //Отображаем результаты value=999; //Изменили значение в value   obj_A.get_x(value); //Работаем как с методом объекта obj_A.show(); //Отображаем результаты   getch(); return; }

Сразу можно обратить внимание, что прототип дружественной функции описан внутри поля private. На самом деле можно его описывать и в других полях, но так видно одно важное отличие от обычных методов класса. В программе два абсолютно одинаковых по смыслу метода и оба выполняют одну и ту же задачу. Один метод дружественный, второй метод обычный. Так вот обычный метод из поля private был бы недоступен и без посредника он бы не знал что от него хотят, а дружественному методу посредник не нужен. Об этом и говорит часть теории:
Дружественная функция – это функция, которая не являясь частью класса имеет доступ ко всем элементам из дружественного себе класса.

Нас интересует именно дружественная функция из класса. Смотрим на её прототип и видим, что туда передается два параметра. Первый параметр является некоторым значением, которое будет заноситься в элемент класса, второй параметр – это ссылка на экземпляр класса. Так вот устроен механизм, что чтобы работать с элементами класса, нужно как-то сообщать независимой функции с каким классом ей работать, поэтому передается и значение с каким ей работать и сам экземпляр класса, элементы которого обрабатывать.

Так как элемент x был объявлен внутри private, то для отображения этого x необходим посредник, в качестве посредника работает функция show() . Функция show() объявлена внутри класса и значит все поля класса ей доступны. Я надеюсь вы изучили, что такое поле private и дальнейшее описание будет лишним

Коротко говоря:
Внутри класса объявлены

Приватный элемент x

Дружественная функция ввода в x значения

Обычная функция ввода в x значения

Функция для отображения приватного элемента

После описания класса идет написание функций.
Первой написана функция, которая принимает некоторый параметр N и принимает экземпляр класса, записывает этот Nв x вовнутрь принятого экземпляра класса. Функция не опирается на класс и описана как самостоятельно-независимая. Возможно это как раз благодаря ключевому слову friend перед прототипом этой самой функции внутри класса. Еще раз отмечу, что такую функцию возможно описать внутри поля private

Второй описывается функция, которая принимает только один параметр. При этом функция опирается на класс A и следовательно заносит принимаемый параметр вовнутрь того класса на который и опирается.

В общем обе функции выполняют одно и то же действие, разница в том, что одна функция выполняется как самостоятельная и независимая от класса, при этом еще и описана внутри private. При всех этих обстоятельствах это не мешает ей работать вне класса без посредников
Вторая же функция опирается на некоторый класс и не может работать с элементами из поля private без посредников
В общем я пришел к тому что

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

Обычная функция класса требует посредников для работы с элементами из private

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

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

В первом случае обычная функция принимает параметры и обрабатывает их

Во втором случае обрабатываем класс через соответствующий объект

При этом первый вариант работы то же самое, что и второй.

Наши рекомендации