|
Программирование >> Синтаксис инициирования исключений
Друзей принято объявлять сначала, перед членами класса и перед ключевыми словами public, protected и private. Это объясняется тем, что на друзей не действуют обычные атрибуты видимости; нечто либо является другом, либо не является. Весь фрагмент программы после определения класса Foo вполне допустим. Друзья имеют доступ ко всем членам Foo, включая закрытые. В этом примере есть одна действительно интересная строка - та, в которой другом объявляется несуществующий класс DoesNotExist. Как ни странно, она не вызовет ни предупреждения, ни ошибки компилятора. Объявления друзей игнорируются на момент компиляции Foo. Они используются лишь тогда, когда будет компилироваться друг. Даже когда друга не существует, компилятор остается в счастливом неведении. Типы и операторы Темы, рассматриваемые в этом разделе, на первый взгляд не кажутся близкими, однако все они вращаются вокруг общей концепции - абстрактных типов данных. Конструкторы Конструктор можно рассматривать двояко - как функцию, инициализирующую объект, или, с позиций математики, как отображение аргументов конструктора на домен класса. Я предпочитаю второй подход, поскольку он помогает разобраться с некоторыми языковыми средствами (например, операторами преобразования). С конструкторами связаны очень сложные правила, но каждый программист C++ должен досконально знать их, иначе минимум три ночи в году ему придется проводить за отладкой. Конструкторы без аргументов Если в вашем классе имеется конструктор, который вызывается без аргументов, он используется по умолчанию в трех следующих случаях. class Foo { public: Foo(); class Bar : public Foo { 1. Базовый класс public: Bar(); class BarBar { private: Foo f; 2. Переменная класса Foo f; 3. Созданный экземпляр Foo Foo* f1 = new Foo; 3. То же, что и предыдущая строка Если в списке инициализации членов (см. следующий раздел) конструктора Bar не указан какой-нибудь другой конструктор Foo, то при каждом создании экземпляра Ваг будет вызываться конструктор Foo без аргументов. Аналогично, если f отсутствует в списке инициализации членов конструктора BarBar, будет использован конструктор Foo без аргументов. Наконец, при каждом создании экземпляра Foo без указания конструктора по умолчанию используется конструктор без аргументов. Конструкторы с аргументами Конструкторы, как и все остальные функции, можно перегружать. Вы можете объявить столько сигнатур конструкторов, сколько вам потребуется. Единственное настоящее отличие между сигнатурами конструкторов и обычных функций заключается в том, что конструкторы не имеют возвращаемого значения и не могут объявляться константными. Если вы объявите какие-либо конструкторы с аргументами, но не объявите конструктора без аргументов, то компилятор не позволит конструировать объекты этого класса, даже в качестве базового для другого класса, с использованием конструктора без аргументов. class Foo { public: Foo(char*); Foo f; Нельзя - нет конструктора без аргументов! class Bar : public Foo { public: Bar(); Bar::Bar() Ошибка! Нет конструктора Foo без аргументов Списки инициализации членов Чтобы избавиться от этой проблемы, в C++ находится очередное применение символу : - для создания списков инициализации членов. Так называется список спецификаций конструкторов, разделенных занятыми и расположенных между сигнатурой конструктора и его телом. class Foo { public: Foo(char*); class Bar : public Foo { public: Bar(char*); class BarBar { private: Foo f; int x; public: BarBar(); Bar::Bar(char* s) : Foo(s) {...} BarBar::BarBar : f( He11o ), x(17) {...} В конструкторе Bar список инициализации членов используется для инициализации базового класса Foo. Компилятор выбирает используемый конструктор на основании сигнатуры, определяемой по фактическим аргументам. При отсутствии списка инициализации членов сконструировать Bar было бы невозможно, поскольку компилятор не мог бы определить, какое значение должно передаваться конструктору базового класса Foo. В конструкторе BarBar список инициализации членов использовался для инициализации (то есть вызова конструкторов) переменных f и х. В следующем варианте конструктор работает не столь эффективно (если только компилятор не отличается сверхъестественным интеллектом): BarBar::BarBar() : f( He11o ) x = 17; Во втором варианте переменная х сначала инициализируется значением 0 (стандартное требование C++) с использованием по умолчанию конструктора int без аргументов, а затем в теле конструктора ей присваивается значение 17. В первом варианте имеется всего одна инициализация и потому экономится один-два машинных такта. В данном примере это несущественно, поскольку переменная х - целая, но если бы она относилась к более сложному классу с конструктором без аргументов и перегруженным оператором присваивания, то разница была бы вполне ощутима. Списки инициализации членов нужны там, где у базового класса или переменной нет конструктора без аргументов (точнее, есть один и более конструктор с аргументами, но нет ни одного определенного пользователем конструктора без аргументов). Списки инициализации членов не обязательны в тех ситуациях, когда все базовые классы и переменные класса либо не имеют конструкторов, либо имеют пользовательский конструктор без аргументов. Порядок вызова конструкторов Если класс не содержит собственных конструкторов, он инициализируется так, словно компилятор создал конструктор без аргументов за вас. Этот конструктор вызывает конструкторы без аргументов базовых классов и переменных класса. Четко определенный порядок вызова конструкторов не зависит от того, используются конструкторы стандартные или перегруженные, с аргументами или без: 1 . Сначала вызываются конструкторы базовых классов в порядке их перечисления в списке наследования (еще один список, в котором после символа : перечисляются базовые классы, разделенные запятыми). 2. Затем вызываются конструкторы переменных класса в порядке их объявления в объявлении класса. 3. После того как будут сконструированы все базовые классы и переменные, выполняется тело вашего конструктора. Описанный порядок применяется рекурсивно, то есть первым конструируется первый базовый класс первого базового класса... и т. д. Он не зависит от порядка, указанного в списке инициализации членов. Если бы дело обстояло иначе, для разных перегруженных конструкторов мог бы использоваться разный порядок конструирования. Тогда компилятору было бы трудно гарантировать, что деструкторы будут вызываться в порядке, обратном порядку вызова конструкторов. Конструкторы копий Конструктор копий (copy constructor) определяется специальной сигнатурой: class Foo { public: Foo(const Foo&); Foo::Foo(const Foo& f)... Конструктор копий предназначен для создания копий объектов. Эта задача может возникнуть в самых разных обстоятельствах. void Fn(Foo f) {...} void Gn(Foo& f) {...} Foo f; Foo f1(f); Foo f2 = f; Конструирование, а не присваивание! Fn(f); Вызывает конструктор копий для передачи по назначению const Foo f3; Gn(f3); Конструктор копий используется для создания неконстантной копии Давайте внимательно рассмотрим этот фрагмент. Строка Foo f1(f); создает новый экземпляр класса Foo, передавая другой экземпляр класса Foo в качестве аргумента. Это всегда можно сделать, если
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |