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

Программирование на C / C++ / Ален И. Голуб. Правила программирования на Си и Си++ [pdf]

.pdf
Скачиваний:
237
Добавлен:
02.05.2014
Размер:
5.67 Mб
Скачать

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

402

namespace cplusplus_primer { class matrix { /* ... */ }; const double pi = 3.1416;

}

namespace cplusplus_primer { void inverse ( matrix & );

matrix operator+ ( const matrix &ml, const matrix &m2 ) {/* ... */ }

}

Два приведенных примера эквивалентны: оба задают пространство имен cplusplus_primer, содержащее класс matrix, функцию inverse(), константу pi и operator+(). Определение пространства имен может состоять из нескольких соединенных частей.

Последовательность

namespace namespace_name {

задает новое пространство, если имя namespace_name не совпадает с одним из ранее объявленных. В противном случае новые объявления добавляются в старое пространство.

Возможность разбить пространство имен на несколько частей помогает при организации библиотеки. Ее исходный код легко разделить на интерфейсную часть и реализацию.

// Эта часть пространства имен // определяет интерфейс библиотеки

namespace cplusplus_primer {

 

class matrix { /* ... */ };

 

const double pi = 3.1416;

);

matrix operator+ ( const matrix &ml, const matrix &m2

void inverse ( matrix & );

 

}

 

// Эта часть пространства имен

 

// определяет реализацию библиотеки

 

namespace cplusplus_primer { void inverse ( matrix &m )

{ /* ... */ }

matrix operator+ ( const matrix &ml, const matrix &m2 ) { /* ... */ }

Например:

}

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

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

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

403

// ---- primer.h ----

namespace cplusplus_primer { class matrix { /*... */ }; const double pi = 3.1416;

matrix operator+ ( const matrix &m1, const matrix &m2 ); void inverse( matrix & );

}

// ---- primer.C ----

#include "primer.h" namespace cplusplus_primer {

void inverse( matrix &m ) { /* ... */ }

matrix operator+ ( const matrix &m1, const matrix &m2 ) { /* ... */ }

}

 

// ---- user.C ----

 

// определение интерфейса библиотеки

 

#include "primer.h"

 

void func( cplusplus_primer::matrix &m

)

{

 

//...

 

cplusplus_primer: :inverse( m );

 

return m;

 

Программа, использующая эту библиотеку, выглядит так:

}

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

8.5.2. Оператор разрешения области видимости

Имя члена пользовательского пространства дополняется поставленным спереди именем этого пространства и оператором разрешения области видимости (::). Использование неквалифицированного члена, например matrix, является ошибкой. Компилятор не

//определение интерфейса библиотеки

#include "primer.h"

//ошибка: нет объявления для matrix

знает, к какому объявлению относится это имя: void func( matrix &m );

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

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

404

// определение интерфейса библиотеки

#include "primer.h"

class matrix { /* пользовательское определение */ };

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

void func( matrix &m );

то определение класса matrix компилятор находит в глобальной области видимости и программа компилируется без ошибок. Поскольку объявление matrix как члена пространства имен cplusplus_primer скрыто в этом пространстве, оно не конфликтует с классом, объявленным в глобальной области видимости.

Именно поэтому мы говорим, что пространства имен решают проблему засорения глобального пространства: имена их членов невидимы, если имя пространства не указано явно, с помощью оператора разрешения области видимости. Существуют и другие механизмы, позволяющие сделать объявление члена пространства имен видимым вне его. Это using-объявления и using-директивы. Мы рассмотрим их в следующем разделе.

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

::member_name

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

Следующий пример демонстрирует использование оператора области видимости для обращения к скрытому члену глобального пространства имен. Функция вычисляет последовательность чисел Фибоначчи. В программе два определения переменной max. Глобальная переменная указывает максимальное значение элемента последовательности, при превышении которого вычисление прекращается, а локальная желаемую длину последовательности при данном вызове функции. (Напоминаем, что параметры функции относятся к ее локальной области видимости.) Внутри функции должны быть доступны обе переменных. Однако неквалифицированное имя max ссылается на локальное объявление этой переменной. Чтобы получить глобальную переменную, нужно использовать оператор разрешения области видимости ::max. Вот текст программы:

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

405

#include <iostream> const int max = 65000;

const int lineLength = 12;

void fibonacci( int max )

{

if ( max < 2 ) return; cout << "0 1 ";

int v1 = 0, v2 = 1, cur;

for ( int ix = 3; ix <= max; ++ix ) { cur = v1 + v2;

if ( cur > ::max ) break; cout << cur << " ";

vl = v2; v2 = cur;

if (ix % "lineLength == 0) cout << end"!;

}

}

#include <iostream> void fibonacci( int ); int main() {

cout << "Числа Фибоначчи: 16\n"; fibonacci( 16 );

return 0;

Так выглядит функция main(), вызывающая fibonacci():

}

Результат работы программы:

Числа

Фибоначчи: 16

34 55 89

0

1 1

2 3 5

8 13

21

144 233 377

610

 

 

8.5.3. Вложенные пространства имен

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

Такие пространства применяются для дальнейшего структурирования кода нашей библиотеки. Например:

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

406

// ---- 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

Программа,

использующая

члены

вложенного

пространства

#include "primer.h"

//да, это ужасно...

//скоро мы рассмотрим механизмы, облегчающие

//использование членов пространств имен!

void func( cplusplus_primer::MatrixLib::matrix &m )

{

// ...

cplusplus_primer::MatrixLib::inverse( m ); return m;

cplusplus_primer::MatrixLib, выглядит так:

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

407

}

Вложенное пространство имен является вложенной областью видимости внутри пространства, содержащего его. В процессе разрешения имен вложенные пространства ведут себя так же, как вложенные блоки. Когда некоторое имя употребляется в пространстве имен, поиск его объявление проводится во всех объемлющих пространствах. В следующем примере разрешение имени 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() приводятся где-то в другом месте текста программы:

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

408

// ---- 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:

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

409

// ---- 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 и в

// ---- primer.C -- #include "primer.h"

namespace cplusplus_primer { MatrixLib::matrix MatrixLib::operator+

( const matrix &ml, const matrix &m2 ) { /* ... */ }

пространстве MatrixLib. В последнем случае это выглядит так:

}

Член может определяться вне своего пространства только при условии, что ранее он был объявлен внутри. Последнее приведенное определение operator+() было бы

namespace cplusplus_primer { namespace MatrixLib {

class matrix { /*...*/ };

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

matrix operator+ ( const matrix &ml, const matrix &m2 ); // ...

}

ошибочным, если бы ему не предшествовало объявление в файле primer.h:

}

8.5.5. ПОО и члены пространства имен

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

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

410

// 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) распространяется и на него. Чтобы удовлетворить этому требованию, программы, в которых используются пространства имен, обычно организуют следующим образом:

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

// ---- primer.h ----

namespace cplusplus_primer { class matrix { /* ... */ }; // объявления функций

extern matrix operator+ ( const matrix &m1, const matrix &m2 ); extern void inverse( matrix & );

// объявления объектов extern bool error_state;

они используются.

}

2. Определения этих членов помещают в исходный файл, содержащий реализацию:

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

411

 

 

// ---- primer.C ----

 

 

 

 

 

 

#include "primer.h"

 

 

 

namespace cplusplus_primer {

 

 

 

// определения функций

 

 

 

void inverse( matrix & )

 

 

 

{ /* ... */ }

matrix &m2 )

 

 

matrix operator+ ( const matrix &ml, const

 

 

{ /" ... */ }

 

 

 

// определения объектов

 

 

 

bool error_state = false;

 

 

 

}

 

 

 

 

 

 

 

Для объявления объекта без его определения используется ключевое слово extern, как и в случае такого объявления в глобальной области видимости.

8.5.6. Безымянные пространства имен

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

Предположим, мы хотим реализовать набор функций для сортировки вектора типа

// ----- SortLib.h -----

void quickSort( double *, double * ); void bubbleSort( double *, double * ); void mergeSort( double *, 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 ) { /* ... */ }