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

  • 8.1.1. Локальная область видимости

  • 8.2.1. Объявления и определения

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


    Скачать 5.41 Mb.
    НазваниеС для начинающих
    Дата24.08.2022
    Размер5.41 Mb.
    Формат файлаpdf
    Имя файлаЯзык программирования C++. Вводный курс.pdf
    ТипДокументы
    #652350
    страница34 из 93
    1   ...   30   31   32   33   34   35   36   37   ...   93
    368
    Через pf вызывается функция, написанная на языке С.
    }
    Вспомним, что присваивание и инициализация указателя на функцию возможны лишь тогда, когда тип в левой части оператора присваивания в точности соответствует типу в правой его части. Следовательно, указатель на С-функцию не может адресовать функцию
    С++ (и инициализация его таким адресом не допускается), и наоборот. Подобная попытка вызывает ошибку компиляции:
    }
    Отметим, что в некоторых реализациях С++ характеристики указателей на функции С и
    С++ одинаковы. Отдельные компиляторы могут допустить подобное присваивание, рассматривая это как расширение языка.
    Если директива связывания применяется к объявлению, она затрагивает все функции, участвующие в данном объявлении.
    В следующем примере параметр pfParm также служит указателем на С-функцию.
    Директива связывания применяется к объявлению функции, к которой этот параметр относится: extern "C" void f1( void(*pfParm)(int) );
    Следовательно, f1() является С-функцией с одним параметром – указателем на С- функцию. Значит, передаваемый ей аргумент должен быть либо такой же функцией, либо указателем на нее, поскольку считается, что указатели на функции, написанные на разных языках, имеют разные типы. (Снова заметим, что в тех реализациях С++, где указатели на функции С и С++ имеют одинаковые характеристики, компилятор может поддерживать расширение языка, позволяющее не различать эти два типа указателей.)
    Коль скоро директива связывания относится ко всем функциям в объявлении, то как же объявить функцию С++, имеющую в качестве параметра указатель на С-функцию? С помощью директивы typedef. Например: extern "C" void exit(int);
    // pf ссылается на C-функцию exit() extern "C" void (*pf)(int) = exit; int main() {
    // ...
    // вызов С-функции, а именно exit()
    (*pf)(99); void (*pfl)(int); extern "C" void (*pf2)(int); int main() { pfl = pf2; // ошибка: pfl и pf2 имеют разные типы
    // ...
    // pfParm - указатель на С-функцию

    С++ для начинающих
    369
    void f2( FC *pfParm );
    Упражнение 7.21
    В разделе 7.5 приводится определение функции factorial(). Напишите объявление указателя на нее. Вызовите функцию через этот указатель для вычисления факториала 11.
    Упражнение 7.22
    Каковы типы следующих объявлений:
    (c) void (*(*papf)[2])(int);
    Как сделать эти объявления более понятными, используя директивы typedef?
    Упражнение 7.23
    Вот функции из библиотеки С, определенные в заголовочном файле : double sqrt(double);
    Как бы вы объявили массив указателей на С-функции и инициализировали его этими четырьмя функциями? Напишите main(), которая вызывает sqrt() с аргументом 97.9 через элемент массива.
    Упражнение 7.24
    Вернемся к примеру sort(). Напишите определение функции int sizeCompare( const string &, const string & );
    Если передаваемые в качестве параметров строки имеют одинаковую длину, то sizeCompare()
    возвращает 0; если первая строка короче второй, то отрицательное число, а если длиннее, то положительное. Напоминаем, что длина строки возвращается операцией size() класса string. Измените main() для вызова sort(), передав в качестве третьего аргумента указатель на sizeCompare().
    // FC представляет собой тип:
    //
    С-функция с параметром типа int, не возвращающая никакого значения extern "C" typedef void FC( int );
    // f2() - C++ функция с параметром -
    // указателем на С-функцию
    (a) int (*mpf)(vector&);
    (b) void (*apf[20])(doub1e); double abs(double); double sin(double); double cos(double);

    С++ для начинающих
    370
    8.
    Область видимости и время жизни
    В этой главе обсуждаются два важных вопроса, касающиеся объявлений в С++. Где употребляется объявленное имя? Когда можно безопасно использовать объект или вызывать функцию, т.е. каково время жизни сущности в программе? Для ответа на первый вопрос мы введем понятие областей видимости и покажем, как они ограничивают применение имен в исходном файле программы. Мы рассмотрим разные типы таких областей: глобальную и локальную, а также более сложное понятие областей видимости пространств имен, которое появится в конце главы.
    Отвечая на второй вопрос, мы опишем, как объявления вводят глобальные объекты и функции (сущности, “живущие” в течение всего времени работы программы), локальные (“живущие” на определенном отрезке выполнения) и динамически размещаемые объекты (временем жизни которых управляет программист). Мы также исследуем свойства времени выполнения, характерные для этих объектов и функций.
    8.1.
    Область видимости
    Каждое имя в С++ программе должно относиться к уникальной сущности (объекту, функции, типу или шаблону). Это не значит, что оно встречается только один раз во всей программе: его можно повторно использовать для обозначения другой сущности, если только есть некоторый контекст, помогающий различить разные значения одного и того же имени. Контекстом, служащим для такого различения, служит область видимости. В
    С++ поддерживается три их типа: локальная область видимости, область видимости
    пространства имен и область видимости класса.
    Локальная область – это часть исходного текста программы, содержащаяся в определении функции (или в блоке). Любая функция имеет собственную такую часть, и каждая составная инструкция (или блок) внутри функции также представляет собой отдельную локальную область.
    Область видимости пространства имен – часть исходного текста программы, не содержащаяся внутри объявления или определения функции или определения класса.
    Самая внешняя часть называется глобальной областью видимости или глобальной областью видимости пространства имен.
    Объекты, функции, типы и шаблоны могут быть определены в глобальной области видимости. Программисту разрешено задать пользовательские пространства имен, заключенные внутри глобальной области с помощью определения пространства имен.
    Каждое такое пространство является отдельной областью видимости. Пользовательское пространство, как и глобальное, может содержать объявления и определения объектов, функций, типов и шаблонов, а также вложенные пользовательские пространства имен.
    (Они рассматриваются в разделах 8.5 и 8.6.)
    Каждое определение класса представляет собой отдельную область видимости класса.
    (О таких областях мы расскажем в главе 13.)
    Имя может обозначать различные сущности в зависимости от области видимости. В следующем фрагменте программы имя s1 относится к четырем разным сущностям:

    С++ для начинающих
    371
    }
    Поскольку определения функций lexicoCompare(), sizeCompare() и sort() представляют собой различные области видимости и все они отличны от глобальной, в каждой из этих областей можно завести переменную с именем s1.
    Имя, введенное с помощью объявления, можно использовать от точки объявления до конца области видимости (включая вложенные области). Так, имя s1 параметра функции lexicoCompare() разрешается употреблять до конца ее области видимости, то есть до конца ее определения.
    Имя глобального массива s1 видимо с точки его объявления до конца исходного файла, включая вложенные области, такие, как определение функции main().
    В общем случае имя должно обозначать одну сущность внутри одной области видимости.
    Если в предыдущем примере после объявления массива s1 добавить следующую строку, компилятор выдаст сообщение об ошибке: void s1(); // ошибка: повторное объявление s1
    Перегруженные функции являются исключением из правила: можно завести несколько одноименных функций в одной области видимости, если они отличаются списком параметров. (Перегруженные функции рассматриваются в главе 9.)
    В С++ имя должно быть объявлено до момента его первого использования в выражении.
    В противном случае компилятор выдаст сообщение об ошибке. Процесс сопоставления имени, используемого в выражении, с его объявлением называется разрешением. С помощью этого процесса имя получает конкретный смысл. Разрешение имени зависит от способа его употребления и от его области видимости. Мы рассмотрим этот процесс в различных контекстах. (В следующем подразделе описывается разрешение имен в локальной области видимости; в разделе 10.9 – разрешение в шаблонах функций; в конце главы 13 – в области видимости классов, а в разделе 16.12 – в шаблонах классов.)
    #include
    #include
    // сравниваем s1 и s2 лексикографически int lexicoCompare( const string &sl, const string &s2 ) { ... }
    // сравниваем длины s1 и s2 int 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";

    С++ для начинающих
    372
    Области видимости и разрешение имен – понятия времени компиляции. Они применимы к отдельным частям текста программы. Компилятор интерпретирует текст программы согласно правилам областей видимости и правилам разрешения имен.
    8.1.1.
    Локальная область видимости
    Локальная область видимости – это часть исходного текста программы, содержащаяся в определении функции (или блоке внутри тела функции). Все функции имеют свои локальные области видимости. Каждая составная инструкция (или блок) внутри функции также представляет собой отдельную локальную область. Такие области могут быть вложенными. Например, следующее определение функции содержит два их уровня
    (функция выполняет двоичный поиск в отсортированном векторе целых чисел):
    }
    Первая локальная область видимости – тело функции binSearch(). В ней объявлены параметры функции vec и val, а также переменные low и high. Цикл while внутри функции задает вложенную локальную область, в которой определена одна переменная mid
    . Параметры vec и val и переменные low и high видны во вложенной области.
    Глобальная область видимости включает в себя обе локальных. В ней определена одна целая константа notFound.
    Имена параметров функции vec и val принадлежат к первой локальной области видимости тела функции, и в ней использовать те же имена для других сущностей нельзя.
    Например:
    // ...
    Имена параметров употребляются как внутри тела функции binSearch(), так и внутри вложенной области видимости цикла while. Параметры vec и val недоступны вне тела функции binSearch().
    Разрешение имени в локальной области видимости происходит следующим образом: просматривается та область, где оно встретилось. Если объявление найдено, имя разрешено. Если нет, просматривается область видимости, включающая текущую. Этот const int notFound = -1; // глобальная область видимости int binSearch( const vector &vec, int val )
    { // локальная область видимости: уровень #1 int low = 0; int high = vec.size() - 1; while ( low <= high )
    { // локальная область видимости: уровень #2 int mid = ( low + high ) / 2; if ( val < vec[ mid ] ) high = mid - 1; else low = mid + 1;
    } return notFound; // локальная область видимости: уровень #1 int binSearch( const vector &vec, int val )
    { // локальная область видимости: уровень #1 int val; // ошибка: неверное переопределение val

    С++ для начинающих
    373
    процесс продолжается до тех пор, пока объявление не будет найдено либо не будет достигнута глобальная область видимости. Если и там имени нет, оно будет считаться ошибочным.
    Из-за порядка просмотра областей видимости в процессе разрешения имен объявление из внешней области может быть скрыто объявлением того же имени во вложенной области.
    Если бы в предыдущем примере переменная low была объявлена в глобальной области видимости перед определением функции binSearch(), то использование low в локальной области видимости цикла while все равно относилось бы к локальному объявлению, скрывающему глобальное:
    }
    Для некоторых инструкций языка C++ разрешено объявлять переменные внутри управляющей части. Например, в цикле for переменную можно определить внутри инструкции инициализации: if ( index != vecSize ) // элемент найден
    Подобные переменные видны только в локальной области самого цикла for и вложенных в него (это верно для стандарта С++, в предыдущих версиях языка поведение было иным). Компилятор рассматривает это объявление так же, как если бы оно было записано в виде:
    } int low; int binSearch( const vector &vec, int val )
    {
    // локальное объявление low
    // скрывает глобальное объявление int low = 0;
    // ...
    // low - локальная переменная while ( low <= high )
    {//...
    }
    // ... for ( int index = 0; index < vecSize; ++index )
    {
    // переменная index видна только здесь if ( vec[ index ] == someValue ) break;
    }
    // ошибка: переменная index не видна
    // представление компилятора
    { // невидимый блок int index = 0; for ( ; index < vecSize; ++index )
    {
    // ...
    }

    С++ для начинающих
    374
    Тем самым программисту запрещается применять управляющую переменную вне локальной области видимости цикла. Если нужно проверить index, чтобы определить, было ли найдено значение, то данный фрагмент кода следует переписать так: if ( index != vecSize ) // элемент найден
    Поскольку переменная, объявленная в инструкции инициализации цикла for, является локальной для цикла, то же самое имя допустимо использовать аналогичным образом и в других циклах, расположенных в данной локальной области видимости:
    }
    Аналогично переменная может быть объявлена внутри условия инструкций if и switch, а также внутри условия циклов while и for. Например:
    }
    Переменные, определенные в условии инструкции if, как переменная pi, видны только внутри if и соответствующей части else, а также во вложенных областях. Значением условия является значение этой переменной, которое она получает в результате инициализации. Если pi равна 0 (нулевой указатель), условие ложно и выполняется ветвь else. Если pi инициализируется любым другим значением, условие истинно и выполняется ветвь if. (Инструкции if, switch, for и while рассматривались в главе 5.)
    Упражнение 8.1
    Найдите различные области видимости в следующем примере. Какие объявления ошибочны и почему? int index = 0; for ( ; index < vecSize; ++index )
    {
    // ...
    }
    // правильно: переменная index видна void fooBar( int *ia, int sz )
    { for (int i=0; i{
    // pi != 0 -- *pi можно использовать здесь int result = calc(*pi);
    // ...
    } else
    {
    // здесь pi тоже видна
    // pi == 0 cout << "
    ошибка: getValue() завершилась неудачно" << endl;

    С++ для начинающих
    375
    }
    Упражнение 8.2
    К каким объявлениям относятся различные использования переменных ix и iy в следующем примере:
    }
    8.2.
    Глобальные объекты и функции
    Объявление функции в глобальной области видимости вводит глобальную функцию, а объявление переменной – глобальный объект. Глобальный объект существует на протяжении всего времени выполнения программы. Время жизни глобального объекта начинается с момента запуска программы и заканчивается с ее завершением.
    Для того чтобы глобальную функцию можно было вызвать или взять ее адрес, она должна иметь определение. Любой глобальный объект, используемый в программе, должен быть определен, причем только один раз. Встроенные функции могут определяться несколько раз, если только все определения совпадают. Такое требование единственности или точного совпадения получило название правила одного определения
    (ПОО). В этом разделе мы покажем, как следует вводить глобальные объекты и функции в программе, чтобы ПОО соблюдалось.
    8.2.1.
    Объявления и определения
    Как было сказано в главе 7, объявление функции устанавливает ее имя, а также тип возвращаемого значения и список параметров. Определение функции, помимо этой 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;
    } 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;

    С++ для начинающих
    376
    информации, задает еще и тело – набор инструкций, заключенных в фигурные скобки.
    Функция должна быть объявлена перед вызовом. Например:
    }
    Определение объекта имеет две формы: type_specifier object_name = initializer;
    Вот, например, определение obj1. Здесь obj1 инициализируется значением 97: int obj1 = 97;
    Следующая инструкция задает obj2, хотя начальное значение не задано: int obj2;
    Объект, определенный в глобальной области видимости без явной инициализации, гарантированно получит нулевое значение. Таким образом, в следующих двух примерах и var1, и var2 будут равны нулю: int var2;
    Глобальный объект можно определить в программе только один раз. Поскольку он должен быть объявлен в исходном файле перед использованием, то для программы, состоящей из нескольких файлов, необходима возможность объявить объект, не определяя его. Как это сделать?
    С помощью ключевого слова extern, аналогичного объявлению функции: оно указывает, что объект определен в другом месте – в этом же исходном файле или в другом.
    Например: extern int i;
    Эта инструкция “обещает”, что в программе имеется определение, подобное int i;
    // объявление функции calc()
    // определение находится в другом файле void calc(int); int main()
    { int loc1 = get(); // ошибка: get() не объявлена calc(loc1); // правильно: calc() объявлена
    // ... type_specifier object_name; int var1 = 0;

    С++ для начинающих
    1   ...   30   31   32   33   34   35   36   37   ...   93


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