Главная · Поиск книг · Поступления книг · 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 ... 11 12 13 14 15 16 17  18 19 20 21 22 23 24 ... 39
    Упражнение 5-1
    ---------------
    Напишите функцию GETFLOAT, аналог  GETINT  для  чисел  с
плавающей точкой. Какой тип должна возвращать GETFLOAT в ка-
честве значения функции?

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

    В языке "C" существует сильная взаимосвязь между указа-
телями и массивами , настолько сильная, что указатели и мас-
сивы действительно следует рассматривать одновременно. Любую
операцию, которую можно выполнить с помощью индексов масси-
ва, можно сделать и с помощью указателей. вариант с указате-
лями обычно оказывается более быстрым, но и несколько более
трудным для непосредственного понимания, по крайней мере для
начинающего. описание

INT A[10]

определяет  массив  размера  10, т.е. Набор из 10 последова-
тельных объектов, называемых A[0], A[1], ...,  A[9].  Запись
A[I] соответствует элементу массива через I позиций от нача-
ла. Если PA - указатель целого, описанный как

   INT *PA

то присваивание

   PA = &A[0]

приводит к тому, что PA указывает на нулевой элемент массива
A; это означает, что PA содержит адрес элемента A[0]. Теперь
присваивание

   X = *PA

будет копировать содержимое A[0] в X.
    Если PA указывает на некоторый определенный элемент мас-
сива  A,  то по определению PA+1 указывает на следующий эле-
мент, и вообще PA-I указывает на элемент, стоящий на I пози-
ций до элемента, указываемого PA, а PA+I на элемент, стоящий
на I позиций после. Таким  образом,  если  PA  указывает  на
A[0], то

   *(PA+1)

ссылается на содержимое A[1], PA+I - адрес A[I], а *(PA+I) -
содержимое A[I].
    Эти замечания справедливы независимо от типа переменных
в массиве A. Суть определения "добавления 1 к указателю", а
также его распространения на всю арифметику указателей, сос-
тоит в том, что приращение масштабируется размером памяти,
занимаемой объектом, на который указывает указатель. Таким
образом, I в PA+I перед прибавлением умножается на размер
объектов, на которые указывает PA.
    Очевидно существует очень тесное соответствие между ин-
дексацией и арифметикой указателей. в действительности ком-
пилятор преобразует ссылку на массив в указатель на начало
массива. В результате этого имя массива является указатель-
ным выражением. Отсюда вытекает несколько весьма полезных
следствий. Так как имя массива является синонимом местополо-
жения его нулевого элемента, то присваивание PA=&A[0] можно
записать как

              PA = A

    Еще более удивительным, по крайней мере на первый взг-
ляд, кажется тот факт, что ссылку на A[I] можно записать в
виде *(A+I). При анализировании выражения A[I] в языке "C"
оно немедленно преобразуется к виду *(A+I); эти две формы
совершенно эквивалентны. Если применить операцию & к обеим
частям такого соотношения эквивалентности, то мы получим,
что &A[I] и A+I тоже идентичны: A+I - адрес I-го элемента от
начала A. С другой стороны, если PA является указателем, то
в выражениях его можно использовать с индексом: PA[I] иден-
тично *(PA+I). Короче, любое выражение, включающее массивы и
индексы, может быть записано через указатели и смещения и
наоборот, причем даже в одном и том же утверждении.

    Имеется одно различие между именем массива и указателем,
которое необходимо иметь в виду. указатель является перемен-
ной, так что операции PA=A и PA++ имеют смысл. Но имя масси-
ва является константой, а не переменной: конструкции типа
A=PA или A++,или P=&A будут незаконными.
    Когда имя массива передается функции, то на самом деле
ей передается местоположение начала этого массива. Внутри
вызванной функции такой аргумент является точно такой же пе-
ременной, как и любая другая, так что имя массива в качестве
аргумента действительно является указателем, т.е. Перемен-
ной, содержащей адрес. мы можем использовать это обстоятель-
ство для написания нового варианта функции STRLEN, вычисляю-
щей длину строки.

 STRLEN(S)       /* RETURN LENGTH OF STRING S */
 CHAR *S;
 {
    INT N;

    FOR (N = 0; *S != '\0'; S++)
            N++;
    RETURN(N);
 }

    Операция увеличения S совершенно законна, поскольку эта
переменная является указателем; S++ никак не влияет на сим-
вольную строку в обратившейся к STRLEN функции, а только
увеличивает локальную для функции STRLEN копию адреса. Опи-
сания формальных параметров в определении функции в виде

CHAR S[];
CHAR *S;

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

F(&A[2])

как и

F(A+2)

передают  функции F адрес элемента A[2], потому что и &A[2],
и A+2 являются указательными  выражениями,  ссылающимися  на
третий элемент A. внутри функции F описания аргументов могут
присутствовать в виде:

F(ARR)
INT ARR[];
{
   ...
}

или

F(ARR)
INT *ARR;
{
   ...
}

Что касается функции F, то тот факт, что ее аргумент в дейс-
твительности ссылается к части большего массива,не имеет для
нее никаких последствий.

     5.4. Адресная арифметика

    Если P является указателем, то каков бы ни был сорт
объекта, на который он указывает, операция P++ увеличивает P
так, что он указывает на следующий элемент набора этих
объектов, а операция P +=I увеличивает P так, чтобы он ука-
зывал на элемент, отстоящий на I элементов от текущего эле-
мента.эти и аналогичные конструкции представляют собой самые
простые и самые распространенные формы арифметики указателей
или адресной арифметики.
    Язык "C" последователен и постоянен в своем подходе к
адресной арифметике; объединение в одно целое указателей,
массивов и адресной арифметики является одной из наиболее
сильных сторон языка. Давайте проиллюстрируем некоторые из
соответствующих возможностей языка на примере элементарной
(но полезной, несмотря на свою простоту) программы распреде-
ления памяти. Имеются две функции: функция ALLOC(N) возвра-
щает в качестве своего значения указатель P, который указы-
вает на первую из N последовательных символьных позиций, ко-
торые могут быть использованы вызывающей функцию ALLOC прог-
раммой для хранения символов; функция FREE(P) освобождает
приобретенную таким образом память, так что ее в дальнейшем
можно снова использовать. программа является "элементарной",
потому что обращения к FREE должны производиться в порядке,
обратном тому, в котором производились обращения к ALLOC.
Таким образом, управляемая функциями ALLOC и FREE память яв-
ляется стеком или списком, в котором последний вводимый эле-
мент извлекается первым. Стандартная библиотека языка "C"
содержит аналогичные функции, не имеющие таких ограничений,
и, кроме того, в главе 8 мы приведем улучшенные варианты.
Между тем, однако, для многих приложений нужна только триви-
альная функция ALLOC для распределения небольших участков
памяти неизвестных заранее размеров в непредсказуемые момен-
ты времени.
    Простейшая реализация состоит в том, чтобы функция раз-
давала отрезки большого символьного массива, которому мы
присвоили имя ALLOCBUF. Этот массив является собственностью
функций ALLOC и FREE. Так как они работают с указателями, а
не с индексами массива, никакой другой функции не нужно
знать имя этого массива. Он может быть описан как внешний
статический, т.е. Он будет локальным по отношению к исходно-
му файлу, содержащему ALLOC и FREE, и невидимым за его пре-
делами. При практической реализации этот массив может даже
не иметь имени; вместо этого он может быть получен в резуль-
тате запроса к операционной системе на указатель некоторого
неименованного блока памяти.
    Другой необходимой информацией является то, какая часть
массива ALLOCBUF уже использована. Мы пользуемся указателем
первого свободного элемента, названным ALLOCP. Когда к функ-
ции ALLOC обращаются за выделением N символов, то она прове-
ряет, достаточно ли осталось для этого места в ALLOCBUF. Ес-
ли достаточно, то ALLOC возвращает текущее значение ALLOCP
(т.е. Начало свободного блока), затем увеличивает его на N,
с тем чтобы он указывал на следующую свободную область. Фун-
кция FREE(P) просто полагает ALLOCP равным P при условии,
что P указывает на позицию внутри ALLOCBUF.

DEFINE NULL 0  /* POINTER VALUE FOR ERROR REPORT */
DEFINE ALLOCSIZE 1000  /* SIZE OF AVAILABLE SPACE */

TATIC CHAR ALLOCBUF[ALLOCSIZE];/* STORAGE FOR ALLOC */
TATIC CHAR *ALLOCP = ALLOCBUF; /* NEXT FREE POSITION */

HAR *ALLOC(N)  /* RETURN POINTER TO N CHARACTERS */
INT N;
(
 IF (ALLOCP + N <= ALLOCBUF + ALLOCSIZE) {
   ALLOCP += N;
   RETURN(ALLOCP - N); /* OLD P */
} ELSE         /* NOT ENOUGH ROOM */
   RETURN(NULL);
)

REE(P)    /* FREE STORAGE POINTED BY P */
HAR *P;
(
 IF (P >= ALLOCBUF && P < ALLOCBUF + ALLOCSIZE)
    ALLOCP = P;
)

    Дадим некоторые пояснения. Вообще говоря, указатель мо-
жет быть инициализирован точно так же, как и любая другая
переменная, хотя обычно единственными осмысленными значения-
ми являются NULL (это обсуждается ниже) или выражение, вклю-
чающее адреса ранее определенных данных соответствующего ти-
па. Описание

 STATIC CHAR *ALLOCP = ALLOCBUF;

определяет ALLOCP как указатель на символы и инициализирует
его так, чтобы он указывал на ALLOCBUF, т.е. На первую сво-
бодную позицию при начале работы программы. Так как имя мас-
сива является адресом его нулевого элемента, то это можно
было бы записать в виде

    STATIC CHAR *ALLOCP = &ALLOCBUF[0];

используйте  ту запись, которая вам кажется более естествен-
ной. С помощью проверки

  IF (ALLOCP + N <= ALLOCBUF + ALLOCSIZE)

выясняется, осталось ли достаточно места, чтобы удовлетво-
рить запрос на N символов. Если достаточно, то новое значе-
ние ALLOCP не будет указывать дальше, чем на последнюю пози-
цию ALLOCBUF. Если запрос может быть удовлетворен, то ALLOC
возвращает обычный указатель (обратите внимание на описание
самой функции). Если же нет, то ALLOC должна вернуть некото-
рый признак, говорящий о том, что больше места не осталось.
В языке "C" гарантируется, что ни один правильный указатель
данных не может иметь значение нуль, так что возвращение ну-
ля может служить в качестве сигнала о ненормальном событии,
в данном случае об отсутствии места. Мы, однако, вместо нуля
пишем NULL, с тем чтобы более ясно показать, что это специ-
альное значение указателя. Вообще говоря, целые не могут ос-
мысленно присваиваться указателям, а нуль - это особый слу-
чай.
    Проверки вида

 IF (ALLOCP + N <= ALLOCBUF + ALOOCSIZE)
и
 IF (P >= ALLOCBUF && P < ALLOCBUF + ALLOCSIZE)

демонстрируют несколько важных аспектов арифметики указате-
лей. Во-первых , при определенных условиях указатели можно
сравнивать. Если P и Q указывают на элементы одного и того
же массива, то такие отношения, как <, >= и т.д., работают
надлежащим образом. Например,

 P < Q

истинно, если P указывает на более ранний элемент массива,
чем Q. Отношения == и != тоже работают. Любой указатель мож-
но осмысленным образом сравнить на равенство или неравенство
с NULL. Но ни за что нельзя ручаться, если вы используете
сравнения при работе с указателями, указывающими на разные
массивы. Если вам повезет, то на всех машинах вы получите
очевидную бессмыслицу. Если же нет, то ваша программа будет
правильно работать на одной машине и давать непостижимые ре-
зультаты на другой.
     Во-вторых, как мы уже видели, указатель и  целое  можно
складывать и вычитать. Конструкция

 P + N

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

Реклама