ТОР 5 статей: Методические подходы к анализу финансового состояния предприятия Проблема периодизации русской литературы ХХ века. Краткая характеристика второй половины ХХ века Характеристика шлифовальных кругов и ее маркировка Служебные части речи. Предлог. Союз. Частицы КАТЕГОРИИ:
|
Операции над указателями.мы рассмотрели две основные операции, связанные с указателями,? & (взятие адреса) и * (разыменование). Помимо этих операций, в Си имеются специфические для этого языка арифметические операции над указателями. Кратко рассмотрим их. Пусть имеются следующие описания:<SPAN lang=EN-US style="mso-ansi-language: EN-US"> int</SPAN> *р1,*р2,<SPAN lang=EN-US style="mso-ansi-language: EN-US">i</SPAN>;
К указателям можно применять операции отношения (смысл их очень простой? больше тот указатель, который указывает на ячейку с большим адресом). Рассмотрим несколько примеров. <SPAN lang=EN-US style="mso-ansi-language: EN-US">#include <stdio.h></SPAN> <SPAN lang=EN-US style="mso-ansi-language: EN-US">#define</SPAN> N 10 <SPAN lang=EN-US style="mso-ansi-language: EN-US">void main(void)</SPAN> {<SPAN lang=EN-US style="mso-ansi-language: EN-US"> int a[N]={0,l,2,3,4,5,6,7,8,9}, *p,i;</SPAN> <SPAN lang=EN-US style="mso-ansi-language: EN-US">p=a;</SPAN> <SPAN lang=EN-US style="mso-ansi-language: EN-US">for (i=0;i<N;i++,p++) printf("\na[%d]=%d"</SPAN>,<SPAN lang=EN-US style="mso-ansi-language: EN-US">i,p)</SPAN>; <SPAN lang=EN-US> }</SPAN> А вот такой пример: <SPAN lang=EN-US>#include <stdio.h> не пройдет компиляцию, ибо а - константа и менять ее значение нельзя. <SPAN lang=EN-US style="mso-ansi-language: EN-US">#include <stdio.h></SPAN> <SPAN lang=EN-US style="mso-ansi-language: EN-US">#define</SPAN> N 10 <SPAN lang=EN-US style="mso-ansi-language: EN-US">void main(void)</SPAN> {<SPAN lang=EN-US style="mso-ansi-language: EN-US"> int a[N>(0,l,2,3,4,5,</SPAN> 6,7, 8, 9},<SPAN lang=EN-US style="mso-ansi-language: EN-US"> *p,i;</SPAN> <SPAN lang=EN-US style="mso-ansi-language: EN-US">p=a;</SPAN> <SPAN lang=EN-US style="mso-ansi-language: EN-US">for (i=0;i<N;i++) printf</SPAN>(<SPAN lang=EN-US style="mso-ansi-language: EN-US">"\na</SPAN>[%d]<SPAN lang=EN-US style="mso-ansi-language: EN-US">=%d"</SPAN>,<SPAN lang=EN-US style="mso-ansi-language: EN-US">i,</SPAN>*<SPAN lang=EN-US style="mso-ansi-language: EN-US">(p+i)</SPAN>); <SPAN lang=EN-US>}</SPAN> Этот пример демонстрирует, что записи<SPAN lang=EN-US style="mso-ansi-language: EN-US"> p[i]</SPAN> и * (p+i) эквивалентны. Запись р [ i ] понимается компилятором именно как * (p+i). После всего сказанного так же должно стать понятно, почему массивы всегда передаются в функции "по ссылке". Потому что всегда в действительности передаются именно указатели. Следующие заголовки функций для компилятора эквивалентны: <SPAN lang=EN-US>void sort(int a[10]) .. Указатели и массивы Любую операцию, которую можно выполнить с помощью индексов массива, можно сделать и с помощью указателей. вариант с указателями обычно оказывается более быстрым, но и несколько более трудным для непосредственного понимания, по крайней мере для начинающего. описание 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] оно немедленно преобразуется к виду *(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, то тот факт, что ее аргумент в действительности ссылается к части большего массива,не имеет для нее никаких последствий. Инициализация массивов указателей. Рассмотрим задачу написания функции MONTH_NAME(N), которая возвращает указатель на символьную строку, содержащую имя N-го месяца. Это идеальная задача для применения внутреннего статического массива. Функция MONTH_NAME содержит локальный массив символьных строк и при обращении к ней возвращает указатель нужной строки. Тема настоящего раздела - как инициализировать этот массив имен. CHAR *MONTH_NAME(N) /* RETURN NAME OF N-TH MONTH */ INT N; \( STATIC CHAR *NAME[] = \( "ILLEGAL MONTH", "JANUARY", … "NOVEMBER", "DECEMBER" \); RETURN ((N < 1 \!\! N > 12)? NAME[0]: NAME[N]); \) Описание массива указателей на символы NAME точно такое же, как аналогичное описание LINEPTR в примере с сортировкой. Инициализатором является просто список символьных строк; каждая строка присваивается соответствующей позиции в массиве. Более точно, символы I-ой строки помещаются в какое-то иное место, а ее указатель хранится в NAME[I]. Поскольку размер массива NAME не указан, компилятор сам подсчитывает количество инициализаторов и соответственно устанавливает правильное число. 5.10. Указатели и многомерные массивы Начинающие изучать язык "с" иногда становятся в тупик перед вопросом о различии между двумерным массивом и массивом указателей, таким как NAME в приведенном выше примере. Если имеются описания INT A[10][10]; INT *B[10]; то A и B можно использовать сходным образом в том смысле, что как A[5][5], так и B[5][5] являются законными ссылками на отдельное число типа INT. Но A - настоящий массив: под него отводится 100 ячеек памяти и для нахождения любого указанного элемента проводятся обычные вычисления с прямоугольными индексами. Для B, однако, описание выделяет только 10 указателей; каждый указатель должен быть установлен так, чтобы он указывал на массив целых. если предположить, что каждый из них указывает на массив из 10 элементов, то тогда где-то будет отведено 100 ячеек памяти плюс еще десять ячеек для указателей. Таким образом, массив указателей использует несколько больший объем памяти и может требовать наличие явного шага инициализации. Но при этом возникают два преимущества: доступ к элементу осуществляется косвенно через указатель, а не посредством умножения и сложения, и строки массива могут иметь различные длины. Это означает, что каждый элемент B не должен обязательно указывать на вектор из 10 элементов; некоторые могут указывать на вектор из двух элементов, другие - из двадцати, а третьи могут вообще ни на что не указывать. Не нашли, что искали? Воспользуйтесь поиском:
|