Программирование >>  Инициализация объектов класса, структура 

1 ... 12 13 14 [ 15 ] 16 17 18 ... 395


IntArray array;

массива встроенного типа, с использованием оператора взятия индекса:

int last pos = array.size()-1;

int temp = array[0]; array[0] = array[last pos]; array[last pos] = temp;

Для реализации доступа мы используем возможность перегрузки операций. Вот как

#include <cassert>

int& IntA\rray::operator[] (int index) {

assert (index >= 0 && index < size); return ia[index];

выглядит функция, реализующая операцию взятия индекса:

Обычно для проектируемого класса перегружают операции присваивания, операцию сравнения на равенство, возможно, операции сравнения по величине и операции ввода/вывода. Как и перегруженных функций, перегруженных операторов, отличающихся типами операндов, может быть несколько. К примеру, можно создать несколько операций присваивания объекту значения другого объекта того же самого или иного типа. Конечно, эти объекты должны быть более или менее похожи . (Подробно о перегрузке операций м1 расскажем в главе 15, а в разделе 3.15 приведем еще несколько примеров.)

Определения класса, различных относящихся к нему констант и, быть может, каких-то еще переменных и макросов по принятым соглашениям помещаются в заголовочный файл, имя которого совпадает с именем класса. Для класса IntArray мы должны создать заголовочный файл IntArray.h. Любая программа, в которой будет использоваться класс IntArray, должна включать этот заголовочный файл директивой препроцессора #include.

По тому же самому соглашению функции-члены класса, определенные вне его описания, помещаются в файл с именем класса и расширением, обозначающим исходный текст С++ программ!. будем использовать расширение . С (напомним, что в разных системах вы можете встретиться с разными расширениями исходных текстов С++ программ) и назовем наш файл IntArray.C.

Упражнение 2.5

Ключевой особенностью класса С++ является разделение интерфейса и реализации. Интерфейс представляет собой набор операций (функций), выполняемых объектом; он определяет имя функции, возвращаемое значение и список параметров. Обычно пользователь не должен знать об объекте ничего, кроме его интерфейса. Реализация скрывает алгоритмы и данные, нужные объекту, и может меняться при развитии объекта,

Теперь нам нужно определить операции доступа к элементам массива IntArray. Мы хотим, чтобы обращение к элементам IntArray выглядело точно так же, как к элементам



никак не затрагивая интерфейс. Попробуйте определить интерфейсы для одного из следующих классов (выберите любой):

(a) матрица

(b) булевское значение

(c) паспортные данн1е человека

(d) дата

(e) указатель

(f) точка Упражнение 2.6

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

Упражнение 2.7

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

2.4. Объектно-ориентированный подход

Вспомним спецификацию нашего массива в нред1дущем разделе. Mi говорили о том, что некоторым пользователям может понадобиться упорядоченный массив, в то время как большинство, скорее всего, удовлетворится и неупорядоченным. Если представить себе, что наш массив IntArray упорядочен, то реализация таких функций, как min() , max() , find() , должна отличаться от их реализации для массива неупорядоченного большей эффективностью. Вместе с тем, для поддержания массива в упорядоченном состоянии все прочие функции должны быть сильно усложнены.

Mi выбрали наиболее общий случай - неунорядоченн1й массив. Но как же быть с теми немногочисленными пользователями, которым обязательно нужна функциональность массива упорядоченного? Мы должны специально для них создать другой вариант массива?

А вот и еще одна категория недовольных пользователей: их не удовлетворяют накладные расходе! на проверку правильности индекса. Mi исходили из того, что корректность работы нашего класса превыше всего, и старались обезопасить себя от ошибочных ситуаций. Но возьмем, к примеру, разработчиков систем виртуальной реальности. Трехмерные изображения должны строиться с максимально возможной скоростью, быть может, за счет точности.

Да, мы можем удовлетворить и тех и других, создав для каждой группы пользователей свой, немного модернизированный, вариант IntArray. Более того, его даже не слишком трудно сделать, поскольку мы старались создать хорошую реализацию и необходимые



неупорядоченный массив без проверки границ индекса class IntArray { ... };

неупорядоченный массив с проверкой границ индекса class IntArrayRC { ... };

упорядоченн массив без проверки границ индекса

вносим необходимые изменения в нужные места и получаем три класса:

class IntSortedArray { ... };

Подобное решение имеет следующие недостатки:

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

если понадобится какая-то общая функция для обработки всех наших массивов, то нам придется написать три копии, поскольку типы ее параметров будут

void process array (IntArray&); void process array (IntArrayRC&);

различаться:

void process array (IntSortedArray&);

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

Парадигма объектно-ориентированного программирования позволяет осуществить все эти пожелания. Механизм наследования обеспечивает пожелания из первого пункта. Если один класс является потомком другого (например, IntArrayRC потомок класса IntArray), то наследник имеет возможность пользоваться всеми данными и функциями-членами, определенными в классе-предке. То есть класс IntArrayRC может просто использовать всю основную функциональность, предоставляемую классом IntArray, и добавить только то, что нужно ему для обеспечения проверки границ индекса.

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

изменения затронут совсем небольшие участки кода. Итак, копируем исходный текст,



1 ... 12 13 14 [ 15 ] 16 17 18 ... 395

© 2006 - 2024 pmbk.ru. Генерация страницы: 0.001
При копировании материалов приветствуются ссылки.
Яндекс.Метрика