Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Программа ГЭ_спец_2012 ответы light.doc
Скачиваний:
31
Добавлен:
15.11.2019
Размер:
3.71 Mб
Скачать

Раздел 11. Объектно-ориентированное программирование

  1. Основные идеи и сущность ООП: использование объекта в качестве основной компоненты программы и децентрализация управления, реализуемая представлением программы совокупностью взаимодействующих объектов, моделирование поведения объектов реального мира, наглядное представление объектов, создание и использование АТД, производные АТД.

Объектно-ориентированное программирование (ООП) — основная методология программирования 90-х годов. Она является результатом тридцатилетнего опыта и практики, которые берут начало в языке Simula 67 и продолжаются в языках Smalltalk, LISP, Clu и в более поздних — Actor, Eiffel, Objective С, Java и C++. ООП — это стиль программирования, который фиксирует поведение реального мира так, что детали разработки скрыты, а это позволяет тому, кто решает задачу, мыслить в терминах, присущих этой задаче, а не программированию.

Объектно-ориентированное программирование — это программирование, сфокуси¬рованное на данных, причем данные и поведение неразрывно связаны. Вместе дан¬ные и поведение представляют собой класс, а объекты являются экземплярами клас¬са. ООП рассматривает вычисления как моделирование поведения. То, что модели¬руется, является объектами, представленными вычислительной абстракцией.

Абстрактный тип данных, АТД (abstract data type, ADT) – определяемые пользователем расширения исходных типов языка. АТД состоит из набора значений и операций, которые могут влиять на эти значения. Например, в С нет типа данных для комплексных чисел, а C++ позволяет добавить такой тип и интегрировать его с существующими.

ООП - это не просто набор некоторых свойств, добавленных в уже существующие языки. Это новый шаг в осмыслении задач и разработки ПО.

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

Объект проявляет свое поведение путем вызова метода в ответ на сообщение. Интерпретация сообщения зависит от объекта и может быть различной для различных классов объектов. Для удобства создания нового типа из уже существующих типов, определенных пользователем используется механизм наследования. Классы могут быть организованы в виде иерархического дерева наследования. С помощью уменьшения взаимозависимости ООП позволяет разрабатывать системы, пригодные для многократного использования. ООП - это взгляд на программирование, сосредоточенный на данных; в котором данные и поведение жестко связаны. Для этого необходимо, чтобы объекты определялись вместе с сообщениями, на которые они могут реагировать.

Принципы Объектно-ориентированного подхода:

1. Действие в объектно-ориентированном программировании инициируется посредством передачи сообщений объекту. Сообщение содержит запрос на осуществление действия. В качестве реакции на сообщение получатель запустит некоторый метод, чтобы удовлетворить принятый запрос.

2. Все объекты являются экземплярами, классов. Все объекты одного класса используют одни и те же методы в ответ на одинаковые сообщения.

3. Принцип наследования. Классы могут быть организованы в иерархическую структуру с наследованием свойств. Дочерний класс наследует атрибуты родительского класса.

4. Принцип полиморфизма. Объекты реагируют на одно и тоже сообщение строго специфичным для них образом.

Центральным элементом ООП является инкапсуляция совокупности данных и соот¬ветствующих им операций. Понятие класса с его функциями-членами и членами данных предоставляет программисту подходящий для реализации инкапсуляции инструмент. Переменные класса являются объектами, которыми можно управлять.

Кроме того, классы обеспечивают сокрытие данных. Права доступа могут уста¬навливаться или ограничиваться для любой группы функций, которым необходим доступ к деталям реализации. Тем самым обеспечивается модульность и надежность.

Еще одной важной концепцией ООП является поощрение повторного использова¬ния кода с помощью механизма наследования. Суть этого механизма — получение но¬вого производного класса из существующего, называемого базовым. При создания про¬изводного класса базовый класс может быть дополнен или изменен. Таким путем могут создаваться иерархии родственных типов данных, которые используют общий код.

Объектно-ориентированное программирование зачастую более сложно, чем обычное процедурное программирование, как оно выглядит на С. Существует по крайней мере один дополнительный шаг на этапе проектирования, перед алгоритми¬зацией и кодированием. Он состоит в разработке таких типов данных, которые соот¬ветствовали бы поставленной проблеме. Зачастую проблема решается «обобщен¬нее», чем это действительно необходимо.

Есть уверенность, что использование ООП принесет дивиденды в нескольких от¬ношениях. Решение будет более модульным, следовательно, более понятным и про¬стым для модификации и обслуживания. Кроме того, такое решение будет более при¬годно для повторного использования. Например, если в программе нужен стек, то он легко заимствуется из существующего кода. В обычном процедурном языке програм¬мирования такие структуры данных часто «вмонтированы» в алгоритм и не могут экспортироваться.

  1. Основные концепции ООП: инкапсуляция и расширяемость типов, семантика объекта - данные как отражение состояния объекта и методы как средства обеспечения взаимодействия объектов; представление данных, классификация методов - конструкторы, деструкторы, селекторы, модификаторы; классы и абстрактные типы данных, наглядное представление классов, объекты как экземпляры классов, основные действия с объектами - создание, инициализация, обработка, разрушение.

Объектно-ориентированная парадигма предлагает новый подход к разработке программного обеспечения. Фундаментальная концепция объектно-ориентированной парадигмы состоит в передаче сообщений объектам.

В объектно-ориентированном программировании существует три основных принципа построения классов:

1. Инкапсуляция – это свойство, позволяющее объединить в классе и данные, и методы, работающие с ними и скрыть детали реализации от пользователя.

2. Наследование – это свойство, позволяющее создать новый класс-потомок на основе уже существующего, при этом все характеристики класса родителя присваиваются классу-потомку.

3.Полиморфизм – свойство классов, позволяющее использовать объекты классов с одинаковым интерфейсом без информации о типе и внутренней структуре объекта.

ООП — это сбалансированный подход к написанию программного обеспечения. Дан¬ные и поведение упакованы вместе. Такая инкапсуляция создает определяемые пользователем типы данных, расширяющие собственные типы языка и взаимодей¬ствующие с ними. Расширяемость типов — это возможность добавлять к языку опре¬деляемые пользователем типы данных, которые так же легко использовать, как и соб¬ственные типы.

Абстрактный тип данных, например строка, является описанием идеального, всем известного поведения типа. Пользователь строки знает, что операции, такие как кон¬катенация или печать, имеют определенное поведение. Операции конкатенации и печати называются методами. Конкретная реализация АТД может иметь ограниче¬ния; например, строки могут быть ограничены по длине. Эти ограничения влияют на открытое всем поведение. В то же время, внутренние или закрытые детали реализа¬ции не влияют прямо на то, как пользователь видит объект. Например строка часто реализуется как массив; при этом внутренний базовый адрес элементов этого масси¬ва и его имя не существенны для пользователя.

На терминологию ООП сильно повлиял язык Smalltalk. Создатели Smalltalk хо¬тели, чтобы программисты порвали со своими старыми привычками и приняли но¬вую методологию программирования. Они изобрели термины, такие как сообщение и метод, взамен традиционных понятий вызов функции и функция-член.

Инкапсуляция — это способность скрывать внутренние детали при предоставле¬нии открытого интерфейса к определяемому пользователем типу. В C++ для обеспе¬чения инкапсуляции используются объявления класса и структуры (class и struct) в сочетании с ключевыми словами доступа private (закрытый), protected (защищенный) и public (открытый).

Ключевое слово public показывает, что доступ к членам, которые стоят за ним, является открытым безо всяких ограничений. Без этого ключевого слова члены клас¬са по умолчанию закрыты. Закрытые члены доступны только функциям-членам сво¬его класса. Открытые члены доступны любой функции внутри области видимости объявления класса. Закрытость позволяет спрятать часть реализации класса, предот¬вращая тем самым непредвиденные изменения структуры данных. Ограничение дос¬тупа или сокрытие данных является особенностью объектно-ориентированного программирования.

В терминологии ООП переменная называется объектом. Функция-член, единствен¬ная работа которой заключается в инициализации объекта класса, называется конст¬руктором (constructor). Во многих случаях инициализация предполагает динами¬ческое распределение памяти. Конструкторы вызываются всякий раз, когда создается объект данного класса. Конструктор с одним аргументом может произво¬дить преобразование типов, если только при объявлении такого конструктора не ис¬пользуется ключевое слово explicit (явный). Деструктор (destructor) — это фун¬кция-член, задача которой состоит в том, чтобы завершить существование переменной класса. Этот процесс часто предполагает динамическое освобождение памяти. Деструктор вызывается неявно, когда автоматический объект выходит за пределы своей области видимости.

Конструкторы часто перегружают, задавая несколько функций-конструкторов, что¬бы предоставить несколько способов инициализации объекта. Перегруженный конструктор выбирается в зависимости от формы каждого из объяв¬лений.

Структуры наследования образуют каркас для построения достаточно общих сис¬тем. Например, база данных, содержащая информацию обо всех людях в университе¬те, может быть унаследована от базового класса person (человек). Базовый класс student можно использовать для создания производного класса студентов-юрис¬тов, как следующей значимой категории объектов.

  1. Классы: компонентные данные, компонентные функции, способы задания доступа, синтаксическая структура определения класса, объявление, определение и инициализация объектов, объекты объявленные как константы, область видимости класса, указатель this, компонентные функции типа static и const (статические и постоянные компонентные функции), тестирование класса.

В объектно-ориентированном программировании существует три основных принципа построения классов:

1. Инкапсуляция – это свойство, позволяющее объединить в классе и данные, и методы, работающие с ними и скрыть детали реализации от пользователя.

2. Наследование – это свойство, позволяющее создать новый класс-потомок на основе уже существующего, при этом все характеристики класса родителя присваиваются классу-потомку.

3.Полиморфизм – свойство классов, позволяющее использовать объекты классов с одинаковым интерфейсом без информации о типе и внутренней структуре объекта.

// объявление классов в С++

class /*имя класса*/

{

private:

/* список свойств и методов для использования внутри класса */

public:

/* список методов доступных другим функциям и объектам программы */

protected:

/*список средств, доступных при наследовании*/

};

Всем не статическим методам скрытно передается указатель на сам объект, доступ к которому осуществляется через ключевое слово this.

  1. Создание и уничтожение объектов: конструкторы – назначение; наличие нескольких конструкторов у одного класса - причины, разделение функций; способы задания конструктора - по умолчанию или явное; вызов конструктора - назначение, способы реализации, задание аргументов; явный и автоматический вызовы - ситуации использования, рекомендации по применению; деструкторы - назначение, основные свойства; способы задания - по умолчанию или явное; вызов деструктора - назначение, способы реализации; явный и автоматический вызовы - ситуации использования, рекомендации по применению; отладка и тестирование системы конструкторов и деструкторов.

Конструктор - функция-член, имя которой совпадает с именем класса. Он создает объекты типа класса. Это включает инициализацию членов-данных, и, часто, распределение свободной памяти, использующее new. Классы, имеющие конструктор с пустым списком параметров, как конструктор, все параметры которого имеют значение по умолчанию, могут быть базовыми типами для объявления массивов,если инициализация неявная. Такой конструктор называется конструктором по умолчанию.

foo::foo() {...} //конструктор по умолчанию

hoo::hoo(int i=0) {...} //конструктор по умолчанию

Деструктор - функция-член с именем, таким же, как и имя класса, которому предшествует символ ~ (тильда). Обычная его цель состоит в том, чтобы удалять значения типа класса. Это обычно выполняется путем применения delete.

Конструктор в форме

тип::тип (const тип & x) используется для выполнения копирования значения одного типа в другой, когда

- Переменная типа инициализируется значением типа.

- Значение типа передано как аргумент в функцию.

- Значение типа возвращается из функции.

Это называется конструктором копии и, если он не указан явно, то генерируется компилятором. По умолчанию происходит почленная инициализация значений.

Классы с конструкторами по умолчанию могут иметь порожденный тип массива. Например,

vect a[5];

является объявлением, которое использует конструктор с пустым списком параметров для того, чтобы создать массив a из пяти объектов, каждый из которых - типа vect размером 10.

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

Вызов конструкторов

Ранние версии C++ оставили неопределенным порядок выполнения для инициализации конструкторов базового класса и конструкторов класса-члена. Большую часть времени эти конструкции не зависели друг от друга, и результаты не могли зависеть от порядка выполнения. Однако, с добавлением множественного наследования, стало не-

желательно и опасно продолжать эту небрежность. Поэтому принят следующий порядок:

1. Явная ссылка на конструкторы базового класса в том порядке, в котором они перечислены после двоеточия.

2. Неупомянутые базовые классы в порядке, в котором они объявлены.

3. Явная ссылка на конструкторы класса-члена в порядке, в котором они перечислены после двоеточия.

Виртуальные базовые классы имеют специальный приоритет и создаются перед любым из своих порожденных классов. Они создаются перед любыми невиртуальными базовыми классами. Порядок создания зависит от их DAG. Этот порядок такой - снизу вверх, справа налево.

Деструкторы вызываются в порядке, обратном конструкторам. Проиллюстрируем это разработкой предыдущего примера.

class tools {

public:

tools(char*);

~tools();

};

class parts {

public:

parts(char*);

~parts();

}

class labor {

public:

labor(int);

~labor();

}

class plans : public tools, public parts, public labor {

special a; //класс-член с конструктором

public:

plans(int m) : labor(m), tools("lathe"),

a(m), parts("widget")

{...}

~plans();

};

В этом случае конструктор члена a(m) появляется прежде, чем конструктор основного класса parts("widget"), но, по правилам, вызывается последним. Так как его конструктор был последним, его деструктор вызывается первым, а затем ~parts, ~tools, ~labor и

~plans.

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

Перегрузка и выбор функций

Перегруженные функции являются важным дополнением C++. Конкретная функция яыбирается в зависимости от соответствия списка аргументов при вызове функции списку параметров в объявлении функции. Когда вызывается перегруженная функ-тия, компилятор должен иметь алгоритм для выбора надлежащей функции. Алгоритм, который выполняет этот выбор, зависит от того, преобразования какого типа присутствуют. Наилучшее соответствие должно быть уникальным. Оно должно быть лучшим по крайней мере для одного аргумента и так же хорошо, как остальные соответствия, для всех других аргументов.

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

Ключевое слово friend (друг) служит спецификатором, уточняющим свойства функции. Оно дает функции-не-члену доступ к скрытым членам класса и предостав¬ляет способ для обхода ограничений сокрытия данных в C++. Однако должна быть веская причина для обхода этих ограничений, поскольку они важны для надежного программирования.

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

Дружественная функция должна быть объявлена внутри объявления класса, по отношению к которому она является дружественной (с которым она дружит), Функция предваряется ключевым словом friend и может встречаться в любой части класса; это не влияет на ее смысл. Мы предпочитаем размещать объявление f riend в открытой части класса. Функция-член одного класса может быть дружественной другому классу. В этом случае для указания ее имени в дружественном классе используется оператор разрешения области видимости. То, что все функции-члены одного класса являются дружественными функциями другого класса, может быть указано как friend class имя_класса.

class tweedledee {

//дружественная функция //функция-член

class tweedledum {

friend int tweedledee::Cheshire();

friend void alice( int Cheshire);

class tweedledumber {

friend class tweedledee;

//все функции-члены из tweedledee получают доступ

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

Ключевое слово operator используется для того, чтобы определить функцию-член, осуществляющую преобразование типа. Оно также используется для перегрузки встроенных операторов C++. Также как имени функции, такому как print, можно придать множество различных смыслов, зависящих от аргументов, так и оператору, например, +, можно приписать дополнительные значения. Перегруженные операторы можно использовать для привычной записи выражений как для встроенных типов, так и для АТД. Это очень удобный способ записи, во многих случаях он делает программы короче и читабельнее.

Унарные и бинарные операторы могут быть перегружены как нестатические функции-члены. Они неявно действуют на объект класса. Большинство унарных операторов можно перегружать как обычные функции, принимающие единственный аргумент класса или ссылку на тип класса. Большинство бинарных операторов можно перегружать как обычные функции, принимающие один или оба аргумента класса или ссылку на тип класса. Операторы =, (), [ ] и -> должны быть перегружены нестатическими функциями-членами.

Доступные для перегрузки операторы включают все арифметические и логические операторы, операторы сравнения, равенства, присваивания и битовые операторы. Более того, операторы автоинкремента и автодекремента ( + + и —) могут иметь различные префиксные и постфиксные значения. Операторы индексации массива [] и вызова функции () также можно перегружать.

Это возможно и для операторов доступа к члену через указатель на объект - >, и обращения к члену через указатель на член ->*. Можно перегружать new и delete. Операторы присваивания, вызова функции, индексирования и оператор доступа к члену через указатель на объект -> можно перегружать только нестатическими функциями-членами.