7. Вызвать конструктор тем же образом, что и обычную функцию, нельзя. Вызов деструктора допустим только с полностью квалифицированным именем.
8.
(*
...
X *p;
...
p->X::-X();// допустимый вызов деструктора
X::X();// недопустимый вызов конструктора
...
*)
9. При определении и разрушении объектов компилятор выполняет вызов конструкторов и деструкторов автоматически.
10.Конструкторы и деструкторы при необходимости распределения объекту памяти могут выполнять неявные вызовы операций new и delete.
11.Объект с конструктором или деструктором не может быть использован в качестве компонента объединения.
Если класс Х имеет один или более конструкторов, то один из них запускается всякий разпри определении объекта х класса Х. Конструктор создает объект х и инициализирует его. Деструкторы выполняют обратный процесс,разрушая объекты класса, созданные конструкторами.
Конструкторы активизируются также при создании локальныхили временных объектов данного класса;деструкторы активизируются всякий раз, когда эти объекты выходят из контекста. Конструкторы
Констукторы отличаются от прочих компонентов функций тем, что имеют то же самое имя, что и класс, к которому они относятся.При создании или копировании объекта данного класса происходит неявный вызов соответствующего конструктора.
Конструкторы глобальных переменных вызываются до вызова функции main. При использовании стартовой директивыpragmaдля инсталлирования некоторой функции до функции main, конструкторы глобальных переменных вызываются до стартовых функций.
Локальные объекты создаются, как только становится активным контекст переменной.Конструктор также запускается при создании временного объекта данного класса.
class x
(*
public:
X(); //конструктор класса Х
*);
Конструктор класса Х не может принимать Х как аргумент:
class X (*
...
public
X(X); // недопустимо *)
Параметры конструкторы могут быть любого типа, за исключением класса, компонентомкоторого является данный конструктор. Конструктор может приниматьв качестве параметра ссылку на свой собственный класс; в таком случае он называется конструктором копирования.Конструктор, не принимающийпараметров вообще, называется конструктором поумолчанию. Далее мы рассмотрим конструкторы по умолчанию; описание же конструктора копирования начинается со стр.116 оригинала.
Конструктор по умолчанию
Конструктором по умолчанию для класса Х называется такой конструктор, который не принимает никаких аргументов: Х::Х(). Если длякласса несуществует конструкторов, определяемых пользователем, то Turbo C++ генерирует конструктор по умолчанию. При таких объявлениях, как Х х, конструктор по умолчанию создает объект х.
Важное примечание:
Как и все функции, конструкторымогут иметь аргументы по умолчанию. Например, конструктор
X::X(int, int = 0)
можетпринимать один или два аргумента. Если данный конструктор будет представлен только с одним аргументом,недостающий второй аргумент будет принят как int 0. Аналогичным образом, конструктор
X::X(int = 5, int = 6)
может принимать двааргумента, один аргумент, либо не принимать аргументов вообще,причем в каждом случаепринимаются соответствующие умолчания. Однако, конструктор по умолчанию Х::Х() не принимаетаргументов вообще, иего не следует путать с конструктором, например, X::X(int = 0), который может либо принимать один аргумент, либо не принимать аргументов.
При вызове конструкторов следует избегать неоднозначностей. В следующем примере возможно неоднозначное восприятие компилятором конструкторапо умолчанию и конструктора, принимающего целочисленный параметр:
class X
(*
public:
X();
X(int i = 0);
*);
main()
(*
X one(10); // так можно: используется X::X(int)
X two; // так нельзя; неоднозначно задано, используется // ли X::X() или X::X(int = 0)
...
return 0;
Конструктор копирования
Конструктор копированиядля класса Х это такой конструктор, который может быть вызван с одним единственным аргументом типа X: X::X(const X&) или X::(const X&, int = 0). В конструкторе копирования допустимыми такжеявляются аргументы по умолчанию. Конструкторыкопирования запускаются при копировании объекта данного класса, обычно в случае объявления с инициализацией объектом другого класса: X x = y. Turbo C++ генерирует конструктор копирования для класса X автоматически, если такой конструктор необходим, но в классе Х неопределен.
Перегрузка конструкторов
Конструкторы могут быть перегружены, чтопозволяет создание объектов взависимости от значений, использованныхпри инициализации.
class X
(*
int integer_part;
double double_part;
public:
X(int i) (* integer_part = i; *)
X(double d) (* double_part = d *)
*);
main()
(*
X one(10); // запускает X::X(int) и устанавливает // integer_part в значение 10
X one(3.14); // запускает X::X(double) ш устанавливает // double_part
...
return 0;
*)
Порядок вызова конструкторов
В случае, когда класс использует одинили более базовых классов, конструкторы базовых классов запускаются до того, как будутвызваны конструкторы производного класса. Конструкторы базового класса вызываются в последовательности их объявления.
Например, в следующем примере
class Y (*...*)
class X : public Y (*...*)
X one;
вызов конструкторов происходит в следующей последовательности:
Y(); // конструктор базового класса
X(); // конструктор производного класса
В случае множественных базовых классов:
class X : public Y, public Z
X one;
конструкторы вызываются в последовательности их объявления:
Y(); // первыми следуют конструкторы базового класса Z();
X();
Конструкторы виртуальных базовых классов запускаются до каких-либо не-виртуальных базовых классов. Если иерархия содержит множественные виртуальные базовые классы, то конструкторы виртуальных базовых классов запускаются в последовательности их объявления. Затем,перед вызовомконструкторов производного класса, конструируются не-виртуальные базовые классы.
Если виртуальный класс является производным от не-виртуальной базы,то для того,чтобывиртуальный базовыйклассбыл сконструирован правильно, эта не-виртуальная база должна являться первой. Код
class X : public Y, virtual public Z
X one;
определяет следующую последовательность:
Z(); // инициализация виртуального базового класса Y(); // не-виртуальный базовый класс
X(); // произвольный класс
Либо, в более сложном случае,
class base;
class base2;
class level1 : public base2, virtual public base; class level2 : public base2, virtual public base; class toplevel : public level1, virtual public level1; toplevel view;
Порядок конструирования view будет принят следующий:
base(); // старший в иерархии виртуальный базовый класс // конструируется только однажды
base2(); // не-виртуальная база виртуальной базы level2 // вызывается для конструирования level2
level2(); // виртуальный базовый класс
base2(); // не-виртуальная база для level1
level1(); // другая не-виртуальная база
toplevel();
В случае, когда иерархия класса содержит множественные вхождения виртуального базового класса, данный базовый класс конструируется только один раз. Однако, если существуют и виртуальные,и не-виртуальные вхождения базовогокласса, то конструктор класса запускается одинраз для всехвиртуальных вхождений и один раз для всех не-виртуальных вхождений базового класса.
Конструкторы элементовмассива запускаютсяв порядке возрастания индексов массива.
Инициализация класса
Объект классас компонентами только public и без конструкторов или базовых классов (обычно это структура) может бытьинициализирован при помощи списка инициализаторов.При наличии конструктора класса объекты либо инициализируются, либо имеютконструктор по умолчанию. Последний используется в случае объектов без явной инициализации.
Объекты классов с конструкторами могут быть инициализированы при помощи задаваемых в круглых скобках списков инициализаторов. Этот список используется как список передаваемых конструктору аргументов. Альтернативнымспособом инициализации является использованиезнака равенства, за которымследует отдельное значение. Это отдельное значение можетиметь тип первого аргумента, принимаемого конструктором данного класса; в этом случае дополнительных аргументов либо не существует, либо они имеютзначения по умолчанию. Значение может также являться объектом данного типа класса. В первом случае для создания объекта вызывается соответствующийконструктор. В последнем случае вызывается конструктор копирования, инициализирующий данный объект.
class X
(*
int i;
public:
X(); // тела функций для ясности опущены
X(int x);
X(const X&);
*);
main()
(*
X one; // запуск конструктора по умолчанию
X two(1); // используется конструктор X::X(int)
X three = 1; // вызывает X::X(int)
X four = one; // запускает X::X(const X&) для копирования X five(two); // вызывает X::X(cont X&)
Конструктор может присваивать значения своимкомпонентам следующим образом. Он может принимать значения в качестве параметров и выполнять присваивание компонентам переменным собственно в теле функции конструктора:
class X
(*
int a, b;
public:
X(int i, int j) (* a = i; b = j *)
*);
Либо он может использовать находящийся до тела функции список инициализаторов:
class X
(*
int a, b;
public:
X(int i, int j) : a(i), b(j) (**)
*);
В обоих случаях инициализация X x(1, 2) присваивает значение 1 x::a и значение 2 x::b. Второй способ, а именно список инициализаторов, обеспечивает механизм передачи значений конструкторам базового класса.
Конструкторы базовогокласса должны быть объявлены с атрибутами public или protected, для того, чтобыобеспечить возможность их вызова из производного класса.
class base1
(*
int x;
public:
base1(int i) (* x = i; *)
*);
class base2
(*
int x;
public:
base2(int i) : x(i) (**)
*);
class top : public base1, public base2
(*
int a, b;
public:
top(int i, int j) : base(i*5), base2(j+i), a(i) (* b = j;*)
*);
В случае такойиерархии класса объявление top one(1, 2) приведет к инициализации base1 значением 5, а base2 значением 3. Методы инициализации могут комбинироваться друг с другом.
Как было описано выше, базовые классы инициализируются в последовательности объявления.Затем происходит инициализация компонентов, также в последовательности их объявления, независимо от их взаимного расположения в списке инициализации.
class X
(*
int a, b;
public:
X(int i, j) : a(i), b(a+j) (**)
*);
В пределах класса объявление X x(1,1) приведет к присваиванию числа 1 x::a и числа 2 x::b.
Конструкторы базовых классов вызываются перед конструированием любых компонентов производных классов. Значения производного класса не могут изменяться и затем влиять на создание базового класса.
class base
(*
int x;
public:
base(int i) : x(i) (**)
*);
class derived : base
(*
int a;
public:
derived(int i) : a(i*10), base(a) (**) // Обратите внимание! // base будет передано неинициализированное a
*)
В данномпримере вызовпроизводного d(1) неприведет к присвоению компоненту базового класса х значения 10. Значение, переданное конструктору базового класса, будет неопределенным.
Если вы хотите иметь список инициализаторов в не-встроенном конструкторе,не помещайте этот список вопределении класса. Вместо этого поместите его в точку определения функции:
derived::derived(int i) : a(i)
(*
...
*)
Деструкторы
Деструктор класса вызывается для освобождения членов объекта до разрушения самого объекта. Деструкторпредставляет собой функцию-компонент, имя которой совпадает с именем класса, перед которым стоит символ тильда, деструктор не может принимать каких-либо параметров, а также не имеет объявленных типа возврата или значения.
class X
(*
public:
-X(); // деструктор класса X
*);
Если деструкторне объявлен для класса явно, компилятор генерирует его автоматически.
Запуск деструкторов
Вызов деструктора выполняется неявно, когда переменная выходит из своего объявленного контекста. Для локальных переменных деструкторы вызываются, когда перестает быть активным блок, в котором они объявлены. В случае глобальных переменныхдеструкторы вызываются как часть процедуры выхода после main.
Когда указатели объектов выходят запределы контекста, неявный вызов деструктора непроисходит. Это значит, чтодля разрушения такого объекта операция delete должна быть задана явно.
Деструкторы вызываются строго в обратной последовательности относительнопоследовательности вызова соответствующих им конструкторов (см. стр.117 оригинала).
atexit, #pragma exit и деструкторы
Все глобальные объекты остаются активными до тех пор, пока не будут выполнены коды во всех процедурах выхода. Локальные переменные, включая те, что объявлены в main, разрушаютсяпри выходе их из контекста. Последовательность выполнения в конце программы Turbo C++ в этом смысле следующая:
- выполняются функции atexit в последовательности их вставки в программу
- выполняются функции #pragma exit в соответствии с кодами их приоритетов
- вызываются деструкторы глобальных переменных.
exit и деструкторы
При вызове exitиз программы деструкторы для каких-либо локальных переменных в текущем контексте не вызываются. Глобальные переменные разрушаются в обычной последовательности.
abort и деструкторы
При вызове abort где-либо из программы деструкторы не вызываются, даже для переменных глобального контекста.
Деструктор можетбыть также вызван явно, одним из двух следующих способов: косвенно, через вызовdelete, или прямо, задавая полностью квалифицированногоимени деструктора. Delete можноиспользовать для разрушения объектов, для которых память распределялась при помощи new. Явный вызов деструктора необходим только в случае объектов, которым распределялся конкретный адрес памяти при помощи new.
class X (*
...
-X();
...
*);
void* operator new(size_t size, void *ptr)
(*
return ptr;
*)
char buffer[sizeof(x)];
main()
(*
X* pointer = new X;
X* exact_pointer;
exect_pointer = new(&buffer) X;// указатель
инициализиру// ется адресом буфера
...
delete pointer;// delete служит для раз-
// рушения указателя
exact_pointer->X::-X();// прямой вызов для отмены
// распределения памяти
*)
Виртуальные деструкторы
Деструктор может быть объявлен как 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.
Точка объявления имени х находится непосредственно после его полного объявления, но до инициализатора, если таковой существует.
Краткое изложение правил определения контекста С++
Следующие правила применимы ко всем именам, включая имена typedef и имена классов, при условии, что то или иноеимя допустимо в С++ в конкретном обсуждаемом контексте:
1. Сначала само имя проверяется на наличие неоднозначностей. Если в пределах контекста неоднозначности отсутствуют, то инициируется последовательность доступа.
2. Если ошибок управления доступа не обнаружено, то проверяется тип объекта, функции, класса, typedef, и т.д.
3. Если имя используется вне какой-либо функции или класса, либо имеет префикс унарной операции контекста доступа ::, и если имя не квалифицировано бинарной операцией ::или операциямивыборакомпонента . или ->, то это имя должно быть именем глобального объекта, функции или нумератора.
4. Если имя n появляется в одном из видов: X::n, x.n (где x это объект класса Х или ссылка на Х), либо ptr->n (где ptrэто указатель на Х),то n является именем компонента Хили компонентом класса, производным от которого является Х.
5. Любое до сих пор не рассмотренное имя, используемое в качестве статической функции-компонента, должно быть объявлено в блоке, в которомоно встречается, либо в объемлющемблоке, либо являться глобальным именем. Объявление локального имени n скрывает объявления n в объемлющих блоках и глобальные объявления n. Имена в различныхконтекстах немогут быть перегружены.
6. Любое не рассмотренное до сих пор имя, используемое в качестве имени нестатической функции компонента класса Х, должно быть объявлено в блоке, в котором оно встречается, либо в объемлющем блоке, быть компонентом класса Х или базового относительно Х класса, либо быть глобальным именем. Объявление локального имени n скрывает объявления n в объемлющих блоках,компонентах класса- функциях и глобальных объявлениях n. Объявление имени компонента скрывает объявления этого имени в базовых классах.
7. Имя аргумента функции в определении функции находится в контексте самого внешнего блока данной функции. Имя аргумента функции в не-определяющем объявлении функции вообще не имеет контекста. Контекст аргумента по умолчанию объявляется точкой объявления данного аргумента, ноне позволяет доступ к локальным переменным или не-статическим компонентам класса. Аргументы по умолчанию вычисляются в каждой точке вызова.
... к сожалению, обратное утверждение не верно. C++ Builder содержит инструменты, которые при помощи drag-and-drop действительно делают разработку визуальной, упрощает программирование благодаря встроенному WYSIWYG - редактору интерфейса и пр. Delphi — язык программирования, который используется в одноимённой среде разработки. Сначала язык назывался Object Pascal. Начиная со среды разработки Delphi ...
... ориентированы на 32 разрядные шинные архитектуры компьютеров с процессорами 80386, 80486 или Pentium. Фирма Novell также подготовила варианты сетевой ОС NetWare, предназначенные для работы под управлением многозадачных, многопользовательских операционных систем OS/2 и UNIX. Версию 3.12 ОС NetWare можно приобрести для 20, 100 или 250 пользователей, а версия 4.0 имеет возможность поддержки до 1000 ...
... завдання поширюється на розробку системи обліку зареєстрованих автомобілів в ДАІ, призначеної для збору, зберігання, а також полегшення для доступу та використання інформації. Програма з обліку зареєстрованих автомобілів в ДАІ, представляє собою, перехід від паперових носіїв інформації до електронних. Система обліку зареєстрованих автомобілів значно допоможе працівникам ДАІ з обліку, аналізу та ...
... меньше времени и ответ клиенту агентство может дать уже в день подачи заявки. Каждая турфирма разрабатывает индивидуальный образец листа бронирования. Согласно Федеральному Закону «Об основах туристской деятельности в Российской Федерации» (гл. IV, ст. 9) – это конкретный заказ туриста или лица, уполномоченного представлять группу туристов, туроператору на формирование туристского продукта. ...
0 комментариев