Смекни!
smekni.com

Препроцессорные средства в C и С++ (стр. 3 из 7)

4.5.1. Понятие о “позднем” связывании

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

4.5.2. Описание виртуальных функций

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

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

Виртуальная функция может быть объявлена в форме:

virtual void print ( ) = 0;

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

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

class TBase //базовый класс для массивов всех типов {int size, //размер элемента count, //текущее число элементов maxCount, //размер выделенной памяти в байтах delta; //приращение памяти в байтах char *pmem; //указатель на выделенную память int changeSize(); //перераспределение памятиprotected: void* getAddr( ){return (void*) pmem;};void addNewItem(void*); //добавление в конец массиваvoid error(const char* msg){cout <<msg<<endl;}; public: int getCount() {return count;}; TBase(int s,int m,int d); TBase(); TBase(TBase&); ~TBase(); virtual void print ( ) = 0; // Чистаявиртуальнаяфункция };

Тогда в производных классах должна быть объявлена замещающая ее функция print, выполняющая реальные действия:

class TIntArray : public TBase { /* Другиеметоды */ virtual void print ( ); }class TRealArray : public TBase { /* Другиеметоды */ virtual void print ( );}

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

TBase *pb; TIntArray aint(5,3);TRealArray areal(4,2);

Тогда для печати массивов могут применяться операторы

pb = &aint; pb->print(); //Печать массива aint pb = &areal; pb->print(); // Печать массива areal

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

Программа:

#include <iostream.h> struct Pet // Базовыйкласс { char *name; virtual void speak() = 0; Pet( char *nm){name=nm;} } struct Dog : public Pet { virtual void speak( ) { cout<<name<<“ говорит “”<<“ Ав - ав”<<endl; }; Dog(char *nm): Pet(nm) { }; }; struct Cat : public Pet { virtual void speak( ) { cout<<name<<“ говорит “ <<“ Мяу-Мяу”<<endl; Cat(char *nm): Pet(nm) { }; } int main () { Pet *mypets[ ] = { new Dog(“Шарик”), new Cat(“Мурка”),new Dog(“Рыжий “)}; // Список животныхconst int sz = sizeof( mypets)/ sizeof( mypets [ 0 ]); for ( int k = 0: k < sz; k++) mypets [ k ]->speak();return 0; }

4.6. “Дружественные” (friend) функции

Функция, объявленная в производном классе, может иметь доступ только к защищенным (protected) или общим (public) компонентам базового класса.

Функция, объявленная вне класса, может иметь доступ только к общим (public) компонентам класса и обращаться к ним по имени, уточненному именем объекта или указателя на объект.

Чтобы получить доступ к личным компонентам объектов некоторого класса Х в функции, не имеющей к ним доступа, эта функция должна быть объявлена дружественной в классе X:

class X { friend void Y:: fprv( int, char*);/* Другие компоненты класса X */ }

Можно объявить все функции класса Y дружественными в классе X;

class Y; class X{ friend Y; /* Другие компоненты класса X */} class Y { void fy1(int, int); int fy2( char*, int);/* Другие компоненты класса Y */ }

Дружественной может быть и функция, не являющаяся компонентой какого-либо класса, например,

class XX { friend int printXX ( );/* Другие компоненты класса ХХ */ }

Здесь функция printXX имеет доступ ко всем компонентам класса XX, независимо от закрепленного за ними уровня доступа.

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

4.7. Статические компоненты класса

Описатель static в С++ имеет различное назначение в зависимости от контекста, в котором он применен.

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

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

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

class TBase //базовый класс для массивов всех типов{ static int nw; int size, //размер элемента count, //текущее число элементов maxCount, //размер выделенной памяти delta; //приращение памяти /* Другие компоненты класса TBase */ } int TBase::nw =1; /* Инициализация статической компоненты класса */

Статические компоненты - функции могут вызываться до создания экземпляров объектов этого класса и поэтому имеют доступ только к статическим данным класса:

class X { static int sx1,sx2; static void fsx ( int k); int x1,x2; /* Другие компоненты класса X */ } int X::sx1 = 1; int X::sx2 = 2; int main () { .......... X:: fsx( 3 ); .............. }

4.8. Переопределение (перегрузка) операций

В языках программирования определена семантика операций, выполняемых над базовыми (предопределенными) типами данных, например, если x, y и z - переменные типа float, то запись x = y + z; предполагает интуитивно очевидные действия, сложение x и y и присваивание переменной z полученной суммы.

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

Переопределенная операция объявляется так:

тип_результата operator знак_операции (формальные параметры) { описание_алгоритма_выполнения_операции }

Например:

class TPoint { int x,y; public: TPoint& operator+=( const TPoint& adder ); TPoint& operator-=( const TPoint& subber ); friend TPoint operator - ( const TPoint& one, const TPoint& two); friend TPoint operator + ( const TPoint& one, const TPoint& two); friend int operator == ( const TPoint& one, const TPoint& two); friend int operator != ( const TPoint& one, const TPoint& two);};

Полное определение этих операций для объектов класса TPoint имеет вид:

inline TPoint& TPoint::operator += ( const TPoint& adder ) { x += adder.x; y += adder.y; return *this;} inline TPoint& TPoint::operator -= ( const TPoint& subber ) { x -= subber.x; y -= subber.y; return *this;}

Остальные операции определяются аналогичным образом.

Пусть в программе имеются объявления:

TPoint x(12,3), y(21,30), z(18,30);

Тогда можно записать:

x +=y; y-=z; TPoint r = x + z:

Общие правила переопределения операций сводятся к следующему:

- Двуместные операции должны иметь два параметра, одноместные - один параметр, причем, если операция объявлена как компонента класса, то неявным первым операндом является экземпляр объекта (следовательно при определении двуместной операции будет задаваться один параметр, одноместная операция объявляется с пустым списком параметров). Если операция переопределяется вне класса (с описателем friend ), то для двуместной операции должны быть заданы два параметра, для одноместной операции - один параметр.

- При переопределении сохраняется приоритет исходной операции т.е. операция + будет выполняться раньше операции = и т.д.

- При переопределении не наследуются свойства коммутативности и ассциативности, т.е. результат выражения х + y - z может отличаться от результата выражения y - z + x и зависит от того, как определены соответствующие операции.

- Не допускается переопределение операций . (точка), .* ( точка -звездочка, обращение к указателю на компоненту класса или структуры), :: (разрешение контекста), а также операции # и ##, используемые при препроцессорной обработке.