Лекция 10. Классы, часть 1 (инкапсуляция)
С усложнением программ основной идеей обеспечения их управляемости стало моделирование. Реальный мир состоит из взаимодействующих между собой объектов. С точки зрения программирования объекты, характеризуемые разнородными данными, можно описать как раз структурами, а взаимодействие объектов обеспечить функциями, которые будут работать с данными этих структур. При классическом процедурном подходе плохо лишь то, что данные «сами по себе», а функции их обработки тоже «сами по себе», тогда как моделировать удобнее и естественнее объекты, обладающие некой целостностью.
В принципе, механизм структур позволяет решить задачу объединения данных и функций, которые их обрабатывают, в одном объекте – ведь в структуры можно включать такие объекты, как указатели на функции. Указатель на функцию — это переменная, которая содержит адрес функции определённого типа с определённым набором параметров. Соответственно, косвенное обращение по этому указателю представляет собой вызов функции. А если переставить указатель на другую функцию того же типа с соответствующим списком параметров, то и будет вызвана другая функция. Поведение программы становится гибким, а значит, возможности моделирования возрастают.
Приведём пример. Мы хотим написать программу, позволяющую табулировать различные функции одним и тем же кодом. Естественным решением будет описание структуры, содержащей нужную информацию о функции:
#include
#define M_PI 3.1415926 /* вспомогательные */
#define EPS 1e-9 /* константы */
using namespace std;
typedef double (*function) (double);
//указатель на функцию вида double имя(double)
struct tab { //структура-табулятор функций
double x1, x2; //пределы табулирования
int n; //количество шагов от x1 до x2
function f; //конкретная функция, которую табулируем,
//заданная указателем на неё
static void run(tab t); //функция, которая табулирует f(x)
};
Какая конкретно функция табулируется, будет определять указатель f, включённый в структуру. Передавая структуру типа tab в функцию табулирования run, мы сможем решить поставленную задачу:
void run(tab t) { //функция табулирования любой функции
double dx = (t.x2 — t.x1) / t.n;
cout << endl; cout.width(10); cout << «X»;
cout.width(10); cout << «Y»;
for (double x = t.x1; x <= t.x2 + EPS; x += dx) {
cout << endl;
cout.width(10); cout << x;
cout.width(10); cout << t.f(x);
}
}
Допишем программу, построив таблицы значений нескольких функций:
//конкретные функции для примера
double f1(double x) { return sin(x); }
double f2(double x) { return cos(x); }
double f3(double x) { return x*x; }
int main() {
tab fun1 = { 0, M_PI, 10, f1 }; run(fun1);
//описали и табулировали одну функцию
tab fun2 = { 0, M_PI/2, 10, f2 }; run(fun2);
//а теперь совсем другую
fun1.f = f3; run(fun1);
//программно поменяли функцию в структуре, переставив
//указатель на другой объект
cin.sync(); cin.get(); return 0;
}
В нашем коде хорошо то, что программа может динамически менять свою логику, подставляя нужные функции в структуры. Однако хватает и неудобств:
-
в функцию run приходится передавать структуру или указатель на неё;
-
по-прежнему функция run никак не связана со структурой;
-
нельзя разделить или ограничить доступ к переменным и функциям структуры.
Можно сделать функцию «полноценным» членом структуры, но тогда всё равно придётся использовать инструментарий классов, немного забегая вперёд (показаны только изменённые участки кода):
struct tab {
double x1, x2;
int n;
function f;
void run(); //изменено
};
//…
void tab::run(void) { //изменено
double dx = (this->x2 — this->x1) / this->n;
cout << endl; cout.width(10); cout << «X»;
cout.width(10); cout << «Y»;
for (double x = this->x1; x <= this->x2 + EPS; x += dx) {
cout << endl;
cout.width(10); cout << x;
cout.width(10); cout << this->f(x);
}
}
//…
int main() {
//изменено
tab fun1 = { 0, M_PI, 10, f1 }; fun1.run();
tab fun2 = { 0, M_PI / 2, 10, f2 }; fun2.run();
fun1.f = f3; fun1.run();
cin.sync(); cin.get(); return 0;
}
Примечания:
-
В этом коде this — «указатель объекта на самого себя», this->x1 означает, что надо взять переменную x1, описанную именно внутри текущей структуры (в fun1 при вызове fun1.run();), а не в другой структуре или глобально.
-
Оператор :: имеет в C++ наивысший приоритет и означает указание области видимости объекта; в нашем случае то, что функция run относится к структурному типу tab.
В таком коде видно, что функция относится именно к структуре, мы вызываем её в виде fun1.run(), а не run(fun1). Кроме того, мы сэкономили память стека, не передавая целые структуры внутрь функций.
Хотя у любого объекта класса есть собственная копия всех данных-членов, каждая функция-член существует в единственном экземпляре. Функция-член может обращаться к данным-членам разных объектов с помощью указателя this. Код с классами легче модифицируется и остаётся управляемым в больших проектах. Все современные языки и системы программирования объектно ориентированы, то есть, используют механизм классов.
Итак, объектно-ориентированное программирование (ООП) — парадигма программирования, основанная на понятиях класса и объекта.
Объект – это сущность, обладающая определённым состоянием, которое характеризуется свойствами, и поведением, которое характеризуется методами.
С точки зрения процедурного программирования, свойствам соответствуют переменные или массивы, описывающие состояние объекта, методам — функции, позволяющие управлять свойствами или обмениваться информацией с внешним миром.
Например, у класса «Студент» могут быть такие свойства как фамилия, группа, дата рождения, стипендия и т.д., методы «показать данные», «сменить номер группы», «начислить стипендию» и т.п.
Объекты всегда принадлежат одному или нескольким классам, которые определяют поведение объекта и являются его моделью. Термины «объект» и «экземпляр класса» взаимозаменяемы.
Класс — абстрактный тип данных, определяющий интерфейс и реализацию для всех своих экземпляров.
Например, объектом (экземпляром) класса «Студент» является конкретный «студент Иванов»:
Student Ivanov;
//или чаще:
Student *Ivanov = new Student ();
Объекты обладают тремя базовыми свойствами (они же – три базовых принципа ООП):
-
Инкапсуляция — механизм языка, разделяющий и ограничивающий доступ к составляющим объект компонентам (методам и свойствам). Например, приватные свойства и методы класса доступны только для экземпляров этого же, но не других классов.
Так, для класса «Служащий» свойство «Зарплата» может быть приватно. А для начисления зарплаты или получения сведений о ней мы можем предусмотреть в классе публичные методы «ПолучитьВеличинуЗП», «НачислитьЗП» и т.п. Вообще, чаще всего свойства класса приватны, а основные методы публичны. Это связано ещё и с тем, что прямое изменение свойств класса в виде объект.свойство=значение не даёт возможности выполнения дополнительных действий и затрудняет модификацию программы.
files -> Общая характеристика исследования
files -> Клиническая психология
files -> Валявский Андрей Как понять ребенка
files -> К вопросу о формировании специальных компетенций руководителей общеобразовательных учреждений в целях создания внутришкольных межэтнических коммуникаций
files -> Русские глазами французов и французы глазами русских. Стереотипы восприятия
kis -> Динамические структуры данных гл
Поделитесь с Вашими друзьями: