Программирование - Ирина Козлова
Шрифт:
Интервал:
Закладка:
Переменная, которая объявлена локально с классом памяти extern, служит ссылкой на переменную с таким же именем, определенную глобально в каком-либо из исходных файлов программы. Цель подобного объявления заключается в том, чтобы сделать определение переменной глобального уровня видимым внутри блока.
15. Объявление переменной, которая служит именем внешнего массива
Рассмотрим пример: объявление переменной i, которая служит именем внешнего массива длинных целых чисел, на локальном уровне.
исходный файл file1.c
main()
{…
}
fun1()
{extern long i[];…
}
/* исходный файл file2.c */
long i[MAX] = {0};
fun2()
{…
}
fun3()
{…
}
Объявление переменной i[] как extern в рассмотренном примере делает ее видимой в функции fun1. Определение данной переменной находится в файле file2.c на глобальном уровне и должно быть единственным. При этом объявлений с классом памяти extern может быть много.
Объявление переменной со спецификатором extern дает знать компилятору о том, что память для переменной не нужна, так как это выполнено где-то в другом месте программы.
В случае объявления переменных на глобальном уровне можно применить спецификатор класса памяти static или extern. Кроме того, можно объявлять переменные без указания класса памяти. Классы памяти auto и register для глобального объявления применять нельзя.
Объявление переменных на глобальном уровне представляет собой или определение переменных, или ссылки на определения, которые сделаны в другой части программы. Объявление глобальной переменной, инициализирующее данную переменную (явно или неявно), служит определением переменной. Определение на глобальном уровне может быть задано в нескольких формах.
1. С помощью класса памяти static. Данная переменная может быть инициализирована явно константным выражением либо по умолчанию нулевым значением. То есть объявления static int i = 0 и static int i одинаковы, и в том и в другом случае переменной i будет присвоено значение 0.
2. Переменная может быть объявлена без указания класса памяти, но с явной инициализацией. Подобной переменной по умолчанию присваивается класс памяти static. То есть объявления int i = 1 и static int i = 1 будут одинаковы.
16. Методы доступа к элементам массивов
Доступ к элементам массива может производиться двумя различными способами.
Первый способ связан с применением обычных индексных выражений в квадратных скобках, например: array[18] = 3 или array[i + 3] = 9. При данном способе доступа записываются два выражения. Второе выражение должно быть заключено в квадратные скобки. Одно из данных выражений должно являться указателем, а второе – выражением целого типа. Последовательность записи данных выражений может быть произвольной, однако в квадратных скобках следует записывать выражение, следующее вторым. Поэтому записи array[16] и 16[array] будут являться одинаковыми и обозначающими элемент массива с номером шестнадцать. Указатель, который используется в индексном выражении, не всегда является константой, которая указывает на какой-либо массив, это может быть и переменная. Например, после выполнения присваивания ptr = array доступ к шестнадцатому элементу массива можно получить, применяя указатель ptr в форме ptr[16] или 16[ptr].
Второй способ доступа к элементам массива связан с применением адресных выражений и операции раза-дресации в виде *(array+16) = 3 или *(array+i+2) = 7. При данном способе доступа адресное выражение соответствует адресу шестнадцатого элемента массива, тоже может быть записано различными способами: *(array+16) или *(16+array).
При работе на компьютере первый способ приводится ко второму, т. е. индексное выражение становится адресным. Для ранее рассмотренных примеров array[16] и 16[array] преобразуются в *(ar-ray+16).
Для доступа к начальному элементу массива, т. е. к элементу с нулевым индексом, можно применять просто значение указателя array или ptr. Любое из присваиваний
*array = 2;
array[0] = 2; *(array+0) = 2; *ptr = 2;
ptr[0] = 2;
*(ptr+0) = 2;
присваивает начальному элементу массива значение 2, но быстрее всего выполнятся присваивания *array = 2 и *ptr = 2, так как в них не требуется выполнять операции сложения.
17. Директивы препроцессора
Директивы препроцессора – это особые инструкции, которые записаны в тексте программы на СИ и выполнены до трансляции программы. Директивы препроцессора дают возможность изменить текст программы. Среди таких действий – замена некоторых лексем в тексте, вставка текста из другого файла, запрет на трансляцию части текста и т. п. Все директивы препроцессора должны начинаться со знака #. После директив препроцессора точки с запятой быть не должно.
Директива #include включает в программу содержимое определенного файла. Эта директива может быть представлена в двух формах:
#include «имя файла» #include <имя файла>
Имя файла должно соответствовать соглашениям операционной системы. Оно может включать в себя либо только имя файла, либо имя файла с предшествующим ему маршрутом. Когда имя файла указано в кавычках, то поиск файла производится по заданному маршруту, а при его отсутствии – в текущем каталоге. Когда имя файла задано в угловых скобках, поиск файла осуществляется в обычных директориях операционной системы, которые задаются командой PATH.
Директива #include может являться вложенной, т. е. во включаемом файле тоже может содержаться директива #include, способная замещаться после включения файла, содержащего эту директиву.
Директива #include часто применяется для включения в программу так называемых заголовочных файлов, которые содержат прототипы библиотечных функций, и поэтому чаще всего программы на СИ начинаются с этой директивы.
Директива #define применяется для замены часто использующихся констант, ключевых слов, операторов или выражений определенными идентификаторами. Идентификаторы, которые заменяют текстовые или числовые константы, называются именованными константами. Идентификаторы, которые заменяют фрагменты программ, называют макроопределениями, при этом макроопределения могут иметь аргументы.
Директива #define может быть записана в двух синтаксических формах:
#define идентификатор текст
#define идентификатор (список параметров) текст
Данная директива заменяет все дальнейшие вхождения идентификатора на текст. Подобный процесс называется макроподстановкой. Текст может быть любым фрагментом программы на СИ, а также может и отсутствовать.
18. Применение директив
Рассмотрим пример:
#define WIDTH 80
#define LENGTH (WIDTH+10)
Данные директивы заменят в тексте программы каж дое слово WIDTH на число 80 и любое слово LENGTH н выражение (80+10) вместе с окружающими его скобками
Скобки, которые содержатся в макроопределении дают возможность избежать недоразумений, связан ных с порядком вычисления операций. К примеру, есл1 в скобках выражение t = LENGTH*7 будет преобразова но в выражение t = 80 + 10*7, а не в выражение t = (80 – + 10)*7, как это получается, если есть скобки, в резуль тате будем иметь 780, а не 630.
Во второй синтаксической форме в директиве #define присутствует список формальных параметров, которы может включать в себя один или несколько идентифика торов, которые разделены запятыми. Формальные пара метры в тексте макроопределения отмечают позиции на которые должны быть подставлены фактические ар гументы макровызова. Любой формальный параметр способен появиться в тексте макроопределения несколь ко раз.
При макровызове за идентификатором следует спи сок фактических аргументов, количество которых сле дует сделать совпадающим с количеством формальны параметров.
Пример:
#define MAX(x,y) ((x) > (y))?(x): (y)
Приведенная директива заменит фрагмент t = = MAX(i,s[i]) на выражение t = ((i) > (s[i])?(i): (s[i]).
Как и в ранее приведенном примере, круглые скобки, в которые заключены формальные параметры макроопределения, дают возможность избежать ошибок, связанных с неправильным порядком осуществления, если фактические аргументы являются выражениями.
Например, если есть скобки, фрагмент
t = MAX(i&j, s[i]||j) будет заменен выражением
t = ((i&j) > (s[i]||j)?(i&j): (s[i]||j); а если скобок нет – фрагментом
t = (i&j>s[i]||j)?i&j: s[i]||j;
где условное выражение вычисляется в другом порядке.
Директива #undef применяется для отмены действия директивы #define. Синтаксис данной директивы следующий: #undef идентификатор.
Директива отменяет операцию текущего определения #define для определенного идентификатора.
19. Рекурсия
Функция является рекурсивной, когда во время обработки появляется ее повторный вызов непосредственно или косвенно, через цепочку вызовов других функций.
Прямая (непосредственная) рекурсия – это вызов функции внутри тела этой функции.
int a()
{…..a()…..}
Косвенная рекурсия – это рекурсия, которая осуществляет рекурсивный вызов функции через цепочку вызова других функций. Все функции, которые входят в цепочку, тоже являются рекурсивными. Рассмотрим пример: