Справочник от Автор24
Поделись лекцией за скидку на Автор24

Принципы объектно-ориентированного программирования

  • 👀 355 просмотров
  • 📌 295 загрузок
Выбери формат для чтения
Статья: Принципы объектно-ориентированного программирования
Найди решение своей задачи среди 1 000 000 ответов
Загружаем конспект в формате doc
Это займет всего пару минут! А пока ты можешь прочитать работу в формате Word 👇
Конспект лекции по дисциплине «Принципы объектно-ориентированного программирования» doc
Лекция №2. Принципы объектно-ориентированного программирования Содержание: инкапсуляция, наследование; полиморфизм; виртуальные методы: понятие о раннем и позднем связывании; примеры. 1. Принцип инкапсуляции Инкапсуляция – такое объединение внутри класса полей и методов, при котором доступ к полю возможен только путем вызова соответствующего метода. При идеальном выполнении принципа инкапсуляции поля класса могут быть только личными (private). Ниже перечислены уровни инкапсуляции, т. е. уровни доступа к элементам класса, два из которых (private и public) мы уже рассмотрели: • private (личный). Этот уровень накладывает самые жесткие ограничения на доступ к элементам класса. Именно, эти элементы могут быть использованы только методами данного класса. Как правило, поля класса объявляются private. • public (общий). Элементы класса данного уровня доступны из любой точки программы (самый «широкий» доступ). • protected (защищенный). Элементы класса данного класса доступны методам данного класса и его наследников (определение класса-наследника см. в п. 2). 2. Принцип наследования Наследование – это возможность определения для базового класса (предка) иерархии производных классов (наследников), в каждом из которых доступны элементы базового класса (их описание становится частью описания производного класса). Иначе говоря, наследование – механизм, посредством которого класс может наследовать элементы другого класса и добавлять к ним свои элементы. Как правило, базовый класс является общим, производные – более специальными, конкретными. Естественно, у класса-наследника обычно больше полей и методов, чем у класса-предка, так как при наследовании обычно добавляются новые элементы. Класс предок и совокупность его наследников всех уровней образуют иерархическое дерево классов. Примеры иерархии классов приведены на рис. 1, 2. Если имеется иерархия классов, то можно рассматривать защищенные (protected) элементы класса, которые доступны для методов своего класса и его наследников. Наследование может быть единичным (наследник имеет одного предка) и множественным (количество предков больше 1). В Си++ допустимо множественное наследование. Наследование может быть общим, личным и защищенным. Видимость полей и функций базового класса из производного определяется секцией, в которой находится объявление компонента, и видом наследования (см. табл. 1): Таблица 1. Видимость компонентов базового класса в производном Вид наследования Объявление компонентов в базовом классе Видимость компонентов в производном классе private не доступны private protected private public private private не доступны protected protected protected public protected private не доступны public protected protected public public Как видно из таблицы 1, при общем наследовании общие и защищенные элементы класса-предка сохраняют свой уровень инкапсуляции и в классе-наследнике. При личном наследовании общие и защищенные элементы класса-предка становятся личными для класса-наследника. При защищенном наследовании защищенные элементы сохраняют свой уровень в классе-наследнике, а общие становятся защищенными. Обратите внимание, что, независимо от вида наследования, личные элементы базового класса не доступны в производных классах. Таким образом, чтобы «достать» их из производного класса, надо использовать унаследованные общие и защищенные методы класса-предка. Описание производного класса в общем виде: class Имя_производного_класса: Вид_наследования_1 Имя_базового_класса_1, Вид_наследования_2 Имя_базового_класса_2, ... Вид_наследования_n Имя_базового_класса_n { Описание_добавляемых_элементов_производного_класса }; Если Вид_наследования не указан, то по правилу умолчания принимается public. Самая простая ситуация — это единичное общее наследование. Заголовок производного класса в этом случае имеет вид: class имя_наследника: public имя предка;//можно без public Ограничение доступа к элементам класса при наследовании имеет смысл. Так как личные элементы класса-предка не доступны в классе-наследнике, то нет проблем в том случае, когда два класса-предка имеют личные элементы (чаще всего поля) с одинаковыми именами. Проблемы с одинаковыми именами защищенных и общих элементов классов-предков также разрешаются ограничением доступа при наследовании. Заметим, что в Объектном Паскале эти проблемы отсутствуют, так как допустимо только единичное наследование. Также следует отметить, что повторное объявление (т. е. переопределение) в классе-наследнике полей класса-предка недопустимо! Повторное использование имен методов (т. е. переопределение методов) разрешено. Пример 1. Модифицирована программа примера лекции 1. Объявлен класс-наследник matr_nasl класса matr. В классе-наследнике добавлены методы calc_max, вычисляющий максимальные значения строк матрицы, и vivod_max, выводящий эти значения. Также в классе-наследнике переопределен метод vivod для вывода минимальных значений строк матрицы (изменена поясняющая фраза, предваряющая вывод). Обратите внимание на следующие моменты: 1. Поля класса-предка объявлены с уровнем доступа protected (а не private, как в лекции1), чтобы эти поля были доступны в классе-наследнике. 2. В С++ конструкторы и деструкторы базового класса в производных классах не наследуются. Однако если базовый класс содержит хотя бы один конструктор и деструктор, то производный класс также должен иметь конструктор и деструктор. При создании объектов производного класса предусмотрен автоматический вызов конструктора базового класса для инициализации его полей, но по умолчанию осуществляется вызов конструктора базового класса без параметров (такой конструктор должен быть запрограммирован). Для инициализации полей базового класса необходимо вызвать конструктор базового класса в списке инициализации конструктора производного класса, передав ему соответствующие аргументы, как показано ниже в коде программы: matr_nasl::matr_nasl(int n1, int m1, char c1):matr(n1, m1, c1){} В пустых фигурных скобках могут присутствовать операторы конструктора производного класса, как, например, показано в примерах 2 и 3. 3. Деструкторы (описанные в программе или созданные автоматически) будут вызываться в порядке, обратном порядку вызова конструкторов. Деструктор класса-наследника, содержащий вывод фразы ends! , сделан для того, чтобы можно было отследить порядок работы деструкторов. Ниже приведен код приложения: #include #include #include using namespace std; class matr {protected: float **a; int n,m; char c; float *min_str; public: matr(int n1, int m1,char c1); ~matr(); void calc_min(); void vvod(); void vivod(); }; matr::matr(int n1, int m1, char c1) {n=n1; m=m1; c=c1; a=new float* [n]; int i,j; for (i=0;i>a[i][j]; } void matr::vivod() {int i; cout<<"Minimums for matrix "<a[i][j]) min_str[i]=a[i][j]; } }; //_________________________________________________ class matr_nasl: public matr {private: float *max_str; public: matr_nasl(int n1, int m1, char c1); ~matr_nasl(); void calc_max(); void vivod_max(); void vivod(); }; matr_nasl::matr_nasl(int n1, int m1, char c1):matr(n1, m1, c1){} //Выше показано, как в конструкторе класса-наследника можно //вызвать конструктор класса-предка matr_nasl::~matr_nasl() { cout<<"ends!"<vvod(); b->vvod(); c->vvod(); a->calc_min(); b->calc_min(); c->calc_min(); a->vivod(); b->vivod(); c->vivod(); a->calc_max(); b->calc_max(); a->vivod_max(); b->vivod_max(); a->~matr_nasl(); b->~matr_nasl(); c->~matr(); _getch(); } 3. Принцип полиморфизма Полиморфизм – возможность определения единого по имени метода для всей иерархии производящих классов, причем в каждом классе этот метод может реализовываться со своими особенностями. Для примера рассмотрим простую иерархическую структуру классов геометрических фигур (см. рис. 2) Каждый из классов может иметь метод, показывающий на экране геометрическую фигуру. Эти методы различны, но удобно, чтобы они имели одинаковое имя (например, show), так как выполняют похожие действия. Принцип полиморфизма дает возможность существования в каждом классе метода show(), реализующего действия, нужные для данного класса. Полиморфизм гарантирует, что для любого экземпляра класса будут вызываться методы именно этого класса, а не какого-либо другого класса иерархии (несмотря на одинаковые имена). Полиморфизм реализуется путем введения виртуальных методов, которые подключаются к программе на этапе ее выполнения; такое подключение называется «поздним связыванием». Обычные, не виртуальные методы, как известно, подключаются к программе на этапе редактирования связей после компиляции («раннее связывание»). Технически «позднее связывание» реализуется следующим образом. В любой экземпляр класса с виртуальными методами добавляется скрытое поле – указатель на таблицу виртуальных методов (VMT – Virtual Method Table), в которой для каждого виртуального метода указывается адрес его реализации в данном классе. Инициализация скрытого поля осуществляется с помощью конструктора. При описании виртуального метода внутри базового класса перед его шаблоном добавляется слева ключевое слово virtual. Тогда в классе-наследнике этот метод может быть переопределен, причем при переопределении метода virtual можно не писать. Если в переопределенном методе нужно вызвать одноименный метод класса-предка, то к нему обращаются так: имя_предка ::имя_метода(фактические параметры) Ниже приведен пример, взятый из пособия: М.М. Маран. Delphi. Начальный курс. Учебное пособие. – М.: Издательство МЭИ, 2002. –100 с. Пример 2. Дано описание класса Pnt (точка) и его производного класса Crcl (круг). Рассмотрим методы класса Pnt: • Конструктор Crcl содержит вызов Pnt — конструктора класса-предка. Этот метод не является виртуальным. • Метод Move описывает движение фигуры. Он состоит в уничтожении изо­бражения (спрятывании) фигуры с помощью метода Hide, изменении положения центра фигуры (x,y) на величину (x, y) и в показе фигуры c помощью метода Show. Так как рассматривается только движение цен­тра фигуры (поступательное движение), то метод Move для всех фигур-наследников одинаков, он не будет переопределяться в классах-наслед­никах. • Методы Hide и Show различны для разных классов-наследников и вызыва­ются внутри метода класса-предка Move (доступного во всех наследниках), так что при их вызове не указывается имя экземпляра класса. Следовательно, методы Hide и Show должны быть объявлены как виртуальные. Код этих методов не приводится. Если методы Hide и Show не объявить виртуальными, то метод Move будет вызывать методы Hide и Show класса-предка Pnt (см. рис.3). Далее приведена заготовка программы: для получения работающего кода не хватает реализации методов show и hide и определения цветов. Обратите внимание, что конструктор производного класса, кроме конструктора базового класса, может содержать еще конструкторы собственных полей производного класса. При создании экземпляра производного класса сначала вызывается конструктор базового класса, а потом конструкторы полей производного класса. class Pnt /*точка*/ { protected: int x,y,color; public: Pnt(int a, int b, int c); virtual void Show(); /*будут переопределяться,*/ virtual void Hide(); /*вызываются Move*/ void Move (int dx, int dy);/*не будет переопределяться, вызывает Show и Hide*/ } Pnt :: Pnt (int a, int b, int c); { x=a; y=b; color=c; } void Pnt :: Show(); { … } void Pnt :: Hide(); { … }; void Pnt :: Move(int dx, int dy); { Hide(); x=x+dx; y=y+dy; Show(); } class Crcl: public Pnt { private: int r; public: Crcl (int a, int b, int c, int rad); void Show(); void Hide(); /*Move не переопределяется, берется из Pnt*/ } Crcl :: Crcl (int a1, int b1, int c1, int rad): Pnt (a1, b1, c1), r(rad) {r=rad;}//Внимание: так в //конструкторе класса-наследника можно вызвать конструктор класса-предка void Crcl :: Show(); {… } void Crcl :: Hide(); { … } void main() {Pnt* P; Crcl* C; P=new Pnt (10,10,cl1); C=new Crcl (50,50,cl2,10); P->Move(10,10);//двигается точка C->Move(50,50); //двигается круг delete P; delete C; } Возможна ситуация, когда в классе-предке нет необходимости иметь реализацию (полное описание) виртуального метода. Например, мы не собираемся работать с точкой (классом Pnt) , а будем работать только с его наследниками-фигурами. Виртуальный метод базового класса, не имеющий реализации, называется абстрактным методом, а класс, его содержащий, абстрактным классом. Пример 3. Класс-предок resh, кроме конструктора, содержит метод resh_ur вычисления корня уравнения на отрезке методом дихотомии и абстрактный виртуальный метод float fx (float x), задающий левую часть уравнения f(x)=0. Каждый из трех классов-наследников необходим для задания конкретного уравнения. Класс resh_ur_3 задает уравнение, известное с точностью до параметра p. #include #include #include using namespace std; class resh { private: float a, b, eps, cor; bool flag; public: resh (float a, float b, float eps); float resh_ur(); virtual float fx (float x)=0; //объявление абстрактного метода: =0 }; class resh_ur_1 : public resh { public: resh_ur_1(float a, float b, float eps) : resh(a, b, eps){} virtual float fx (float x) ; }; class resh_ur_2 : public resh { public: resh_ur_2(float a, float b, float eps) : resh(a, b, eps){} virtual float fx(float x); }; class resh_ur_3 : public resh { private: float p; public: resh_ur_3 (float a, float b, float eps, float p); virtual float fx(float x); }; resh::resh(float a, float b, float eps) { this->a=a; this->b=b; this->eps=eps; } resh_ur_3::resh_ur_3 (float a, float b, float eps, float p) : resh(a, b, eps), p(p) { this->p=p; } float resh::resh_ur() { float x; { if (fx(a)*fx(b)>0) { flag=false; return (a-1); } else { flag=true; while (b-a>eps) { x=(a+b)/2; if (fx(a)*fx(x)<=0) b=x; else a=x; } cor=x; } } return (cor); } float resh_ur_1::fx(float x) { return (x*x-1); } float resh_ur_2::fx(float x) { return (x-cos(x)); } float resh_ur_3::fx(float x) { return (x-p*cos(x)); } void main() { resh_ur_1* ur1; resh_ur_2* ur2; resh_ur_3 *ur3; float kor1, kor2, kor3; ur1=new resh_ur_1(0, 2, 0.001); cout<<"kor1="<resh_ur()<
Статья: Принципы объектно-ориентированного программирования
Найди решение своей задачи среди 1 000 000 ответов
Найти решение задачи
«Принципы объектно-ориентированного программирования» 👇
Готовые курсовые работы и рефераты
Купить от 250 ₽
Решение задач от ИИ за 2 минуты
Решить задачу
Найди решение своей задачи среди 1 000 000 ответов
Найти

Тебе могут подойти лекции

Смотреть все 588 лекций
Все самое важное и интересное в Telegram

Все сервисы Справочника в твоем телефоне! Просто напиши Боту, что ты ищешь и он быстро найдет нужную статью, лекцию или пособие для тебя!

Перейти в Telegram Bot