Глава 8. Указатели и массивы
Указатели и одномерные массивы
доступ к элементам массива
передача одномерных массивов в функцию
Указатели и двумерные массивы
массивы указателей
указатели на указатели
массивы указателей и двумерные массивы
Использование указателей при работе с обычными переменными не имеет особого смысла, а при использовании арифметических операций с указателями даже опасно, так как нет гарантии, что переменные будут расположены в памяти в том порядке, в котором они были объявлены.
Одно из основных применений указателей – работа с массивами. В языке С++ указатели и массивы тесно связаны между собой. В С++ указатель, который ссылается на массив, можно индексировать так, как если бы это было имя массива.
Указатели и одномерные массивы
Поскольку каждый элемент одномерного массива занимает в памяти компьютера одинаковое количество байтов, при этом, все элементы массива расположены подряд, это позволяет эффективно использовать указатели и арифметические операции при работе с ним.
При объявлении массива ему выделяется память, однако, после этого имя массива воспринимается как константный указатель того типа, к которому относятся элементы массива, и содержит адрес первого элемента (элемент с нулевым индексом) массива.
Имя массива является константным указателем на начало массива.
Применение операции адреса (&) к первому элементу массива даст адрес массива. Например, если объявлен массив
int mas[6];
то записи mas и &mas[0] эквивалентны и определяют адрес 1-го элемента массива, т.е. адрес самого массива. Оба значения являются константами типа указатель, их нельзя изменять на протяжении всей работы программы. Однако эти значения можно присваивать переменным типа указатель:
int mas[6];
int * p;
p = &mas[0]; // или p = mas; – указателю p
// присваивается адрес массива
Индекс элемента массива означает смещение элемента от начала массива на величину, равную
индекс * sizeof(тип)
байтов, где тип – тип элементов массива.
Используя операцию разыменования (*), действие операции индексирования ([]) можно объяснить так:
имя_массива[индекс] = *(имя_массива + индекс)
Поэтому, например, для массива mas записи
mas[i] и *(mas + i) обозначают одну величину – значение соответствующего элемента массива, а записи
&mas[i] и (mas + i) обозначают один адрес элемента.
Индекс элемента массива определяет не его номер, а смещение относительно начала массива. Именно этим объясняется то, что индексы массивов начинаются с нуля.
Доступ к элементам массива
Если указателю присвоен адрес массива, то доступ к элементам массива можно осуществлять как с помощью индексирования имени массива, так и с помощью введенного указателя на начало массива. Если указатель p указывает на некоторый элемент массива, то p + 1 указывает на следующий элемент, p + i – на i-ый элемент после p, а p – i – на i-ый элемент перед p. Другими словами, если p указывает на mas[0], то:
(p+i) – адрес элемента mas[i]
*(p+i) – содержимое элемента mas[i]
Отсюда следует, что доступ к i-ому элементу одномерного массива mas, адрес начала которого хранится в указателе p, возможен следующими способами:
mas[i] *(mas + i) p[i] *(p + i)
При работе с массивами возможен любой из предлагаемых вариантов. Однако следует отметить, что компилятор преобразует все индексные выражения в адресные, т.е. встречая запись mas[i], компилятор преобразует её в *(mas+i), а запись p[i] – в *(p + i).
Указатель, который ссылается на массив, можно индексировать так, как если бы это было имя массива. Доступ к элементам массива возможен как с помощью индексов, так и с помощью указателей.
Несмотря на то, что объявления
int mas[6]; и int * p;
во многом схожи, они не являются полными аналогами.
Между именем массива и указателем, имеющим значение адрес массива, имеется существенное различие. Указатель – это переменная, содержащая некоторый адрес, поэтому можно использовать операторы:
int mas[] = {1, 2, 3, 4, 5};
int *p; p = mas;
int y = *p; // содержимое mas[0] присваивается y
*p++; p++;
(*p)++;
Выражение *p++ разыменовывает указатель *p и передвигает его к следующему элементу массива. Это возможно, поскольку известен тип адресуемых данных, а значит, и их размер. Выражение (*p)++ – разыменовывает указатель *p и к значению добавляет единицу.
А имя массива – это указатель-константа, поэтому следующие операторы не допускаются:
mas = p; mas++; *mas++;
Язык С++ позволяет программам разыменовывать имена массивов с помощью такого выражения, как *имя_массива:
int mas[5];
int x = *mas; // x = mas[0]
*mas = 77; // mas[0] = 77;
cout<<&mas<<" "<<&mas[0]; // вывод адреса массива mas
// Пример 8.1 Адресация элементов одномерного массива mas.
#include <iostream>
using namespace std;
const int n = 5;
int main(){
int mas[n]; // массив из n целых чисел
int * p = mas; // p – указатель на массив типа int
for(int i = 0; i < n; i++) // инициализация элементов массива,
mas[i] = i; // используя индекс
for(int i = 0; i < n; i++) // вывод массива, используя
cout<<*p++<<' '; // указатель p – 0 1 2 3 4
cout<<endl;
for(int i = 0; i < n; i++){
p = &mas[i];
cout<<*p<<' '; // 0 1 2 3 4
}
cout<<endl;
for(int i = 0; i < n; i++)
cout<<*(mas+i)<<' '; // 0 1 2 3 4
cout<<endl;
p = mas; *p = 5;
for(int i = 0; i < n; i++)
cout<<(*p)++<<' '; // 5 6 7 8 9
cout<<endl;
for(int * p = mas; p < mas + n; p++) // обнуление элементов
*p = 0; // массива mas – 0 0 0 0 0
system("pause");
}
Следует отметить, что нельзя выполнить оператор
int *q = &mas; // нельзя, т.к. типы не совпадают
но, выполнив операцию приведения типа, можно выполнить следующие операторы:
int *q = (int*)&mas; // можно, выполнили приведение типа
cout<<q<<endl; // можно, вывод адреса массива mas