Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Язык Программирования C++. Вводный курс [rus]

.pdf
Скачиваний:
1161
Добавлен:
16.08.2013
Размер:
6 Мб
Скачать
☆

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

632

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;

// ... и т.д.

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

}

Анонимное объединение позволяет убрать один уровень доступа, поскольку обращение к его членам идет как к членам класса Token. У него не может быть закрытых или защищенных членов, а также функций-членов. Такое объединение, определенное в глобальной области видимости, должно быть объявлено в безымянном пространстве имен или иметь модификатор static.

13.8. Битовое поле – член, экономящий память

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

class File { // ...

unsigned int modified : 1; // битовое поле

знака:

};

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

633

После идентификатора битового поля следует двоеточие, а за ним – константное выражение, задающее число битов. К примеру, modified – это поле из одного бита.

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

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

typedef unsigned int Bit;

class File { public:

Bit mode: 2; Bit modified: 1;

Bit prot_owner: 3; Bit prot_group: 3; Bit prot_world: 3; // ...

unsigned int, ассоциированном с первым полем mode:

};

Доступ к битовому полю осуществляется так же, как к прочим членам класса. Скажем, к битовому полю, являющемуся закрытым членом класса, можно обратиться лишь из

void File::write()

{

modified = 1; // ...

}

void File::close()

{

if ( modified )

// ... сохранить содержимое

функций-членов и друзей этого класса:

}

Вот простой пример использования битового поля длиной больше 1 (примененные здесь

enum { READ = 01, WRITE = 02 }; // режимы открытия файла

int main() { File myFile;

myFile.mode |= READ;

if ( myFile.mode & READ )

cout << "myFile.mode is set to READ\n";

побитовые операции рассматривались в разделе 4.11):

}

Обычно для проверки значения битового поля-члена определяются встроенные функции- члены. Допустим, в классе File можно ввести члены isRead() и isWrite():

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

634

inline int File::isRead() { return mode & READ; } inline int File::isWrite() { return mode & WRITE; }

if ( myFile.isRead() ) /* ... */

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

К битовому полю нельзя применять оператор взятия адреса (&), поэтому не может быть и указателя на подобные поля-члены. Кроме того, полю запрещено быть статическим членом.

В стандартной библиотеке C++ имеется шаблон класса bitset, который облегчает манипуляции с битовыми множествами. Мы рекомендуем использовать его вместо битовых полей. (Шаблон класса bitset и определенные в нем операции рассматривались в разделе 4.12.)

Упражнение 13.17

Перепишите примеры из этого подраздела так, чтобы в классе File вместо объявления и

прямого манипулирования битовыми полями использовался класс bitset и его операторы.

13.9. Область видимости класса A

Тело класса определяет область видимости. Объявления членов класса внутри тела вводят их имена в область видимости класса.

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

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

class String { public:

typedef int index_type;

// тип параметра - это на самом деле String::index_type char& operator[]( index_type )

объявления:

};

Порядок объявления членов класса в его теле важен: нельзя ссылаться на члены, которые будут объявлены позже. Например, если объявление оператора operator[]() находится

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

635

раньше объявления typedef index_type, то приведенное ниже объявление operator[]() оказывается ошибочным, поскольку в нем используется еще неизвестное

class String { public:

// ошибка: имя index_type не объявлено char &operator[]( index_type );

typedef int index_type;

имя index_type:

};

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

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

class String { public:

typedef int index_type;

char &operator[]( index_type elem ) { return _string[ elem ]; }

private:

char *_string;

operator[]() определен как встроенный внутри тела класса:

};

На первом этапе просматриваются имена, использованные в объявлении operator[](), чтобы найти имя типа параметра index_type. Поскольку первый шаг выполняется тогда, когда в теле класса встретилось определение функции-члена, то имя index_type должно быть объявлено до определения operator[]().

Обратите внимание, что член _string объявлен в теле класса после определения operator[](). Это правильно, и _string не является в теле operator[]()

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

Аргументы по умолчанию также разрешаются на втором шаге. Например, в объявлении функции-члена clear() используется имя статического члена bkground, который определен позже:

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

636

class Screen { public:

//bkground относится к статическому члену,

//объявленному позже в определении класса

Screen& clear( char = bkground ); private:

static const char bkground = '#';

};

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

class Screen { public:

//...

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

Screen& clear( char = bkground );

private:

const char bkground = '#';

предыдущий пример так:

};

то имя аргумента по умолчанию разрешается нестатическим членом bkground, а это считается ошибкой.

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

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

class String { public:

typedef int index_type;

char& operator[]( index_type ); private:

char *_string;

};

// в operator[]() есть обращения к index_type и _string inline char& operator[]( index_type elem )

{

return _string[ elem ];

operator[]() из класса String:

}

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

637

Обратите внимание, что в списке параметров встречается typedef index_type без квалифицирующего имени класса String::.Текст, следующий за именем члена String::operator[] и до конца определения функции, находится в области видимости класса. Объявленные в этой области типы рассматриваются при разрешении имен типов, использованных в списке параметров функции-члена.

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

class Account: // ...

private:

static double _interestRate; static double initInterestRate();

};

// ссылается на Account::initInterest()

операторов, ссылаться на члены класса:

double Account::_interestRate = initInterest();

Инициализатор _interestRate вызывает статическую функцию-член Account::initInterest() несмотря на то, что ее имя не квалифицировано именем класса.

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

class Account: // ...

private:

static const int nameSize = 16; static const char name[nameSize];

// nameSize не квалифицировано именем класса Account

члену класса nameSize:

const char Account::name[nameSize] = "Savins Account";

Хотя член nameSize не квалифицирован именем класса Account, определение name не является ошибкой, так как оно находится в области видимости своего класса и может ссылаться на его члены после того, как компилятор прочитал Account::name.

В определении члена, которое появляется вне тела, часть программы перед определяемым именем не находится в области видимости класса. При обращении к члену в этой части следует пользоваться оператором разрешения области видимости. Например, если типом статического члена является typedef Money, определенный в классе Account, то имя Money должно быть квалифицировано, когда статический член данных определяется вне тела класса:

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

638

class Account {

typedef double Money; //...

private:

static Money _interestRate; static Money initInterest();

};

// Money должно быть квалифицировано именем класса Account::

Account::Money Account::_interestRate = initInterest();

С каждым классом ассоциируется отдельная область видимости, причем у разных классов эти области различны. К членам одного класса нельзя напрямую обращаться в определениях членов другого класса, если только один из них не является для второго базовым. (Наследование и базовые классы рассматриваются в главах 17 и 18.)

13.9.1. Разрешение имен в области видимости класса

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

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

1.Просматриваются объявления членов класса, появляющиеся перед употреблением имени.

2.Если на шаге 1 разрешение не привело к успеху, то просматриваются объявления в пространстве имен перед определением класса. Напомним, что глобальная область видимости – это тоже область видимости пространства имен. (О пространствах имен речь шла в разделе 8.5.)

typedef double Money; class Account {

//...

private:

static Money _interestRate; static Money initInterest();

//...

Например:

};

Сначала компилятор ищет объявление Money в области видимости класса Account. При этом учитываются только те объявления, которые встречаются перед использованием Money. Поскольку таких объявлений нет, далее поиск ведется в глобальной области видимости. Объявление глобального typedef Money найдено, именно этот тип и используется в объявлениях _interestRate и initInterest().

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

639

Имя, встретившееся в определении функции-члена класса, разрешается следующим образом:

1.Сначала просматриваются объявления в локальных областях видимости функции- члена. (О локальных областях видимости и локальных объявлениях говорилось в разделе 8.1.)

2.Если шаг 1 не привел к успеху, то просматриваются объявления для всех членов класса.

3.Если и этого оказалось недостаточно, просматриваются объявления в пространстве имен перед определением функции-члена.

int _height;

class Screen { public:

Screen( int _height ) {

_height = 0; // к чему относится _height? К параметру

}

private:

short _height;

Имена, встречающиеся в теле встроенной функции-члена, разрешаются так:

};

В поисках объявления имени _height, которое встретилось в определении конструктора Screen, компилятор просматривает локальную область видимости функции и находит его там. Следовательно, это имя относится к объявлению параметра.

Если бы такое объявление не было найдено, компилятор начал бы поиск в области видимости класса Screen, просматривая все объявления его членов, пока не встретится объявление члена _height. Говорят, что имя члена _height скрыто объявлением параметра конструктора, но его можно использовать в теле конструктора, если

int _height;

class Screen { public:

Screen( long _height ) {

this->_height = 0; // относится к Screen::_height

//тоже правильно:

//Screen::_height = 0;

}

private:

short _height;

квалифицировать имя члена именем его класса или явно использовать указатель this:

};

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

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

640

объявлением члена класса, однако его можно использовать в теле конструктора, если

int _height;

class Screen { public:

Screen( long _height ) {

::_height = 0; // относится к глобальному объекту

}

private:

short _height;

квалифицировать оператором разрешения глобальной области видимости:

};

Если конструктор объявлен вне определения класса, то на третьем шаге разрешения имени просматриваются объявления в глобальной области видимости, которые встретились перед определением класса Screen, а также перед определением функции-

class Screen { public:

// ...

void setHeight( int ); private:

short _height;

};

int verify(int);

void Screen::setHeight( int var ) {

//var: относится к параметру

//_height: относится к члену класса

//verify: относится к глобальной функции

_height = verify( var );

члена:

}

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

Имя, встретившееся в определении статического члена класса, разрешается следующим образом:

1.Просматриваются объявления всех членов класса.

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

Упражнение 13.18

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

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

641

Упражнение 13.19

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

Упражнение 13.20

К каким объявлениям относится имя Type при использовании в теле класса Exersise и в определении его функции-члена setVal()? (Напоминаем, что разные вхождения могут относиться к разным объявлениям.) К каким объявлениям относится имя initVal при

typedef int Type; Type initVal();

class Exercise { public:

// ...

typedef double Type; Type setVal( Type ); Type initVal();

private: int val;

};

Type Exercise::setVal( Type parm ) { val = parm + initVal();

употреблении в определении функции-члена setVal()?

}

Определение функции-члена setVal() ошибочно. Можете ли вы сказать, почему? Внесите необходимые изменения, чтобы в классе Exercise использовался глобальный typedef Type и глобальная функция initVal().

13.10. Вложенные классы A

Класс, объявленный внутри другого класса, называется вложенным. Он является членом объемлющего класса, а его определение может находиться в любой из секций public, private или protected объемлющего класса.

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

Соседние файлы в предмете Программирование на C++