рефераты

рефераты

 
 
рефераты рефераты

Меню

Реферат: Язык С рефераты

ние 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