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

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

627

Указатели на члены можно также объявлять как данные-члены класса. Модифицируйте определение класса Screen так, чтобы оно содержало указатель на его функцию-член того же типа, что home() и end().

Упражнение 13.15

Модифицируйте имеющийся конструктор класса Screen (или напишите новый) так, чтобы он принимал параметр типа указателя на функцию-член класса Screen, для которой список формальных параметров и тип возвращаемого значения такие же, как у home() и end(). Реализуйте для этого параметра значение по умолчанию и используйте параметр для инициализации члена класса, описанного в упражнении 13.14. Напишите функцию-член Screen, позволяющую пользователю задать ее значение.

Упражнение 13.16

Определите перегруженный вариант repeat(), который принимает параметр типа cursorMovements.

13.7. Объединение класс, экономящий память

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

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

int i = 0;

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

1.Ключевое слово int.

2.Идентификатор i.

3.Оператор =

4.Константа 0 типа int.

5.Точка с запятой.

Лексический анализатор передает эти лексемы синтаксическому анализатору, парсеру, который идентифицирует полученную последовательность. Полученная информация

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

Type ID Assign Constant Semicolon

парсеру увидеть следующее:

(Тип ИД Присваивание Константа Точка с запятой)

Далее парсер анализирует значения каждой лексемы. В данном случае он видит:

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

628

Type <==> int

ID <==> i

Constant <==> 0

Для Assign и Semicolon дополнительной информации не нужно, так как у них может быть только одно значение: соответственно := и ;.

Таким образом, в представлении лексемы могло бы быть два члена token и value. token это уникальный код, показывающий, что лексема имеет тип Type, ID, Assign,

Constant или Semicolon, например 85 для ID и 72 для Semicolon.value содержит конкретное значение лексемы. Так, для лексемы ID в предыдущем объявлении value будет содержать строку "i", а для лексемы Type некоторое представление типа int.

Представление члена value несколько проблематично. Хотя для любой отдельной лексемы в нем хранится всего одно значение, их типы для разных лексем могут различаться. Для лексемы ID в value хранится строка символов, а для Constant целое число.

Конечно, для хранения данных нескольких типов можно использовать класс. Разработчик компилятора может объявить, что value принадлежит к типу класса, в котором для каждого типа данных есть отдельный член.

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

union TokenValue { char _cval; int _ival; char *_sval; double _dval;

момент члена, применяется объединение. Вот как оно определяется:

};

Если самым большим типом среди всех членов TokenValue является dval, то размер TokenValue будет равен размеру объекта типа double. По умолчанию члены объединения открыты. Имя объединения можно использовать в программе всюду, где

//объект типа TokenValue TokenValue last_token;

//указатель на объект типа TokenValue

допустимо имя класса:

TokenValue *pt = new TokenValue;

Обращение к членам объединения, как и к членам класса, производится с помощью операторов доступа:

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

629

last_token._ival = 97;

char ch = pt->_cval;

union TokenValue { public:

char _cval; // ...

private: int priv;

}

int main() { TokenValue tp;

tp._cval = '\n'; // правильно

//ошибка: main() не может обращаться к закрытому члену

//TokenValue::priv

tp.priv = 1024;

Члены объединения можно объявлять открытыми, закрытыми или защищенными:

}

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

union illegal_members {

Screen s;

// ошибка: есть конструктор

Screen *ps;

// правильно

static int is;

// ошибка: статический член

int &rfi;

// ошибка: член-ссылка

оператор присваивания. Например:

};

Для объединения разрешается определять функции-члены, включая конструкторы и деструкторы:

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

630

union TokenValue { public:

TokenValue(int ix) : _ival(ix) { } TokenValue(char ch) : _cval(ch) { } // ...

int ival() { return _ival; } char cval() { return _cval; }

private:

int _ival; char _cval; // ...

};

int main() { TokenValue tp(10); int ix = tp.ival(); //...

}

enum TokenKind ( ID, Constant /* и другие типы лексем */ } class Token {

public: TokenKind tok;

TokenValue val;

Вот пример работы объединения TokenValue:

};

int lex() {

Token curToken; char *curString; int curIval;

// ...

case ID: // идентификатор curToken.tok = ID; curToken.val._sval = curString; break;

case Constant: // целая константа curToken.tok = Constant; curToken.val._ival = curIval; break;

// ... и т.д.

Объект типа Token можно использовать так:

}

Опасность, связанная с применением объединения, заключается в том, что можно случайно извлечь хранящееся в нем значение, пользуясь не тем членом. Например, если в последний раз значение присваивалось _ival, то вряд ли понадобится значение, оказавшееся в _sval. Это, по всей вероятности, приведет к ошибке в программе.

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

631

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

char *idVal;

// проверить значение дискриминанта перед тем, как обращаться к sval if ( curToken.tok == ID )

хранится в объединении. В классе Token роль такого объекта играет член tok: idVal = curToken.val._sval;

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

#include <cassert>

// функции доступа к члену объединения sval string Token::sval() {

assert( tok==ID ); return val._sval;

для каждого хранящегося в объединении типа данных:

}

Имя в определении объединения задавать необязательно. Если оно не используется в программе как имя типа для объявления других объектов, его можно опустить. Например, следующее определение объединения Token эквивалентно приведенному

class Token { public:

TokenKind tok;

// имя типа объединения опущено union {

char _cval; int _ival; char *_sval; double _dval;

} val;

выше, но без указания имени:

};

Существует анонимное объединение объединение без имени, за которым не следует определение объекта. Вот, например, определение класса Token, содержащее анонимное объединение: