Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Шпоры / Билет 54-59

.doc
Скачиваний:
24
Добавлен:
16.04.2013
Размер:
61.95 Кб
Скачать

Билет 54.

Оператор присваивания для типа класса по умолчанию генерируется компилятором для выполнения почленного присваивания. Это прекрасно, когда подходит поверхностное копирование. Для типов вроде my_string или vect, которые нуждаются в глубоком копировании, это некорректно. В качестве практического правила можно принять следующее: каждый раз, когда класс нуждается в явно определенном копирующем конструкторе, он нуждается и в определении оператора присваивания.

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

имя_класса& operator[]{целый_тип);

Такие функции могут использоваться с обеих сторон присваивания. Было бы также удобно иметь возможность присваивать один массив другому. Пользователь с помощью перегрузки может задать поведение такого присваивания. При этом хорошим стилем является соответствие стандартному использованию присваивания.

Билет 55.

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

Оператор вызова функции () перегружается как нестатическая функция-член. Он может быть перегружен для различных сигнатур. Он часто используется в oпeрациях итератора или в тех операциях, где нужны множественные индексы.

• inline double& matrix::operator()(int i, int j)

{assert( i >= 0 && i < c_size && j >= 0 && j < r_size) ;

return p[i] [j] ;}

Данная функция-член дает удобную многоаргументную нотацию для доступа к эле­ментам. Это приводит к тому, что в клиентском коде можно использовать выражения вида m (i, j ) для доступа к элементам матрицы. Отметьте, что индексы матрицы проверяются на нахождение в пределах границ с помощью утверждений.

• matrix&matrix::operator+=(matrix& m)

{assert(m.c_size = = c_size && m.r_size = = r_size);

Для проверки аргументов функции-члена на предусловие используется макро про­верки утверждений. Присваиваемая матрица должна быть такого же размера, как и та, которой она присваивается. Этот код заменил инструкцию if-else, отвечаю­щую за выход по ошибке. Сравните с кодом, написанным для класса vect.

• for (i = 0; i <. c_size; ++i)

for(j=0; j<r_size; ++j);

p[i][j]+=m.p[i][j];

Этот вложенный цикл прозрачен и эффективен. Почленное сложение выполнено без накладных расходов.

• return (*this) ;

Возвращаемый тип является ссылкой на matrix. Разыменовывая указатель this, мы получаем lvalue объекта matrix. Это обычный прием, позволяющий выполнять множественные (порторные) присваивания.

Билет 56.

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

Указатель на член класса отличается от указателя на класс. Указатель на тип члена класса выглядит как Т::*, где Т – имя класса. В С++ есть два оператора, служащих для разыменования указателя на член класса. Вот они:

.*

->*

В конструкциях объект. *указатель_на_член и указатель->*указатель_ на_ член сначала производится доступ к объекту, а затем доступ к указанному члену и его разыменование.

Билет 57.

Большинство классов содержит операции по распределению и освобождению памя­ти. Иногда для обеспечения эффективности или надежности требуется более изощ­ренное использование памяти, чем то, которое предоставляется с помощью простого, вызова операторов new и delete.Оператор new имеет следующий общий вид:

:: opt new размещениеopt тип инициализаторopt

Здесь opt обозначает необязательные (optional) части. Вот некоторые примеры:

::new char[10]; //настаивает на глобальном new new(buff) Х(а); //вызов с buff используя X: :X(a)

До сих пор для выделения свободной памяти мы использовали глобальный оператор new (). Система неявно предоставляет аргумент sizeof (тип} для этой функции. Вот прототип функции:

void* operator new(size_t size) ;

Операторы new и delete могут быть перегружены. Это свойство предоставляет ме­ханизм, позволяющий пользователю манипулировать свободной памятью. Напри­мер, в традиционном программировании на С для доступа к свободной памяти ис­пользуется malloc (), она возвращает void* указатель на выделенную память. В этой схеме память освобождается с помощью функции free () из stdlib.h. Использу­ем перегрузку операторов new и delete, чтобы позволить объекту Х управлять сво­бодной памятью традиционным для С образом.

#include <stdlib.h> //определены malloc() и free ()

сlass X { public: void* operator new(size_t size)

return (malloc(size)); }

void operator delete(void* ptr) { free(ptr); }

X(unsigned size) { new(size); }

~X( ) { delete(this); }

. . . . .};

В этом примере класс X предоставляет перегруженные формы new () и delete (). Если класс перегружает operator new (), глобальный оператор по-прежнему дос­тупен с помощью оператора разрешения области видимости : :.

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

Синтаксис размещения (placement) предоставляет разделенный запятыми список аргументов, используемый для выбора перегруженного оператора new () с соответствующей сигнатурой. Эти дополнительные аргументы часто применяются для того, чтобы поместить создаваемый объект по конкретному адресу. Такая форма операто­ра new использует заголовочный файл new.h.

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

Оператор delete бывает двух видов. Он может иметь следующие сигнатуры:

void operator delete(void* p) ;

void operator delete(void* p, size_t);

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

В файле new.h имеется указатель _new_handler на функцию, которая вызывает обработчик ошибок для оператора new. Если память исчерпана, указатель _new_handler используется для вызова системной процедуры. С помощью set_new_handler () пользователь может указать явную процедуру <<недостаточ-но памяти>>, которая заменит системную. Функции-члены new () и delete () всегда неявно статические. Функция new () вызывается до того, как объект существует и, следовательно, у него еще нет this. Функция delete() вызывается деструктором при уничтожении объекта.

Билет 58.

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

Многие полезные типы являются вариантами других, и часто бывает утомительно создавать для каждого из них один и тот же код. Производный класс наследует описание базового класса; затем он может быть изменен добавлением новых членов, изменением существующих функций-членов и изменением прав доступа. В полезности такой концепции можно убедиться на примере того, как таксономическая классификация компактно резюмирует большие объемы знаний. Скажем, располагая сведениями о понятии «млекопитающие» и зная, что и слон, и мышь являются млекопитающими, можно сделать из описания значительно более краткими. Основное базовое понятие содержит информацию о том, что млекопитающие – это теплокровные высшие позвоночные, вскармливающие своих детенышей молоком. Эта информация наследуется понятиями «мышь» и «слон», но подробно она изложена лишь однажды – в базовом понятии. В терминах С++ и слон, и мышь являются производными от базового класса млекопитающих.

С++ поддерживает виртуальные функции-члены (virtual member function). Это функции, объявленные в базом классе и переопределенные в производных классах. Иерархия классов, которая определена открытым наследованием, создает родственный набор пользовательских типов, на все объекты которых может указывать указатель базового класса. Получая доступ к виртуальной функции с помощью этого указателя, С++ выбирает надлежащее определение функции на этапе выполнения. Объект, на который направлен указатель, должен нести в себе информацию о типе, так чтобы его можно было распознать динамически; в этом заключается характерная особенность кода на С++. Каждый объект знает, как на него можно воздействовать. Такая форма полиморфизма называется чистым полиморфизмом (pure polymorphism).

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

Методология объективно-ориентированного проектирования:

  1. Выбор надлежащей совокупности типов.

  2. Проектирование взаимосвязей между типами и применение наследования для использования общего кода.

  3. Использование виртуальных функций для полиморфной обработки родственных объектов.

Класс можно сделать производным от существующего с использованием следующей формы:

Class имя_класса :

(public | protected | private)opt имя_базового_класса

{объявление членов};

Ключевое слово Class как всегда можно заменить ключевым словом struct, если подразумевается, что все члены открыты. Одним из аспектов производного класса является видимость (открытость) его членов-наследников. Ключевые слова public, protected и private используются для указания того, насколько члены базового класса будут доступны для производного.

Ключевое слово protected введено для того, чтобы сохранить сокрытие данных для членов, которые должны быть доступны из производного класса, но в других случаях действуют как закрытые (private). Это промежуточная форма доступа между public и private.

Билет 59.

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

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

Объявление идентификатора в области видимости скрывает все объявления этого идентификатора в объемлющих областях видимости. Базовый класс является объемлющей областью видимости для любого их своих произвольных классов. Это правило не зависит от того, были ли имена объявлены как virtual. Ограничения доступа (private, protected) не связаны с выбором функции. Если выбранная функция недоступна, это вызывает ошибку компиляции.

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

Виртуальные функции позволяют делать выбор на этапе выполнения. Рассмотрим приложение – систему автоматизированного проектирования, в котором должна вычисляться площадь фигур в проекте. Различные фигуры будут наследоваться из базового класса shape (фигура):

Соседние файлы в папке Шпоры