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

char *s;

j= k+i // ошибка, указатели нельзя складывать k= k+2;// +8 к числовому значению указателя s=s+2;// +2 к числовому значению указателя

Два указателя можно вычитать друг из друга, если они одного типа. Эта операция дает расстояние между объектами. При этом расстояние измеряется в единицах кратных размеру элемента данного типа.

Пример:

 

int x[5],*i , *r,

j ;

i = &x[0] ;

 

k = &x[4] ;

 

j = k-i ;

\\ j = 4

Отметим, что j= 4, а не 8 как можно было бы предположить исходя из того, что каждый элемент массива x[] занимает 2 байта.

6.4.4. Сравнение указателей

Если два указателя установлены на одинаковый тип данных, то их можно сравнивать в операциях: == , != , < , <= , > , >= при этом значения указателей рассматриваются просто как целые числа, а результат сравнения равен 0 (ложь) или 1 (истина).

Пример:

int *ptr1, *ptr2, a[10]; ptr1=a+5;

ptr2=a+7;

if (prt1>ptr2) a[3]=4;

В данном примере значение ptr1 меньше значения ptr2 и поэтому оператор a[3]=4 не будет выполнен.

Любой указатель можно проверить на равенство (==) или неравенство (!=) со специальным значением NULL, которое означает отсутствие адреса в указателе.

6.5. Применение указателей в выражениях

Как известно, приоритет операций имеет большое значение при вычислении выражений. Унарные операции & и * имеют более высокий приоритет, чем арифметические и поэтому выполняются раньше них.

Унарные операции *(разадресация), ++ , -- имеют одинаковый приоритет и выполняются слева направо.

Следующий пример иллюстрирует взаимодействие унарных адресных операций при вычислении выражений. Напомним, что операция присваивание это, по сути, засылка результата вычисления в переменную, стоящую слева от знака равенства.

Пример:

int x[4] = {0,2,4,6},*p , y ;

p = &x[0] ;

 

y = *p ;

\\ 1

y = *p++

\\ 2

y = ++*p

\\ 3

y = *++p

\\ 4

y = (*p)++

\\ 5

y = ++(*p)

\\ 6

В данном примере к массиву, заданному при его определении происходит обращение с помощью указателя p. Пример хорошо показывает двойственную природу указателя: с одной стороны указатель имеет свое собственное значение адрес переменной, с другой стороны он дает возможность обращения к самой переменной. Поэтому, манипулируя с указателем, мы имеем возможность, с одной стороны, "перебирать" элементы массива, изменяя значение самого указателя (адреса, записанного в него), с другой обращаться к элементам этого массива.

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

Строка 1.

Обращение к нулевому элементу (операция разыменования).

Пересылка нулевого элемента массива в y.

PDF created with FinePrint pdfFactory Pro trial version http://www.fineprint.com

Строка 2.

Обращение к нулевому элементу (операция разыменования).

Пересылка нулевого элемента массива в y.

Инкремент указателя (перемещение на следующий, 1-ый элемент). Строка 3.

Инкремент 1-ого элемента массива.

Пересылка 1-ого элемента массива в y.

Строка 4.

Инкремент указателя (перемещение на 2-ой элемент).

Пересылка 2-ого элемента массива в y.

Строка 5.

Инкремент 2-ого элемента массива.

Пересылка 2-ого элемента массива в y. Строка 6.

Инкремент 2-ого элемента массива.

Пересылка 2-ого элемента массива в y.

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

Вязыке С между указателями и массивами существует тесная связь. Например, когда объявляется массив в виде int array[25], то этим определяется не только выделение памяти для двадцати пяти элементов массива типа int, но и для указателя с именем array, значение которого равно адресу первого по счету (нулевого) элемента массива.

Сам массив остается безымянным, а доступ к элементам массива осуществляется через указатель с именем array. Следует отметить, что указатель array является константным, значение которого можно использовать в выражениях, но изменить нельзя.

Пример: Имя массива можно приравнять указателю, так как оно также является указателем.

int arrey[25]; int *ptr; ptr = array;

1.

*p (y=0)

0

 

2

 

4

 

 

6

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

*p (y=0)

 

 

 

 

P

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2.

 

0

 

2

 

4

 

 

6

 

p++

 

0

2

 

4

 

6

 

 

 

 

 

 

 

P

 

 

 

 

 

 

 

 

 

 

*p (y=3)

 

 

 

 

P

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

3.

x[1]++

 

 

0

 

3

 

4

 

 

6

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

0

3

 

4

 

6

 

 

 

 

 

 

 

 

 

 

 

P

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

4.

p++

 

 

 

 

 

 

 

 

 

 

 

 

 

 

*p (y=4)

 

 

 

 

 

 

P

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

0

 

 

3

 

4

 

6

 

 

0

3

 

4

 

6

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

P

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

P

 

 

x[2]++

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

5.

*p (y=4)

 

0

 

 

3

 

4

 

6

 

 

0

3

 

5

 

6

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

P

 

 

6.

x[2]++

 

 

 

 

 

 

 

 

 

 

 

 

P

 

 

*p (y=6)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

0

 

3

 

6

 

 

6

 

 

 

0

3

 

6

 

6

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

P

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

P

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Рис. 6.2. Обращение к массиву с применением указателей.

Здесь указатель ptr устанавливается на адрес первого элемента массива, причем присваивание ptr=array можно записать в эквивалентной форме: ptr=&array[0].

Для доступа к элементам массива существует два различных способа.

PDF created with FinePrint pdfFactory Pro trial version http://www.fineprint.com

Первый способ связан с использованием обычных индексных выражений в квадратных скобках, например, инструкция array[10] = 35 записывает константу 35 в одиннадцатый элемент массива array (в элемент с индексом 10), а инструкция array[i+4] = rev записывает значение переменной rev в (i+4)-ый элемент массива array.

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

*(array+10)

= 35

*(array+i+4)= 7.

 

Рассматривая имя массива как указатель, мы проделали те же действие, что и в предыдущем примере. Операция * в данном случае – это "разименование" или обращение к объекту через указатель.

Подобным же образом можно обращаться к элементам массива с помощъю независимого (переменного) указателя.

Пример: Сравнение двух способов доступа к элементам массива.

//определение массива mas и указателя q char mas[100] , *q;

//инициализация указателя установка на нулевой элемент массива q = mas;

//Доступ к элементам массива

//(следующие записи эквивалентны):

mas[0];

//

*q

mas[10];

//

*(q+10)

mas[n-1];

//

*(q+n-1)

Использование индексации для доступа к массиву более наглядно, но использование указателя более эффективно. При индексировании элемента массива, его адрес каждый раз вычисляется по начальному адресу (имени массива) и смещению (индексу), что снижает эффективность полезных вычислений. Поскольку быстродействие программы одно из важнейших её свойств, предпочтительнее использовать указатели-переменные.Сравним два варианта работы с массивами на примере функции putstr().

Пример: Вывод строки символов на экран терминала.

//создание строки (массив символов с именем s

//с нулевым элементом в конце)

char s[]="строка текста"; int i;

for (i=0 ; s[i] != 0 ; ++i) putchar(s[i]);

/* Доступ с помощью указателя*/

while (*s !=0) putchar (*s++);

}

Первая часть примера работает с массивом при помощи индексирования его элементов, инструкция putchar(s[i]) выводит один символ на экран дисплея. В цикле перебираются элементы массива s до тех пор, пока не встретится элемент с нулевым значением.

Вторая часть примера делает ту же работу, рассматривая имя массива как указатель.

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

Пример: Обработка элементов массива.

. . .

 

char s[20];

// Определение массива типа char

// Определение и установка указателя на 0-ой элемент массива

char *p = &s[0];

// аналог *p=s

*(p+10)= 'l';

//Записываем символ 'l' в 11-ый элемент

p++;

// установка указателя на 2-ой элемент

*(p+10) = 'k';

/ Меняем значение 12-ого элемента

if ( *(p-1) != ' \n' )

// Проверка 1-ого элемента

...

Следует помнить, что индексация ведется с нуля, поэтому первый элемент массива имеет нулевой индекс, второй элемент первый индекс и так далее. В примере массив и указатель имеют тип char (размер элемента = 1 байт). Прибавляя к адресу 1, мы сдвигаемся на один байт. А как быть, если мы работаем, например, с типом int, у которого размер - 4 байта? Оказывается, что и в этом случае все показанные выше арифметические операции с указателями дадут правильный результат. Разумеется, к адресу, будет добавляться не 1, а 4, но нам об этом даже не придется думать:

Пример:

PDF created with FinePrint pdfFactory Pro trial version http://www.fineprint.com

. . .

 

int s[20];

// Определение массива типа char

// Определение и установка указателя на 0-ой элемент массива

int *p = &s[0];

// аналог *p=s

*(p+10)= 'l';

//Записываем символ 'l' в 11-ый элемент

p++;

// установка указателя на 2-ой элемент

*(p+10) = 'k';

/ Меняем значение 12-ого элемента

if ( *(p-1) != ' \n' )

// Проверка 1-ого элемента

...

 

Пример, приведенный ниже, отражает на экране абсолютные значения адресов элементов массивов различных типов, что позволяет наглядно увидеть особенности арифметики указателей.

Пример: Вывести на экран элементы массива и их адреса. #include <stdio.h>

void main()

 

 

{

 

 

char s[]="string",*p;

// работа с массивом типа char

for (p=s; *p !=0; p++)

 

//p+1(в абсолютных значениях)

printf("s: %c\tp=%p\n",*p , p);

 

int q[]={10,20,30,40,50,0},*p1;

// работа с массивом типа int

for (p1=q; *p1 !=0; p1++) //p+4(в абсолютных значениях) printf("q[0]=%d\tp1=%p\n",*p1,p1);

}

Для вывода значения указателя (адреса элемента массива) на экран дисплея в функции printf() используется спецификатор формата %p (тип укаазатель).

Итак, мы разобрали одно из назначений указателей обращение к элементам массивов

(или других сложных типов данных).

Другое, не менее важное применение указателей передача параметров и возврат значений из функций. Переменные, объявленные в функциях это локальные (временные) переменными, которые уничтожаются при выходе из функции, поэтому часто возникает необходимость обращения к переменным, определенным за пределами функции, то есть к внешним объектам по отношению к функции. Когда управление передается в функцию (или в программный блок), без применения указателей программа имеет доступ только к параметрам и переменным функции ("локальная память"), переменные же в других функциях (или блоках) недоступны ("внешняя, по отношению к функции память").

Указатели в параметрах функции имеют два важных применения:

передачу массива в функцию;

доступ к "внешней" памяти из тела функции (или блока);

возврат из функции более одного значения.

Более подробно о применении указателей при работе с функциями этом смотрите в разделе "функции".

6.7. Ссылочный тип данных

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

Ссылка позволяет косвенно манипулировать объектом, точно так же, как это делается с помощью указателя. Однако эта косвенная манипуляция не требует специального синтаксиса (операции разименования "*"), необходимого для указателей.

Подводя итог вышесказанному, можно отметить, что ссылку можно рассматривать как альтернативное имя объекта, а также как безопасный вариант указателя.

Ссылки имеют три особенности, отличающие их от указателей:

При объявлении ссылка обязательно инициализируется, то есть должна быть направлена на уже существующий объект.

Ссылка пожизненно указывает на один и тот же объект.

При обращении к объекту по ссылке не требуется указывать операцию разыменования (*), так как она выполняется автоматически.

Создание ссылки похоже на создание обычной переменной тип, имя, инициализатор. Но справа от типа надо поставить символ &, и обязательно явно инициализировать ссылку, то есть в момент создания связать с тем объектом, на который она будет ссылаться:

PDF created with FinePrint pdfFactory Pro trial version http://www.fineprint.com

Пример:

 

int i;

// целая переменная

int& ref = i; // определение ссылки на переменную i

int& ref1;

// ошибка: ссылка должна быть инициализирована

После этого можно пользоваться ссылкой ref так же, как самой переменной i:

i = 8;

// "прямой" доступ к i

ref = 8;

// "косвенный" доступ к i через ссылку

При работе со ссылками необходимо учитывать следующее:

Ссылка не может существовать сама по себе она обязательно связана, с какой либо переменной.

У ссылки нет своего адреса если взять адрес ссылки, то получим адрес связанной с ней переменной.

Все, что мы можем сделать со ссылкой это создать ее.

Ссылку нельзя уничтожить или перенаправить на другую переменную. Она уничтожится автоматически при выходе из блока, в котором была объявлена. Все операции со ссылками реально воздействуют на адресуемые ими объекты. В том числе и операция взятия адреса.

Пример:

 

 

int val = 1024;

 

int &refVal = val;

// ссылка на val

refVal += 2;

// val=val+2

int ii = refVal;

// ii = val,

// устанавливает указатель pi на переменную val. int *pi = &refVal;

Если несколько ссылок определяются в одной инструкции через запятую, перед каждой ссылкой должен стоять амперсэнд (&)

int &rval3 = val3, &rval2 = val2;

Если указателю присвоить нулевое значение, это означает, что указатель не установлен (не указывает ни на один объект), если же нулевое значение присвоить ссылке, это будет означать, что ссылка связана с переменной, значение которой равно нулю.

Пример:

int *pi = 0; // pi не указывает ни на какой объект. const int &ri = 0;

означает примерно следующее: int temp = 0;

const int &ri = temp;

Что касается операции присваивания, то работа с указателями тоже отличается от аналогичной работы со ссылками, следующий пример демонстрирует это:

//работа с указателями

//определение переменных val1 и val2

int val1 = 1024, val2 = 2048;

//установка указателей

int *pi1 = &val1, *pi2 = &val2; pi1 = pi2;

переменная val1, на которую указывает pi1, остается неизменной, а указатель pi1 получает значение адреса переменной val2. Таким образом, pi1 и pi2 теперь указывают на один и тот же объект val2.

Проделаем подобные операции со ссылками.

int &ri1 = val1, &ri2 = val2; //создание ссылок ri1 и ri2 ri1 = ri2;

Операция присваивания ri1 = ri2 меняет саму переменную val1 (записывает в неё значение из val2), но ссылка ri1 по-прежнему адресует val1. Пример наглядно показывает, что с точки зрения синтаксиса работа со ссылкой ничем не отличается от работы с переменной, на которую она ссылается.

Следует отметить, что механизм ссылок отсутствует в классическом С, но все современные компиляторы С++ поддерживают ссылки.

Ссылки редко используются как самостоятельные объекты, обычно они употребляются в качестве формальных параметров и возвращаемых значений функций.

PDF created with FinePrint pdfFactory Pro trial version http://www.fineprint.com

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]