Смекни!
smekni.com

Вычитание указателей тоже возможно: если 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.

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.

И как последнее сокращение мы опять отметим, что сравнение с \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++] == '&bsol;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 == '&bsol;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)

};

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 &bsol;!&bsol;! 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 &bsol;!&bsol;! 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]

как в большинстве языков. В остальном с двумерными массивами можно в основном обращаться таким же образом, как в других языках. Элементы хранятся по строкам, т.е. При обращении к элементам в порядке их размещения в памяти быстрее всего изменяется самый правый индекс.