Главная

Популярная публикация

Научная публикация

Случайная публикация

Обратная связь

ТОР 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>;

<TBODY>Операция Пример Описание
<указатель>+<целое> <SPAN lang=EN-US style="mso-ansi-language: EN-US">p2=pl+i;</SPAN> В результате прибавления к указателю целого числа (давайте считать, что положительного, иначе фактически получится операция вычитания) получается указатель, отстоящий от данного на количество единиц типа, на данные которого указывает указатель, в сторону возрастания адресов памяти. Пусть, к примеру, указатель р1 имел значение 100 (указывал на ячейку памяти с адресом 100). Тогда после выполнения операции р2=р1+5 ука&shy;затель р2 будет иметь значение 110 (ведь данные типа int занимают в памяти 2 байта). Если бы мы описали указатели на<SPAN lang=EN-US style="mso-ansi-language: EN-US"> char,</SPAN> считать было бы чуть проще, но и пример был бы менее показательным.
<указатель>-<целое> <SPAN lang=EN-US style="mso-ansi-language: EN-US">p2=pl-i;</SPAN> Совершенно аналогичная операция, только результат отсчитывается в сторону убывания адресов.
<указатель>++, <указатель>-- <SPAN lang=EN-US style="mso-ansi-language: EN-US">pl++; </SPAN>р1--; Эти операции тоже не должны вызывать вопросов. Надо только все время помнить, что действие (сдвиг указателя) производится "в единицах типа. на который он указывает".
<указатель>--<указатель> <SPAN lang=EN-US style="mso-ansi-language: EN-US">i=pl-p2;</SPAN> Разность указателей (они должны указывать на данные одного типа' дает целое число, абсолютная величина которого показывает, сколько единиц типа, на данные которого указывает указатель, целиком помещается между ними, а знак разности говорит о том, какой из указателей "больше". </TBODY>

К указателям можно применять операции отношения (смысл их очень простой? больше тот указатель, который указывает на ячейку с большим адресом). Рассмотрим несколько примеров.


<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>#<SPAN lang=EN-US>define N 10
void main (void)
{ int a[N]={0,l,2,3,4,5,6,7,8,9}, i;
for (i=0;i<N;i++,a++) printf ("\na [%d.] =%d", i, a);
}</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,</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])
void sort (int a[])
void sort(int *a)</SPAN>

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

Любую операцию, которую можно выполнить с помощью индексов масси­ва, можно сделать и с помощью указателей. вариант с указате­лями обычно оказывается более быстрым, но и несколько более трудным для непосредственного понимания, по крайней мере для начинающего. описание

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 элементов; некоторые могут указывать на вектор из двух эле­ментов, другие - из двадцати, а третьи могут вообще ни на что не указывать.






Не нашли, что искали? Воспользуйтесь поиском:

vikidalka.ru - 2015-2024 год. Все права принадлежат их авторам! Нарушение авторских прав | Нарушение персональных данных