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

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

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

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

412

Хотя функция 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 можно переписать на С,

//SortLib.C

//swap() невидима для других файлов программы

static void swap( double *d1, double *d2 ) { /* ... */ }

сохранив свойства swap():

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

413

// определения функций сортировки такие же, как и раньше

Во многих программах на С++ используются объявления с ключевым словом static. Предполагается, что они должны быть заменены безымянными пространствами имен по мере того, как все большее число компиляторов начнет поддерживать это понятие.

Упражнение 8.11

Зачем нужно определять собственное пространство имен в программе? Упражнение 8.12

Имеется следующее объявление operator*(), члена вложенного пространства имен

namespace cplusplus_primer { namespace MatrixLib {

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

matrix operator* ( const matrix &, const matrix & ); // ...

}

cplusplus_primer::MatrixLib:

}

Как определить эту функцию в глобальной области видимости? Напишите только прототип.

Упражнение 8.13 Объясните, зачем нужны безымянные пространства имен.

8.6. Использование членов пространства имен А

Использование квалифицированных имен при каждом обращении к членам пространств может стать обременительным, особенно если имена пространств достаточно длинны. Если бы удалось сделать их короче, то такие имена проще было бы читать и набивать. Однако употребление коротких имен увеличивает риск их совпадения с другими, поэтому желательно, чтобы в библиотеках применялись пространства с длинными именами.

К счастью, существуют механизмы, облегчающие использование членов пространств имен в программах. Псевдонимы пространства имен, using-объявления и using-

директивы помогают преодолеть неудобства работы с очень длинными именами.

8.6.1. Псевдонимы пространства имен

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

namespace International_Business_Machines

пространства. Например, длинное имя

{ /* ... */ }

может быть ассоциировано с более коротким синонимом:

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

414

namespace IBM = International_Business_Machines;

Объявление псевдонима начинается ключевым словом namespace, за которым следует короткий псевдоним, а за ним знак равенства и исходное полное имя пространства. Если полное имя не соответствует никакому известному пространству, это ошибка.

Псевдоним может относиться и к вложенному пространству имен. Вспомним слишком

#include "primer.h"

// трудно читать!

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

{

// ...

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

длинное определение функции func() выше:

}

Разрешается задать псевдоним для обозначения вложенного cplusplLis_primer::MatrixLib, сделав определение функции более удобным для

#include "primer.h"

// более короткий псевдоним

namespace mlib = cplusplus_primer::MatrixLib;

// читать проще!

void func( mlib::matrix &m )

{

// ...

mlib::inverse( m ); return m;

восприятия:

}

Одно пространство имен может иметь несколько взаимозаменяемых псевдонимов. Например, если псевдоним Lib ссылается на cplusplus_primer, то определение

// псевдоним alias относится к пространству имен cplusplus_primer namespace alias = Lib;

void func( cplusplus_primer::matrix &m ) { // ...

alias::inverse( m ); return m;

функции func() может выглядеть и так:

}

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

415

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-объявлением, как и любым другим, имеет следующие характеристики:

оно должно быть уникальным в своей области видимости;

оно скрывает одноименную сущность во внешней области;

оно скрывается объявлением одноименной сущности во вложенной области.

Например:

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

416

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

using blip::bj; //

скрывает глобальную bj

++bj;

//

bj в функции manip()ссылается на blip::bj

//

blip::bj == 16

int bk;

//

объявление локальной bk

using blip::bk; // ошибка: повторное определение bk в manip()

}

int wrongInit = bk; // ошибка: bk невидима

// надо использовать blip::bk

Using-объявления в функции manip() позволяют ссылаться на членов пространства blib с помощью неквалифицированных имен. Такие объявления не видны вне manip(), и неквалифицированные имена могут применяться только внутри этой функции. Вне ее необходимо употреблять квалифицированные имена.

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

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

8.6.3. Using-директивы

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

Сделать видимыми имена из библиотеки, используемой в нашей программе, можно с помощью using-объявления. Предположим, что файл primer.h содержит интерфейс новой версии библиотеки, в котором глобальные объявления помещены в пространство имен cplusplus_primer. Нужно заставить нашу программу работать с новой библиотекой. Два using-объявления сделают видимыми имена класса matrix и функции inverse() из пространства cplusplus_primer:

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

417

#include "primer.h"

using cplusplus_primer::matrix; using cplusplus_primer::inverse;

//using-объявления позволяют использовать

//имена matrix и inverse без спецификации void func( matrix &m ) {

//...

inverse( m ); return m;

}

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

Using-директива начинается ключевым словом using, за которым следует ключевое слово namespace, а затем имя некоторого пространства имен. Это имя должно ссылаться на определенное ранее пространство, иначе компилятор выдаст ошибку. Using-директива

позволяет сделать все имена из этого пространства видимыми в неквалифицированной форме.

#include "pnmer.h"

//using-директива: все члены cplusplus_primer

//становятся видимыми

using namespace cplusplus_primer;

// имена matrix и inverse можно использовать без спецификации void func( matrix &m ) {

// ...

inverse( m ); return m;

Например, предыдущий фрагмент кода может быть переписан так:

}

Using-директива делает имена членов пространства имен видимыми за его пределами, в том месте, где она использована. Например, приведенная using-директива создает иллюзию того, что все члены cplusplus_primer объявлены в глобальной области видимости перед определением func(). При этом члены пространства имен не получают

namespace A { int i, j;

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

}

выглядит как

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

418

int i, J;

для фрагмента программы, содержащего в области видимости следующую using- директиву:

using namespace A;

Рассмотрим пример, позволяющий подчеркнуть разницу между using-объявлением (которое сохраняет пространство имен, но создает ассоциированные с его членами локальные синонимы) и using-директивой (которая полностью удаляет границы

namespace blip {

int bi = 16, bj = 15, bk = 23; // прочие объявления

}

int bj = 0;

void manip() {

using namespace blip; // using-директива -

 

 

 

// коллизия имен ::bj and blip::bj

 

 

 

// обнаруживается только при

 

 

++bi;

// использовании bj

 

 

// blip::bi == 17

 

 

++bj;

// ошибка: неоднозначность

 

 

++::bj;

// глобальная bj или blip::bj?

 

 

// правильно: глобальная bj == 1

 

 

++blip::bj;

// правильно: blip::bj == 16

 

 

int bk = 97;

// локальная bk скрывает blip::bk

 

 

++bk;

// локальная bk == 98

пространства имен).

 

 

 

}

 

 

 

 

 

 

 

Во-первых, using-директивы имеют область видимости. Такая директива в функции manip() относится только к блоку этой функции. Для manip() члены пространства имен blip выглядят так, как будто они объявлены в глобальной области видимости, а следовательно, можно использовать их неквалифицированные имена. Вне этой функции необходимо употреблять квалифицированные.

Во-вторых, ошибки неоднозначности, вызванные применением using-директивы, обнаруживают себя при реальном обращении к такому имени, а не при встрече в тексте самой этой директивы. Например, переменная bj, член пространства blib, выглядит для manip() как объявленная в глобальной области видимости, вне blip. Однако в глобальной области уже есть такая переменная. Возникает неоднозначность имени bj в функции manip(): оно относится и к глобальной переменной, и к члену пространства blip. Ошибка проявляется только при упоминании bj в функции manip(). Если бы это имя вообще не использовалось в manip(), коллизия не проявилась бы.

В-третьих, using-директива не затрагивает употребление квалифицированных имен. Когда в manip() упоминается ::bj, имеется в виду переменная из глобальной области видимости, а blip::bj обозначает переменную из пространства имен blip.

И наконец члены пространства blip выглядят для функции manip() так, как будто они объявлены в глобальной области видимости. Это означает, что локальные объявления внутри manip() могут скрывать имена членов пространства blip. Локальная

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

419

переменная bk скрывает blip::bk. Ссылка на bk внутри manip() не является неоднозначной речь идет о локальной переменной.

Using-директивы использовать очень просто: стоит написать одну такую директиву, и все члены пространства имен сразу становятся видимыми. Однако чрезмерное увлечение ими

namespace cplusplus_primer { class matrix { };

// прочие вещи ...

}

namespace DisneyFeatureAnimation { class matrix { };

// здесь тоже ...

using namespace cplusplus_primer; using namespace DisneyFeatureAnimation;

matrix m; //ошибка, неоднозначность:

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

// cplusplus_primer::matrix или DisneyFeatureAnimation::matrix?

Ошибки неоднозначности, вызываемые using-директивой, обнаруживаются только в момент использования. В данном случае при употреблении имени matrix. Такая ошибка, найденная не сразу, может стать сюрпризом: заголовочные файлы не менялись и никаких новых объявлений в программу добавлено не было. Ошибка появилась после того, как мы решили воспользоваться новыми средствами из библиотеки.

Using-директивы очень полезны при переводе приложений на новые версии библиотек, использующие пространства имен. Однако употребление большого числа using-директив возвращает нас к проблеме засорения глобального пространства имен. Эту проблему можно свести к минимуму, если заменить using-директивы более селективными using- объявлениями. Ошибки неоднозначности, вызываемые ими, обнаруживаются в момент объявления. Мы рекомендуем пользоваться using-объявлениями, а не using-директивами, чтобы избежать засорения глобального пространства имен в своей программе.

8.6.4. Стандартное пространство имен std

Все компоненты стандартной библиотеки С++ находятся в пространстве имен std. Каждая функция, объект и шаблон класса, объявленные в стандартном заголовочном файле, таком, как <vector> или <iostream>, принадлежат к этому пространству.

Если все компоненты библиотеки объявлены в std, то какая ошибка допущена в данном примере:

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

420

#include <vector> #include <string> #include <iterator>

int main()

{

//привязка istream_iterator к стандартному вводу istream_iterator<string> infile( cin );

//istream_iterator, отмечающий end-of-stream istream_iterator<string> eos;

//инициализация svec элементами, считываемыми из cin vector<string> svec( infile, eos );

//...

}

Правильно, этот фрагмент кода не компилируется, потому что члены пространства имен std должны использоваться с указанием их специфицированных имен. Для того чтобы исправить положение, мы можем выбрать один из следующих способов:

заменить имена членов пространства std в этом примере соответствующими специфицированными именами;

применить using-объявления, чтобы сделать видимыми используемые члены пространства std;

употребить using-директиву, сделав видимыми все члены пространства std.

Членами пространства имен std в этом примере являются: шаблон класса istream_iterator, стандартный входной поток cin, класс string и шаблон класса vector.

Простейшее решение добавить using-директиву после директивы препроцессора #include:

using namespace std;

В данном примере using-директива делает все члены пространства std видимыми. Однако не все они нам нужны. Предпочтительнее пользоваться using-объявлениями,

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

using std::istream_iterator; using std::string;

using std::cin;

Using-объявления, необходимые для компиляции этого примера, таковы: using std::vector;

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

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

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

421

В нашей книге мы не употребляли using-объявлений. Это сделано, во-первых, для того, чтобы сократить размер кода, а во-вторых, потому, что большинство примеров компилировались в реализации С++, не поддерживающей пространства имен. Подразумевается, что using-объявления указаны для всех членов пространства имен std, используемых в примерах.

Упражнение 8.14

Поясните разницу между using-объявлениями и using-директивами.

Упражнение 8.15

Напишите все необходимые using-объявления для примера из раздела 6.14.

Упражнение 8.16

namespace Exercise { int ivar = 0; double dvar = 0;

const int limit = 1000;

}

int ivar = 0;

//1

void manip() { //2

double dvar = 3.1416; int iobj = limit + 1; ++ivar;

++::ivar;

Возьмем следующий фрагмент кода:

}

Каковы будут значения объявлений и выражений, если поместить using-объявления для всех членов пространства имен Exercise в точку //1? В точку //2? А если вместо using- объявлений использовать using-директиву?