Реферат: Язык С
INT *PX;
·
99 -
с описанием для 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
·
100 -
полагает 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);
·
101 -
так как операция & выдает адрес
переменной, то &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
полагает, что она работа-
ет именно с указателем.
·
102 -
Сама 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©;
RETURN©;
}
Выражение *PN используется всюду в
GETINT как обычная пере-
менная типа INT. Мы также
использовали функции GETCH и
UNGETCH (описанные в главе 4) , так
что один лишний символ,
кототрый приходится считывать,
может быть помещен обратно во
ввод.
Упражнение 5-1.
Напишите функцию GETFLOAT,
аналог GETINT для чисел с
плавающей точкой. Какой тип
должна возвращать GETFLOAT в ка-
честве значения функции?
5.3. Указатели и массивы
В языке “C” существует
сильная взаимосвязь между указа-
телями и массивами , настолько
сильная, что указатели и мас-
сивы действительно следует
рассматривать одновременно. Любую
операцию, которую можно выполнить с
помощью индексов масси-
ва, можно сделать и с помощью
указателей. вариант с указате-
лями обычно оказывается более
быстрым, но и несколько более
трудным для непосредственного
понимания, по крайней мере для
начинающего. описание
INT A[10]
определяет массив размера 10,
т.е. Набор из 10 последова-
тельных объектов, называемых A[0],
A[1], ..., A[9]. Запись
A[I] соответствует элементу массива
через I позиций от нача-
ла. Если PA - указатель целого,
описанный как
·
103 -
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). Короче, любое
выражение, включающее массивы и
индексы, может быть записано через
указатели и смещения и
наоборот, причем даже в одном и том
же утверждении.
·
104 -
Имеется одно различие между
именем массива и указателем,
которое необходимо иметь в виду.
указатель является перемен-
ной, так что операции 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)
·
105 -
передают функции 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”
содержит аналогичные функции, не
имеющие таких ограничений,
·
106 -
и, кроме того, в главе 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;
)
·
107 -
Дадим некоторые пояснения.
Вообще говоря, указатель мо-
жет быть инициализирован точно так
же, как и любая другая
переменная, хотя обычно
единственными осмысленными значения-
ми являются NULL (это обсуждается
ниже) или выражение, вклю-
чающее адреса ранее определенных
данных соответствующего ти-
па. Описание
STATIC CHAR *ALLOCP = ALLOCBUF;
определяет ALLOCP как указатель на
символы и инициализирует
его так, чтобы он указывал на
ALLOCBUF, т.е. На первую сво-
бодную позицию при начале работы
программы. Так как имя мас-
сива является адресом его нулевого
элемента, то это можно
было бы записать в виде
STATIC CHAR *ALLOCP =
&ALLOCBUF[0];
используйте ту запись, которая вам
кажется более естествен-
ной. С помощью проверки
IF (ALLOCP + N <= ALLOCBUF
+ ALLOCSIZE)
выясняется, осталось ли достаточно
места, чтобы удовлетво-
рить запрос на N символов. Если
достаточно, то новое значе-
Страницы: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28
|