Доступ к элементам массива с помощью указателя

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

int arr[3] = {1, 2, 3};
std::cout << arr[1] << std::endl;     // 2
std::cout << *(arr + 1) << std::endl; // 2
std::cout << *(1 + arr) << std::endl; // 2
std::cout << 1[arr] << std::endl;     // 2

Последняя инструкция может показаться странной. Однако, если учесть, что выражение 1[arr] воспринимается компилятором как *(1 + arr), то все встанет на свои места.

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

int *p = nullptr;
int arr[3] = {1, 2, 3};
p = arr;
std::cout << *p << std::endl;       // 1
std::cout << *(p + 1) << std::endl; // 2
std::cout << *(p + 2) << std::endl; // 3

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

p = &arr[0]; // Эквивалентно: p = arr;

Таким же образом можно присвоить указателю адрес произвольного элемента массива, например, третьего:

int *p = nullptr;
int arr[3] = {1, 2, 3};
p = &arr[2];                  // Указатель на третий элемент массива
std::cout << *p << std::endl; // 3

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

std::cout << *p << std::endl;

Указатели часто используются для перебора элементов массива. В качестве примера создадим массив из трех элементов, а затем выведем значения с помощью цикла for:

const int ARR_SIZE = 3;
int *p = nullptr, arr[ARR_SIZE] = {10, 20, 30};
// Устанавливаем указатель на первый элемент массива
p = arr; // Оператор & не указывается!!!
for (int i = 0; i < ARR_SIZE; ++i) {
   std::cout << *p << std::endl;
   ++p; // Перемещаем указатель на следующий элемент
}
p = arr; // Восстанавливаем положение указателя

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

В следующей строке объявляется указатель на тип int и массив. Количество элементов массива задается константой ARR_SIZE. При объявлении массив инициализируется начальными значениями.

Внутри цикла for выводится значение элемента на который ссылается  указатель, а затем значение указателя увеличивается на единицу (++p;). Обратите внимание на то, что изменяется адрес, а не значение элемента массива. При увеличении значения указателя используются правила адресной арифметики, а не правила обычной арифметики. Увеличение значения указателя на единицу означает, что значение будет увеличено на размер типа. Например, если тип int занимает 4 байта, то при увеличении значения на единицу указатель вместо адреса 0x0012FF30 будет содержать адрес 0x0012FF34. Значение увеличилось на 4, а не на 1. В нашем примере вместо двух инструкций внутри цикла можно использовать одну:

std::cout << *p++ << std::endl;

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

std::cout << *(p++) << std::endl;

Если скобки расставить так:

std::cout << (*p)++ << std::endl;

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

Указатель можно использовать в качестве переменной-счетчика в цикле for. В этом случае начальным значением будет адрес первого элемента массива, а условием продолжения — адрес меньше адреса первого элемента плюс количество элементов. Приращение осуществляется аналогично обычному, только вместо обычной арифметики применяется адресная арифметика. Пример:

const int ARR_SIZE = 3;
int arr[ARR_SIZE] = {10, 20, 30};
for (int *p = arr, *p2 = arr + ARR_SIZE; p < p2; ++p) {
   std::cout << *p << std::endl;
}

Вывести все значения с помощью цикла while и указателя можно так:

const int ARR_SIZE = 3;
int *p = nullptr, i = ARR_SIZE, arr[ARR_SIZE] = {10, 20, 30};
p = arr;
while (i-- > 0) {
   std::cout << *p++ << std::endl;
}
p = arr;

Учебник C++ (MinGW-W64)
Учебник C++ (MinGW-W64) в формате PDF

Помощь сайту

ЮMoney (Yandex-деньги): 410011140483022

ПАО Сбербанк:
Счет: 40817810855006152256
Реквизиты банка:
Наименование: СЕВЕРО-ЗАПАДНЫЙ БАНК ПАО СБЕРБАНК
Корреспондентский счет: 30101810500000000653
БИК: 044030653
КПП: 784243001
ОКПО: 09171401
ОКОНХ: 96130
Скриншот реквизитов