Читать интересную книгу Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 181 182 183 184 185 186 187 188 189 ... 337
проверяют выход за пределы допустимого диапазона. И снова здесь нам приходится непосредственно работать с физической памятью, так как на системную поддержку рассчитывать не приходится.

 

 А не мог ли компилятор как-то увидеть, что массив lac содержит только двадцать элементов, так что выражение lac[200] — это ошибка? В принципе мог бы, но, как нам известно, в настоящее время не существует ни одного такого компилятора. Дело в том, что отследить границы массива на этапе компиляции невозможно в принципе, а перехват простейших ошибок (таких как приведены выше) не решает всех проблем. 

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

Указатель может ссылаться на элемент массива. Рассмотрим пример.

double ad[10];

double* p = &ad[5]; // ссылается на элемент ad[5]

Указатель p ссылается на переменную типа double, известную как ad[5].

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

*p =7;

p[2] = 6;

p[–3] = 9;

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

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

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

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

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

Аналогично,

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

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

 

 Использование операций +, –, += и –= для переноса указателей называется арифметикой указателей (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 несколько десяткой лет назад, и отменить их невозможно, не выбросив в мусорную корзину огромное количество программ. Частично это объясняется тем, что арифметика указателей обеспечивает определенное удобство в некоторых низкоуровневых приложениях, например в механизме управления памятью.

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

 

 Имя массива относится ко всем элементам массива. Рассмотрим пример.

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]). Возможно, вы заметили, что такое обозначение дает очень небольшое преимущество, и мы с вами согласны. Одна из причин, по которым имена массивов могут превращаться в указатели, состоит в желании избежать передачи большого объема данных по значению. Рассмотрим пример.

int strlen(const char a[]) // аналогична стандартной

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

{

  int count = 0;

  while (a[count]) { ++count; }

  return count;

}

char lots [100000];

void f()

{

 int nchar = strlen(lots);

 // ... 

Наивно (но частично обоснованно) мы могли бы ожидать, что при выполнении этого вызова будут скопированы 100 тыс. символов, заданных как аргумент функции strlen(), но этого не происходит. Вместо этого объявление аргумента char p[] рассматривается как эквивалент объявления char* p, а вызов strlen(lots) — как эквивалент вызова strlen(&lots[0]). Это предотвращает затратное копирование, но должно вас удивить. Почему вы должны

1 ... 181 182 183 184 185 186 187 188 189 ... 337
На этом сайте Вы можете читать книги онлайн бесплатно русская версия Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп.
Книги, аналогичгные Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп

Оставить комментарий