zabika.ru   1 ... 4 5 6 7 8

Реализация зависимостей
Зависимости в языке C++ реализуются с помощью указателей или с помощью специальных объектов. Например, зависимость "много-к-одному" между классами Item и Group реализована через указатели:
class Item{

public:

virtual void cut ();

virtual void move (Length deltax, Length deltay) = 0;

virtual Boolean pick (Length px, Length py) = 0;

virtual void ungroup () = 0;
private:

Group* group;

friend Group::add_item (Item*);

friend Group::remove_item (Item*);
public:

Group* get_group () {return group;};

};
class Group: public Item

{

public:

void cut ();

void move (Length deltax, Length deltay);

Boolean pick (Length px, Length py) = 0;

void ungroup () { };
private:

ItemSet* items;
public:

void add_item (Item*);

void remove_item (Item*);

ItemSet* get_items () {return items;}

};
Каждый раз, когда к зависимости добавляется (или из нее удаляется) связь, оба указателя должны быть изменены:
void Group::add_item (Item* item)

{

item->group = this;

items->add (item);

}

void Group::remove_item (Item* item);

{

item->group = 0;

items->remove (item);

}

Методы Group::add_item и Group::remove_item могут изменять приватные (private) атрибуты класса Item, хотя они определены в его подклассе Group, так как они определены как дружественные (friends) для класса Item.

В рассмотренном примере опущены проверки:


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


Иногда связанные между собой (зависимые) объекты включают в так называемые коллективные классы. В качестве примера такого класса рассмотрим класс ItemSet (набор объектов):
class ItemSet

{

public:

ItemSet(); //создать пустой набор объектов

~ItemSet(); //уничтожить набор объектов

void add(Item*); //включить объект в набор

void remove(Item*); //исключить объект из набора

Boolean includes(Item*); //проверить наличие объекта в наборе

int size(Item*); //определить количество объектов в наборе

};
Коллективные классы часто используются в библиотеках классов. При работе с коллективными классами удобно использовать итераторы, т.е. объекты, с помощью которых можно "просмотреть" коллектив.

Зависимости между классами можно реализовать и с помощью специальных классов (каждый объект такого класса описывает связь между объектами соответствующих классов). В этом случае атрибуты класса соответствуют атрибутам описываемой им зависимости.
Дружественные функции
Дружественные функции – один из аспектов видимости в языке С++. Они представляют собой функции, которые описаны с модификатором friend в определении класса. Дружественным функциям разрешается читать и записывать поля данных объекта, описанные и как private, и как protected.

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

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


В объектно-ориентированных языках программирования полиморфизм – естественное следствие следующих особенностей:


  • механизма пересылки сообщений;

  • наследования;

  • принципа подстановки.

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

Чистый полиморфизм имеет место, когда одна и та же функция применяется к аргументам различных типов. В случае чистого полиморфизма есть одна функция (тело кода) и несколько ее интерпретаций. Другая крайность наблюдается, когда имеется множество различных функций (то есть тел кода с одним и тем же именем). Такая ситуация называется перегрузкой или “полиморфизмом ad hoc” .

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

В языке С++ полиморфные переменные возникают при использовании указателей или ссылок.

(Для понимания дальнейшего материала нужны некоторые пояснения: будем использовать понятие статический тип для обозначения типа, присвоенного переменной при ее описании. Термин динамический тип характеризует тип фактического значения. Тот факт что статический и динамический тип не обязаны совпадать, является одним из главнейших достоинств объектно-ориентированного программирования.)

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

Чтобы понять этот процесс, рассмотрим следующие два класса:

class One


{

public:

virtual int value( )

{

return 1;

}

};

class Two

{

public:

virtual int value ( )

{

return 2;

}

};
Класс One описывает виртуальный метод, который возвращает значение 1. Этот метод переопределяется в классе Two на метод, возвращающий значение 2.

Определим следующие функции:
void directAssign (One x)

{

printf(“по значению переменная равна %d \ n”,

x .value ( ) );

}

void byPointer (One * x)

{

printf(“по указателю переменная равна %d \ n”,

x -> value ( ) );

}

void byReference (One & x)

{

printf(“по ссылке переменная равна %d \ n”,

x .value ( ) );

}
Эти функции используют в качестве аргумента значение класса One, которое передается соответственно по значению, через указатель и через ссылку. При выполнении этих функций с аргументом класса Two для первой параметр преобразуется к классу One, и в результате будет напечатано значение 1. Две другие функции допускают полиморфный аргумент. В обоих случаях переданное значение сохранит свой динамический тип данных, и напечатано будет значение 2.

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

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

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

В языке C++ также возможно параметрическое программирование (программирование с использованием родовых компонентов). Родовые компоненты обладают свойством адаптивности к конкретной ситуации, в которой такой компонент используется, что позволяет разрабатывать достаточно универсальные и в то же время высокоэффективные компоненты программ (в частности, объекты).

В C++ определено два вида шаблонов: шаблоны-классы и шаблоны-функции.

Шаблоны-классы могут использоваться многими способами, но наиболее очевидным является их использование в качестве адаптивных объектов памяти.

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

Возможность параметрического программирования на языке C++ обеспечивается стандартной библиотекой шаблонов STL (Standard Template Library).

В библиотеке STL много разнообразного материала, так много, что перечислять его нет смысла, но за всей данной разработкой стоят несколько четких концепций, которые могут быть исключительно полезны для понимания всех составляющих STL. Первыми из них являются концепции контейнера(container) и итератора(iterator).

Контэйнер представляет собой объект, в котором другие объекты могут храниться, как массивы. Итератор представляет собой механизм, с помощью которого происходит обращение к объектам в контейнере. Например, в стандартном массиве вы располагаете индексом для обращения к элементам массива; этот индекс и есть итератор.

Эти контейнеры и итераторы реализованы в форме шаблонов. Реализация контейнера с шаблонами позволяет ему работать с любыми видами объектов.


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

Лучший способ описания STL – на примере. Листинг программы представляет собой пример использования контейнера list, содержащего связанный список объектов.

___________________________________________________________________

#include

#include

#include
using namespace std;
// переименование некоторых типов для облегчения

//последующей работы при создании списка и его

//итератора.

typedef list strList;

typedef list :: iterator strIter;
int main( )

{

strList myList;

//

//Добавление строк

//

myList.insert(myList.end(),”first”);

myList.insert(myList.end(),”second”);

myList.insert(myList.end(),”third”);

myList.insert(myList.end(),”fourth”);

myList.insert(myList.end(),”fifth”);

myList.push_front(“Head”); //добавить в начало

myList.push_back(“Tail”); //добавить в конец
//Вывод списка

//

for (strIter iter=myList.begin( ); iter !=myList.end(); ++iter)

cout << *iter << endl;

cout << endl; //добавление пустой строки
//Убираем один из элементов

//

myList.erase(find(myList.begin( ),myList.end( ), “third”));
// Сейчас показываем в обратном порядке

//

strIter iter = myList.end ( );

--iter;

for (int ix = myList.size( ) ; ix > 0 ; --iter ; --ix )

cout << *iter << endl;
return 0;

}

__________________________________________________________________

Первое, что представляет интерес в этой программе – тот факт что STL не использует расширения .h для своих заголовочных файлов.


Типы strList и strIter создаются, чтобы сделать код более удобочитаемым. В противном случае пришлось бы постоянно набирать громоздкие названия при описании списка контейнера и итератора.

Следующий интересный фрагмент – вызов элемента-функции insert списка контейнера. Аргументы этой функции элемента локализованы в контейнере, в который вставляется новый элемент; в нем же располагается и сам этот элемент. Для записи элементов в коней списка вызывается элемент-функция end. Она возвращает итератор, указывающий на коней списка.

Элементы-функции push_front и push_back содержат один аргумент - объект, заносимый в контейнер, и добавляют элементы соответственно в начало и в конец контейнера.

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


Результат выдаваемый программой:
Head

first

second

third

fourth

fifth

Tail
Head

fifth

fourth

third

second

first

Tail


<< предыдущая страница   следующая страница >>