И.Г. Головин, И.А. Волкова - Языки и методы программирования (1160773), страница 20
Текст из файла (страница 20)
В C++ этими средствами являются переопределениеконструктора копирования и операции присваивания, в Java и C# —наследование и реализация специального интерфейса.Конструкторы. Конструктор — это специальная функция, которая не имеет типа возвращаемого значения (не путайте с процедурой, которая реализуется как v o id -функция), а имя конструкторасовпадает с именем класса:class Х{X(аргументы конструктора){ тело конструктора}}Конструктор класса вызывается автоматически при размещенииобъекта в памяти. Конкретный момент вызова зависит от классапамяти:• конструкторы статических объектов вызываются до входа в функцию main;• конструкторы квазистатических объектов выполняются при входев блок, точнее в момент, когда управление достигает объявлениясоответствующих объектов;• конструкторы динамических объектов выполняются при вызовеоперации new.Заметим, что первые две ситуации могут встречаться только в C++,поскольку в Java и C# объекты размещаются только в динамическойпамяти.Пусть у нас есть класс X с конструкторами следующего вида:class Х{Х() { ..
.}X (int i) {. . .}X(int i, double k) {...}}В языке C++ аргументы вызова конструктора указываются следующим образом:Х а ; // специальный случай — вызов Х()X b (1); // вызов X(int)Хс(0, 1.5); // вызов X(int, double)X * рХ = new Х(); // вызов Х()рХ= new X (—1); // вызов X(int)int а;рХ = new Х(а, 0.0); вызов X(int)З а м е ч а н и е . В первой строке здесь более логичным кажется написатьX а ( ) , как в четвертой строке. Однако по синтаксису языка С, перешедшему по наследству в C++, конструкция X а () синтаксически является объ96явлением прототипа некоторой функции а, не имеющей параметров и возвращающей значение типа X, поэтому пришлось вводить специальныйслучай X а.В языках Java и C# аргументы вызова конструктора указываютсяв операции new:X а = new X (); I I вызов X ()X b = new Х(1); / / вызов X ( in t)X с = new X(0, 1 .
5 ) ; / / вызов X ( in t)В случае если транслятор не находит конструктор с нужным прототипом, то выдается сообщение об ошибке.Поскольку конструктор предназначен для автоматического вызова,то на него накладываются достаточно очевидные ограничения:• конструктор не может быть вызван как обычная функция;• нельзя получить адрес конструктора.Заметим, что в C++ существует еще один контекст вызова конструктора, который иногда ошибочно принимают за явный вызов:при создании временного объекта. Временные объекты создаютсякомпилятором либо неявно (например, при выполнении неявныхпреобразований объектов классов), либо явно, например при выполнении оператора возврата:X MakeX(int i){return X(i); // вызов X(int)}Память под временные объекты отводится компилятором.Особенность конструкторов состоит в том, что часть его кодагенерируется автоматически транслятором.
Эту часть кода называютсистемной. Кроме системной может быть и пользовательская часть,представляющая собой тело конструктора из объявления. Системнаячасть конструктора всегда выполняется перед пользовательской частью. Необходимость системной части вызвана тем, что класс можетнаследовать какому-то базовому классу, поэтому необходимо вызватьконструктор базового класса. Кроме того, в языке C++ класс можетсодержать подобъекты (т.е. члены-данные, которые сами являютсяобъектами других классов). В Java такая ситуация невозможна (тамчастью объекта может быть только ссылка, но не сам объект), а в C#объект может содержать структуры. Системная часть содержит вызовыконструктора базового класса (во всех языках) и конструкторов —членов подобъектов (подструктур) в языках C++ и С#.Конечно, системная часть может быть пустой (если класс не является ничьим наследником и не содержит подобъектов).
Примеромтакого класса является рассмотренная структура Complex на языкеC++. «Умный» компилятор, конечно, игнорирует пустую системнуючасть.97В языках C# и Java системная часть включает в себя еще выполнение инициализаторов членов-данных. Например, в примере состеком можно было указать инициализатор члена top:int top = 0;Инициализирующим выражением может быть любое корректноевыражение (необязательно константное).Особая роль конструктора умолчания.
Конструктором умолчания называется конструктор без параметров: X (). Его особая рольпроявляется при генерации системной части: если не указано, какойименно конструктор (базового класса, подобъекта) вызывается, тогенерируется вызов конструктора умолчания.В силу важности конструктора умолчания он может быть либоописан явно, либо сгенерирован автоматически. Конструкторумолчания генерируется автоматически (такие конструкторы называют неявными), если в классе нет ни одного явно объявленногоконструктора. Откуда, кстати, следует, что любой класс имеет хотябы один конструктор (либо сгенерированный неявный конструкторумолчания, либо явно объявленный конструктор).В классе stack мы могли бы обойтись без явных конструкторов,если бы убрали конструктор Stack (int size) и вставили инициализаторы:int top = 0;int []body = new int[DefaultStackSize] ;static int DefaultStackSize;DefaultStackSize — это статический член класса Stack.
Егоможно объявить как константу (тогда все стеки будут иметь одини тот же размер, что явно не лучший вариант) либо разрешить менять динамически перед вызовом сгенерированного конструктораумолчания:Stack.DefaultStackSize = 64;Stack si = new Stack (); // размер - 64Stack s2 = new Stack(); // размер - 64Stack.DefaultStackSize = 128;Stack s3 = new Stack(); // размер - 128В данном случае лучше, конечно, использовать первоначальныйвариант с явным конструктором.В правиле генерации неявного конструктора есть только одно исключение: в структурах языка C# конструктор умолчания всегда неявный независимо от наличия или отсутствия других конструкторов(его нельзя определять явно).
Сгенерированный конструктор будетприсваивать членам-данным умолчательные значения (в языках C#и Java у всех типов значений есть значения по умолчанию, а у ссылок — это null).98А что делать, если конструктора умолчания нет? Например, длястека придумать умолчательное значение размера трудно (чем значение 128 лучше или хуже значения 32?), поэтому лучше обойтисьбез конструктора умолчания, заставив программиста — пользователястека указать размер явно при вызове new.В этом случае необходим механизм явного указания, какие конструкторы (и с какими параметрами) требуется вызвать. В C++ следуетуказывать вызовы конструкторов базовых классов и подобъектов,в C# и Java — только вызовы конструкторов базового класса.В C++ для указанных целей существует конструкция списокинициализации, в которой указаны параметры вызовов конструкторов. Список инициализации указывается через двоеточие послезаголовка конструктора и непосредственно перед блоком-теломконструктора:заголовок : список_инициализации телоПусть класс D явлется наследником класса В, у которого естьконструктор В (int).
Пусть также D содержит член-данное subobj —подобъект класса S, а у класса s есть конструктор S (double):class В { ... В (int); ...};class S { ... S (double); ...};class D : public В {int k;S subobj;D () ;};Тогда конструктор D () должен включать в себя список инициализации для базового класса В и подобъекта subobj, например:D ::D () : subobj(1.0), В (-1) { ... }В список инициализации можно включать и инициализаторы дляпростых типов данных:D ::D () : subobj (1.0), В (-1), к (0) { ...
}Это особенно удобно, так как C++ не позволяет инициализироватьчлены-данные в объявлении класса, а для членов-ссылок это вообщеединственно возможный способ инициализации.В языке C# список инициализации «вырождается» в инициализатор конструктора. Он может содержать только либо вызов конструктора базового класса вида base (аргументы) (здесь base — это неимя, а ключевое слово!), либо вызов альтернативного конструкторавида this (аргументы):class D : В{99D(int i): base(i){ ... }D() : t h i s (0) { . .
.}И, наконец, в Java списка инициализации (или инициализатораконструктора) нет, а вместо него действует следующее правило: первым оператором конструктора может быть либо вызов конструкторабазового класса вида super (аргументы) (ключевое слово superв Java является аналогом base в С#), либо вызов альтернативногоконструктора вида this (аргументы). Например, конструктор умолчания в классе Stack мог бы иметь видStack() { this(DefaultStackSize); }Обсудим вопрос об инициализации статических членов-данных.Статические члены должны быть инициализированы не в конструкторе объекта, так как по определению статические члены классасуществуют независимо от конкретных экземпляров класса.В C++ программист должен разместить и инициализировать статические члены-данные где-либо в программе по своему усмотрению(главное, чтобы такое определение было в программе в единственномэкземпляре):class S {static int counter;};int S::counter = 0;В языке C# статические члены можно инициализировать приобъявлении члена.
Для нетривиальной инициализации существуетпонятие статического конструктора:class X{- - •const int N = 64;static byte [] sqrs;static X(){sqrs = new byte[N];for (int i = 0; i < N; i++)sqrs[i] = i*i;}}В языке Java инициализировать в объявлении можно только константы, а для остальных случаев есть аналог статического конструктора — статический блок:class X{100static final int N = 64;static int [] sqrs;static // статический блок{sqrs = new int[N];for (int i = 0; i < N; i++)sqrs [i] = i*i;}}Деструкторы и финализаторы.