Мы поможем в написании ваших работ!
ЗНАЕТЕ ЛИ ВЫ?
|
Указатели символов и функции
Строчная константа, как, например, "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/.
Указатели - не целые
Вы, возможно, обратили внимание в предыдущих "с"-прог-раммах на довольно непринужденное отношение к копированиюуказателей. В общем это верно, что на большинстве машин ука-затель можно присвоить целому и передать его обратно, не из-менив его; при этом не происходит никакого масштабированияили преобразования и ни один бит не теряется. к сожалению,это ведет к вольному обращению с функциями, возвращающимиуказатели, которые затем просто передаются другим функциям,- необходимые описания указателей часто опускаются. Рассмот-рим, например, функцию 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 предупредит о таких конструкциях, еслиони по неосторожности все же появятся).
Многомерные массивы
В языке "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. Так как за экономию памяти у наспока не награждают, такой способ проще, чем подгонка индек-сов. Если двумерный массив передается функции, то описаниесоответствующего аргумента функции должно содержать количес-тво столбцов; количество строк несущественно, поскольку, каки прежде, фактически передается указатель. В нашем конкрет-ном случае это указатель объектов, являющихся массивами из 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 указателей на целые.
|