Функции и структура программ☛Язык программирования C ✎ |
Теперь, обратим наше внимание на воплощение getop - функции, добывающая следующий оператор или операнд. Это простая задача. Пропустить пробелы и табуляцию. Если следующий символ не напоминает число или десятичную точку, вернуть его. В противном случае, собрать цепочку чисел (которые могут включать десятичную точку) и вернуть NUMBER, которое будет сигнализировать, что число - успешно добыто.
# Include
int getch (void);
void ungetch (int);
/ * Getop: получить следующий знак или числовой операнд * /
int getop (char s [])
{
int i, c;
while ((s [0] = c = getch ()) == '' | | c == '\ t')
;
s [1] = '\ 0';
if (! isdigit (c) & & c! = '.')
return c; / * не является числом * /
i = 0;
if (isdigit (c)) / * сохранить часть, которая является целым * /
while (isdigit (s [+ + i] = c = getch ()))
;
if (c == '') / * сохранить дробную часть * /
while (isdigit (s [+ + i] = c = getch ()))
;
s [i] = '\ 0';
if (c! = EOF)
ungetch (c);
return NUMBER;
}
Что такое getch и ungetch? Часто бывают случаи, когда программа не может определить, что она прочитала достаточно ввода, пока не прочитано много. Один из примеров - когда программа собирает знаки, составляющие число, и пока не встретится первый не-цифровой знак, число не считаться полным. Но тогда программой прочтено один лишний знак, к которому она не была готова.
Такую проблему можно было бы решить, будь возможной отмене прочитанного нежелательного знака. Тогда, каждый раз, как программа прочтет на один символ того, она сможет отбросить его назад на ввод, так остальные кода вести себя так, будто этого знака никогда прочтено не было. К счастью, отмена получения знака довольно легко имитировать путем написания пары кооперирующих функций. Поэтому, getch подает следующий знак ввода на рассмотрение, тогда как ungetch возвращает его обратно перед тем, как прочитать новый ввод.
Как они сотрудничают вместе - не сложно. Функция ungetch помещает вытолкнут знак в совместно используемый буфер - символьный массив. Функция getch читает из буфера, если там что-то есть, и вызывает getchar, если буфер пуст. Должно существовать также индексная переменная, которая бы регистрировала положение текущего знака в буфере.
Поскольку, для getch и ungetch, буфер и индекс - общие, и должны сохранять свои значения между вызовами, то они должны были внешними для обеих функций. Мы можем написать getch и ungetch с внешними зминннимы, как:
# Define BUFSIZE 100
char buf [BUFSIZE]; / * буфер для ungetch * /
int bufp = 0; / * следующая свободная позиция в buf * /
int getch (void) / * получить (возможно вытолкнут) знак * /
{
return (bufp> 0)? buf [- bufp]: getchar ();
}
void ungetch (int c) / * вытолкнуть знания обратно в ввод * /
{
if (bufp> = BUFSIZE)
printf ("ungetch: too many characters \ n");
else
buf [bufp + +] = c;
}
Стандартная библиотека включает функцию ungetch, позволяющая вытолкнуть один знак: мы рассмотрим ее в далее. Мы же использовали массив для вытолкнуть знаков, вместо одного единственного, чтобы продемонстрировать более общий подход.
Правила области действия
Функции и внешние переменные, которые составляют программу на C, не обязательно должны быть компилируемых одновременно; исходный текст программы может храниться в нескольких файлах и предварительно компилируемые функции могут быть загруженными из библиотек. Среди вопросов, которые нас могут заинтересовать, является
Как написать объявление, поэтому переменные объявлены должным образом во время компиляции?
Как объявление огранизованной, поэтому все части будут соединены как следует при загрузке программы?
Как объявление организованно, поэтому существовать лишь одна копия?
Как начать внешние переменные?
Давайте обсудим эти вопросы путем реогранизации нашей программы-калькулятора в несколько файлов. С практической точки зрения, калькулятор, это слишком маленькая программа, чтобы разбивать ее, но она служит хорошим примером вопросов, викикають в больших программах.
Областью действия названия является та часть программы, в которой это название можно использовать. При использовании автоматических переменных, объявленных в начале функции, областью действия появляться функция, в которой это название было объявлено. Локальные переменные с тем же названием, но в разных функциях не имеют никаких остосункив друг с другом. То же действительно и для параметров функций, которые в действительности также являются локальными переменными.
Область действия внешней переменной или функции продолжается, начиная с момента их объявили и до конца собран файла. Например, если main, sp, val, push и pop обозначено в одном файле, в последовательности, указанной ниже:
main () {... }
int sp = 0;
double val [MAXVAL];
void push (double f) {... }
double pop (void) {... }
тогда переменные sp и val могут быть использованы в push и pop просто путем вызова их названия; дополнительные объявления излишни. Но эти названия не видит main, так же она не видит pop и push.
С другой стороны, если обратиться к внешней переменной до того, как ее обозначено или если его обозначено в отличном файле от того, где она используется, тогда обязательно оператор extern.
Важно различать объявления внешних переменных и их определения. Объявление заявляет о свойствах переменной (главным образом ее тип); определение также выделяет пространство для хранения. Если строки
int sp;
double val [MAXVAL];
появятся внешне любой функции, они определяют внешние переменную sp и val, выделят место для их хранения, также служат объявлением для остальных данного исходного файла. Кроме того, строки
extern int sp;
extern double val [];
объявляют для остальных исходного файла, sp является int и val является массивом типа double (чей размер определен где в другом месте), но они не создают самых переменных или выделяют место для их хранения. Каждый файл, входящий в программу, должен содержать только одно определение внешних переменных; другие файлы могут содержать объявления из extern, чтобы добраться до них. (Возможны также объявление extern в самом файле, содержащем эти определенные переменные.) Размеры массивов должны быть указаны с определениями, но не обязательны с extern-объявлениями.
Инициализация внешней переменной происходит только при условии, что существует ее определение.
Хотя это не характерно для такой программы, но функции push и pop могли бы быть визначинимы в одном файле, тогда как переменные val и sp определен и инициирован в другом. В таком случае, такие определения и объявления нужно бы было связать вместе:
в первом файле:
extern int sp;
extern double val [];
void push (double f) {... }
double pop (void) {... }
во втором файле:
int sp = 0;
double val [MAXVAL];
Поскольку объявление extern в первом файле находятся впереди и снаружи определений функций, они касаются всех функций. Одного объявления хватит для целого первой дорожке. Такая же организация была бы нужна, если бы sp и val следовали за своим использованием в том же файле.
Файлы заголовка
Давайте рассмотрим теперь разделение программы-калькулятора на несколько исходных файлов, будто каждая из ее составляющих была значительно больше. Функция main оказалась бы в одном файле, который мы назвали бы main.c; push, pop и их переменные размещены бы во втором, stack.c; getop в третьем - getop.c. Мы разделили их друг от друга, поскольку они составляли бы отдельно компилируемые библиотеку в настоящий программе.
Существует еще одна вещь, о которой стоит позаботиться - это определение и объявление, совместно используемые этими файлами. Насколько это возможно, мы бы хотели сосредоточить эти вещи в одном месте, поэтому существовать только одна копия, которую будет прочитано и которую следует поддерживать в рабочем состоянии при развитии программы. Соответственно, мы поместим этот общий материал в файле заголовка calc.h, который будет включен в случае необходимости.
Надо найти компромисс между желанием, чтобы каждый файл имел доступ только к информации, необходимой для осуществления своей задачи и практически действительностью того, что трудно поддерживать в рабочем состоянии много файлов заголовка. В случае программ умеренного размера, наверное, лучше иметь одих файл заголовка, содержащего общие данные для других частей программы; именно это решение мы продемонстрировали здесь. Для больших программ, может потребоваться больше увеличившейся организации и большему количеству файлов заголовка.
Статические переменные
Переменные sp и val со stack.c а также buff и bufp с getch.c существуют для частного использования в функциях соответствующих исходных файлов и не предназначены для доступа чем другим. Объявление static внешней переменной или функции ограничивает зону действия этого объекта остальными исходного файла, который будет собран. Внешний static, таким образом, дает возможность скрыть такие названия как buf и bufp в комбинации с getch-ungetch, которые в свою очередь, тоже должны быть внешними для совместного их использования, но чьи подробности не должны быть видимыми пользователям getch и ungetch. Статический тип хранения данных указывается путем добавления слова static перед обычным объявлением. Если две функции и две переменные собран в одном файле, как то
static char buf [BUFSIZE]; / * буфер для ungetch * /
static int bufp = 0; / * последующее свободное положение в buf * /
int getch (void) {... }
void ungetch (int c) {... }
тогда никакая другая функция не в состоянии будет обратиться в buf и bufp и эти названия не створюватимусть конфликта с такими же названиями в других файлах той же программы. Аналогично, переменные, используемые функциями push i pop для манипулирования стеком, можно скрыть, если объявить sp и val как static.
Внешние статические переменные часто употребляются с переменными, но их можно применять также к функциям. Привычно, имена функций являются глобальными, т.е. их видно из любой части программы. Однако, если функция объявлена как static, ее название станет невидимой вне файла, в котором она была объявлена.
Определение static применяется также в отношении внутренних переменных. Внутренние статические переменные появляться локальными для данной функции, так же как автоматические переменные, но в отличие от последних, они будут оставаться действительными, сохраняя свое значение, вместо появления и исчезновения каждый раз как функцию вызвано. Это означает, что внутренние статические переменные предоставляют личное, постоянное сохранение данных в пределах одной функции.
Другие материалы по теме:
- Внешние переменные и область действия- Функции и структура программ
- Язык программирования c в игровой индустрии: движки и производительность
- Введение в c++
- Типы, операторы и выражения
