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

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


    Прохождения игр    
Aliens Vs Predator |#1| To freedom!
Aliens Vs Predator |#10| Human company final
Aliens Vs Predator |#9| Unidentified xenomorph
Aliens Vs Predator |#8| Tequila Rescue

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


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

Язык Си

Предыдущая страница Следующая страница
1 ... 12 13 14 15 16 17 18  19 20 21 22 23 24 25 ... 39
кой вид объектов 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  дает число просмотренных символов,

т.е. Длину строки.  Арифметика  указателей  последовательна:
если  бы мы имели дело с переменными типа 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.

    Первая функция - это 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.

    И как последнее сокращение мы опять отметим, что сравне-
ние с \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 S0 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 S0 IF S>T */
CHAR *S, *T;
{
 FOR ( ; *S == *T; S++, T++)
    IF (*S == '\0')
            RETURN(0);
 RETURN(*S-*T);
}
     так как ++ и -- могут быть как постфиксными,  так  и
префиксными операциями, встречаются другие комбинации * и
++ и --, хотя и менее часто.

     Например

 *++P

увеличивает 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);
    }

на практике существует сильное стремление опускать описания:

    *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)
};

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. Так как за экономию памяти у нас
пока не награждают, такой способ проще, чем подгонка  индек-
сов.
    Если двумерный массив передается функции, то описание
Предыдущая страница Следующая страница
1 ... 12 13 14 15 16 17 18  19 20 21 22 23 24 25 ... 39
Ваша оценка:
Комментарий:
  Подпись:
(Чтобы комментарии всегда подписывались Вашим именем, можете зарегистрироваться в Клубе читателей)
  Сайт:
 
Комментарии (1)

Реклама