Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих (Стенли Липпман) 3-е хххх.pdf
Скачиваний:
86
Добавлен:
30.05.2015
Размер:
5.92 Mб
Скачать

С++ для начинающих

655

13.12. Локальные классы A

Класс, определенный внутри тела функции, называется локальным. Он виден только в той локальной области, где определен. Не существует синтаксиса, позволяющего обратиться к члену такого класса, в отличие от вложенного, извне локальной области видимости, содержащей его определение. Поэтому функции-члены локального класса должны определяться внутри определения самого класса. На практике это ограничивает их сложность несколькими строками кода; помимо всего прочего, такой код становится трудно читать.

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

Класс, вложенный в локальный, может быть определен вне определения объемлющего класса, но только в локальной области видимости, содержащей это определение. Имя

вложенного класса в таком определении должно быть квалифицировано именем

void foo( int val )

{

class Bar { public:

int barVal;

class nested; // объявление вложенного класса обязательно

};

// определение вложенного класса class Bar::nexted {

// ...

};

объемлющего класса. Объявление вложенного класса в объемлющем нельзя опускать:

}

Уобъемлющей функции нет никаких специальных прав доступа к закрытым членам локального класса. Разумеется, это можно обойти, объявив ее другом данного класса. Однако необходимость делать его члены закрытыми вообще сомнительна, поскольку часть программы, из которой разрешается обратиться к нему, весьма ограничена. Локальный класс инкапсулирован в своей локальной области видимости. Дальнейшая инкапсуляция путем сокрытия информации не требуется: вряд ли на практике найдется причина, по которой не все члены локального класса должны быть открыты.

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

С++ для начинающих

656

int a, val;

void foo( int val )

{

static int si;

enum Loc { a = 1024, b }; class Bar {

public:

Loc locVal;

// правильно

int barVal;

 

void fooBar ( Loc l = a ) { // правильно: Loc::a

barVal = val;

// ошибка: локальный объект

barVal = ::val;

// правильно: глобальный объект

barVal = si;

// правильно: статический локальный объект

locVal = b;

// правильно: элемент перечисления

}

 

};

 

// ...

 

}

 

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

Как всегда, если первое найденное объявление таково, что употребление имени оказывается некорректным, поиск других объявлений не производится. Несмотря на то что использование val в fooBar() выше является ошибкой, глобальная переменная val не будет найдена, если только ее имени не предшествует оператор разрешения глобальной области видимости.

С++ для начинающих

657

14. Инициализация, присваивание и

уничтожение класса

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

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

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

14.1. Инициализация класса

class Data { public:

int ival; char *ptr;

Рассмотрим следующее определение класса:

};

Чтобы безопасно пользоваться объектом класса, необходимо правильно инициализировать его члены. Однако смысл этого действия для разных классов различен. Например, может ли ival содержать отрицательное значение или нуль? Каковы правильные начальные значения обоих членов класса? Мы не ответим на эти вопросы, не понимая абстракции, представляемой классом. Если с его помощью описываются служащие компании, то ptr, вероятно, указывает на фамилию служащего, а ival его уникальный номер. Тогда отрицательное или нулевое значения ошибочны. Если же класс представляет текущую температуру в городе, то допустимы любые значения ival. Возможно также, что класс Data представляет строку со счетчиком ссылок: в таком случае ival содержит текущее число ссылок на строку по адресу ptr. При такой абстракции ival инициализируется значением 1; как только значение становится равным 0, объект класса уничтожается.

Мнемонические имена класса и обоих его членов сделали бы, конечно, его назначение более понятным для читателя программы, но не дали бы никакой дополнительной

С++ для начинающих

658

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

приведенных ниже инструкций представляет корректную инициализацию объекта класса

Data dat01( "Venus and the Graces", 107925 );

Data dat02( "about" );

Data dat03( 107925 );

Data:

Data dat04;

Бывают ситуации (как в случае с dat04), когда нам нужен объект класса, но его начальные значения мы еще не знаем. Возможно, они станут известны позже. Однако начальное значение задать необходимо, хотя бы такое, которое показывает, что разумное начальное значение еще не присвоено. Другими словами, инициализация объекта иногда сводится к тому, чтобы показать, что он еще не инициализирован. Большинство классов предоставляют специальный конструктор по умолчанию, для которого не требуется задавать начальных значений. Как правило, он инициализирует объект таким образом, чтобы позже можно было понять, что реальной инициализации еще не проводилось.

Обязан ли наш класс Data иметь конструктор? Нет, поскольку все его члены открыты. Унаследованный из языка C механизм поддерживает явную инициализацию,

int main()

{

//local1.ival = 0; local1.ptr = 0 Data local1 = { 0, 0 };

//local2.ival = 1024;

//local3.ptr = "Anna Livia Plurabelle" Data.local2 - { 1024, "Anna Livia Plurabelle" };

//...

аналогичную используемой при инициализации массивов:

}

Значения присваиваются позиционно, на основе порядка, в котором объявляются данные- члены. Следующий пример приводит к ошибке компиляции, так как ival объявлен перед

//ошибка: ival = "Anna Livia Plurabelle";

//ptr = 1024

ptr:

Data.local2 - { "Anna Livia Plurabelle", 1024 };

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