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

Содержание

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++ указатель — это переменная, которая используется для хранения адресов памяти других переменных. Это переменная, которая указывает на тип данных (например, int или string) того же типа и создается с помощью оператора *.

Синтаксис указателя в C++:

data_type_of_pointer *name_of_variable = & normal_variable;

Содержание

  1. Что такое указатель на указатель или двойной указатель в C++?
  2. Как объявить указатель на указатель в C++?
  3. Каков будет размер указателя на указатель в С++?

Что такое указатель на указатель или двойной указатель в C++?

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

На приведенной ниже диаграмме объясняется концепция двойных указателей :

На приведенной выше диаграмме показано представление памяти указателя на указатель или двойного указателя, мы можем легко понять, что адрес переменной (т. е. адрес 1) хранится в указателе 1, а адрес указателя 1 (т.  е. адрес 2) хранится в указателе 2. Это известно как двойные указатели или указатель на указатель.

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

Объявление указателя на указатель аналогично объявлению указателя в C++. Разница в том, что мы должны использовать дополнительный оператор * перед именем указателя в C++.

Синтаксис указателя на указатель (двойной указатель) в С++:

data_type_of_pointer **name_of_variable = & normal_pointer_variable;

Пример:

int val = 169;

int *ptr = &val; // storing address of val to pointer ptr.

int **double_ptr = &ptr; // pointer to a pointer declared which is pointing to an integer.

На приведенной ниже диаграмме объясняется концепция двойных указателей:

На приведенной выше диаграмме показано представление в памяти указателя на указатель. Первый указатель ptr1 хранит адрес переменной, а второй указатель ptr2 хранит адрес первого указателя.

Ниже приведена программа C++ для реализации Pointer to Pointer :

С++

#include <bits/stdc++.h>

usingnamespacestd;

 

intmain()

{

  intvariable = 169;

   

  

  

  int* pointer1;

 

  

  

  int** pointer2;

 

  

  

  pointer1 = &variable;

 

  

  

  pointer2 = &pointer1;

 

  

  

  

  cout << "Value of variable :- "<< 

           variable << "\n";

  cout << "Value of variable using single pointer :- "<< 

           *pointer1 << "\n";

  cout << "Value of variable using double pointer :- "<< 

           **pointer2 << "\n";

  return0;

}

Выход

Value of variable :- 169
Value of variable using single pointer :- 169
Value of variable using double pointer :- 169

Каков будет размер указателя на указатель в С++?

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

Ниже приведена программа на C++ для проверки размера двойного указателя:

С++

#include <bits/stdc++.h>

usingnamespacestd;

 

int

main()

{

  intval = 169;

  int* ptr = &val;

  int** double_ptr = &ptr;

 

  cout << " Size of normal Pointer: "<< 

            sizeof(ptr) << "\n";

  cout << " Size of double Pointer: "<< 

            sizeof(double_ptr) << "\n";

  return0;

}

Выход

 Size of normal Pointer: 8
 Size of double Pointer: 8

Примечание. Вывод приведенного выше кода также зависит от типа используемой машины. Размер указателя не фиксирован в языке программирования C++ и полностью зависит от других факторов, таких как архитектура ЦП и используемая ОС. Обычно для 64-битной операционной системы назначается размер памяти 8 байт, а для 32-битной операционной системы размер памяти 4 байта.

Основы работы с указателями

Этот документ знакомит с основами работы указателей в нескольких языках программирования — C, C++, Java и Pascal. Этот документ является сопутствующим документом для Pointer Fun с цифровым видео Binky или его можно использовать отдельно.

Это документ 106 в Стэнфордской образовательной библиотеке CS. Этот и другие бесплатные материалы доступны на сайте cslibrary.stanford.edu. Некоторые документы, связанные с этим, включают…

Раздел 1. Правила указателя

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

1) Указатели и пуанты

Указатель хранит ссылку на что-то. К сожалению, не существует фиксированного термина для того, на что указывает указатель, и в разных языках программирования существует большое разнообразие вещей, на которые указывают указатели. Мы используем термин pointee для того, на что указывает указатель, и мы придерживаемся основных свойств отношения указатель/указатель, которые верны для всех языков. Термин «ссылка» означает почти то же самое, что и «указатель» — «ссылка» подразумевает обсуждение на более высоком уровне, в то время как «указатель» подразумевает традиционную скомпилированную языковую реализацию указателей в виде адресов. Для основных правил указателя/указателя, описанных здесь, термины фактически эквивалентны.


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

Выделение указателя и выделение указателя, на который он указывает, — это два отдельных шага. Вы можете думать о структуре указателя/указателя как о работе на двух уровнях. Оба уровня должны быть настроены, чтобы все работало. Наиболее распространенная ошибка заключается в том, что вы концентрируетесь на написании кода, управляющего уровнем указателя, но забываете настроить уровень указателя. Иногда операции с указателями, которые не касаются указателей, называются «поверхностными», а операции над указателями — «глубокими».

2) Разыменование

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

Операция разыменования указателя работает только в том случае, если указатель имеет указатель — указатель должен быть выделен, а указатель должен указывать на него. Самая распространенная ошибка в коде указателя — забывание настроить указатель. Наиболее распространенный сбой во время выполнения из-за этой ошибки в коде — сбой операции разыменования. В Java неправильное разыменование будет вежливо помечено системой выполнения. В скомпилированных языках, таких как C, C++ и Pascal, неправильное разыменование иногда приводит к сбою, а иногда и к повреждению памяти каким-то незаметным случайным образом. По этой причине ошибки указателей в скомпилированных языках трудно отследить.

3) Назначение указателя

Назначение указателя между двумя указателями заставляет их указывать на одного и того же указателя. Итак, присваивание y = x; заставляет y указывать на тот же указатель, что и x . Назначение указателя не касается указателей. Он просто изменяет один указатель, чтобы он имел ту же ссылку, что и другой указатель. После назначения указателя говорят, что два указателя «разделяют» указатель.

Раздел 2. Код Бинки, пример

В этом разделе представлен тот же пример кода, что и в Веселье указки с видео Бинки. Существуют версии кода на нескольких компьютерных языках. Все версии имеют одинаковую структуру и демонстрируют одни и те же основные правила и уроки работы с указателями; они просто различаются по своему синтаксису. Независимо от какого-либо конкретного языка, основная структура примера такова…

Предположим, у вас есть тип указателя с именем «Node», который содержит две вещи: int и указатель на другой Node (объявление для такого типа Node приведено ниже). С таким типом указателя вы можете расположить три указателя Node в структуре, где они указывали бы друг на друга вот так…

Указатель с именем x указывает на первый указатель Node. Первый узел содержит указатель на второй, второй содержит указатель на третий, а третий содержит указатель обратно на первый. Эту структуру можно построить, используя только те правила размещения, разыменования и назначения указателей, которые мы видели. Используя приведенное ниже объявление, каждый узел содержит целое число с именем , значением и указатель на другой узел с именем next .

Напишите код для построения структуры на приведенном выше рисунке. Для удобства вы можете использовать временные указатели в дополнение к х . Единственный новый синтаксис, необходимый в C, заключается в том, что оператор -> разыменовывает указатель для доступа к полю в указателе, поэтому -> значение обращается к полю с именем value в указателе x .

Представленная здесь структура Node на самом деле является реальным типом данных, используемым для построения структуры данных «связанный список». Связанные списки представляют собой реалистичное прикладное использование указателей и являются отличной областью для развития ваших навыков работы с указателями. См. «Основы связанных списков» и «Задачи связанных списков» в Стэнфордской образовательной библиотеке CS для получения большого количества материалов со связанными списками.

Copyright Nick Parlante, 1999. Этот материал можно копировать и распространять при условии сохранения стандартного уведомления Стэнфордской образовательной библиотеки CS на первой странице: «Это документ 106 в Стэнфордской образовательной библиотеке CS. Этот и другие бесплатные материалы доступны по адресу cslibrary.stanford.edu.»

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

1. Разместите два указателя x и y . Выделение указателей не выделить любые указатели.
2. Выделите указатель и установите x , чтобы он указывал на него. Каждый язык имеет для этого свой синтаксис. Важно то, что память динамически выделяется для одного указателя, и x указывают на этот указатель.
3. Разыменовать x , чтобы сохранить 42 в его указателе. Это базовый пример операции разыменования. Начните с x , следуйте стрелке, чтобы получить доступ к ее указателю.
4. Попробуйте разыменовать и , чтобы сохранить 13 в его указателе. Это дает сбой, потому что y не имеет указателя — он никогда не был назначен.
5. Присвоить y = x; , так что y указывает на х пуант. Теперь x и y указывают на одну и ту же точку — они «совместно используются».
6. Попробуйте разыменовать y , чтобы сохранить 13 в своем указателе. На этот раз это работает, потому что предыдущее задание дало и пуант.

Указатели в Go-Go 101

Главная новинка!

GO 101

Go Generics 101

GO.

Go Agg 101

Go 101 Блог

Go 101 Приложения и библиотеки

Тема: темный/светлый

Три новые книги Go Optimizations 101, Подробности и советы 101 и Go Generics 101 публикуются сейчас. Наиболее выгодно покупать их все через этот комплект книг. в книжном магазине Leanpub.

Хотя Go вбирает в себя многие функции из всех видов других языков, Go в основном рассматривается как язык семейства C. Одним из доказательств является то, что Go также поддерживает указатели. Указатели Go и указатели C очень похожи во многих аспектах. но есть также некоторые различия между указателями Go и указателями C. В этой статье будут перечислены все виды концепций и деталей, связанных с указателями в Go.

Адреса памяти

Адрес памяти означает определенную ячейку памяти в программировании.

Как правило, адрес памяти хранится в виде собственного (целого) слова без знака. Размер родного слова составляет 4 (байта) на 32-битных архитектурах и 8 (байт) на 64-битных архитектурах. Таким образом, теоретический максимальный размер памяти составляет 2 32 байт, он же 4 ГБ (1 ГБ == 2 30 байт), в 32-разрядных архитектурах, и составляет 2 64 байт, также известный как 16EB (1EB == 1024PB, 1PB == 1024TB, 1TB == 1024GB), на 64-битных архитектурах.

Адреса памяти часто представляются шестнадцатеричными целочисленными литералами, например 0x1234CDEF .

Значение Адреса

Адрес значения означает начальный адрес сегмента памяти. занимает прямая часть значения.

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

Указатель — это один из типов в Go. Значение указателя используется для хранения адреса памяти, который обычно является адресом другого значения.

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

Типы и значения указателя перехода

В Go безымянный тип указателя может быть представлен как *T , где T может быть произвольным типом. Тип T называется базовым типом указателя типа *T .

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

Если базовый тип именованного типа указателя *T , тогда базовый тип именованного типа указателя равен T .

Два безымянных типа указателя с одним и тем же базовым типом являются одним и тем же типом.

Пример:

 *int // Тип безымянного указателя, базовым типом которого является int.
**int // Безымянный тип указателя, базовый тип которого *int.

// Ptr — это именованный тип указателя, базовый тип которого — int.
введите Ptr *int
// PP — это тип именованного указателя, базовым типом которого является Ptr. 
тип ПП *Ptr
 

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

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

О слове «Справка»

В Go 101 слово «ссылка» указывает на отношение. Например, если значение указателя хранит адрес другого значения, тогда мы можем сказать, что значение указателя (непосредственно) ссылается на другое значение, а другое значение имеет хотя бы одну ссылку. Использование слова «ссылка» в Go 101 соответствует спецификации Go.

Когда значение указателя ссылается на другое значение, мы также часто говорим, что значение указателя указывает на другое значение.

Как получить значение указателя и что такое адресуемые значения?

Есть два способа получить ненулевое значение указателя.

  1. Встроенная функция new может использоваться для выделения памяти для значения любого типа. new(T) выделит память для значения T (анонимная переменная) и вернуть адрес Значение T . Выделенное значение является нулевым значением типа T . Возвращенный адрес рассматривается как значение указателя типа *T .
  2. Мы также можем взять адреса значений, к которым можно обращаться в Go. Для адресуемого значения t типа T , мы можем использовать выражение &t , чтобы взять адрес t , где и — оператор для получения адресов значений. Тип &t рассматривается как *T .

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

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

В следующем разделе будет показан пример получения значений указателя.

Разыменование указателя

Учитывая значение указателя p типа указателя, базовый тип которого T , как вы можете получить значение по адресу, хранящемуся в указателе (также известном как значение на который ссылается указатель)? Просто используйте выражение , где * называется оператором разыменования. *p называется разыменованием указателя p . Разыменование указателя — это процесс, обратный взятию адреса. Результатом *p является значение типа T (базовый тип типа p ).

Разыменование нулевого указателя вызывает панику во время выполнения.

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

 основной пакет

импортировать "фмт"

основная функция () {
p0 := new(int) // p0 указывает на нулевое значение int.
fmt.Println(p0) // (шестнадцатеричная адресная строка)
fmt.Println(*p0) // 0

// x является копией значения в
// адрес, хранящийся в p0.
х := *p0
// Оба берут адрес x.
// x, *p1 и *p2 представляют одно и то же значение.
р1, р2 := &х, &х
fmt.Println(p1 == p2) // правда
fmt.Println(p0 == p1) // ложь
p3 := &*p0 // p3 := &(*p0) p3 := p0
// Теперь p3 и p0 хранят один и тот же адрес.
fmt.Println(p0 == p3) // правда
*p0, *p1 = 123 789fmt.Println(*p2, x, *p3) // 789 789 123

fmt.Printf("%T, %T \n", *p0, x) // целое, целое
fmt.Printf("%T, %T\n", p0, p1) // *int, *int
}
 

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

Зачем нужны указатели?

Сначала рассмотрим пример.

 основной пакет

импортировать "фмт"

функция двойной (x int) {
х += х
}

основная функция () {
переменная а = 3
двойной (а)
fmt.Println(a) // 3
}
 

Ожидается, что функция double в приведенном выше примере изменит входной аргумент, удвоив его. Однако это не удается. Почему? Потому что все присвоения значений, включая передачу аргументов функции, в Go являются копированием значений. Функция double изменила копию ( x ) переменной на , но не переменную на .

Одно из решений для исправления вышеуказанной функции double — позволить ей вернуться результат модификации. Это решение не всегда работает для всех сценариев. В следующем примере показано другое решение с использованием параметра указателя.

 основной пакет

импортировать "фмт"

функция double(x *int) {
*х += *х
x = nil // строка предназначена только для пояснения
}

основная функция () {
переменная а = 3
двойной (&а)
fmt. Println(a) // 6
р := &а
двойной (р)
fmt.Println(a, p == nil) // 12 ложь
}
 

Мы можем обнаружить, что, изменив параметр на тип указателя, переданный аргумент указателя &a и его копия x , используемые в теле функции, ссылаются на одно и то же значение, так модификация на *x эквивалентно модификации *p , также известной как переменная a . Другими словами, изменение тела функции double теперь может быть отражено вне функции.

Наверняка модификация самой копии переданного аргумента указателя по-прежнему не может быть отражено в переданном аргументе указателя. После второго вызова функции double локальный указатель p не модифицируется до ноль .

Короче говоря, указатели предоставляют косвенные способы доступа к некоторым значениям. Многие языки не имеют концепции указателей. Однако указатели просто скрыты под другими понятиями в этих языках.

Указатели возврата локальных переменных безопасны в Go

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

 функция newInt() *int {
а := 3
возврат &a
}
 

Ограничения на указатели в Go

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

Значения указателя Go не поддерживают арифметические операции

В Go указатели не могут выполнять арифметические операции. Для указателя p , p++ и p-2 оба незаконны.

Если p является указателем на числовое значение, компиляторы увидят *p++ является юридическим утверждением и обрабатывается как (*p)++ . Другими словами, приоритет оператора разыменования указателя * больше, чем оператор приращения ++ и оператор декремента -.

Пример:

 основной пакет

импортировать "фмт"

основная функция () {
а := int64(5)
р := &а

// Следующие две строки не компилируются.
/*
р++
р = (&а) + 8
*/

*р++
fmt.Println(*p, а) // 6 6
fmt.Println(p == &a) // верно

*&а++
*&*&а++
**&р++
*&*р++
fmt.Println(*p, а) // 10 10
}
 
Значение указателя не может быть преобразовано в произвольный тип указателя

В Go значение указателя типа T1 может быть напрямую и явно преобразовано. к другому типу указателя T2 , только если одно из следующих двух условий получить удовлетворение.

  1. Базовые типы типа T1 и T2 идентичны (игнорируя теги структуры), в частности, если либо T1 , либо T2 это безымянный тип и их базовые типы идентичны (учитывая теги структуры), тогда преобразование может быть неявным. Типы и значения структур будут объяснены в следующая статья.
  2. Типы T1 и T2 являются безымянными типами указателей. базовые типы их базовых типов идентичны (игнорируя теги структуры).

Например, для показанных ниже типов указателей:

 тип MyInt int64
введите Та *int64
введите Tb *MyInt
 

существуют следующие факты:

  1. значения типа *int64 могут быть неявно преобразованы в тип Ta , и наоборот, поскольку их базовые типы оба *int64 .
  2. значения типа *MyInt могут быть неявно преобразованы в тип Tb , и наоборот, так как их базовые типы оба *МойИнт .
  3. значения типа *MyInt могут быть явно преобразованы в тип *int64 , и наоборот, потому что они оба безымянные, а базовые типы их базовых типов оба int64 .
  4. значения типа Ta не могут быть напрямую преобразованы в тип Tb , даже если явно. Однако, по только что перечисленным первым трем фактам, значение pa тип Ta можно косвенно преобразовать в тип Tb путем вложения трех явных преобразований Tb((*MyInt)((*int64)(pa))) .

Никакие значения этих типов указателей не могут быть преобразованы в тип *uint64 любыми безопасными способами.

Значение указателя нельзя сравнивать со значениями произвольного типа указателя

В Go указатели можно сравнить с операторами == и != . Два значения указателя Go можно сравнивать только в том случае, если выполняется одно из следующих трех условий. удовлетворены.

  1. Типы двух указателей Go идентичны.
  2. Одно значение указателя может быть неявно преобразовано в тип указателя другого. Другими словами, базовые типы двух типов должны быть идентичными. и любой из двух типов двух указателей Go является безымянным типом.
  3. Один и только один из двух указателей представлен голым (нетипизированным) идентификатором nil .

Пример:

 основной пакет

основная функция () {
введите MyInt int64
введите Та *int64
введите Tb *MyInt

// 4 нулевых указателя разных типов.
вар па0 Та
переменная pa1 *int64
вар pb0 Tb
переменная pb1 *MyInt

// Следующие 6 строк компилируются нормально.
// Все результаты сравнения верны.
_ = pa0 == pa1
_ = pb0 == pb1
_ = pa0 == ноль
_ = pa1 == ноль
_ = pb0 == ноль
_ = pb1 == ноль

// Ни одна из следующих 3 строк не компилируется нормально.
/*
_ = pa0 == pb0
_ = pa1 == pb1
_ = pa0 == Tb(ноль)
*/
}
 
Значение указателя не может быть присвоено значениям указателя других типов указателя

Условиями присвоения значения указателя другому значению указателя являются такие же, как условия для сравнения значения указателя с другим значением указателя, которые перечислены выше.

Можно обойти ограничения указателя перехода

Как упоминалось в начале этой статьи, механизмы (в частности, тип unsafe.Pointer ) предоставлено небезопасный стандартный пакет можно использовать для снятия ограничений, наложенных на указатели в Go. Тип unsafe.Pointer подобен void* в C. Вообще небезопасные способы использовать не рекомендуется.


Индекс↡


Цифровые версии этой книги доступны в следующих местах:

  • магазин Линпаб, $19,99+ .
  • Магазин Amazon Kindle, (в настоящее время недоступен) .
  • магазин Apple Books, 19,99 $ .
  • Google Play магазин, 19,99 $ .
  • бесплатные электронные книги, включая форматы pdf, epub и azw3.

Тапир, автор Go 101, писал книги из серии Go 101. и поддержка сайта go101.org с июля 2016 года. Новое содержание будет постоянно добавляться в книгу и на веб-сайт время от времени. Tapir также является независимым разработчиком игр. Вы также можете поддержать Go 101, играя в игры Tapir. (сделано как для Android, так и для iPhone/iPad):

  • Color Infection (★★★★★), основанная на физике оригинальная казуальная игра-головоломка. 140+ уровней.
  • Rectangle Pushers (★★★★★), оригинальная казуальная игра-головоломка. Два режима, 104+ уровней.
  • Let’s Play With Particles, оригинальная казуальная игра в жанре экшн. Включены три мини-игры.
Индивидуальные пожертвования через PayPal также приветствуются.


Индекс:

  • О Go 101 — почему написана эта книга.
  • Благодарности
  • Введение в Go — почему стоит изучать Go.
  • The Go Toolchain — как компилировать и запускать программы Go.
  • Ознакомьтесь с кодом Go
    • Введение элементов исходного кода
    • Ключевые слова и идентификаторы
    • . Основные типы и литералы их значений
    • .
    • Константы и переменные — также вводит нетипизированные значения и выводы типа.
    • Общие операторы — также вводятся дополнительные правила вывода типов.
    • Объявления функций и вызовы
    • Кодовые пакеты и импорт пакетов
    • Выражения, операторы и простые операторы
    • Основные потоки управления
    • Горутины, отложенные вызовы функций и паника/восстановление
  • Перейти Тип системы
    • Обзор системы Go Type — необходимо прочитать, чтобы освоить программирование Go.
    • Стрелки
    • Конструкции
    • Value Parts — для более глубокого понимания ценностей Go.
    • Массивы, фрагменты и карты — первоклассные типы контейнеров граждан.
    • Струны
    • Функции — типы и значения функций, включая вариативные функции.
    • Каналы
    • — лучший способ выполнять параллельную синхронизацию.
    • Методы
    • Интерфейсы — поля значений, используемые для отражения и полиморфизма.
    • Type Embedding — расширение типа Go way.
    • Указатели небезопасного типа
    • Обобщения — использование и чтение составных типов
    • Reflections — стандартный пакет Reflect .
  • Некоторые специальные темы
    • Правила разрыва строки
    • Подробнее об отложенных вызовах функций
    • Некоторые варианты использования при панике/восстановлении
    • Подробное объяснение механизма паники/восстановления — также объясняет завершающие этапы вызовов функций.
    • Кодовые блоки и области идентификаторов
    • Заказы на оценку выражений
    • Стоимость копирования стоимости в Go
    • Устранение проверки границ
  • Параллельное программирование
    • Обзор параллельной синхронизации
    • Варианты использования канала
    • Как изящно закрыть каналы
    • Другие методы параллельной синхронизации — стандартный пакет синхронизации .
Оставить комментарий

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

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