<< Пред. стр. 40 (из 121) След. >>
void fibonacci( int );int main() {
cout << "Числа Фибоначчи: 16\n";
fibonacci( 16 );
return 0;
}
Результат работы программы:
Числа Фибоначчи: 16
0 1 1 2 3 5 8 13 21 34 55 89
144 233 377 610
8.5.3. Вложенные пространства имен
Мы уже упоминали, что пользовательские пространства имен могут быть вложенными. Такие пространства применяются для дальнейшего структурирования кода нашей библиотеки. Например:
// ---- primer.h ----
namespace cplusplus_primer {
// первое вложенное пространство имен:
// матричная часть библиотеки
namespace MatrixLib {
class matrix { /* ... */ };
const double pi = 3.1416;
matrix operators+ ( const matrix &ml, const matrix &m2 );
void inverse( matrix & );
// ...
}
// второе вложенное пространство имен:
// зоологическая часть библиотеки
namespace AnimalLib {
class ZooAnimal { /* ... */ };
class Bear : public ZooAnimal { /* ... */ };
class Raccoon : public Bear { /* ... */ };
// ...
}
}
Пространство имен cplusplus_primer содержит два вложенных: MatrixLib и AnimalLib.
cplusplus_primer предотвращает конфликт между именами из нашей библиотеки и именами из глобального пространства вызывающей программы. Вложенность позволяет делить библиотеку на части, в которых сгруппированы связанные друг с другом объявления и определения. MatrixLib содержит сущности, имеющие отношение к классу matrix, а AnimalLib – к классу ZooAnimal.
Объявление члена вложенного пространства скрыто в этом пространстве. Имя такого члена автоматически дополняется поставленными спереди именами самого внешнего и вложенного пространств.
Например, класс, объявленный во вложенном пространстве MatrixLib, имеет имя
cplusplus_primer::MatrixLib::matrix
а функция
cplusplus_primer::MatrixLib::inverse
Программа, использующая члены вложенного пространства cplusplus_primer::MatrixLib, выглядит так:
#include "primer.h"
// да, это ужасно...
// скоро мы рассмотрим механизмы, облегчающие
// использование членов пространств имен!
void func( cplusplus_primer::MatrixLib::matrix &m )
{
// ...
cplusplus_primer::MatrixLib::inverse( m );
return m;
}
Вложенное пространство имен является вложенной областью видимости внутри пространства, содержащего его. В процессе разрешения имен вложенные пространства ведут себя так же, как вложенные блоки. Когда некоторое имя употребляется в пространстве имен, поиск его объявление проводится во всех объемлющих пространствах. В следующем примере разрешение имени Type происходит в таком порядке: сначала ищем его в пространстве имен MatrixLib, затем в cplusplus_primer и наконец в глобальной области видимости:
typedef double Type;
namespace cplusplus_primer {
typedef int Type; // скрывает ::Type
namespace MatrixLib {
int val;
// Type: объявление найдено в cplusplus_primer
int func(Type t) {
double val; // скрывает MatrixLib::val
val = ...;
}
// ...
}
}
Если некоторая сущность объявляется во вложенном пространстве имен, она скрывает объявление одноименной сущности из объемлющего пространства.
В предыдущем примере имя Type из глобальной области видимости скрыто объявлением Type в пространстве cplusplus_primer. При разрешении имени Type, упоминаемого в MatrixLib, оно будет найдено в cplusplus_primer, поэтому у функции func() параметр имеет тип int.
Аналогично сущность, объявленная в пространстве имен, скрывается одноименной сущностью из вложенной локальной области видимости. В предыдущем примере имя val из MatrixLib скрыто новым объявлением val. При разрешении имени val внутри func() будет найдено его объявление в локальной области видимости, и потому присваивание в func() относится именно к локальной переменной.
8.5.4. Определение члена пространства имен
Мы видели, что определение члена пространства имен может появиться внутри определения самого пространства. Например, класс matrix и константа pi появляются внутри вложенного пространства имен MatrixLib, а определения функций operator+() и inverse() приводятся где-то в другом месте текста программы:
// ---- primer.h ----
namespace cplusplus_primer {
// первое вложенное пространство имен:
// матричная часть библиотеки
namespace MatrixLib {
class matrix { /* ... */ };
const double pi = 3.1416;
matrix operators+ ( const matrix &ml, const matrix &m2 );
void inverse( matrix & );
// ...
}
}
Член пространства имен можно определить и вне соответствующего пространства. В таком случае имя члена должно быть квалифицировано именами пространств, к которым он принадлежит. Например, если определение функции operator+() помещено в глобальную область видимости, то оно должно выглядеть следующим образом:
// ---- primer.C ----
#include "primer.h"
// определение в глобальной области видимости
cplusplus_primer::MatrixLib::matrix
cplusplus_primer::MatrixLib::operator+
( const matrix& ml, const matrix &m2 )
{ /* ... */ }
Имя operator+() квалифицировано в данном случае именами пространств cplusplus_primer и MatrixLib. Однако обратите внимание на тип matrix в списке параметров operator+(): употреблено неквалифицированное имя. Как такое может быть?
В определении функции operator+() можно использовать неквалифицированные имена для членов своего пространства, поскольку определение принадлежит к его области видимости. При разрешении имен внутри функции operator+() используется MatrixLib. Заметим, однако, что в типе возвращаемого значения все же нужно указывать квалифицированное имя, поскольку он расположен вне области видимости, заданной определением функции:
cplusplus_primer::MatrixLib::operator+
В определении operator+() неквалифицированные имена могут встречаться в любом объявлении или выражении внутри списка параметров или тела функции. Например, локальное объявление внутри operator+() способно создать объект класса matrix:
// ---- primer.C ----
#include "primer.h"
cplusplus_primer::MatrixLib::matrix
cplusplus_primer::MatrixLib::operator+
( const matrix &ml, const matrix &m2 )
{
// объявление локальной переменной типа
// cplusplus_primer::MatrixLib::matrix
matrix res;
// вычислим сумму двух объектов matrix
return res;
}
Хотя члены могут быть определены вне своего пространства имен, такие определения допустимы не в любом месте. Их разрешается помещать только в пространства, объемлющие данное. Например, определение operator+() может появиться в глобальной области видимости, в пространстве имен cplusplus_primer и в пространстве MatrixLib. В последнем случае это выглядит так:
// ---- primer.C --
#include "primer.h"
namespace cplusplus_primer {
MatrixLib::matrix MatrixLib::operator+
( const matrix &ml, const matrix &m2 ) { /* ... */ }
}
Член может определяться вне своего пространства только при условии, что ранее он был объявлен внутри. Последнее приведенное определение operator+() было бы ошибочным, если бы ему не предшествовало объявление в файле primer.h:
namespace cplusplus_primer {
namespace MatrixLib {
class matrix { /*...*/ };
// следующее объявление не может быть пропущено
matrix operator+ ( const matrix &ml, const matrix &m2 );
// ...
}
}
8.5.5. ПОО и члены пространства имен
Как уже было сказано, определение пространства имен может состоять из разрозненных частей и размещаться в разных файлах. Следовательно, член пространства разрешено объявлять во многих файлах. Например:
// primer.h
namespace cplusplus_primer {
// ...
void inverse( matrix & );
}
// usel.C
#include "primer.h"
// объявление cplusplus_primer::inverse() в use1.C
// use2.C
#include "primer.h"
// объявление cplusplus_primer::inverse() в use2.C
Объявление cplusplus::inverse() в primer.h ссылается на одну и ту же функцию в обоих исходных файлах use1.C и use2.C.
Член пространства имен является глобальной сущностью, хотя его имя квалифицировано. Требование ПОО (правило одного определения, см. раздел 8.2) распространяется и на него. Чтобы удовлетворить этому требованию, программы, в которых используются пространства имен, обычно организуют следующим образом:
Объявления функций и объектов, являющихся членами пространства имен, помещают в заголовочный файл, который включается в каждый исходный файл, где они используются.
// ---- primer.h ----
namespace cplusplus_primer {
class matrix { /* ... */ };
// объявления функций
extern matrix operator+ ( const matrix &m1, const matrix &m2 );
extern void inverse( matrix & );
// объявления объектов
extern bool error_state;
}
Определения этих членов помещают в исходный файл, содержащий реализацию:
// ---- primer.C ----
#include "primer.h"
namespace cplusplus_primer {
// определения функций
void inverse( matrix & )
{ /* ... */ }
matrix operator+ ( const matrix &ml, const matrix &m2 )
{ /" ... */ }
// определения объектов
bool error_state = false;
}
Для объявления объекта без его определения используется ключевое слово extern, как и в случае такого объявления в глобальной области видимости.
8.5.6. Безымянные пространства имен
Может возникнуть необходимость определить объект, функцию, класс или любую другую сущность так, чтобы она была видимой только в небольшом участке программы. Это еще один способ решения проблемы засорения глобального пространства имен. Поскольку мы уверены, что эта сущность используется ограниченно, можно не тратить время на выдумывание уникального имени. Если мы объявляем объект внутри функции или блока, его имя видимо только в этом блоке. А как сделать некоторую сущность доступной нескольким функциям, но не всей программе?
Предположим, мы хотим реализовать набор функций для сортировки вектора типа double:
// ----- SortLib.h -----
void quickSort( double *, double * );
void bubbleSort( double *, double * );
void mergeSort( double *, double * );
void heapSort( double *, double * );
Все они используют одну и ту же функцию swap() для того, чтобы менять местами элементы вектора. Однако она не должна быть видна во всей программе, поскольку нужна только четырем названным функциям. Локализуем ее в файле SortLib.C. Приведенный код не дает желаемого результата. Как вы думаете, почему?
// ----- SortLib.C -----
void swap( double *dl, double *d2 ) { /* ... */ }
// только эти функции используют swap()
void quickSort( double *d1, double *d2 ) { /* ... */ }
void bubbleSort( double *d1, double *d2 ) { /* ... */ }
void mergeSort( double *d1, double *d2 ) { /* ... */ }
void heapSort( double *d1, double *d2 ) { /* ... */ }
Хотя функция swap() определена в файле SortLib.C и не появляется в заголовочном файле SortLib.h, где содержится описание интерфейса библиотеки сортировки, она объявлена в глобальной области видимости. Следовательно, это имя является глобальным, при этом сохраняется возможность конфликта с другими именами.
Язык С++ предоставляет возможность использования безымянного пространства имен для объявления сущности, локальной по отношению к файлу. Определение такого пространства начинается ключевым словом namespace. Очевидно, что никакого имени за этим словом нет, а сразу же идет блок в фигурных скобках, содержащий различные объявления. Например:
// ----- SortLib.C -----
namespace {
void swap( double *dl, double *d2 ) { /* ... */ }
}
// определения функций сортировки не изменяются
Функция swap() видна только в файле SortLib.C. Если в другом файле в безымянном пространстве имен содержится определение swap(), то это другая функция. Наличие двух функций swap() не является ошибкой, поскольку они различны. Безымянные пространства имен отличаются от прочих: определение такого пространства локально для одного файла и не может размещаться в нескольких.
Имя swap() может употребляться в неквалифицированной форме в файле SortLib.C после определения безымянного пространства. Оператор разрешения области видимости для ссылки на его члены не нужен.
void quickSort( double *d1, double *d2 ) {
// ...
double* elem = d1;
// ...
// ссылка на член безымянного пространства имен swap()
swap( d1, elem );
// ...
}
Члены безымянного пространства имен относятся к сущностям программы. Поэтому функция swap() может быть вызвана во время выполнения. Однако имена этих членов видны только внутри одного файла.
До того как в стандарте С++ появилось понятие пространства имен, наиболее удачным решением проблемы локализации было использование ключевого слова static, унаследованного из С. Член безымянного пространства имеет свойства, аналогичные глобальной сущности, объявленной как static. В языке С такая сущность невидима вне файла, в котором объявлена. Например, текст из SortLib.C можно переписать на С, сохранив свойства swap():
// SortLib.C
// swap() невидима для других файлов программы
static void swap( double *d1, double *d2 ) { /* ... */ }
// определения функций сортировки такие же, как и раньше
Во многих программах на С++ используются объявления с ключевым словом static. Предполагается, что они должны быть заменены безымянными пространствами имен по мере того, как все большее число компиляторов начнет поддерживать это понятие.
Упражнение 8.11
Зачем нужно определять собственное пространство имен в программе?
Упражнение 8.12
Имеется следующее объявление operator*(), члена вложенного пространства имен cplusplus_primer::MatrixLib:
namespace cplusplus_primer {
namespace MatrixLib {
class matrix { /*...*/ };
matrix operator* ( const matrix &, const matrix & );
// ...
}
}
Как определить эту функцию в глобальной области видимости? Напишите только прототип.
Упражнение 8.13
Объясните, зачем нужны безымянные пространства имен.
8.6. Использование членов пространства имен А
Использование квалифицированных имен при каждом обращении к членам пространств может стать обременительным, особенно если имена пространств достаточно длинны. Если бы удалось сделать их короче, то такие имена проще было бы читать и набивать. Однако употребление коротких имен увеличивает риск их совпадения с другими, поэтому желательно, чтобы в библиотеках применялись пространства с длинными именами.
К счастью, существуют механизмы, облегчающие использование членов пространств имен в программах. Псевдонимы пространства имен, using-объявления и using-директивы помогают преодолеть неудобства работы с очень длинными именами.
8.6.1. Псевдонимы пространства имен
Псевдоним пространства имен используется для задания короткого синонима имени пространства. Например, длинное имя
namespace International_Business_Machines
{ /* ... */ }
может быть ассоциировано с более коротким синонимом:
namespace IBM = International_Business_Machines;
Объявление псевдонима начинается ключевым словом namespace, за которым следует короткий псевдоним, а за ним – знак равенства и исходное полное имя пространства. Если полное имя не соответствует никакому известному пространству, это ошибка.
Псевдоним может относиться и к вложенному пространству имен. Вспомним слишком длинное определение функции func() выше:
#include "primer.h"
// трудно читать!
void func( cplusplus_primer::MatrixLib::matrix &m )
{
// ...
cplusplLis_primer::MatrixLib::inverse( m );
return m;
}
Разрешается задать псевдоним для обозначения вложенного cplusplLis_primer::MatrixLib, сделав определение функции более удобным для восприятия:
#include "primer.h"
// более короткий псевдоним
namespace mlib = cplusplus_primer::MatrixLib;
// читать проще!
void func( mlib::matrix &m )
{
// ...
mlib::inverse( m );
return m;
}
Одно пространство имен может иметь несколько взаимозаменяемых псевдонимов. Например, если псевдоним Lib ссылается на cplusplus_primer, то определение функции func() может выглядеть и так:
// псевдоним alias относится к пространству имен cplusplus_primer
namespace alias = Lib;
void func( cplusplus_primer::matrix &m ) {
// ...
alias::inverse( m );
return m;
}
8.6.2. Using-объявления
Имеется механизм, позволяющий обращаться к членам пространства имен, используя их имена без квалификатора, т.е. без префикса namespace_name::. Для этого применяются using-объявления.
Using-объявление начинается ключевым словом using, за которым следует квалифицированное имя члена пространства. Например:
namespace cplusplus_primer {
namespace MatrixLib {
class matrix { /* ... */ };
// ...
}
}
// using-объявление для члена matrix
using cplusplus_primer::MatrixLib::matrix;
Using-объявление вводит имя в ту область видимости, в которой оно использовано. Так, предыдущее using-объявление делает имя matrix глобально видимым.
После того как это объявление встретилось в программе, использование имени matrix в глобальной области видимости или во вложенных в нее областях относится к этому члену пространства имен. Пусть далее идет следующее объявление:
void func( matrix &m );
Оно вводит функцию func() с параметром типа cplusplus_primer:: MatrixLib::matrix.
Using-объявление ведет себя подобно любому другому объявлению: оно имеет область видимости, и имя, введенное им, можно употреблять начиная с места объявления и до конца области видимости. Using-объявление может использоваться в глобальной области видимости, равно как и в области видимости любого пространства имен. Оно употребляется и в локальной области. Имя, вводимое using-объявлением, как и любым другим, имеет следующие характеристики:
оно должно быть уникальным в своей области видимости;
оно скрывает одноименную сущность во внешней области;
оно скрывается объявлением одноименной сущности во вложенной области.
Например:
namespace blip {
int bi = 16, bj = 15, bk = 23;
// прочие объявления
}
int bj = 0;
void manip() {
using blip::bi; // bi в функции manip() ссылается на blip::bi
++bi; // blip::bi == 17