Литмир - Электронная Библиотека
A
A

Этот указатель можно индексировать и разыменовывать.

*p =7;

p[2] = 6;

p[–3] = 9;

Теперь ситуация выглядит следующим образом.

Программирование. Принципы и практика использования C++ Исправленное издание - _203.png

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

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

p += 2; // переносим указатель p на два элемента вправо

Итак, приходим к следующей ситуации.

Программирование. Принципы и практика использования C++ Исправленное издание - _204.png

Аналогично,

p –= 5; // переносим указатель p на пять элементов вправо

В итоге получим следующее.

Программирование. Принципы и практика использования C++ Исправленное издание - _205.png

 

Программирование. Принципы и практика использования C++ Исправленное издание - _002.png
 Использование операций
+
,
,
+=
и
–=
для переноса указателей называется арифметикой указателей (pointer arithmetic). Очевидно, поступая так, мы должны проявлять большую осторожность, чтобы не выйти за пределы массива.

p += 1000;     // абсурд: p ссылается на массив, содержащий

               // только 10 чисел

double d = *p; // незаконно: возможно неправильное значение

               // (совершенно непредсказуемое)

*p = 12.34;    // незаконно: можно задеть неизвестные данные

К сожалению, не все серьезные ошибки, связанные с арифметикой указателей, легко обнаружить. Лучше всего просто избегать использования арифметики указателей.

Наиболее распространенным использованием арифметик указателей является инкрементация указателя (с помощью оператора

++
) для ссылки на следующий элемент и декрементация указателя (с помощью оператора
––
) для ссылки на предыдущий элемент. Например, мы могли вы вывести элементы массива ad следующим образом:

for (double* p = &ad[0]; p<&ad[10]; ++p) cout << *p << '\n';

И в обратном порядке:

for (double* p = &ad[9]; p>=&ad[0]; ––p) cout << *p << '\n';

Это использование арифметики указателей не слишком широко распространено. Однако, по нашему мнению, последний (“обратный”) пример небезопасен. Почему

&ad[9]
, а не
&ad[10]
? Почему
>=
, а не
>
? Эти примеры были бы одинаково хороши (и одинаково эффективны), если бы мы использовали индексацию. Кроме того, они были бы совершенно эквивалентны в классе
vector
, в котором проверка выхода за пределы допустимого диапазона осуществляется проще.

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

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

double* p1 = &ad[0];

double* p2 = p1+7;

double* p3 = &p1[7];

if (p2 != p3) cout << "impossible!\n";

 

Программирование. Принципы и практика использования C++ Исправленное издание - _001.png
 В основном это произошло по историческим причинам. Эти правила были разработаны для языка C несколько десяткой лет назад, и отменить их невозможно, не выбросив в мусорную корзину огромное количество программ. Частично это объясняется тем, что арифметика указателей обеспечивает определенное удобство в некоторых низкоуровневых приложениях, например в механизме управления памятью.

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

 

Программирование. Принципы и практика использования C++ Исправленное издание - _001.png
 Имя массива относится ко всем элементам массива. Рассмотрим пример.

char ch[100];

Размер массива

ch
, т.е.
sizeof(ch)
, равен 100. Однако имя массива без видимых причин превращается в указатель.

char* p = ch;

Здесь указатель

p
инициализируется адресом
&ch[0]
, а размер
sizeof(p)
равен 4 (а не 100). Это свойство может быть полезным. Например, рассмотрим функцию
strlen()
, подсчитывающую количество символов в массиве символов, завершающимся нулем.

int strlen(const char* p) // аналогична стандартной

                          // функции strlen()

{

  int count = 0;

  while (*p) { ++count; ++p; }

  return count;

}

Теперь можем вызвать ее как с аргументом

strlen(ch)
, так и с аргументом
strlen(&ch[0]
). Возможно, вы заметили, что такое обозначение дает очень небольшое преимущество, и мы с вами согласны. Одна из причин, по которым имена массивов могут превращаться в указатели, состоит в желании избежать передачи большого объема данных по значению. Рассмотрим пример.

246
{"b":"847443","o":1}