С.Б. Липпман, Ж. Лажойе - Язык программирования С++ Вводный курс (1114944), страница 61
Текст из файла (страница 61)
Вследующем фрагменте программы имя s1 относится к четырем разным сущностям:370С++ для начинающих#include <iostream>#include <string>// сравниваем s1 и s2 лексикографическиint lexicoCompare( const string &sl, const string &s2 ) { ... }// сравниваем длины s1 и s2int sizeCompare( const string &sl, const string &s2 ) { ... }typedef int ( PFI)( const string &, const string & );// сортируем массив строкvoid sort( string *s1, string *s2, PFI compare =lexicoCompare ){ ... }string sl[10] = { "a", "light", "drizzle", "was", "falling","when", "they", "left", "the", "school" };int main(){// вызов sort() со значением по умолчанию параметра compare// s1 - глобальный массивsort( s1, s1 + sizeof(s1)/sizeof(s1[0]) - 1 );// выводим результат сортировкиfor ( int i = 0; i < sizeof(s1) / sizeof(s1[0]); ++i )cout << s1[ i ].c_str() << "\n\t";}Поскольку определения функций lexicoCompare(), sizeCompare() и sort()представляют собой различные области видимости и все они отличны от глобальной, вкаждой из этих областей можно завести переменную с именем s1.Имя, введенное с помощью объявления, можно использовать от точки объявления доконца области видимости (включая вложенные области).
Так, имя s1 параметрафункции lexicoCompare() разрешается употреблять до конца ее области видимости, тоесть до конца ее определения.Имя глобального массива s1 видимо с точки его объявления до конца исходного файла,включая вложенные области, такие, как определение функции main().В общем случае имя должно обозначать одну сущность внутри одной области видимости.Если в предыдущем примере после объявления массива s1 добавить следующую строку,компилятор выдаст сообщение об ошибке:void s1(); // ошибка: повторное объявление s1Перегруженные функции являются исключением из правила: можно завести несколькоодноименных функций в одной области видимости, если они отличаются спискомпараметров. (Перегруженные функции рассматриваются в главе 9.)В С++ имя должно быть объявлено до момента его первого использования в выражении.В противном случае компилятор выдаст сообщение об ошибке. Процесс сопоставленияимени, используемого в выражении, с его объявлением называется разрешением.
Спомощью этого процесса имя получает конкретный смысл. Разрешение имени зависит отспособа его употребления и от его области видимости. Мы рассмотрим этот процесс вразличных контекстах. (В следующем подразделе описывается разрешение имен влокальной области видимости; в разделе 10.9 – разрешение в шаблонах функций; вконце главы 13 – в области видимости классов, а в разделе 16.12 – в шаблонах классов.)371С++ для начинающихОбласти видимости и разрешение имен – понятия времени компиляции. Они применимык отдельным частям текста программы. Компилятор интерпретирует текст программысогласно правилам областей видимости и правилам разрешения имен.8.1.1. Локальная область видимостиЛокальная область видимости – это часть исходного текста программы, содержащаяся вопределении функции (или блоке внутри тела функции).
Все функции имеют своилокальные области видимости. Каждая составная инструкция (или блок) внутри функциитакже представляет собой отдельную локальную область. Такие области могут бытьвложенными. Например, следующее определение функции содержит два их уровняconst int notFound = -1; // глобальная область видимостиint binSearch( const vector<int> &vec, int val ){ // локальная область видимости: уровень #1int low = 0;int high = vec.size() - 1;while ( low <= high ){ // локальная область видимости: уровень #2int mid = ( low + high ) / 2;if ( val < vec[ mid ] )high = mid - 1;else low = mid + 1;}return notFound; // локальная область видимости: уровень #1(функция выполняет двоичный поиск в отсортированном векторе целых чисел):}Первая локальная область видимости – тело функции binSearch().
В ней объявленыпараметры функции vec и val, а также переменные low и high. Цикл while внутрифункции задает вложенную локальную область, в которой определена одна переменнаяmid. Параметры vec и val и переменные low и high видны во вложенной области.Глобальная область видимости включает в себя обе локальных. В ней определена однацелая константа notFound.Имена параметров функции vec и val принадлежат к первой локальной областивидимости тела функции, и в ней использовать те же имена для других сущностей нельзя.int binSearch( const vector<int> &vec, int val ){ // локальная область видимости: уровень #1int val; // ошибка: неверное переопределение valНапример:// ...Имена параметров употребляются как внутри тела функции binSearch(), так и внутривложенной области видимости цикла while. Параметры vec и val недоступны вне телафункции binSearch().Разрешение имени в локальной области видимости происходит следующим образом:просматривается та область, где оно встретилось.
Если объявление найдено, имяразрешено. Если нет, просматривается область видимости, включающая текущую. Этот372С++ для начинающихпроцесс продолжается до тех пор, пока объявление не будет найдено либо не будетдостигнута глобальная область видимости. Если и там имени нет, оно будет считатьсяошибочным.Из-за порядка просмотра областей видимости в процессе разрешения имен объявление извнешней области может быть скрыто объявлением того же имени во вложенной области.Если бы в предыдущем примере переменная low была объявлена в глобальной областивидимости перед определением функции binSearch(), то использование low влокальной области видимости цикла while все равно относилось бы к локальномуint low;int binSearch( const vector<int> &vec, int val ){// локальное объявление low// скрывает глобальное объявлениеint low = 0;// ...// low - локальная переменнаяwhile ( low <= high ){//...}// ...объявлению, скрывающему глобальное:}Для некоторых инструкций языка C++ разрешено объявлять переменные внутриуправляющей части.
Например, в цикле for переменную можно определить внутриfor ( int index = 0; index < vecSize; ++index ){// переменная index видна только здесьif ( vec[ index ] == someValue )break;}// ошибка: переменная index не виднаинструкции инициализации:if ( index != vecSize ) // элемент найденПодобные переменные видны только в локальной области самого цикла for и вложенныхв него (это верно для стандарта С++, в предыдущих версиях языка поведение былоиным).
Компилятор рассматривает это объявление так же, как если бы оно было записано// представление компилятора{ // невидимый блокint index = 0;for ( ; index < vecSize; ++index ){// ...}в виде:}373С++ для начинающихТем самым программисту запрещается применять управляющую переменную внелокальной области видимости цикла.
Если нужно проверить index, чтобы определить,int index = 0;for ( ; index < vecSize; ++index ){// ...}// правильно: переменная index виднабыло ли найдено значение, то данный фрагмент кода следует переписать так:if ( index != vecSize ) // элемент найденПоскольку переменная, объявленная в инструкции инициализации цикла for, являетсялокальной для цикла, то же самое имя допустимо использовать аналогичным образом и вvoid fooBar( int *ia, int sz ){for (int i=0; i<sz; ++i) ...
// правильноfor (int i=0; i<sz; ++i) ... // правильно, другое ifor (int i=0; i<sz; ++i) ... // правильно, другое iдругих циклах, расположенных в данной локальной области видимости:}Аналогично переменная может быть объявлена внутри условия инструкций if и switch,if ( int *pi = getValue() ){// pi != 0 -- *pi можно использовать здесьint result = calc(*pi);// ...}else{// здесь pi тоже видна// pi == 0cout << "ошибка: getValue() завершилась неудачно" << endl;а также внутри условия циклов while и for. Например:}Переменные, определенные в условии инструкции if, как переменная pi, видны тольковнутри if и соответствующей части else, а также во вложенных областях.
Значениемусловия является значение этой переменной, которое она получает в результатеинициализации. Если pi равна 0 (нулевой указатель), условие ложно и выполняетсяветвь else. Если pi инициализируется любым другим значением, условие истинно ивыполняется ветвь if.
(Инструкции if, switch, for и while рассматривались в главе 5.)Упражнение 8.1Найдите различные области видимости в следующем примере. Какие объявленияошибочны и почему?374С++ для начинающихint ix = 1024;int ix() ;void func( int ix, int iy ) {int ix = 255;if (int ix=0) {int ix = 79;{int ix = 89;}}else {int ix = 99;}}Упражнение 8.2К каким объявлениям относятся различные использования переменных ix и iy вint ix = 1024;void func( int ix, int iy ) {ix = 100;for( int iy = 0; iy < 400; iy += 100 ) {iy += 100;ix = 300;}iy = 400;следующем примере:}8.2.
Глобальные объекты и функцииОбъявление функции в глобальной области видимости вводит глобальную функцию, аобъявление переменной – глобальный объект. Глобальный объект существует напротяжении всего времени выполнения программы. Время жизни глобального объектаначинается с момента запуска программы и заканчивается с ее завершением.Для того чтобы глобальную функцию можно было вызвать или взять ее адрес, онадолжна иметь определение.
Любой глобальный объект, используемый в программе,должен быть определен, причем только один раз. Встроенные функции могутопределяться несколько раз, если только все определения совпадают. Такое требованиеединственности или точного совпадения получило название правила одного определения(ПОО). В этом разделе мы покажем, как следует вводить глобальные объекты и функциив программе, чтобы ПОО соблюдалось.8.2.1. Объявления и определенияКак было сказано в главе 7, объявление функции устанавливает ее имя, а также типвозвращаемого значения и список параметров. Определение функции, помимо этой375С++ для начинающихинформации, задает еще и тело – набор инструкций, заключенных в фигурные скобки.// объявление функции calc()// определение находится в другом файлеvoid calc(int);int main(){int loc1 = get(); // ошибка: get() не объявленаcalc(loc1);// правильно: calc() объявлена// ...Функция должна быть объявлена перед вызовом.