KnigaRead.com/
KnigaRead.com » Компьютеры и Интернет » Программирование » Брайан Керниган - Язык программирования Си. Издание 3-е, исправленное

Брайан Керниган - Язык программирования Си. Издание 3-е, исправленное

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Брайан Керниган, "Язык программирования Си. Издание 3-е, исправленное" бесплатно, без регистрации.
Перейти на страницу:

В файле 1:


extern int sp;

extern double val[];


void push(double f) {…}

double pop(void) {…}


В файле2:


int sp = 0;

double val[MAXVAL];

Поскольку объявления extern находятся в начале файла1 и вне определений функций, их действие распространяется на все функции, причем одного набора объявлений достаточно для всего файла1. Та же организация extern-объявлений необходима и в случае, когда программа состоит из одного файла, но определения sp и val расположены после их использования.

4.5 Заголовочные файлы

Теперь представим себе, что компоненты программы-калькулятора имеют существенно большие размеры, и зададимся вопросом, как в этом случае распределить их по нескольким файлам. Программу main поместим в файл, который мы назовем main.с; push, pop и их переменные расположим во втором файле, stack.с; a getop - в третьем, getop.c. Наконец, getch и ungetch разместим в четвертом файле getch.с; мы отделили их от остальных функций, поскольку в реальной программе они будут получены из заранее скомпилированной библиотеки.

Существует еще один момент, о котором следует предупредить читателя, - определения и объявления совместно используются несколькими файлами. Мы бы хотели, насколько это возможно, централизовать эти объявления и определения так, чтобы для них существовала только одна копия. Тогда программу в процессе ее развития будет легче и исправлять, и поддерживать в нужном состоянии. Для этого общую информацию расположим в заголовочном файле calc.h, который будем по мере необходимости включать в другие файлы. (Строка #include описывается в параграфе 4.11) В результате получим программу, файловая структура которой показана ниже:

main.с:

#include ‹stdio.h›

#include ‹stdlib.h›

#include "calc.h"

#define MAXOP 100

main() {

 …

}


calc.h:

#define NUMBER '0'

void push(double);

double pop(void);

int getop(char[]);

int getch(void);

void ungetch(int);


getop.c:

#include ‹stdio.h›

#include ‹ctype.h›

#include "calc.h"

getop (){

 …

}


getch.c:

#include ‹stdio.h›

#define BUFSIZE 100

char buf[BUFSIZE];

intbufp = 0;

int getch(void) {

 …

}

void ungetch(int) {

 …

}


stack.с:

#include ‹stdio.h›

#include "calc.h"

#define MAXVAL 100

int sp = 0;

double val[MAXVAL];

void push(double) {

 …

}

double pop(void) {

 …

}

Неизбежен компромисс между стремлением, чтобы каждый файл владел только той информацией, которая ему необходима для работы, и тем, что на практике иметь дело с большим количеством заголовочных файлов довольно трудно. Для программ, не превышающих некоторого среднего размера, вероятно, лучше всего иметь один заголовочный файл, в котором собраны вместе все объекты, каждый из которых используется в двух различных файлах; так мы здесь и поступили. Для программ больших размеров потребуется более сложная организация с большим числом заголовочных файлов.

4.6 Статические переменные

Переменные sp и val в файле stack.с, а также buf и bufp в getch.с находятся в личном пользовании функций этих файлов, и нет смысла открывать к ним доступ кому-либо еще. Указание static, примененное к внешней переменной или функции, ограничивает область видимости соответствующего объекта концом файла. Это способ скрыть имена. Так, переменные buf и bufp должны быть внешними, поскольку их совместно используют функции getch и ungetch, но их следует сделать невидимыми для "пользователей" функций getch и ungetch.

Статическая память специфицируется словом static, которое помещается перед обычным объявлением. Если рассматриваемые нами две функции и две переменные компилируются в одном файле, как в показанном ниже примере:

static char buf[BUFSIZE]; /* буфер для ungetch */

static int bufp = 0; /* след. свободная позиция в buf */


int getch(void) {…}


void ungetch(int с) {…}

то никакая другая программа не будет иметь доступ ни к buf, ни к bufp, и этими именами можно свободно пользоваться в других файлах для совсем иных целей. Точно так же, помещая указание static перед объявлениями переменных sp и val, с которыми работают только push и pop, мы можем скрыть их от остальных функций.

Указание static чаще всего используется для переменных, но с равным успехом его можно применять и к функциям. Обычно имена функций глобальны и видимы из любого места программы. Если же функция помечена словом static, то ее имя становится невидимым вне файла, в котором она определена.

Объявление static можно использовать и для внутренних переменных. Как и автоматические переменные, внутренние статические переменные локальны в функциях, но в отличие от автоматических, они не возникают только на период работы функции, а существуют постоянно. Это значит, что внутренние статические переменные обеспечивают постоянное сохранение данных внутри функции.

Упражнение 4.11. Модифицируйте функцию getop так, чтобы отпала необходимость в функции ungetch. Подсказка: используйте внутреннюю статическую переменную.

4.7 Регистровые переменные

Объявление register сообщает компилятору, что данная переменная будет интенсивно использоваться. Идея состоит в том, чтобы переменные, объявленные register, разместить на регистрах машины, благодаря чему программа, возможно, станет более короткой и быстрой. Однако компилятор имеет право проигнорировать это указание. Объявление register выглядит следующим образом:

register int х;

register char с;

и т. д. Объявление register может применяться только к автоматическим переменным и к формальным параметрам функции. Для последних это выглядит так:

f(register unsigned m, register long n)

{

 register int i;

 …

}

На практике существуют ограничения на регистровые переменные, что связано с возможностями аппаратуры. Располагаться в регистрах может лишь небольшое число переменных каждой функции, причем только определенных типов. Избыточные объявления register ни на что не влияют, так как игнорируются в отношении переменных, которым не хватило регистров или которые нельзя разместить на регистре. Кроме того, применительно к регистровой переменной независимо от того, выделен на самом деле для нее регистр или нет, не определено понятие адреса (см. главу 5). Конкретные ограничения на количество и типы регистровых переменных зависят от машины.

4.8 Блочная структура

Поскольку функции в Си нельзя определять внутри других функций, он не является языком, допускающим блочную структуру программы в том смысле, как это допускается в Паскале и подобных ему языках. Но переменные внутри функций можно определять в блочно-структурной манере. Объявления переменных (вместе с инициализацией) разрешено помещать не только в начале функции, но и после любой левой фигурной скобки, открывающей составную инструкцию. Переменная, описанная таким способом, "затеняет" переменные с тем же именем, расположенные в объемлющих блоках, и существует вплоть до соответствующей правой фигурной скобки. Например, в

if (n › 0) {

 int i; /* описание новой переменной i */

 for (i = 0; i ‹ n; i++)

  …

}

областью видимости переменной i является ветвь if, выполняемая при n›0; и эта переменная никакого отношения к любым i, расположенным вне данного блока, не имеет. Автоматические переменные, объявленные и инициализируемые в блоке, инициализируются каждый раз при входе в блок. Переменные static инициализируются только один раз при первом входе в блок.

Автоматические переменные и формальные параметры также "затеняют" внешние переменные и функции с теми же именами. Например, в

int x;

int y;

f(double х)

{

 double y;

}

x внутри функции f рассматривается как параметр типа double, в то время как вне f это внешняя переменная типа int. То же самое можно сказать и о переменной y.

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*