Указатели и аргументы функций 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Указатели и аргументы функций



Так как в "с" передача аргументов функциям осуществляет-ся "по значению", вызванная процедура не имеет непосредст-венной возможности изменить переменную из вызывающей прог-раммы. Что же делать, если вам действительно надо изменитьаргумент? например, программа сортировки захотела бы поме-нять два нарушающих порядок элемента с помощью функции сименем SWAP. Для этого недостаточно написать SWAP(A, B); определив функцию SWAP при этом следующим образом: SWAP(X, Y) /* WRONG */ INT X, Y; { INT TEMP; TEMP = X; X = Y; Y = TEMP; } из-за вызова по значению SWAP не может воздействовать наагументы A и B в вызывающей функции. К счастью, все же имеется возможность получить желаемыйэффект. Вызывающая программа передает указатели подлежащихизменению значений: SWAP(&A, &B);так как операция & выдает адрес переменной, то &A являетсяуказателем на A. В самой SWAP аргументы описываются как ука-затели и доступ к фактическим операндам осуществляется черезних. SWAP(PX, PY) /* INTERCHANGE *PX AND *PY */INT *PX, *PY;{ INT TEMP; TEMP = *PX; *PX = *PY; *PY = TEMP;} Указатели в качестве аргументов обычно используются вфункциях, которые должны возвращать более одного значения.(Можно сказать, что SWAP вOзвращает два значения, новые зна-чения ее аргументов). В качестве примера рассмотрим функциюGETINT, которая осуществляет преобразование поступающих всвоболном формате данных, разделяя поток символов на целыезначения, по одному целому за одно обращение. Функция GETINTдолжна возвращать либо найденное значение, либо признак кон-ца файла, если входные данные полностью исчерпаны. Эти зна-чения должны возвращаться как отдельные объекты, какое бызначение ни использовалось для EOF, даже если это значениевводимого целого. Одно из решений, основывающееся на описываемой в главе 7функции ввода SCANF, состоит в том, чтобы при выходе на ко-нец файла GETINT возвращала EOF в качестве значения функции;любое другое возвращенное значение говорит о нахождении нор-мального целого. Численное же значение найденного целоговозвращается через аргумент, который должен быть указателемцелого. Эта организация разделяет статус конца файла и чис-ленные значения. Следующий цикл заполняет массив целыми с помощью обраще-ний к функции GETINT: INT N, V, ARRAY[SIZE]; FOR (N = 0; N < SIZE && GETINT(&V)!= EOF; N++) ARRAY[N] = V; В результате каждого обращения V становится равным следующе-му целому значению, найденному во входных данных. Обратитевнимание, что в качестве аргумента GETINT необходимо указать&V а не V. Использование просто V скорее всего приведет кошибке адресации, поскольку GETINT полагает, что она работа-ет именно с указателем. Сама GETINT является очевидной модификацией написаннойнами ранее функции ATOI: GETINT(PN) /* GET NEXT INTEGER FROM INPUT */ INT *PN; { INT C,SIGN; WHILE ((C = GETCH()) == ' ' \!\! C == '\N' \!\! C == '\T'); /* SKIP WHITE SPACE */ SIGN = 1; IF (C == '+' \!\! C == '-') { /* RECORD SIGN */ SIGN = (C == '+')? 1: -1; C = GETCH(); } FOR (*PN = 0; C >= '0' && C <= '9'; C = GETCH()) *PN = 10 * *PN + C - '0'; *PN *= SIGN; IF (C!= EOF) UNGETCH(C); RETURN(C); } Выражение *PN используется всюду в GETINT как обычная пере-менная типа INT. Мы также использовали функции GETCH иUNGETCH (описанные в главе 4), так что один лишний символ,кототрый приходится считывать, может быть помещен обратно воввод. Упражнение 5-1 --------------- Напишите функцию GETFLOAT, аналог GETINT для чисел сплавающей точкой. Какой тип должна возвращать GETFLOAT в ка-честве значения функции?

Указатели и массивы

В языке "C" существует сильная взаимосвязь между указа-телями и массивами, настолько сильная, что указатели и мас-сивы действительно следует рассматривать одновременно. Любуюоперацию, которую можно выполнить с помощью индексов масси-ва, можно сделать и с помощью указателей. вариант с указате-лями обычно оказывается более быстрым, но и несколько болеетрудным для непосредственного понимания, по крайней мере дляначинающего. описание INT A[10] определяет массив размера 10, т.е. Набор из 10 последова-тельных объектов, называемых A[0], A[1],..., A[9]. ЗаписьA[I] соответствует элементу массива через I позиций от нача-ла. Если PA - указатель целого, описанный как INT *PA то присваивание PA = &A[0] приводит к тому, что PA указывает на нулевой элемент массиваA; это означает, что PA содержит адрес элемента A[0]. Теперьприсваивание X = *PA будет копировать содержимое A[0] в X. Если PA указывает на некоторый определенный элемент мас-сива A, то по определению PA+1 указывает на следующий эле-мент, и вообще PA-I указывает на элемент, стоящий на I пози-ций до элемента, указываемого PA, а PA+I на элемент, стоящийна I позиций после. Таким образом, если PA указывает наA[0], то *(PA+1) ссылается на содержимое A[1], PA+I - адрес A[I], а *(PA+I) -содержимое A[I]. Эти замечания справедливы независимо от типа переменныхв массиве A. Суть определения "добавления 1 к указателю", атакже его распространения на всю арифметику указателей, сос-тоит в том, что приращение масштабируется размером памяти,занимаемой объектом, на который указывает указатель. Такимобразом, I в PA+I перед прибавлением умножается на размеробъектов, на которые указывает PA. Очевидно существует очень тесное соответствие между ин-дексацией и арифметикой указателей. в действительности ком-пилятор преобразует ссылку на массив в указатель на началомассива. В результате этого имя массива является указатель-ным выражением. Отсюда вытекает несколько весьма полезныхследствий. Так как имя массива является синонимом местополо-жения его нулевого элемента, то присваивание PA=&A[0] можнозаписать как PA = A Еще более удивительным, по крайней мере на первый взг-ляд, кажется тот факт, что ссылку на A[I] можно записать ввиде *(A+I). При анализировании выражения A[I] в языке "C"оно немедленно преобразуется к виду *(A+I); эти две формысовершенно эквивалентны. Если применить операцию & к обеимчастям такого соотношения эквивалентности, то мы получим,что &A[I] и A+I тоже идентичны: A+I - адрес I-го элемента отначала A. С другой стороны, если PA является указателем, тов выражениях его можно использовать с индексом: PA[I] иден-тично *(PA+I). Короче, любое выражение, включающее массивы ииндексы, может быть записано через указатели и смещения инаоборот, причем даже в одном и том же утверждении. Имеется одно различие между именем массива и указателем,которое необходимо иметь в виду. указатель является перемен-ной, так что операции PA=A и PA++ имеют смысл. Но имя масси-ва является константой, а не переменной: конструкции типаA=PA или A++,или P=&A будут незаконными. Когда имя массива передается функции, то на самом делеей передается местоположение начала этого массива. Внутривызванной функции такой аргумент является точно такой же пе-ременной, как и любая другая, так что имя массива в качествеаргумента действительно является указателем, т.е. Перемен-ной, содержащей адрес. мы можем использовать это обстоятель-ство для написания нового варианта функции STRLEN, вычисляю-щей длину строки. STRLEN(S) /* RETURN LENGTH OF STRING S */ CHAR *S; { INT N; FOR (N = 0; *S!= '\0'; S++) N++; RETURN(N); } Операция увеличения S совершенно законна, поскольку этапеременная является указателем; S++ никак не влияет на сим-вольную строку в обратившейся к STRLEN функции, а толькоувеличивает локальную для функции STRLEN копию адреса. Опи-сания формальных параметров в определении функции в виде CHAR S[];CHAR *S; совершенно эквивалентны; какой вид описания следует предпо-честь, определяется в значительной степени тем, какие выра-жения будут использованы при написании функции. Если функциипередается имя массива, то в зависимости от того, что удоб-нее, можно полагать, что функция оперирует либо с массивом,либо с указателем, и действовать далее соответвующим обра-зом. Можно даже использовать оба вида операций, если это ка-жется уместным и ясным. Можно передать функции часть массива, если задать в ка-честве аргумента указатель начала подмассива. Например, еслиA - массив, то как F(&A[2]) как и F(A+2) передают функции F адрес элемента A[2], потому что и &A[2],и A+2 являются указательными выражениями, ссылающимися натретий элемент A. внутри функции F описания аргументов могутприсутствовать в виде: F(ARR)INT ARR[];{...} или F(ARR)INT *ARR;{...} Что касается функции F, то тот факт, что ее аргумент в дейс-твительности ссылается к части большего массива,не имеет длянее никаких последствий.

Адресная арифметика

Если P является указателем, то каков бы ни был сортобъекта, на который он указывает, операция P++ увеличивает Pтак, что он указывает на следующий элемент набора этихобъектов, а операция P +=I увеличивает P так, чтобы он ука-зывал на элемент, отстоящий на I элементов от текущего эле-мента.эти и аналогичные конструкции представляют собой самыепростые и самые распространенные формы арифметики указателейили адресной арифметики. Язык "C" последователен и постоянен в своем подходе кадресной арифметике; объединение в одно целое указателей,массивов и адресной арифметики является одной из наиболеесильных сторон языка. Давайте проиллюстрируем некоторые изсоответствующих возможностей языка на примере элементарной(но полезной, несмотря на свою простоту) программы распреде-ления памяти. Имеются две функции: функция ALLOC(N) возвра-щает в качестве своего значения указатель P, который указы-вает на первую из N последовательных символьных позиций, ко-торые могут быть использованы вызывающей функцию ALLOC прог-раммой для хранения символов; функция FREE(P) освобождаетприобретенную таким образом память, так что ее в дальнейшемможно снова использовать. программа является "элементарной",потому что обращения к FREE должны производиться в порядке,обратном тому, в котором производились обращения к ALLOC.Таким образом, управляемая функциями ALLOC и FREE память яв-ляется стеком или списком, в котором последний вводимый эле-мент извлекается первым. Стандартная библиотека языка "C"содержит аналогичные функции, не имеющие таких ограничений,и, кроме того, в главе 8 мы приведем улучшенные варианты.Между тем, однако, для многих приложений нужна только триви-альная функция ALLOC для распределения небольших участковпамяти неизвестных заранее размеров в непредсказуемые момен-ты времени. Простейшая реализация состоит в том, чтобы функция раз-давала отрезки большого символьного массива, которому мыприсвоили имя ALLOCBUF. Этот массив является собственностьюфункций ALLOC и FREE. Так как они работают с указателями, ане с индексами массива, никакой другой функции не нужнознать имя этого массива. Он может быть описан как внешнийстатический, т.е. Он будет локальным по отношению к исходно-му файлу, содержащему ALLOC и FREE, и невидимым за его пре-делами. При практической реализации этот массив может дажене иметь имени; вместо этого он может быть получен в резуль-тате запроса к операционной системе на указатель некоторогонеименованного блока памяти. Другой необходимой информацией является то, какая частьмассива ALLOCBUF уже использована. Мы пользуемся указателемпервого свободного элемента, названным ALLOCP. Когда к функ-ции ALLOC обращаются за выделением N символов, то она прове-ряет, достаточно ли осталось для этого места в ALLOCBUF. Ес-ли достаточно, то ALLOC возвращает текущее значение ALLOCP(т.е. Начало свободного блока), затем увеличивает его на N,с тем чтобы он указывал на следующую свободную область. Фун-кция FREE(P) просто полагает ALLOCP равным P при условии,что P указывает на позицию внутри ALLOCBUF. DEFINE NULL 0 /* POINTER VALUE FOR ERROR REPORT */DEFINE ALLOCSIZE 1000 /* SIZE OF AVAILABLE SPACE */ TATIC CHAR ALLOCBUF[ALLOCSIZE];/* STORAGE FOR ALLOC */TATIC CHAR *ALLOCP = ALLOCBUF; /* NEXT FREE POSITION */ HAR *ALLOC(N) /* RETURN POINTER TO N CHARACTERS */INT N;(IF (ALLOCP + N <= ALLOCBUF + ALLOCSIZE) { ALLOCP += N; RETURN(ALLOCP - N); /* OLD P */} ELSE /* NOT ENOUGH ROOM */ RETURN(NULL);) REE(P) /* FREE STORAGE POINTED BY P */HAR *P;(IF (P >= ALLOCBUF && P < ALLOCBUF + ALLOCSIZE) ALLOCP = P;) Дадим некоторые пояснения. Вообще говоря, указатель мо-жет быть инициализирован точно так же, как и любая другаяпеременная, хотя обычно единственными осмысленными значения-ми являются NULL (это обсуждается ниже) или выражение, вклю-чающее адреса ранее определенных данных соответствующего ти-па. Описание STATIC CHAR *ALLOCP = ALLOCBUF; определяет ALLOCP как указатель на символы и инициализируетего так, чтобы он указывал на ALLOCBUF, т.е. На первую сво-бодную позицию при начале работы программы. Так как имя мас-сива является адресом его нулевого элемента, то это можнобыло бы записать в виде STATIC CHAR *ALLOCP = &ALLOCBUF[0]; используйте ту запись, которая вам кажется более естествен-ной. С помощью проверки IF (ALLOCP + N <= ALLOCBUF + ALLOCSIZE) выясняется, осталось ли достаточно места, чтобы удовлетво-рить запрос на N символов. Если достаточно, то новое значе-ние ALLOCP не будет указывать дальше, чем на последнюю пози-цию ALLOCBUF. Если запрос может быть удовлетворен, то ALLOCвозвращает обычный указатель (обратите внимание на описаниесамой функции). Если же нет, то ALLOC должна вернуть некото-рый признак, говорящий о том, что больше места не осталось.В языке "C" гарантируется, что ни один правильный указательданных не может иметь значение нуль, так что возвращение ну-ля может служить в качестве сигнала о ненормальном событии,в данном случае об отсутствии места. Мы, однако, вместо нуляпишем NULL, с тем чтобы более ясно показать, что это специ-альное значение указателя. Вообще говоря, целые не могут ос-мысленно присваиваться указателям, а нуль - это особый слу-чай. Проверки вида IF (ALLOCP + N <= ALLOCBUF + ALOOCSIZE)и IF (P >= ALLOCBUF && P < ALLOCBUF + ALLOCSIZE) демонстрируют несколько важных аспектов арифметики указате-лей. Во-первых, при определенных условиях указатели можносравнивать. Если P и Q указывают на элементы одного и тогоже массива, то такие отношения, как <, >= и т.д., работаютнадлежащим образом. Например, P < Q истинно, если 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 дает число просмотренных символов, т.е. Длину строки. Арифметика указателей последовательна:если бы мы имели дело с переменными типа FLOAT, которые за-нимают больше памяти, чем переменные типа CHAR, и если бы Pбыл указателем на FLOAT, то оператор P++ передвинул бы P наследующее FLOAT. таким образом, мы могли бы написать другойвариант функции ALLOC, распределяющей память для FLOAT,вместо CHAR, просто заменив всюду в ALLOC и FREE описательCHAR на FLOAT. Все действия с указателями автоматически учи-тывают размер объектов, на которые они указывают, так чтобольше ничего менять не надо. За исключением упомянутых выше операций (сложение и вы-читание указателя и целого, вычитание и сравнение двух ука-зателей), вся остальная арифметика указателей является неза-конной. Запрещено складывать два указателя, умножать, де-лить, сдвигать или маскировать их, а также прибавлять к нимпеременные типа FLOAT или DOUBLE.


Поделиться:


Последнее изменение этой страницы: 2016-08-26; просмотров: 154; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.209.66.87 (0.012 с.)