Главная · Поиск книг · Поступления книг · Top 40 · Форумы · Ссылки · Читатели

Настройка текста
Перенос строк


    Прохождения игр    
Demon's Souls |#13| Storm King
Demon's Souls |#12| Old Monk & Old Hero
Demon's Souls |#11| Мaneater part 2
Demon's Souls |#10| Мaneater (part 1)

Другие игры...


liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня
Rambler's Top100
Образование - Керниган, Ричи Весь текст 454.51 Kb

Язык Си

Предыдущая страница Следующая страница
1 ... 10 11 12 13 14 15 16  17 18 19 20 21 22 23 ... 39
возможность включать во время компиляции содержимое других
файлов.

     4.11.1. Включение файлов

    Для облегчения работы с наборами конструкций #DEFINE и
описаний (среди прочих средств) в языке "с" предусмотрена
возможность включения файлов. Любая строка вида

 #INCLUDE "FILENAME"

заменяется содержимым файла с именем FILENAME. (Кавычки обя-
зательны). Часто одна или две строки такого вида появляются
в начале каждого исходного файла, для того чтобы включить
общие конструкции #DEFINE и описания EXTERN для глобальных
переменных. Допускается вложенность конструкций #INCLUDE.
    Конструкция #INCLUDE является предпочтительным способом
связи описаний в больших программах. Этот способ гарантиру-
ет, что все исходные файлы будут снабжены одинаковыми опре-
делениями и описаниями переменных, и, следовательно, исклю-
чает особенно неприятный сорт ошибок. Естественно, когда ка-
кой-TO включаемый файл изменяется, все зависящие от него
файлы должны быть перекомпилированы.

     4.11.2. Макроподстановка

    Определение вида

 #DEFINE TES     1

приводит к макроподстановке самого простого вида - замене
имени на строку символов. Имена в #DEFINE имеют ту же самую
форму, что и идентификаторы в "с"; заменяющий текст совер-
шенно произволен. Нормально заменяющим текстом является ос-
тальная часть строки; длинное определение можно продолжить,
поместив \ в конец продолжаемой строки. "Область действия"
имени, определенного в #DEFINE, простирается от точки опре-
деления до конца исходного файла. имена могут быть переопре-
делены, и определения могут использовать определения, сде-
ланные ранее. Внутри заключенных в кавычки строк подстановки
не производятся, так что если, например, YES - определенное
имя, то в PRINTF("YES") не будет сделано никакой подстанов-
ки.
    Так как реализация #DEFINE является частью работы
маKропредпроцессора, а не собственно компилятора, имеется
очень мало грамматических ограничений на то, что может быть
определено. Так, например, любители алгола могут объявить

#DEFINE THEN
#DEFINE BEGIN {
#DEFINE END   ;}

и затем написать

IF (I > 0) THEN
   BEGIN
           A = 1;
           B = 2
   END

    Имеется также возможность определения макроса с аргумен-
тами, так что заменяющий текст будет зависеть от вида обра-
щения к макросу. Определим, например, макрос с именем MAX
следующим образом:

#DEFINE MAX(A, B)  ((A) > (B) ? (A) : (B))

когда строка

X = MAX(P+Q, R+S);

будет заменена строкой

X = ((P+Q) > (R+S) ? (P+Q) : (R+S));

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

    Конечно, если вы тщательно рассмотрите приведенное выше
расширение MAX, вы заметите определенные недостатки. Выраже-
ния вычисляются дважды; это плохо, если они влекут за собой
побочные эффекты, вызванные, например, обращениями к функци-
ям или использованием операций увеличения. Нужно позаботить-
ся о правильном использовании круглых скобок, чтобы гаранти-
ровать сохранение требуемого порядка вычислений. (Рассмотри-
те макрос

  #DEFINE SQUARE(X)  X * X

при обращении к ней, как SQUARE(Z+1)). Здесь возникают даже
некоторые чисто лексические проблемы: между именем макро и
левой круглой скобкой, открывающей список ее аргументов, не
должно быть никаких пробелов.
    Тем не менее аппарат макросов является весьма ценным.
Один практический пример дает описываемая в главе 7 стандар-
тная библиотека ввода-вывода, в которой GETCHAR и PUTCHAR
определены как макросы (очевидно PUTCHAR должна иметь аргу-
мент), что позволяет избежать затрат на обращение к функции
при обработке каждого символа.
    Другие возможности макропроцессора описаны в приложении
А.

    Упражнение 4-9
    ---------------
    Определите макрос SWAP(X, Y), который обменивает значе-
ниями два своих аргумента типа INT. (В этом случае поможет
блочная структура).

      * 5. Указатели и массивы *

    Указатель - это переменная, содержащая адрес другой пе-
ременной. указатели очень широко используются в языке "C".
Это происходит отчасти потому, что иногда они дают единст-
венную возможность выразить нужное действие, а отчасти пото-
му, что они обычно ведут к более компактным и эффективным
программам, чем те, которые могут быть получены другими спо-
собами.
    Указатели обычно смешивают в одну кучу с операторами
GOTO, характеризуя их как чудесный способ написания прог-
рамм, которые невозможно понять. Это безусловно спрAведливо,
если указатели используются беззаботно; очень просто ввести
указатели, которые указывают на что-то совершенно неожидан-
ное. Однако, при определенной дисциплине, использование ука-
зателей помогает достичь ясности и простоты. Именно этот ас-
пект мы попытаемся здесь проиллюстрировать.

     5.1. Указатели и адреса

    Так как указатель содержит адрес объекта, это дает воз-
можность "косвенного" доступа к этому объекту через указа-
тель. Предположим, что х - переменная, например, типа INT, а
рх - указатель, созданный неким еще не указанным способом.
Унарная операция & выдает адрес объекта, так что оператор

 рх = &х;

    присваивает адрес х переменной рх; говорят, что рх "ука-
зывает" на х. Операция & применима только к переменным и
элементам массива, конструкции вида &(х-1) и &3 являются не-
законными. Нельзя также получить адрес регистровой перемен-
ной.
    Унарная операция * рассматривает свой операнд как адрес
конечной цели и обращается по этому адресу, чтобы извлечь
содержимое. Следовательно, если Y тоже имеет тип INT, то

 Y = *рх;

присваивает Y содержимое того, на что указывает рх. Так пос-
ледовательность

 рх = &х;
 Y = *рх;

присваивает Y то же самое значение, что и оператор

Y = X;

Переменные, участвующие во всем этом необходимо описать:

INT X, Y;
INT *PX;

с описанием для X и Y мы уже  неодонократно  встречались.
Описание указателя

INT *PX;

является новым и должно рассматриваться как мнемоническое;
оно говорит, что комбинация *PX имеет тип INT. Это означает,
что если PX появляется в контексте *PX, то это эквивалентно
переменной типа INT. Фактически синтаксис описания перемен-
ной имитирует синтаксис выражений, в которых эта переменная
может появляться. Это замечание полезно во всех случаях,
связанных со сложными описаниями. Например,

DOUBLE ATOF(), *DP;

говорит, что ATOF() и *DP имеют в выражениях значения типа
DOUBLE.

     Вы должны также заметить, что из этого описания следу-
ет, что указатель может указывать только на определенный вид
объектов.
     Указатели могут входить в выражения. Например, если PX
указывает на целое X, то *PX может появляться в любом кон-
тексте, где может встретиться X. Так оператор

Y = *PX + 1

присваивает Y значение, на 1 большее значения X;

PRINTF("%D\N", *PX)

печатает текущее значение X;

D = SQRT((DOUBLE) *PX)

получает в D квадратный корень из X, причем до передачи фун-
кции SQRT значение X преобразуется к типу DOUBLE. (Смотри
главу 2).
    В выражениях вида

Y = *PX + 1

унарные операции * и & связаны со своим операндом более
крепко, чем арифметические операции, так что такое выражение
берет то значение, на которое указывает PX, прибавляет 1 и
присваивает результат переменной Y. Мы вскоре вернемся к то-
му, что может означать выражение

 Y = *(PX + 1)

    Ссылки на указатели могут появляться и в левой части
присваиваний. Если PX указывает на X, то

 *PX = 0

полагает X равным нулю, а

 *PX += 1

увеличивает его на единицу, как и выражение

 (*PX)++

Круглые скобки в последнем примере необходимы; если их опус-
тить, то поскольку унарные операции, подобные * и ++, выпол-
няются справа налево, это выражение увеличит PX, а не ту пе-
ременную, на которую он указывает.
    И наконец, так как указатели являются переменными, то с
ними можно обращаться, как и с остальными переменными. Если
PY - другой указатель на переменную типа INT, то

 PY = PX

копирует содержимое PX в PY, в результате чего PY указывает
на то же, что и PX.

     5.2. Указатели и аргументы функций

    Так как в "с" передача аргументов функциям осуществляет-
ся "по значению", вызванная процедура не имеет непосредст-
венной возможности изменить переменную из вызывающей прог-
раммы. Что же делать, если вам действительно надо изменить
аргумент? например, программа сортировки захотела бы поме-
нять два нарушающих порядок элемента с помощью функции с
именем SWAP. Для этого недостаточно написать

 SWAP(A, B);

определив функцию SWAP при этом следующим образом:

 SWAP(X, Y)      /* WRONG */
 INT X, Y;
 {
    INT TEMP;

    TEMP = X;
    X = Y;
    Y = TEMP;
 }

из-за  вызова  по  значению  SWAP не может воздействовать на
агументы A и B в вызывающей функции.
    К счастью, все же имеется возможность получить  желаемый
эффект.  Вызывающая  программа передает указатели подлежащих
изменению значений:

SWAP(&A, &B);
так как операция & выдает адрес переменной, то  &A  является
указателем на A. В самой SWAP аргументы описываются как ука-
затели и доступ к фактическим операндам осуществляется через
них.

SWAP(PX, PY)    /* INTERCHANGE *PX AND *PY */
INT *PX, *PY;
{
   INT TEMP;

   TEMP = *PX;
   *PX = *PY;
   *PY = TEMP;
}

    Указатели в качестве аргументов обычно используются в
функциях, которые должны возвращать более одного значения.
(Можно сказать, что SWAP вOзвращает два значения, новые зна-
чения ее аргументов). В качестве примера рассмотрим функцию
GETINT, которая осуществляет преобразование поступающих в
своболном формате данных, разделяя поток символов на целые
значения, по одному целому за одно обращение. Функция GETINT
должна возвращать либо найденное значение, либо признак кон-
ца файла, если входные данные полностью исчерпаны. Эти зна-
чения должны возвращаться как отдельные объекты, какое бы
значение ни использовалось для EOF, даже если это значение
вводимого целого.
    Одно из решений, основывающееся на описываемой в главе 7
функции ввода SCANF, состоит в том, чтобы при выходе на ко-
нец файла GETINT возвращала EOF в качестве значения функции;
любое другое возвращенное значение говорит о нахождении нор-
мального целого. Численное же значение найденного целого
возвращается через аргумент, который должен быть указателем
целого. Эта организация разделяет статус конца файла и чис-
ленные значения.
    Следующий цикл заполняет массив целыми с помощью обраще-
ний к функции GETINT:

INT N, V, ARRAY[SIZE];

FOR (N = 0; N < SIZE && GETINT(&V) != EOF; N++)
   ARRAY[N] = V;

В результате каждого обращения V становится равным следующе-
му целому значению, найденному во входных данных. Обратите
внимание, что в качестве аргумента GETINT необходимо указать
&V а не V. Использование просто V скорее всего приведет к
ошибке адресации, поскольку GETINT полагает, что она работа-
ет именно с указателем.

    Сама  GETINT  является очевидной модификацией написанной
нами ранее функции ATOI:

  GETINT(PN)    /* GET NEXT INTEGER FROM INPUT */
  INT *PN;
  {
    INT C,SIGN;

    WHILE ((C = GETCH()) == ' ' \!\! C == '\N'
    \!\! C == '\T'); /* SKIP WHITE SPACE */
    SIGN = 1;
    IF (C == '+' \!\! C == '-') { /* RECORD
        SIGN */
       SIGN = (C == '+') ? 1 : -1;
       C = GETCH();
    }
    FOR (*PN = 0; C >= '0' && C <= '9'; C = GETCH())
       *PN = 10 * *PN + C - '0';
    *PN *= SIGN;
    IF (C != EOF)
       UNGETCH(C);
    RETURN(C);
  }

Выражение *PN используется всюду в GETINT как обычная пере-
менная типа INT. Мы также использовали функции GETCH и
UNGETCH (описанные в главе 4) , так что один лишний символ,
кототрый приходится считывать, может быть помещен обратно во
ввод.

Предыдущая страница Следующая страница
1 ... 10 11 12 13 14 15 16  17 18 19 20 21 22 23 ... 39
Ваша оценка:
Комментарий:
  Подпись:
(Чтобы комментарии всегда подписывались Вашим именем, можете зарегистрироваться в Клубе читателей)
  Сайт:
 
Комментарии (1)

Реклама