Хотя на настоящий момент смысл может быть и неясен, но следует заметить, что арифметические действия с указателями допустимы также для нулевых указателей (см. раздел 2.3.2) и для указателей на объекты, не являющиеся массивом. В последнем случае указатели должны указывать на тот же объект или следующий после него. Если p
— нулевой указатель, то к нему можно добавить (или вычесть) целочисленное константное выражение (см. раздел 2.4.4) со значением 0. Можно также вычесть два нулевых указателя из друг друга, и результатом будет 0.
Результатом добавления целочисленного значения к указателю является указатель. Если полученный указатель указывает на элемент, то к его значению можно обратиться:
int ia[] = {0,2,4,6,8}; //
int last = *(ia + 4); //
//
Выражение *(ia + 4)
вычисляет адрес четвертого элемента после ia
и обращается к значению полученного указателя. Это выражение эквивалентно выражению ia[4]
.
Помните, в разделе 3.4.1 обращалось внимание на необходимость круглых скобок в выражениях, содержащих оператор обращения к значению и точечный оператор. Аналогично необходимы круглые скобки вокруг части сложения указателей:
last = *ia + 4; //
Этот код обращается к значению ia
и добавляет 4
к полученному значению. Причины подобного поведения рассматриваются в разделе 4.1.2.
Как уже упоминалось, в большинстве мест, где используется имя массива, в действительности используется указатель на первый элемент этого массива. Одним из мест, где компилятор осуществляет это преобразование, является индексирование массива.
int ia[] = {0,2,4,6,8}; //
Рассмотрим выражение ia[0]
, использующее имя массива. При индексировании массива в действительности индексируется указатель на элемент в этом массиве.
int i = ia[2]; //
//
int *p = ia; //
i = *(p + 2); //
Оператор индексирования можно использовать для любого указателя, пока он указывает на элемент (или позицию после конца) в массиве.
int *p = &ia[2]; //
int j = p[1]; //
//
int k = p[-2]; //
Последний пример указывает на важное отличие между массивами и такими библиотечными типами, как vector
и string
, у которых есть операторы индексирования. Библиотечные типы требуют, чтобы используемый индекс был беззнаковым значением. Встроенный оператор индексирования этого не требует. Индекс, используемый со встроенным оператором индексирования, может быть отрицательным значением. Конечно, полученный адрес должен указывать на элемент (или позицию после конца) массива, на который указывает первоначальный указатель.
В отличие от индексов для векторов и строк, индекс встроенного массива не является беззнаковым.
Упражнение 3.34. С учетом, что указатели p1
и p2
указывают на элементы в том же массиве, что делает следующий код? Какие значения p1
или p2
делают этот код недопустимым?
p1 += p2 - p1;
Упражнение 3.35. Напишите программу, которая использует указатели для обнуления элементов массива.
Упражнение 3.36. Напишите программу, сравнивающую два массива на равенство. Напишите подобную программу для сравнения двух векторов.
Хотя язык С++ поддерживает строки в стиле С, использовать их в программах С++ не следует. Строки в стиле С — на удивление богатый источник разнообразных ошибок и наиболее распространенная причина проблем защиты.