рефераты

рефераты

 
 
рефераты рефераты

Меню

Реферат: Turbo C++ Programer`s guide рефераты

Деструктор может быть объявлен как virtual. Это позволяет указателю объекта базового класса вызывать необходимый деструктор в случае, когда указатель фактически ссылаетсяна объект производного класса.Деструктор класса, производного от класса с виртуальным деструктором, сам является виртуальным.

Внимание: пример проограммы см в файле prog_1.app

Однако, если ни один из деструкторов не был объявлен виртуальным,delete palette[0], delete palette[1] и delete palette[2] вызывают только деструктор дляклассаcolor.Это приведет к неправильному разрушению первых двух элементов, которые фактически имели тип red и brightred.

Перегруженные операции

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

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

..*   ::   ?:

Также невозможна перегрузка символов препроцессора # и ##.

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

Операция-функция, вызываемая саргументами, ведет себя как операция, выполняющая определенные действия с операндами в выражении. Операция-функция изменяет число аргументов или правила приоритета и ассоциативности операции (таблица 1.20 на стр.75 оригинала), сравнительно с ее нормальным использованием. Рассмотрим класс complex:

class complex (*

double real, imag; // по умолчанию private

public:

...

complex() (* real = imag = 0; *) // встроенный конструктор

complex(double r, double i = 0) (* // еще один

real = r; imag = i;

*)

...

*)

Данный класс был создан только для примера. Онотличен от класса complex из библиотеки исполняющей системы.

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

complex AddComplex(complex c1, complex c2);

однако будет более естественным иметь возможность записать:

complex c1(0,1), c2(1,0), c3

c3 = c1 + c2;

вместо

c3 = AddComplex(c1, c2);

Операция + легко может быть перегружена, есливключить в класс complex следующее объявление:

friend complex operator +(complex c1, complex c2*);

и сделав его определение (возможно, встроенное) следующим образом:

complex operator +(complex c1, complex c2)

(*

return complex(c1.real + c2.real, c1.imag + c2.imag); *)

Операции-функции

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

c3 = c1.operator + (c2);   // то же, что c3 = c1 + c2

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

Перегруженные операции и наследование

За исключением операции-функции присвоения =() (см. раздел "Перегрузка операции присвоения =" на стр.127 оригинала) все функции операций для класса X наследуются классом, производным от X, согласно стандартным правилам разрешения для перегруженных функций. Если Х является базовым классом для Y, перегруженная функция операции для Х может далее быть перегружена для Y.

Перегрузка new и delete

Операцииnew и delete могут быть перегружены таким образом, чтобыдаватьальтернативныеварианты подпрограммы управления свободной памятью (кучей). Определяемая пользователем операция new должна возвращать void* и иметь в качестве первого аргумента size_t. Определяемая пользователем операция delete должна иметь тип void и первый аргумент void*; второй аргумент, типа size_t, является опциональным.

Тип size_t определяется в stdlib.h.

Например,

#include <stdlib.h>

class X (*

...

public:

void* operator                      new(size_t                   size)               (*           return

newalloc(size);*)

void operator delete(void* p*) (*newfree(p); *)

X() (* /* здесь инициализация */

X(char ch) (* /* здесь тоже */ *)

-X() (* /* очистка */ *)

...

*);

Аргумент size задает размер создаваемого объекта, а newalloc и newfree это определяемые пользователем функции распределения и отмены распределения памяти. Вызовы конструктора и деструктора для объектов класса Х (или объектов, производных от Х, для которых не существует собственных перегруженных операций new и delete) приведет к запуску соответствующихопределяемых пользователем X::operator new() и X::operator delete(), соответственно.

Операции-функции X::operator new иX::operator delete являются статическими компонентами Х, как при явном объявлении их static, так и без него, поэтому они не могут быть виртуальными функциями.

Стандартные, предопределенные (глобальные) операции new и delete могут при этом также использоваться в контексте Х, как явно с операциямиглобального контекста (::operator new и ::operator delete), так и неявно, при создании и разрушении объектов классов, отличных от класса Х и не являющихся производными от класса Х. Например, можно использовать стандартныеnew иdeleteпри определении перегруженных версий:

void* X::operator new(size_t)

(*

void* ptr = new char[s];  // вызов стандартной new

...

return ptr;

*)

void X::operator delete(void* ptr)

(*

...

delete (void*) ptr; // вызов стандартной delete

*)

Причинойтого, что размер аргумента определяется size, является то,что классы, производные от Х, наследуют X::operator new. Размер объектапроизводного классаможетсущественно отличаться от размера, определяемого базовым классом.

Перегрузка унарных операций

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

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

class X (*

...

X operator ++() (* /* здесь подпрограмма инкремента X * / *)

*)

...

X x, y;

y = ++x; // то же, что и  y = x++ !

Перегрузка бинарных операций

Перегрузка бинарной операции выполняется при помощи объявления не-статической функции компонента, принимающей один аргумент, либо при помощи объявления не являющейся компонентом функции (обычно friend), принимающей два аргумента. Если @ представляет собой бинарную операцию, тоx@y можно интерпретировать либо как x operator@(y), либо как operator@(x,y), в зависимости от выполненных объявлений. Если объявлены обе формы, то разрешение неоднозначности зависитот переданныхпри вызове операции стандартных аргументов.

Перегрузка операции присвоения =

Операцияприсвоения = может быть перегружена только при помощи объявления не-статической функции-компонента. Например,

Внимание :              пример программы смотрите в файле  struct.app

Данный код,                        совместно                     с                  объявлениями

String::operator=(), позволяет выполнять строковые присвоения str1 = str2, как это делается в прочих языках программирования. Вотличие от прочих функций операций, функция операции присвоения не может наследоваться производными классами. Если для какого-либо класса Х не существует определяемой операции =, то операция = определяется по умолчанию как покомпонентное присвоение компонентов класса Х:

X& X::operator = (const X& source)

(*

// покомпонентное присвоение

*)

Перегрузка операции вызова функции ()

Вызов функции

первичное-выражение(<список-выражений>)

рассматривается в качестве двоичной операции с операндами первичное-выражение и список-выражений (возможно, пустой). Соответствующая функция операции это operator(). Данная функция может являться определяемой пользователем для класса Х (и любых производныхклассов) только в качестве не-статической функции-компонента. Вызов x(arg1, arg2), где x есть объект класса Х, интерпретируется в таком случае как x.operator()(arg1,arg2).

Перегрузка операции индексирования []

Аналогичным образом, операция индексирования

первичное-выражение [выражение]

рассматривается как двоичнаяоперация с операндами первичное-выражение и выражение. Соответствующая функция операции это operator[]; она может быть определена пользователем для класса Х (и любых производных классов) только посредством не-статической функции-компонента. Выражение x[y], где x это объект класса Х, интерпретируется в данном случае как x.operator[](y).

Перегрузка операции доступа к компоненту класса ->

Доступ к компоненту класса при помощи

первичное-выражение->выражение

рассматривается как унарнаяоперация. Функция operator-> должна рассматриваться как не-статическая функция-компонента. Выражение x->m, где x это объект класса (*, интерпретируетсякак (x.operator->())->m, таким образом, что operator-> () должен либо возвращать указательна объект данного класса, либо возвращать объект класса, для которого определяется operator->.

Виртуальные функции   -------------------------------

Виртуальные функциипозволяют производным классам обеспечивать разные версии функциибазового класса. Вы можете объявить виртуальную функцию в базовом классе и затем переопределить ее влюбом производном классе, даже если число и тип ее аргументов остается без изменений. Вы также можете объявить функции int Base::Fun (int) и int Derived::Fun (int), даже если они не являются виртуальными. Версия базового класса доступна объектам производного класса через переопределение контекста. Если они являются виртуальными, то доступна только функция, связанная с фактическим типом объекта.

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

Говорят,что переопределенная функция переопределяет функцию базового класса. Для объявления виртуальной функции служит спецификатор virtual. Спецификатор virtual подразумевает компонентство в классе, поэтому виртуальная функция не может быть глобальной (не являющейся компонентом) функцией.

Если базовый класс В содержит виртуальную функцию vf, и класс D, являющийся производным от класса B, содержит функцию vf того же типа,то если функция vf вызывается дляобъекта d или D, выполняется вызов D::vf, даже если доступ определен через указатель или ссылку на B. Например,

struct B (*

virtual void vf1();

virtual void vf2();

virtual void vf3();

void f();

*)

class D : public B (*

virtual void vf(); // спецификатор virtual допустим, но // избыточен

void vf2(int);  // не virtual, поскольку здесь

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

char f();  // так нельзя: изменяется только тип

// возврата

void f();

*);

void extf()

(*

D d;  // объявление объекта D

B* bp = &d; // стандартное преобразование из D* в B* bp->vf1(); // вызов D::vf1

bp->vf2(); // вызов B::vf2, так как vf2 из D имеет // отличные аргументы

bp->f();   // вызов B::f (не виртуальной)

*)

Переопределяющаяфункция vf1 в D автоматически становится виртуальной. Спецификатор virtual может быть использован в определении переопределяющейфункции в производном классе, но на самом деле он является в данном случае избыточным.

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

Примечание:

Виртуальные функции должны являться компонентами некоторого класса, ноони не могут бытьстатическими компонентами. Виртуальная функция может являться другом (friend) другого класса.

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

class B (*

virtual void vf(int) = 0;// = 0 означает "чистую" функцию

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

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

Виртуальные функции заставляют определенным образом расплачиваться за свою универсальность: каждый объект производного класса должен содержать указатель на таблицу функций с тем, чтобы во время выполнения программы вызвать нужную (поздняя компоновка). См. главу 5, в документе "Начало работы".

Абстрактные классы

Глава 5 документа "Начало работы" приводит пример абстрактного класса в действии.

Абстрактным называется класс с как минимум одной чистой виртуальной функцией. Виртуальная функция задается как "чистая" при помощи соответствующего спецификатора.

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

class shape  (*                       // абстрактный класс

point center;

...

public:

where() (* return center; *)

move(point p) (* center = p; draw(); *)

virtual void rotate(int) = 0;   // чистая виртуальная функция

virtual void draw() = 0;                                       // чистая виртуальная функция

virtual void hilite() = 0;                                        // чистая виртуальная функция

...

*)

shape x; // ошибка: попытка создания объекта абстрактного

// класса

shape* sptr; // указатель на абстрактный класс допустим

shape f(); // ошибка: абстрактный класс не может являться

// типом возврата

int q(shape s); // ошибка: абстрактный класс не может являться

// типом аргумента функции

shape& h(shape&);// ссылка на абстрактный класс в качестве типа

// возврата или аргумента функции допустим

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

Например, с использованием показанного выше класса shape:

class circle : public shape (* // circle является производным // от абстрактного класса

int radius;                    // private

public:

void rotate(int) (* *)                              // определяется виртуальная

// функция:

// действия по вращению окруж-

// ности отсутствуют

void draw();                  // circle::draw должна быть

// где-либо определена

void hilite() = 0;                           // переопределение функции

// как "чистой"

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

Контекст С++

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

Контекст класса

Имя М компонента класса Х имеет контекст класса "локальный по отношению Х"; оно может использоваться в следующих ситуациях:

- в функциях-компонентах Х

- в выражениях типа x.M, где x есть объект Х

- в выражениях типа xptr->M, где xptr есть указатель объекта Х

- в выражениях типа X::M или D::M, где D есть производный класс от X

- в ссылках вперед в пределах класса, которому принадлежит компонент

Классы, перечислимые данные или имена typedef, объявленные в пределах класса Х, либо имена функций, объявленные как "друзья" Х, не являются компонентами Х; их имена просто имеют объемлющий контекст.

Скрытые имена

Имя может бытьскрытоявным объявлением того же имени в объемлющем блоке или в классе. Скрытый компонент класса тем не менее остается доступным при помощи модификатора контекста, заданного с именем класса X:M. Скрытое имя с контекстом файла (глобальное) позволяет ссылку на него при помощи унарной операции ::, например ::g. Имя класса Х может быть скрыто именем объекта, функции, либо нумератора, заданного в контексте Х, независимо от последовательности объявления имен. Однако, имя скрытого класса Х доступно прииспользовании префикса Х с соответствующим ключевым словом class, struct или union.

Точка объявления имени х находится непосредственно после его полного объявления, но до инициализатора, если таковой существует.

Страницы: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40