Указатель на указатель: указатели — Указатель на указатель — что это?

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

Добавлено 9 июня 2021 в 21:53

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

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

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

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

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

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

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

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

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

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

5
5

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

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[10][5];

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

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

Но это не сработает.

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

int (*array)[5] = 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, и т.д.

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

for (int count = 0; count < 10; ++count)
    delete[] array[count];
delete[] array; // это нужно сделать в последнюю очередь

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

Поскольку выделение и освобождение памяти для двумерных массивов сложно, и в них легко ошибиться, часто бывает проще «сгладить» двумерный массив (размером 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;

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

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

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

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

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

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

int ***ptrx3;

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

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

int ****ptrx4;

Или больше, если хотите.

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

Заключение

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

Оригинал статьи:

  • 9.21 — Pointers to pointers and dynamic multidimensional arrays

Теги

arrayC++ / CppLearnCppДинамическое распределение памяти
Для начинающихМногомерный массивОбучениеПрограммированиеУказатель / Pointer (программирование)

Назад

Оглавление

Вперед

C++ — Указатели

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

Как вы знаете, каждая переменная является местом памяти, и каждая ячейка памяти имеет свой адрес, который можно получить, используя оператор ampersand (&), который обозначает адрес в памяти. Рассмотрим следующее, которое будет печатать адрес определенных переменных —


#include <iostream>

using namespace std;
int main () {
   int  var1;
   char var2[10];

   cout << "Address of var1 variable: ";
   cout << &var1 << endl;

   cout << "Address of var2 variable: ";
   cout << &var2 << endl;

   return 0;
}

Когда приведенный выше код компилируется и выполняется, он производит следующий результат:


Address of var1 variable: 0xbfebd5c0
Address of var2 variable: 0xbfebd5b6

Что такое указатели?

Указатель является переменной, значение которого является адрес другой переменной.  Как и любая переменная или константа, вы должны объявить указатель, прежде чем сможете с ним работать. Общая форма объявления переменной указателя —


type *var-name;

Здесь тип — это базовый тип указателя; он должен быть допустимым типом C ++, а var-name — это имя переменной-указателя. Звездочкой, которую вы использовали для объявления указателя, является та же самая звездочка, которую вы используете для умножения. Однако в этом утверждении звездочка используется для обозначения переменной как указателя. Ниже приведена действительная декларация указателя —


int    *ip;    // pointer to an integer
double *dp;    // pointer to a double
float  *fp;    // pointer to a float
char   *ch     // pointer to character

Фактический тип данных для всех указателей, будь то целое число, float, character или other, является тем же самым, длинным шестнадцатеричным числом, которое представляет адрес памяти.  Единственное различие между указателями разных типов данных — это тип данных переменной или константы, на которые указывает указатель.

Использование указателей в C ++

Существует несколько важных операций, которые мы будем делать с указателями очень часто. (a) Мы определяем переменную указателя. (b)Назначьте адрес переменной указателю. (c) Наконец, получите доступ к значению по адресу, доступному в переменной указателя. Это делается с помощью унарного оператора *, который возвращает значение переменной, расположенную по адресу, указанному его операндом. В следующем примере используются эти операции —


#include <iostream>

using namespace std;

int main () {
   int  var = 20;   // actual variable declaration.
   int  *ip;        // pointer variable 

   ip = &var;       // store address of var in pointer variable

   cout << "Value of var variable: ";
   cout << var << endl;

   // print the address stored in ip pointer variable
   cout << "Address stored in ip variable: ";
   cout << ip << endl;

   // access the value at the address available in pointer
   cout << "Value of *ip variable: ";
   cout << *ip << endl;

   return 0;
}

Когда приведенный выше код скомпилирован и исполнен, он производит результат следующим образом:


Value of var variable: 20
Address stored in ip variable: 0xbfc601ac
Value of *ip variable: 20

Указатели в C ++

У указателей есть много, но простых понятий, и они очень важны для программирования на С ++.  Существует несколько важных понятий указателей, которые должны быть понятны программисту на C ++ —

Нулевые указатели

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

Арифметика указателей

Существует четыре арифметических оператора, которые могут использоваться для указателей: ++, -, +, —

Указатели против массивов

Существует тесная связь между указателями и массивами.

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

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

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

C ++ позволяет иметь указатель на указатель и так далее.

Передача указателей на функции

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

Возвращаемый указатель из функций

C ++ позволяет функции возвращать указатель на локальную переменную, статическую переменную и динамически распределенную память.

Pointer to Pointer C++ — Темы Scaler

Учебное пособие по C++

Указатели на указатели в C++

Указатели на указатели в C++

Бесплатный курс C++: Learn the Essentials 90 005 Пратик Наранг

Бесплатно

5

Зарегистрировано:

26093

Начать обучение Посмотреть все курсы

Бесплатный курс C++: Learn the Essentials

Prateek Narang

Бесплатно

5

Зарегистрировано: 26093

Начало обучения

Обзор

Мы знаем, что указатель хранит адрес переменной , на которую он указывает. Тип данных указателя совпадает с типом данных переменной, поэтому, когда мы объявляем указатель на указатель, первый указатель сохраняет адрес переменной , а второй указатель сохраняет адрес первого указателя . Указатель на указатель также называется двойным указателем, и он хранит адрес другого указателя, который, в свою очередь, хранит адрес переменной, на которую он указывает.

Область действия

В этой статье рассматриваются следующие темы:

  1. Что такое указатель на указатель, также известный как двойной указатель , в C++?
  2. Как объявить двойные указатели (синтаксис) и схематическое понимание двойных указателей.

Как объявить указатель на указатель в CPP?

Объявление указателя на указатель в C++ или double-pointer

очень похоже на объявление одиночного указателя; разница только в том, что дополнительные * добавлен.

Например:

 
 `int *age;` // Объявление одиночного указателя.
`int **val;` // Объявление двойного указателя или указателя на указатель в C++.
 

Общий синтаксис обсуждается ниже:

  • Синтаксис
 
 тип данных **pointer_variable
 

Здесь тип данных — это тип данных одиночного указателя, который, в свою очередь, является типом данных переменной, адрес которой хранится в этом единственном указателе. Тип данных может быть int, float, double, или char .

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

  • Диаграмма, объясняющая концепцию двойных указателей

Чтобы понять, как работает двойной указатель или указатель на указатель в C++, рассмотрим приведенную выше диаграмму:

  1. Во-первых, у нас есть целочисленная переменная age, и ее значение равно 21. Она хранится по адресу 1000 ( по умолчанию адреса представляют собой шестнадцатеричные числа, но для простоты мы рассматриваем их как целые числа).
  2. ptr1 — это целочисленная переменная-указатель, в которой хранится адрес переменной age, т. е. 1000, и ее адрес равен 2000. Он объявляется следующим образом:
 
 int *ptr1 = &age;
 
  1. ptr2 — еще одна целочисленная переменная указателя, в которой хранится адрес переменной указателя ptr1, т. е. 2000, и ее адрес равен 3000. Он объявляется следующим образом:
 
 int **ptr2 = &ptr1;
 

Вот как мы объявляем двойной указатель или указатель на указатель в C++ и заставляем его указывать на другую переменную-указатель. Здесь мы говорим только о двойных указателях, но это может быть расширено до любой степени , например, указатель на указатель на указатель, и т. д.

Программа CPP для демонстрации указателя на указатель

  • В приведенном ниже коде мы стремимся понять простую работу двойного указателя . В коде мы объявили целочисленную переменную age , в котором хранится значение 21, и переменная с одним указателем ptr1 , в которой хранится адрес переменной age, и переменная с двойным указателем ptr2 , в которой хранится адрес ptr1 . Наконец, мы печатаем значение переменной age, используя переменную age, переменную с одним указателем и переменную с двойным указателем.
 
 #include
использование пространства имен std;
основной ()
{
   // Объявление целочисленной переменной.
   возраст = 21;
   
   // Объявление одной переменной-указателя,
   // хранение адреса переменной возраста.
   int *ptr1 = &возраст;
   
   // Объявление переменной с двойным указателем, хранящей адрес ptr1.
   интервал **ptr2 = &ptr1;
   
   // Вывод значения с использованием переменной age.
   cout<<"Значение, хранящееся в переменной age "<

Вывод:

 
 Значение, сохраненное в переменной age 21
Доступ к значению с использованием одного указателя 21
Доступ к значению с помощью двойного указателя 21
 
  • Практический вариант использования двойного указателя заключается в объявлении двойного указателя, ниже код объясняет, как объявить двумерный массив размером nXm .
 
 #include
использование пространства имен std;
основной ()
{
    // Объявить двойной указатель.
    интервал ** обр;
    
    // Динамическое выделение пространства строки.
    обр = новый int*[n];
    // Динамическое выделение пространства столбца.
    for(int i=0;i

Заключение

  • Указатель на указатель или двойной указатель хранит адрес другой переменной-указателя .
  • Двойной указатель объявляется следующим образом:
 
 тип данных **pointer_variable
 
  • Мы можем манипулировать или получать доступ к значению переменной, на которую указывает одиночный указатель, используя двойной указатель.
  • Одно из практических применений двойного указателя находится в объявлении 2d массивы .

Указатели на константы и указатели на константы в C и C++

— Написано Triangles 10 декабря 2018 г. • обновлено 08 марта 2019 г. • ID 68 —

Указатель, указатель на константу, указатель на константу, указатель на константу: что еще?

Эта тема всегда смущала меня с самого начала: пришло время разобраться в этом навсегда. В C и C++ вы можете иметь указатели, указывающие на объекты. А также постоянные указатели на объекты. Или указатели на постоянные объекты. Или даже оба. Какая реальная разница?

Самый простой способ решить проблему константных/неконстантных указателей — найти различные комбинации. Здесь у вас есть два актера: указатель и объект, указывающий на . Как они могут взаимодействовать вместе:

  1. ни указатель, ни объект не являются константами;
  2. объект является константой;
  3. указатель константный;
  4. и указатель, и объект являются константами.

В качестве примера возьмем int . Эти различные возможности можно выразить следующим образом:

 инт* а; // Указатель на int
константа int* а; // Указатель на const int
int* константа; // Постоянный указатель на int
const int* const a; // Константный указатель на const int
 

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

 struct Object { int x; };
 

, из которого создаются экземпляры двух объектов:

 Object* object1 = new Object{1};
Объект* объект2 = новый объект{2};
 

Давайте посмотрим, что вы можете сделать, смешивая четыре возможности, перечисленные выше.

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *