Главная страница
Навигация по странице:

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

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

  • 9.1.1. Зачем нужно перегружать имя функции

  • 9.1.2. Как перегрузить имя функции

  • Язык программирования C++. Вводный курс. С для начинающих


    Скачать 5.41 Mb.
    НазваниеС для начинающих
    Дата24.08.2022
    Размер5.41 Mb.
    Формат файлаpdf
    Имя файлаЯзык программирования C++. Вводный курс.pdf
    ТипДокументы
    #652350
    страница39 из 93
    1   ...   35   36   37   38   39   40   41   42   ...   93
    416
    // надо использовать blip::bk
    Using-объявления в функции manip() позволяют ссылаться на членов пространства blib с помощью неквалифицированных имен. Такие объявления не видны вне manip(), и неквалифицированные имена могут применяться только внутри этой функции. Вне ее необходимо употреблять квалифицированные имена.
    Using-объявление упрощает использование членов пространства имен. Оно вводит только одно имя. Using-объявление может находиться в определенной области видимости, и, значит, мы способны точно указать, в каком месте программы те или иные члены разрешается употреблять без дополнительной квалификации.
    В следующем подразделе мы расскажем, как ввести в определенную область видимости все члены некоторого пространства имен.
    8.6.3. Using-
    директивы
    Пространства имен появились в стандартном С++. Предыдущие версии С++ их не поддерживали, и, следовательно, поставляемые библиотеки не помещали глобальные объявления в пространства имен. Множество программ на С++ было написано еще до того, как компиляторы стали поддерживать такую опцию. Заключая содержимое библиотеки в пространство имен, мы можем испортить старое приложение, использующее ее предыдущие версии: все имена из этой библиотеки становятся квалифицированными, т.е. должны включать имя пространства вместе с оператором разрешения области видимости. Те приложения, в которых эти имена употребляются в неквалифицированной форме, перестают компилироваться.
    Сделать видимыми имена из библиотеки, используемой в нашей программе, можно с помощью using-объявления. Предположим, что файл primer.h содержит интерфейс новой версии библиотеки, в котором глобальные объявления помещены в пространство имен cplusplus_primer. Нужно заставить нашу программу работать с новой библиотекой. Два using-объявления сделают видимыми имена класса matrix и функции inverse()
    из пространства cplusplus_primer: 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 в функции manip()ссылается на blip::bj
    ++bj; // blip::bj == 16 int bk; // объявление локальной bk using blip::bk; // ошибка: повторное определение bk в manip()
    } int wrongInit = bk; // ошибка: bk невидима

    С++ для начинающих
    417
    }
    Но если библиотека достаточно велика и приложение часто использует имена из нее, то для подгонки имеющегося кода к новой библиотеке может потребоваться много using- объявлений. Добавлять их все только для того, чтобы старый код скомпилировался и заработал, утомительно и чревато ошибками. Решить эту проблему помогают using-
    директивы, облегчающие переход на новую версию библиотеки, где впервые стали применяться пространства имен.
    Using-директива начинается ключевым словом using, за которым следует ключевое слово namespace
    , а затем имя некоторого пространства имен. Это имя должно ссылаться на определенное ранее пространство, иначе компилятор выдаст ошибку. Using-директива позволяет сделать все имена из этого пространства видимыми в неквалифицированной форме.
    Например, предыдущий фрагмент кода может быть переписан так:
    }
    Using-директива делает имена членов пространства имен видимыми за его пределами, в том месте, где она использована. Например, приведенная using-директива создает иллюзию того, что все члены cplusplus_primer объявлены в глобальной области видимости перед определением func(). При этом члены пространства имен не получают локальных псевдонимов, а как бы перемещаются в новую область видимости. Код
    } выглядит как
    #include "primer.h" using cplusplus_primer::matrix; using cplusplus_primer::inverse;
    // using- объявления позволяют использовать
    // имена matrix и inverse без спецификации void func( matrix &m ) {
    // ... inverse( m ); return m;
    #include "pnmer.h"
    // using- директива: все члены cplusplus_primer
    // становятся видимыми using namespace cplusplus_primer;
    // имена matrix и inverse можно использовать без спецификации void func( matrix &m ) {
    // ... inverse( m ); return m; namespace A { int i, j;

    С++ для начинающих
    418
    int i,
    J
    ; для фрагмента программы, содержащего в области видимости следующую using- директиву: using namespace A;
    Рассмотрим пример, позволяющий подчеркнуть разницу между using-объявлением
    (которое сохраняет пространство имен, но создает ассоциированные с его членами локальные синонимы) и using-директивой (которая полностью удаляет границы пространства имен).
    }
    Во-первых, 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. Локальная namespace blip { int bi = 16, bj = 15, bk = 23;
    // прочие объявления
    } int bj = 0; void manip() { using namespace blip; // using- директива -
    // коллизия имен ::bj and blip::bj
    // обнаруживается только при
    // использовании bj
    ++bi; // blip::bi == 17
    ++bj; // ошибка: неоднозначность
    // глобальная bj или blip::bj?
    ++::bj; // правильно: глобальная bj == 1
    ++blip::bj; // правильно: blip::bj == 16 int bk = 97; // локальная bk скрывает blip::bk
    ++bk; // локальная bk == 98

    С++ для начинающих
    419
    переменная bk скрывает blip::bk. Ссылка на bk внутри manip() не является неоднозначной – речь идет о локальной переменной.
    Using-директивы использовать очень просто: стоит написать одну такую директиву, и все члены пространства имен сразу становятся видимыми. Однако чрезмерное увлечение ими возвращает нас к старой проблеме засорения глобального пространства имен:
    // cplusplus_primer::matrix или DisneyFeatureAnimation::matrix?
    Ошибки неоднозначности, вызываемые using-директивой, обнаруживаются только в момент использования. В данном случае – при употреблении имени matrix. Такая ошибка, найденная не сразу, может стать сюрпризом: заголовочные файлы не менялись и никаких новых объявлений в программу добавлено не было. Ошибка появилась после того, как мы решили воспользоваться новыми средствами из библиотеки.
    Using-директивы очень полезны при переводе приложений на новые версии библиотек, использующие пространства имен. Однако употребление большого числа using-директив возвращает нас к проблеме засорения глобального пространства имен. Эту проблему можно свести к минимуму, если заменить using-директивы более селективными using- объявлениями. Ошибки неоднозначности, вызываемые ими, обнаруживаются в момент объявления. Мы рекомендуем пользоваться using-объявлениями, а не using-директивами, чтобы избежать засорения глобального пространства имен в своей программе.
    8.6.4.
    Стандартное пространство имен std
    Все компоненты стандартной библиотеки С++ находятся в пространстве имен std.
    Каждая функция, объект и шаблон класса, объявленные в стандартном заголовочном файле, таком, как или , принадлежат к этому пространству.
    Если все компоненты библиотеки объявлены в std, то какая ошибка допущена в данном примере: namespace cplusplus_primer { class matrix { };
    // прочие вещи ...
    } namespace DisneyFeatureAnimation { class matrix { };
    // здесь тоже ... using namespace cplusplus_primer; using namespace DisneyFeatureAnimation; matrix m; //
    ошибка, неоднозначность:

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

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

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

    употребить using-директиву, сделав видимыми все члены пространства std.
    Членами пространства имен std в этом примере являются: шаблон класса istream_iterator
    , стандартный входной поток cin, класс string и шаблон класса vector
    Простейшее решение – добавить using- директиву после директивы препроцессора #include: using namespace std;
    В данном примере using-директива делает все члены пространства std видимыми.
    Однако не все они нам нужны. Предпочтительнее пользоваться using-объявлениями, чтобы уменьшить вероятность коллизии имен при последующем добавлении в программу глобальных объявлений.
    Using-объявления, необходимые для компиляции этого примера, таковы: using std::vector;
    Но куда их поместить? Если программа состоит из большого количества файлов, можно для удобства создать заголовочный файл, содержащий все эти using-объявления, и включать его в исходные файлы вслед за заголовочными файлами стандартной библиотеки.
    #include
    #include
    #include int main()
    {
    // привязка istream_iterator к стандартному вводу istream_iterator infile( cin );
    // istream_iterator, отмечающий end-of-stream istream_iterator eos;
    // инициализация svec элементами, считываемыми из cin vector svec( infile, eos );
    // ... using std::istream_iterator; using std::string; using std::cin;

    С++ для начинающих
    421
    В нашей книге мы не употребляли using-объявлений. Это сделано, во-первых, для того, чтобы сократить размер кода, а во-вторых, потому, что большинство примеров компилировались в реализации С++, не поддерживающей пространства имен.
    Подразумевается, что using-объявления указаны для всех членов пространства имен std, используемых в примерах.
    Упражнение 8.14
    Поясните разницу между using-объявлениями и using-директивами.
    Упражнение 8.15
    Напишите все необходимые using-объявления для примера из раздела 6.14.
    Упражнение 8.16
    Возьмем следующий фрагмент кода:
    }
    Каковы будут значения объявлений и выражений, если поместить using-объявления для всех членов пространства имен Exercise в точку //1? В точку //2? А если вместо using- объявлений использовать using-директиву? 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;

    С++ для начинающих
    423
    9
    9.
    Перегруженные функции
    Итак, мы уже знаем, как объявлять, определять и использовать функции в программах. В этой главе речь пойдет об их специальном виде – перегруженных функциях. Две функции называются перегруженными, если они имеют одинаковое имя, объявлены в одной и той же области видимости, но имеют разные списки формальных параметров. Мы расскажем, как объявляются такие функции и почему они полезны. Затем мы рассмотрим вопрос об их разрешении, т.е. о том, какая именно из нескольких перегруженных функций вызывается во время выполнения программы. Эта проблема является одной из наиболее сложных в C++. Тем, кто хочет разобраться в деталях, будет интересно прочитать два раздела в конце главы, где тема преобразования типов аргументов и разрешения перегруженных функций раскрывается более подробно.
    9.1.
    Объявления перегруженных функций
    Теперь, научившись объявлять, определять и использовать функции в программах, познакомимся с перегрузкой – еще одним аспектом в C++. Перегрузка позволяет иметь несколько одноименных функций, выполняющих схожие операции над аргументами разных типов.
    Вы уже воспользовались предопределенной перегруженной функцией. Например, для вычисления выражения
    1 + 3 вызывается операция целочисленного сложения, тогда как вычисление выражения
    1.0 + 3.0 осуществляет сложение с плавающей точкой. Выбор той или иной операции производится незаметно для пользователя. Операция сложения перегружена, чтобы обеспечить работу с операндами разных типов. Ответственность за распознавание контекста и применение операции, соответствующей типам операндов, возлагается на компилятор, а не на программиста.
    В этой главе мы покажем, как определять собственные перегруженные функции.
    9.1.1.
    Зачем нужно перегружать имя функции
    Как и в случае со встроенной операцией сложения, нам может понадобиться набор функций, выполняющих одно и то же действие, но над параметрами различных типов.
    Предположим, что мы хотим определить функции, возвращающие наибольшее из переданных значений параметров. Если бы не было перегрузки, пришлось бы каждой такой функции присвоить уникальное имя. Например, семейство функций max() могло бы выглядеть следующим образом:

    С++ для начинающих
    424
    int matrix_max( const matrix & );
    Однако все они делают одно и то же: возвращают наибольшее из значений параметров. С точки зрения пользователя, здесь лишь одна операция – вычисление максимума, а детали ее реализации большого интереса не представляют.
    Отмеченная лексическая сложность отражает ограничение программной среды: всякое имя, встречающееся в одной и той же области видимости, должно относиться к уникальной сущности (объекту, функции, классу и т.д.). Такое ограничение на практике создает определенные неудобства, поскольку программист должен помнить или каким-то образом отыскивать все имена. Перегрузка функций помогает справиться с этой проблемой.
    Применяя перегрузку, программист может написать примерно так: int ix = max( j, k ); int iy = max( vec );
    Этот подход оказывается чрезвычайно полезным во многих ситуациях.
    9.1.2.
    Как перегрузить имя функции
    В C++ двум или более функциям может быть дано одно и то же имя при условии, что их списки параметров различаются либо числом параметров, либо их типами. В данном примере мы объявляем перегруженную функцию max(): int max( const matrix & );
    Для каждого перегруженного объявления требуется отдельное определение функции max()
    с соответствующим списком параметров.
    Если в некоторой области видимости имя функции объявлено более одного раза, то второе (и последующие) объявление интерпретируется компилятором так:

    если списки параметров двух функций отличаются числом или типами параметров, то функции считаются перегруженными: void print( vector & ); int i_max( int, int ); int vi_max( const vector & ); vector vec;
    //... int max ( int, int ); int max( const vector & );
    // перегруженные функции void print( const string & );

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

    если тип возвращаемого значения и списки параметров в объявлениях двух функций одинаковы, то второе объявление считается повторным: void print( const string & );
    Имена параметров при сравнении объявлений во внимание не принимаются; если списки параметров двух функций одинаковы, но типы возвращаемых значений различны, то второе объявление считается неправильным (несогласованным с первым) и помечается компилятором как ошибка:
    // возвращаемых значений
    Перегруженные функции не могут различаться лишь типами возвращаемого значения;

    если списки параметров двух функций разнятся только подразумеваемыми по умолчанию значениями аргументов, то второе объявление считается повторным: int max ( int *ia, int = 10 );
    Ключевое слово typedef создает альтернативное имя для существующего типа данных, новый тип при этом не создается. Поэтому если списки параметров двух функций различаются только тем, что в одном используется typedef, а в другом тип, для которого typedef служит псевдонимом, такие списки считаются одинаковыми, как, например, в следующих двух объявлениях функции calc(). В таком случае второе объявление даст ошибку компиляции, поскольку возвращаемое значение отличается от указанного раньше: extern int calc( double );
    Спецификаторы const или volatile при подобном сравнении не принимаются во внимание. Так, следующие два объявления считаются одинаковыми:
    // объявления одной и той же функции void print( const string &str ); unsigned int max( int i1, int i2 ); int max( int i1, int i2 ); // ошибка: отличаются только типы
    // объявления одной и той же функции int max ( int *ia, int sz );
    // typedef не вводит нового типа typedef double DOLLAR;
    // ошибка: одинаковые списки параметров, но разные типы
    // возвращаемых значений extern DOLLAR calc( DOLLAR );

    С++ для начинающих
    426
    void f( const int );
    Спецификатор const важен только внутри определения функции: он показывает, что в теле функции запрещено изменять значение параметра. Однако аргумент, передаваемый по значению, можно использовать в теле функции как обычную инициированную переменную: вне функции изменения не видны. (Способы передачи аргументов, в частности передача по значению, обсуждаются в разделе 7.3.) Добавление спецификатора const к параметру, передаваемому по значению, не влияет на его интерпретацию.
    Функции, объявленной как f(int), может быть передано любое значение типа int, равно как и функции f(const int). Поскольку они обе принимают одно и то же множество значений аргумента, то приведенные объявления не считаются перегруженными. f() можно определить как void f( int i ) { } или как void f( const int i ) { }
    Наличие двух этих определений в одной программе – ошибка, так как одна и та же функция определяется дважды.
    Однако, если спецификатор const или volatile применяется к параметру указательного или ссылочного типа, то при сравнении объявлений он учитывается. void f( const int& );
    1   ...   35   36   37   38   39   40   41   42   ...   93


    написать администратору сайта