Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих.pdf
Скачиваний:
183
Добавлен:
01.05.2014
Размер:
3.97 Mб
Скачать

// правильно: double(*pf)(), а не double(Account::*pf) ()

double(*pf)() = &Account::interest;

double daily = pf() / 365 * unit.amount();

Упражнение 13.11

К какому типу принадлежат члены _screen и _cursor класса Screen? Упражнение 13.12

Определите указатель на член и инициализируйте его значением Screen::_screen; присвойте ему значение Screen::_cursor.

Упражнение 13.13

Определите typedef для каждой из функций-членов класса Screen. Упражнение 13.14

Указатели на члены можно также объявлять как данные-члены класса. Модифицируйте определение класса 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

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

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

Type <==>

i n t

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;

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

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;

};

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

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:

};

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

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;

// ... и т.д.

}

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

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

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, содержащее анонимное

class Token { public:

TokenKind tok; // анонимное

объединение

union {

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

};

объединение:

};

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

int lex() {

Token curToken; char *curString; int curIval;

//... выяснить, что находится в лексеме

//... затем установить curToken

case ID: curToken.tok = ID;

curToken._sval = curString; break;

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

// ... и т.д.

предыдущее определение:

}