Cpp указатель на указатель

10.21 – Указатели на указатели и динамические многомерные массивы

Этот урок не является обязательным, он предназначен для продвинутых читателей, которые хотят узнать больше о C++. Никакие будущие уроки не будут основаны на этом уроке.

Указатель на указатель – это именно то, что можно ожидать из названия: указатель, содержащий адрес другого указателя.

Указатели на указатели

Обычный указатель на int объявляется с помощью одной звездочки:

int *ptr; // указатель на int, одна звездочка

Указатель на указатель на int объявляется с помощью двух звездочек

int **ptrptr; // указатель на указатель на int, две звездочки

Указатель на указатель работает так же, как обычный указатель – вы можете выполнять через него косвенное обращение, чтобы получить значение, на которое он указывает. А поскольку это значение само по себе является указателем, вы можете снова выполнить косвенное обращение уже через этот указатель, чтобы перейти к базовому значению. Эти косвенные обращения могут выполняться последовательно:

int value = 5; int *ptr = &value; // Косвенное обращение через указатель на int для получения значения int std::cout 

Показанный выше код напечатает:

Обратите внимание, что вы не можете установить указатель на указатель, используя непосредственно значение:

int value = 5; int **ptrptr = &&value; // недопустимо

Это связано с тем, что оператор адреса ( operator& ) требует l-значение (l-value), но &value является r-значением (r-value).

Однако указатель на указатель может иметь значение null:

int **ptrptr = nullptr; // до C++11 используйте вместо этого 0

Массивы указателей

Указатели на указатели имеют несколько применений. Чаще всего они используется для динамического размещения массива указателей:

int **array = new int*[10]; // распределяем массив из 10 указателей int

Это работает так же, как обычный динамически размещаемый массив, за исключением того, что элементы массива имеют тип «указатель на int », а не int .

Двумерные динамически размещаемые массивы

Другое распространенное использование указателей на указатели – облегчение динамического размещения многомерных массивов (для обзора многомерных массивов смотрите урок «10.5 – Многомерные массивы»).

В отличие от двумерного фиксированного массива, который можно легко объявить следующим образом:

Динамическое размещение двумерного массива немного сложнее. У вас может возникнуть соблазн попробовать что-то вроде этого:

int **array = new int[10][5]; // не сработает!

Здесь есть два возможных решения. Если крайнее правое измерение массива является константой времени компиляции, вы можете сделать так:

Круглые скобки здесь необходимы для обеспечения правильного приоритета. В C++11 или новее это подходящий случай для использования автоматического определения типа:

auto array = new int[10][5]; // намного проще!

К сожалению, это относительно простое решение не работает, если какое-либо не крайнее левое измерение массива не является константой времени компиляции. В этом случае мы должны немного усложнить ситуацию. Сначала мы размещаем массив указателей (как показано выше). А затем мы перебираем этот массив указателей и для каждого элемента массива размещаем еще один динамический массив. Наш динамический двумерный массив – это динамический одномерный массив динамических одномерных массивов!

int **array = new int*[10]; // размещаем массив из 10 указателей int - это наши строки for (int count = 0; count < 10; ++count) array[count] = new int[5]; // это наши столбцы

Затем мы можем получить доступ к нашему массиву, как обычно:

array[9][4] = 3; // Это то же самое, что (array[9])[4] = 3;

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

int **array = new int*[10]; // размещаем массив из 10 указателей int - это наши строки for (int count = 0; count < 10; ++count) array[count] = new int[count+1]; // это наши столбцы

Обратите внимание, что в приведенном выше примере array[0] – это массив длиной 1, array[1] - это массив длиной 2, и т.д.

Для освобождения памяти динамически размещенного с помощью этого метода двумерного массива также требуется цикл:

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

Поскольку выделение и освобождение памяти для двумерных массивов сложно, и в них легко ошибиться, часто бывает проще «сгладить» двумерный массив (размером x на y) в одномерный массив размером x * y:

// Вместо этого: int **array = new int*[10]; // размещаем массив из 10 указателей int - это наши строки for (int count = 0; count < 10; ++count) array[count] = new int[5]; // это наши столбцы // Сделаем так int *array = new int[50]; // массив 10x5, сведенный в единый массив

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

int getSingleIndex(int row, int col, int numberOfColumnsInArray) < return (row * numberOfColumnsInArray) + col; >// устанавливаем значение array[9,4] равным 3, используя наш плоский массив array[getSingleIndex(9, 4, 5)] = 3;

Передача указателя по адресу

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

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

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

Указатель на указатель на указатель на…

Также возможно объявить указатель на указатель на указатель:

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

Вы даже можете объявить указатель на указатель на указатель на указатель:

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

Заключение

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

Источник

Ссылки на указатели

Ссылки на указатели можно объявлять так же, как ссылки на объекты. Ссылка на указатель — это изменяемое значение, которое используется как обычный указатель.

Пример

В этом примере кода показана разница между использованием указателя на указатель и ссылки на указатель.

Функции Add1 и Add2 функционально эквивалентны, хотя они не называются по-разному. Разница заключается в том, что Add1 использует двойное косвенное обращение, но Add2 использует удобство ссылки на указатель.

// references_to_pointers.cpp // compile with: /EHsc #include #include // C++ Standard Library namespace using namespace std; enum < sizeOfBuffer = 132 >; // Define a binary tree structure. struct BTree < char *szText; BTree *Left; BTree *Right; >; // Define a pointer to the root of the tree. BTree *btRoot = 0; int Add1( BTree **Root, char *szToAdd ); int Add2( BTree*& Root, char *szToAdd ); void PrintTree( BTree* btRoot ); int main( int argc, char *argv[] ) < // Usage message if( argc < 2 ) < cerr char *szBuf = new char[sizeOfBuffer]; if (szBuf == NULL) < cerr // Read a text file from the standard input device and // build a binary tree. while( !cin.eof() ) < cin.get( szBuf, sizeOfBuffer, '\n' ); cin.get(); if ( strlen( szBuf ) ) < switch ( *argv[1] ) < // Method 1: Use double indirection. case '1': Add1( &btRoot, szBuf ); break; // Method 2: Use reference to a pointer. case '2': Add2( btRoot, szBuf ); break; default: cerr > > // Display the sorted list. PrintTree( btRoot ); > // PrintTree: Display the binary tree in order. void PrintTree( BTree* MybtRoot ) < // Traverse the left branch of the tree recursively. if ( MybtRoot->Left ) PrintTree( MybtRoot->Left ); // Print the current node. cout szText Right ) PrintTree( MybtRoot->Right ); > // Add1: Add a node to the binary tree. // Uses double indirection. int Add1( BTree **Root, char *szToAdd ) < if ( (*Root) == 0 ) < (*Root) = new BTree; (*Root)->Left = 0; (*Root)->Right = 0; (*Root)->szText = new char[strlen( szToAdd ) + 1]; strcpy_s((*Root)->szText, (strlen( szToAdd ) + 1), szToAdd ); return 1; > else < if ( strcmp( (*Root)->szText, szToAdd ) > 0 ) return Add1( &((*Root)->Left), szToAdd ); else return Add1( &((*Root)->Right), szToAdd ); > > // Add2: Add a node to the binary tree. // Uses reference to pointer int Add2( BTree*& Root, char *szToAdd ) < if ( Root == 0 ) < Root = new BTree; Root->Left = 0; Root->Right = 0; Root->szText = new char[strlen( szToAdd ) + 1]; strcpy_s( Root->szText, (strlen( szToAdd ) + 1), szToAdd ); return 1; > else < if ( strcmp( Root->szText, szToAdd ) > 0 ) return Add2( Root->Left, szToAdd ); else return Add2( Root->Right, szToAdd ); > > 
Usage: references_to_pointers.exe [1 | 2] where: 1 uses double indirection 2 uses a reference to a pointer. Input is from stdin. Use ^Z to terminate input. 

Источник

Зачем использовать Указатель на указатель?

Подскажите зачем использовать Указатель на указатель? И как работают двумерные массивы за счет указателей?

Зачем нужен указатель на указатель при работе с однонаправленным списком?
День добрый. Столкнулся с непониманием этой темы. В частности, совершенно непонятен алгоритм.

Реализация двоичных деревьев поиска: Зачем в параметрах функции используется указатель на указатель
Всем привет, встретил в книге такой пример добавления узла в дерево: typedef struct tree

Как получить ссылку на указатель или указатель на указатель в массиве?
В процессе реализации сортировки пузырьком натолкнулся на такую проблему: как поменять значения.

А почему нельзя передавать в ф-ю добавления элемента в стек один указатель? Почему нужен именно указатель на указатель?
Вот код ф-ии добавления элемента в стек: void push1(Node **top, int d) < // top.

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

Массив это и есть указатель на первый элемент последовательно записанной однотипной информации. здесь показано как создавать динамические двумерные массивы - http://sawersoft.ucoz.ua/blog/. 12-08-06-1

Недавно столкнулся сам с подобной проблемой, вроде допёр, что как, сейчас попробую объяснить. Есть типы данных, простые (навроде int, char, double) или созданные пользователем(ну там, какие придумаешь Rocket, Car, Child), любому типу данных нужна память, в которой он будет располагаться (ну, например, его поля - данные). Эта память выделяется при создании объекта. Можно получить адрес расположения объекта и сохранить его в указателе. Типа

int size = 5; int *pointerToSize = &size;

Таким образом указатель с одной стороны - хранит просто какой-то адрес (ну например 0x..1ef) с другой стороны - содержит информацию о том, на что именно указывает - в приведённом примере - на int. Это нужно для того, что-бы при выполнении адресной арифметики можно было не задумываясь каждый раз о размере объекта смещаться ровно на величину объекта. Т.е. если int занимает в памяти 4 байта. То pointerToSize += 1; Заставит изменить значение адреса, хранящегося по указателю на 4 байта, хоть мы в данном случае и попадём в область памяти, которая нам не принадлежит. За счет этого механизма достаточно удобно обходить массивы элементов - нужно просто иметь указатель на начало массива и прибавлять к нему столько единиц, на сколько нужно переместиться. Теперь про указатели на указатели. Указатель на некоторый объект - сам по себе является объектом и тоже хранится в памяти. Т.е. например pointerToSize хранит в себе некоторый адрес, но этот адрес ведь тоже надо где-то хранить. Где? Да всё там-же в общей памяти. Ровно на строчке int *pointerToSize = &size; она была выделена. Таким образом мы можем взять и захотеть сохранить адрес, по которому лежит сам pointerToSize:

Вот и всё - получается цепочка - где-то в памяти есть адрес, по которому лежит pointerToPointer, сам pointerToPointer хранит в себе другой адрес в памяти, по которому лежит pointerToSize, а уж pointerToSize хранит в себе адрес на конкретное число. Получается вот что - если одномерный массив легко обходить по одинарному указателю - как я писал выше, то двумерный - по двойному. Представим что строка - это одномерный массив, если 3 строки написать одна под другой - получится 2-мерный массив. Двойной указатель будет содержать в себе адрес на левый верхний угол массива, а если к этому указателю прибавить скажем 1 - он станет указывать на 1ую строчку. Т.е. он будет смещаться на размер одномерного указателя - и попадёт на адрес одномерного указателя, который в свою очередь будет указывать на первую строчку в массиве. (это всё чисто для понимания, в памяти всё будет представлено последовательно).

Источник

Читайте также:  Android java class oncreate
Оцените статью