Реферат: Язык С
ние ALLOCP не будет указывать
дальше, чем на последнюю пози-
цию ALLOCBUF. Если запрос может
быть удовлетворен, то ALLOC
возвращает обычный указатель
(обратите внимание на описание
самой функции). Если же нет, то
ALLOC должна вернуть некото-
рый признак, говорящий о том, что
больше места не осталось.
В языке “C” гарантируется, что ни
один правильный указатель
данных не может иметь значение
нуль, так что возвращение ну-
ля может служить в качестве сигнала
о ненормальном событии,
в данном случае об отсутствии
места. Мы, однако, вместо нуля
пишем NULL, с тем чтобы более ясно
показать, что это специ-
альное значение указателя. Вообще
говоря, целые не могут ос-
мысленно присваиваться указателям,
а нуль - это особый слу-
чай.
Проверки вида
IF (ALLOCP + N <= ALLOCBUF +
ALOOCSIZE)
и
IF (P >= ALLOCBUF && P
< ALLOCBUF + ALLOCSIZE)
демонстрируют несколько важных
аспектов арифметики указате-
лей. Во-первых , при определенных
условиях указатели можно
сравнивать. Если P и Q указывают на
элементы одного и того
же массива, то такие отношения, как
<, >= и т.д., работают
надлежащим образом. Например,
P < Q
·
108 -
истинно, если P указывает на более
ранний элемент массива,
чем Q. Отношения == и != тоже
работают. Любой указатель мож-
но осмысленным образом сравнить на
равенство или неравенство
с NULL. Но ни за что нельзя
ручаться, если вы используете
сравнения при работе с указателями,
указывающими на разные
массивы. Если вам повезет, то на
всех машинах вы получите
очевидную бессмыслицу. Если же нет,
то ваша программа будет
правильно работать на одной машине
и давать непостижимые ре-
зультаты на другой.
Во-вторых, как мы уже видели,
указатель и целое можно
складывать и вычитать.
Конструкция
P + N
подразумевает N-ый объект за тем,
на который P указывает в
настоящий момент. Это справедливо
независимо от того, на ка-
кой вид объектов P должен
указывать; компилятор сам масшта-
бирует N в соответствии с
определяемым из описания P разме-
ром объектов, указываемых с помощью
P. например, на PDP-11
масштабирующий множитель равен 1
для CHAR, 2 для INT и
SHORT, 4 для LONG и FLOAT и 8 для
DOUBLE.
Вычитание указателей тоже
возможно: если P и Q указывают
на элементы одного и того же
массива, то P-Q - количество
элементов между P и Q. Этот факт
можно использовать для на-
писания еще одного варианта функции
STRLEN:
STRLEN(S) /* RETURN LENGTH
OF STRING S */
CHAR *S;
{
CHAR *P = S;
WHILE (*P != '\0')
P++;
RETURN(P-S);
}
При описании указатель P в
этой функции инициализирован
посредством строки S, в результате
чего он указывает на пер-
вый символ строки. В цикле WHILE по
очереди проверяется каж-
дый символ до тех пор, пока не
появится символ конца строки
\0. Так как значение \0 равно нулю,
а WHILE только выясняет,
имеет ли выражение в нем значение
0, то в данном случае яв-
ную проверку можно опустить. Такие
циклы часто записывают в
виде
WHILE (*P)
P++;
Так как P указывает на
символы, то оператор P++ передви-
гает P каждый раз так, чтобы он
указывал на следующий сим-
вол. В результате P-S дает
число просмотренных символов,
·
108 -
т.е. Длину строки. Арифметика
указателей последовательна:
если бы мы имели дело с
переменными типа FLOAT, которые за-
нимают больше памяти, чем
переменные типа CHAR, и если бы P
был указателем на FLOAT, то
оператор P++ передвинул бы P на
следующее FLOAT. таким образом, мы
могли бы написать другой
вариант функции ALLOC,
распределяющей память для FLOAT,
вместо CHAR, просто заменив всюду в
ALLOC и FREE описатель
CHAR на FLOAT. Все действия с
указателями автоматически учи-
тывают размер объектов, на которые
они указывают, так что
больше ничего менять не надо.
За исключением упомянутых выше
операций (сложение и вы-
читание указателя и целого,
вычитание и сравнение двух ука-
зателей), вся остальная арифметика
указателей является неза-
конной. Запрещено складывать два
указателя, умножать, де-
лить, сдвигать или маскировать их,
а также прибавлять к ним
переменные типа FLOAT или DOUBLE.
5.5. Указатели символов и
функции
Строчная константа, как,
например,
“I AM A STRING”
является массивом символов.
Компилятор завершает внутреннее
представление такого массива
символом \0, так что программы
могут находить его конец. Таким
образом, длина массива в па-
мяти оказывается на единицу больше
числа символов между
двойными кавычками.
По-видимому чаще всего
строчные константы появляются в
качестве аргументов функций,
как, например, в
PRINTF (“HELLO, WORLD\N”);
когда символьная строка, подобная
этой, появляется в прог-
рамме, то доступ к ней
осуществляется с помощью указателя
символов; функция PRINTF фактически
получает указатель сим-
вольного массива.
Конечно, символьные массивы не
обязаны быть только аргу-
ментами функций. Если описать
MESSAGE как
CHAR *MESSAGE;
то в результате оператора
MESSAGE = “NOW IS THE TIME”;
переменная MESSAGE станет
указателем на фактический массив
символов. Это не копирование
строки; здесь участвуют только
указатели. в языке “C” не
предусмотрены какие-либо операции
для обработки всей строки символов
как целого.
Мы проиллюстрируем другие
аспекты указателей и массивов,
разбирая две полезные функции
из стандартной библиотеки вво-
да-вывода, которая будет
рассмотрена в главе 7.
·
109 -
Первая функция - это
STRCPY(S,T), которая копирует стро-
ку т в строку S. Аргументы написаны
именно в этом порядке по
аналогии с операцией присваивания,
когда для того, чтобы
присвоить T к S обычно пишут
S = T
сначала приведем версию с
массивами:
STRCPY(S, T) /* COPY T TO S */
CHAR S[], T[];
{
INT I;
I = 0;
WHILE ((S[I] = T[I]) != '\0')
I++;
}
Для сопоставления ниже дается
вариант STRCPY с указате-
лями.
STRCPY(S, T) /* COPY T TO S;
POINTER VERSION 1 */ CHAR *S, *T;
{
WHILE ((*S = *T) != '\0') {
S++;
T++;
}
}
Так как аргументы передаются
по значению, функция STRCPY
может использовать S и T так, как
она пожелает. Здесь они с
удобством полагаются указателями,
которые передвигаются
вдоль массивов, по одному символу
за шаг, пока не будет ско-
пирован в S завершающий в T символ
\0.
На практике функция STRCPY была
бы записана не так, как
мы показали выше. Вот вторая
возможность:
STRCPY(S, T) /* COPY T TO S;
POINTER VERSION 2 */ CHAR *S, *T;
{
WHILE ((*S++ = *T++) != '\0')
;
}
Здесь увеличение S и T внесено
в проверочную часть. Зна-
чением *T++ является символ, на
который указывал T до увели-
чения; постфиксная операция ++ не
изменяет T, пока этот сим-
вол не будет извлечен. Точно так же
этот символ помещается в
старую позицию S, до того как S
будет увеличено. Конечный
результат заключается в том, что
все символы, включая завер-
шающий \0, копируются из T в S.
·
110 -
И как последнее сокращение мы
опять отметим, что сравне-
ние с \0 является излишним, так что
функцию можно записать в
виде
STRCPY(S, T) /* COPY T TO S;
POINTER VERSION 3 */
CHAR *S, *T;
{
WHILE (*S++ = *T++)
;
}
хотя с первого взгляда эта запись
может показаться загадоч-
ной, она дает значительное
удобство. Этой идиомой следует
овладеть уже хотя бы потому, что
вы с ней будете часто вст-
речаться в “C”-программах.
Вторая функция - STRCMP(S, T),
которая сравнивает сим-
вольные строки S и т, возвращая
отрицательное, нулевое или
положительное значение в
соответствии с тем, меньше, равно
или больше лексикографически S, чем
T. Возвращаемое значение
получается в результате вычитания
символов из первой пози-
ции, в которой S и T не совпадают.
STRCMP(S, T) /* RETURN <0 IF
S<T, 0 IF S==T, >0 IF S>T */
CHAR S[], T[];
{
INT I;
I = 0;
WHILE (S[I] == T[I])
IF (S[I++] == '\0')
RETURN(0);
RETURN(S[I]-T[I]);
}
Вот версия STRCMP с
указателями:
STRCMP(S, T) /* RETURN <0 IF
S<T, 0 IF S==T, >0 IF S>T */ CHAR *S, *T;
{
FOR ( ; *S == *T; S++, T++)
IF (*S == '\0')
RETURN(0);
RETURN(*S-*T);
}
так как ++ и—могут быть как
постфиксными, так и
префиксными операциями,
встречаются другие комбинации * и
++ и --, хотя и менее часто.
Например
*++P
·
111 -
увеличивает P до извлечения
символа, на который указывает
P, а
*--P
сначала уменьшает P.
Упражнение 5-2.
Напишите вариант с
указателями функции STRCAT из главы
2: STRCAT(S, T) копирует строку
T в конец S.
Упражнение 5-3.
Напишите макрос для STRCPY.
Упражнение 5-4.
Перепишите подходящие программы
из предыдущих глав и уп-
ражнений, используя
указатели вместо индексации массивов.
Хорошие возможности для этого
предоставляют функции GETLINE
/главы 1 и 4/, ATOI, ITOA и
их варианты /главы 2, 3 и 4/,
REVERSE /глава 3/, INDEX и
GETOP /глава 4/.
5.6. Указатели - не целые.
Вы, возможно, обратили
внимание в предыдущих “с”-прог-
раммах на довольно непринужденное
отношение к копированию
указателей. В общем это верно, что
на большинстве машин ука-
затель можно присвоить целому и
передать его обратно, не из-
менив его; при этом не происходит
никакого масштабирования
или преобразования и ни один бит не
теряется. к сожалению,
это ведет к вольному обращению с
функциями, возвращающими
указатели, которые затем просто
передаются другим функциям,
·
необходимые описания указателей часто опускаются. Рассмот-
рим, например, функцию STRSAVE(S),
которая копирует строку S
в некоторое место для хранения,
выделяемое посредством обра-
щения к функции ALLOC, и возвращает
указатель на это место.
Правильно она должна быть
записана так:
CHAR STRSAVE(S) / SAVE
STRING S SOMEWHERE */ CHAR *S;
{
CHAR *P, *ALLOC();
IF ((P = ALLOC(STRLEN(S)+1)) != NULL)
STRCPY(P, S);
RETURN(P);
}
на практике существует сильное
стремление опускать описания:
·
112 -
STRSAVE(S) / SAVE
STRING S SOMEWHERE */
{
CHAR *P;
IF ((P = ALLOC(STRLEN(S)+1)) != NULL)
STRCPY(P, S);
RETURN(P);
}
Эта программа будет правильно
работать на многих маши-
нах, потому что по умолчанию
функции и аргументы имеют тип
INT, а указатель и целое обычно
можно безопасно пересылать
туда и обратно. Однако такой стиль
программирования в своем
существе является рискованным,
поскольку зависит от деталей
реализации и архитектуры машины и
может привести к непра-
вильным результатам на конкретном
используемом вами компиля-
торе. Разумнее всюду использовать
полные описания. (Отладоч-
ная программа LINT предупредит о
таких конструкциях, если
они по неосторожности все же
появятся).
5.7. Многомерные массивы.
В языке “C” предусмотрены
прямоугольные многомерные мас-
сивы, хотя на практике существует
тенденция к их значительно
более редкому использованию по
сравнению с массивами указа-
телей. В этом разделе мы рассмотрим
некоторые их свойства.
Рассмотрим задачу преобразования
дня месяца в день года
и наоборот. Например, 1-ое марта
является 60-м днем невисо-
косного года и 61-м днем
високосного года. Давайте введем
две функции для выполнения этих
преобразований: DAY_OF_YEAR
преобразует месяц и день в день
года, а MONTH_DAY преобразу-
ет день года в месяц и день. Так
как эта последняя функция
возвращает два значения, то
аргументы месяца и дня должны
быть указателями:
MONTH_DAY(1977, 60, &M,
&D)
Полагает M равным 3 и D равным
1 (1-ое марта).
Обе эти функции нуждаются в
одной и той же информацион-
ной таблице, указывающей число дней
в каждом месяце. Так как
число дней в месяце в високосном и
в невисокосном году отли-
чается, то проще представить их в
виде двух строк двумерного
массива, чем пытаться прослеживать
во время вычислений, что
именно происходит в феврале. Вот
этот массив и выполняющие
эти преобразования функции:
STATIC INT DAY_TAB[2][13] = {
(0, 31, 28, 31, 30, 31, 30, 31,
31, 30, 31, 30, 31),
(0, 31, 29, 31, 30, 31, 30, 31,
31, 30, 31, 30, 31)
};
·
113 -
DAY_OF_YEAR(YEAR,
MONTH, DAY) /* SET DAY OF YEAR */
INT YEAR,
MONTH, DAY; /* FROM MONTH & DAY */
{
INT I, LEAP;
LEAP = YEAR%4 == 0 && YEAR%100
!= 0 \!\! YEAR%400 == 0;
FOR (I = 1; I < MONTH; I++)
DAY += DAY_TAB[LEAP][I];
RETURN(DAY);
{
MONTH_DAY(YEAR, YEARDAY,
PMONTH, PDAY) /*SET MONTH,DAY */
INT YEAR, YEARDAY, *PMONTH, PDAY;
/ FROM DAY OF YEAR */
{
LEAP = YEAR%4 == 0 &&
YEAR%100 != 0 \!\! YEAR%400 == 0;
FOR (I = 1; YEARDAY >
DAY_TAB[LEAP][I]; I++)
YEARDAY -= DAY_TAB[LEAP][I];
*PMONTH = I;
*PDAY = YEARDAY;
}
Массив DAY_TAB должен быть
внешним как для DAY_OF_YEAR, так
и для MONTH_DAY, поскольку он
используется обеими этими фун-
кциями.
Массив DAY_TAB является первым
двумерным массивом, с ко-
торым мы имеем дело. По определению
в “C” двумерный массив
по существу является одномерным
массивом, каждый элемент ко-
торого является массивом. Поэтому
индексы записываются как
DAY_TAB[I][J]
а не
DAY_TAB [I, J]
как в большинстве языков. В
остальном с двумерными массивами
можно в основном обращаться таким
же образом, как в других
языках. Элементы хранятся по
строкам, т.е. При обращении к
элементам в порядке их размещения в
памяти быстрее всего из-
меняется самый правый индекс.
Массив инициализируется с
помощью списка начальных зна-
чений, заключенных в фигурные
скобки; каждая строка двумер-
ного массива инициализируется
соответствующим подсписком. Мы
поместили в начало массива DAY_TAB
столбец из нулей для то-
го, чтобы номера месяцев изменялись
естественным образом от
1 до 12, а не от 0 до 11. Так
как за экономию памяти у нас пока не награждают, такой способ проще, чем
подгонка индек-сов.
Если двумерный массив
передается функции, то описание
соответствующего аргумента функции
должно содержать количес-
тво столбцов; количество строк
несущественно, поскольку, как
и прежде, фактически передается
указатель. В нашем конкрет-
ном случае это указатель объектов,
являющихся массивами из
·
114 -
13 чисел типа INT. Таким образом,
если бы требовалось пере-
дать массив DAY_TAB функции F, то
описание в F имело бы вид:
F(DAY_TAB)
INT DAY_TAB[2][13];
{
...
}
Так как количество строк является
несущественным, то описа-
ние аргумента в F могло бы быть
таким:
INT DAY_TAB[][13];
или таким
INT (*DAY_TAB)[13];
в которм говорится, что аргумент
является указателем массива
из 13 целых. Круглые скобки
здесь необходимы, потому что
квадратные скобки [] имеют более
высокий уровень старшинст-
ва, чем *; как мы увидим в
следующем разделе, без круглых
скобок
INT *DAY_TAB[13];
является описанием массива из
13 указателей на целые.
5.8. Массивы указателей;
указатели указателей
Так как указатели сами
являются переменными, то вы впол-
не могли бы ожидать использования
массива указателей. Это
действительно так. Мы проиллюстрируем
это написанием прог-
раммы сортировки в алфавитном
порядке набора текстовых
строк, предельно упрощенного
варианта утилиты SORT операци-
онной систем UNIX.
В главе 3 мы привели функцию
сортировки по шеллу, кото-
рая упорядочивала массив целых. Этот
же алгоритм будет рабо-
тать и здесь, хотя теперь мы будем
иметь дело со строчками
текста различной длины, которые, в
отличие от целых, нельзя
сравнивать или перемещать с помощью
одной операции. Мы нуж-
даемся в таком представлении
данных, которое бы позволяло
удобно и эффективно обрабатывать
строки текста переменной
длины.
Здесь и возникают массивы
указателей. Если подлежащие
сортировке сроки хранятся одна за
другой в длинном символь-
ном массиве (управляемом, например,
функцией ALLOC), то к
каждой строке можно обратиться с
помощью указателя на ее
первый символ. Сами указатели можно
хранить в массиве. две
строки можно сравнить, передав их
указатели функции STRCMP.
·
115 -
Если две расположенные в
неправильном порядке строки должны
быть переставлены, то фактически
переставляются указатели в
массиве указателей, а не сами
тексты строк. Этим исключаются
Страницы: 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
|