Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
28
Добавлен:
17.04.2015
Размер:
78.15 Кб
Скачать

5 4 2 1 3 // Порядок интерпретации описания

16. Многоуровневая адресация

В языке Си можно использовать многоуровневую косвеную адресация, то есть косвеную адресация 1,2 и т.д. уровней. При этом для объявления и обращения к значениям с помощью указателей можно использовать соответственно несколко символов звёздочка *.Звёздочки при объявлении как бы уточняют назначение имени переменной, определяя уровень косвеной адресации для обращения к значениям с помощью этих указателей.Пример объявления переменной и указателей для многоуровневой косвеной адресации значений дан ниже:

int i=123; //где i-имя переменной

int *pi=&i; //pi –указатель на переменную

int **ppi=π //ppi-указатель на ‘указатель на переменную’

int ***pppi=&ppi; //pppi-указатель на ‘указатель на ‘указатель на переменную’’.

Для обращения к значениям с помощью указателей можно применять следующее правило, жёстко связывающее форму обращения с объявлением этих указателей:

Полное количество звёздочек косвеной адресации, равное количеству звёздочек при объявлении указателя, определяет значение переменной.

Уменьшение количества звёздочек косвенной адресации добавляет к имени переменной слово ‘указатель’, причём этих слов может быть столько, сколько может быть уровней косвенной адресации для этих имён указателей, то есть столько, сколько звёздочек стоит в объявлении указателя.

// Указатель на указатель на указатель

#include <stdio.h>

#include <math.h>

#include <stdlib.h>

int main()

{

int i=10,*pi,**ppi,***pppi;

pi=&i;

ppi=π

pppi=&ppi;

printf("Содержимое ячейки i= %i\n",i);

printf("Адрес ячейки pi= %i\n",pi);

printf("Адрес адреса ячейки ppi= %i\n",ppi);

printf("Адрес адреса адреса адреса ячейки pppi= %i\n",pppi);

system("PAUSE");

return 0;

}

//Содержимое ячейки i=10

//Адрес ячейки i 2293620

//Адрес адреса ячейки 2293616

//Адрес адреса адреса ячейки 2293612

17. Операции над указателями.

Язык Си предоставляет возможности для выполнения над указателя-ми операций присваивания, целочисленой арифметики и сравнений.

На языке Си можно:

Присвоить указателю значение адреса данных, или нуль.

Увеличить (уменьшить) значение указателя

Прибавить (вычесть) из значения указателя целое число

Сложить или вычесть значение одного указателя из другово

Сравнить два указателя с помощью операций отношения.

Переменной-указателю можно задать значение с помощью одного из способов:

Присвоить указателю адрес переменной, имеющей место в оперетивной памяти, или нуль, например: ptri=&i; ptri=NULL;

Объявить указатель вне функции (в том числе main) либо в любой функции, снабдив его описателем stastic, при этом начальным значением указателя является нулевой адрес (NULL).

Присвоить указателю значение другого указателя, который к этому времени уже инициализирован (имеет определённое значение), нап-ример: ptri=ptrj; -это двойное указание одной и той же переменной.

Присвоить переменной-указателю значение с помощью функций malloc и calloc.

Изменение значений указателя можно производить с помощью операций +, ++, -, --. Бинарные операции (+ и -) можно выполнять над указателями, если оба указателя ссылаются на переменные одного типа, так как объём оперативной памяти для различных типов данных может быть разный. Например, значение типа char занимает 1(один) байт, значение типа int занимает 4 байта, а под значение типа float выделяется аж 4(четыре) байта. Добавление 1 к указателю добавит ‘квант памяти’ ,то есть количество байтов, которое занимает одно значение адресуемого типа. Для указателя на элементы массива это означает, что осуществляется переход к адресу следующего элемента массива, а не следующего байта. То есть значение указателя при переходе от элемента к элементу массива целых чисел будет увеличиваться на 4, а типа float-на 4.

В языке Си++ связь между указателями и массивами настолько тесная, что программисты обычно предпочитают использовать указатели при работе с массивами. Поскольку массивы являются некоторым аналогом указателей, язык Си позволяет программам разыминовывать имена массивов с помощью такого выражения как *имя_массива, например:

int mas[10],*ptrm;

ptrm=&mas[0];

*prtm==mas[0]==*(mas+0) ; -значение нулевого элемента массива mas

*(ptrm+i)==mas[i]==*(mas+i); --значение i-го элемента массива mas

А операции над элементами массива mas можно представить в виде:

*mas+2==mas[0]+2; *(mas+i)-3==mas[i]-3;

А вот задачка, предложенная на собеседовании в одной софтверной фирме:

*(&(mas[i+1])+2)++;

ptrm==&mas[i+1]; упрощаем данное выражение, i здесь не играет роли

ptrm+2==&(mas[i+1])+2; здесь указатель переводится на 2 элемента вперёд

*ptrm++==(*ptrm=*ptrm+1); здесь содержимое ячейки массива извлекается и к нему прибавляется единичка

Если указатель имеет префиксные (слева от имени указателя) и постфиксные (справа от имени указателя) операции, то они выполняются в следующей последовательности:

Префиксные операции в последовательности справа налево.

Использование значения, полученного после выполнения префиксных операций

Постфиксная операция над указателем.

Например в выражении *p++ сначала выполняется префиксная операция над указателем ,то есть определяется значение *p-содержимое, расположенное по адресу px, а затем выполняется посфиксная операция ++ увелечение значения указателя на квант памяти, то есть на 2 байта (если указатель типа int)

А, например в выражениии (++(*p)+2) сначала:

*p -так как префиксные операции выполняются справа налево.

*p=*p+1 -самая ‘левая’ префиксная операция

+2 -выполнение посфиксной операции

Типичные ошибки при работе с указателями. Основная ошибка, которая периодически возникает даже у опытных программистов – указатель ассоциируется с адресуемой им памятью. Память – это прежде всего ресурс, а указатель – ссылка на него. Здесь же отметим наиболее грубые ошибки:

· неинициализированный указатель.После определенияуказательссы-лается «в никуда», тем не менее программист работает через него с переменной или массивом, записывая данные по случайным адресам;

· несколько указателей, ссылающихся на общий массив – это всетаки один массив, а не несколько. Если программа работает с несколькими массивами, то они должны либо создаваться динамически, либо бра-ться из двумерного массива;

· выход указателя за границы памяти. Например, конец строки отме-чается символов ‘\0’, начало же формально соответствует начальному положению указателя. Если в процессе работы со строкой требуется возвращение на ее начало, то начальный указатель необходимо запо-минать, либо дополнительно отсчитывать символы.

В процессе определения указателей мы рассмотрели основные операции над ними:

· операция присваивания указателей одного типа. Назначение указателю адреса переменной p=&a есть одни из вариантов такой операции;

· операция косвенного обращения по указателю (разыменования указателя);

· операция адресной арифметики «указатель+целое» и все производные от нее.

Кроме того, имеется еще ряд операций, понимание которых не выходит за рамки уже имеющейся интерпретации указателя.

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