Я следовал этому уроку о том, как работает указатель на указатель .
Позвольте мне процитировать соответствующий отрывок:
int i = 5, j = 6, k = 7; int *ip1 = &i, *ip2 = &j;
Теперь мы можем установить
int **ipp = &ip1;
и
ipp
указывает, наip1
что указываетi
.*ipp
естьip1
, и**ipp
естьi
, или 5. Мы можем проиллюстрировать ситуацию с нашей знакомой нотацией с прямоугольником и стрелкой, например так:Если тогда мы говорим
*ipp = ip2;
мы изменили указатель, на который указывает
ipp
(то естьip1
), чтобы он содержал копиюip2
, так чтоip1
теперь он ( ) указывает наj
:
Мой вопрос: почему на втором рисунке ipp
все еще указывает, ip1
но нет ip2
?
ipp
при определении, чтобы ваш вопрос былipp
указание на этоip1
так*ipp = ip2
же, какip1 = ip2
int **ipp
гораздо менее интуитивно, чемint** ipp
, особенно когда**ipp
подразумевает совершенно другую вещь вне объявлений типов.int **ipp
кажется мне довольно интуитивным. Это означает, что я делаю . Что является правдой.**ipp
int
ipp
isint**
, так что просто напишитеint**
вместо магического понимания «imp
разыменование является int» .Ответы:
Забудьте на секунду об указательной аналогии. На самом деле указатель содержит адрес памяти. Оператор
&
"address of" - т.е. возвращает адрес в памяти объекта.*
Оператор дает объект указатель ссылается, т.е. данного указателя , содержащий адрес, он возвращает объект по этому адресу памяти. Поэтому, когда вы это делаете*ipp = ip2
, вы*ipp
получаете объект по адресу, вipp
котором находится,ip1
и затем присваиваетеip1
значениеip2
, которое хранится в нем , который является адресомj
.Просто
&
-> Адрес*
-> Значение висточник
Потому что вы изменили значение, на которое указывает
ipp
не значениеipp
. Таким образом, по-ipp
прежнему указывает наip1
(значениеipp
),ip1
значение 's теперь совпадаетip2
со значением', поэтому они оба указываютj
.Это:
такой же как:
источник
int *ip1 = &i
и*ipp = ip2;
, т. Е. Если вы удалитеint
оператор from из первого оператора, тогда назначения выглядят очень похоже, но*
в обоих случаях происходит нечто очень разное.Как и большинство вопросов для начинающих в теге C, на этот вопрос можно вернуться, вернувшись к первым принципам:
&
Оператор превращает переменную в указатель.*
Оператор превращает указатель в переменную.(Технически я должен сказать «lvalue» вместо «variable», но я чувствую, что более понятно описать изменяемые места хранения как «переменные».)
Итак, у нас есть переменные:
Переменная
ip1
содержит указатель.&
Оператор превращаетсяi
в указатель и что значение указателя присваиваетсяip1
. Так чтоip1
содержит указатель наi
.Переменная
ip2
содержит указатель.&
Оператор превращаетсяj
в указатель и указатель присваиваетсяip2
. Так чтоip2
содержит указатель наj
.Переменная
ipp
содержит указатель.&
Оператор включает переменнуюip1
в указатель и что значение указателя присваиваетсяipp
. Так чтоipp
содержит указатель наip1
.Подведем итоги истории:
i
содержит 5j
содержит 6ip1
содержит "указатель наi
"ip2
содержит "указатель наj
"ipp
содержит "указатель наip1
"Теперь мы говорим
*
Оператор поворачивает указатель обратно в переменный. Мы выбираем значениеipp
, которое является «указателем на»ip1
и превращаем его в переменную. Какую переменную?ip1
Конечно!Поэтому это просто другой способ сказать
Таким образом, мы получаем значение
ip2
. Что это? "указатель наj
". Мы присваиваем это значение указателяip1
, такip1
что теперь "указатель наj
"Мы изменили только одно: значение
ip1
:i
содержит 5j
содержит 6ip1
содержит "указатель наj
"ip2
содержит "указатель наj
"ipp
содержит "указатель наip1
"Переменная изменяется, когда вы присваиваете ей. Подсчитать назначения; не может быть больше изменений переменных, чем присваиваний! Вы начинаете путем присвоения
i
,j
,ip1
,ip2
иipp
. Затем вы присваиваете*ipp
, что, как мы видели, означает то же самое, что и «присваиватьip1
». Так как вы не назначали наipp
второй раз, это не изменилось!Если вы хотите изменить,
ipp
то вам нужно назначитьipp
:например.
источник
надеюсь, что этот кусок кода может помочь.
это выводит:
источник
Мое личное мнение таково, что картинки со стрелками, указывающими так или иначе, затрудняют понимание указателей. Это делает их похожими на некоторые абстрактные, таинственные сущности. Они не.
Как и все остальное в вашем компьютере, указатели являются числами . Название «указатель» - это просто причудливый способ сказать «переменная, содержащая адрес».
Поэтому позвольте мне все перемешать, объяснив, как на самом деле работает компьютер.
У нас есть
int
, у него есть имяi
и значение 5. Это хранится в памяти. Как и все, что хранится в памяти, ему нужен адрес, иначе мы не сможем его найти. Допустим,i
заканчивается по адресу 0x12345678 и его приятельj
со значением 6 заканчивается сразу после него. Предполагая, что 32-битный процессор, где int составляет 4 байта, а указатели - 4 байта, переменные сохраняются в физической памяти следующим образом:Теперь мы хотим указать на эти переменные. Мы создаем один указатель на int
int* ip1
и одинint* ip2
. Как и все в компьютере, эти переменные-указатели также размещаются в памяти. Предположим, что они заканчиваются на следующих соседних адресах в памяти, сразу послеj
. Мы устанавливаем указатели для хранения адресов ранее выделенных переменных:ip1=&i;
(«скопировать адрес i в ip1») иip2=&j
. Что происходит между строк:Итак, мы получили всего лишь 4 байтовых блока памяти, содержащих числа. Там нет мистических или магических стрел нигде в поле зрения.
Фактически, просто глядя на дамп памяти, мы не можем определить, содержит ли адрес 0x12345680
int
илиint*
. Разница в том, как наша программа использует содержимое, хранящееся по этому адресу. (Задача нашей программы - просто сказать процессору, что делать с этими числами.)Затем мы добавляем еще один уровень косвенности с
int** ipp = &ip1;
. Опять же, мы просто получаем кусок памяти:Шаблон кажется знакомым. Еще один фрагмент из 4 байтов, содержащий число.
Теперь, если бы у нас был дамп памяти вышеупомянутого вымышленного небольшого ОЗУ, мы могли бы вручную проверить, куда указывают эти указатели. Мы посмотрим, что хранится по адресу
ipp
переменной, и найдем содержимое 0x12345680. Который, конечно, адрес, гдеip1
хранится. Мы можем перейти по этому адресу, проверить его содержимое и найти адресi
, а затем, наконец, мы можем перейти по этому адресу и найти номер 5.Поэтому, если мы возьмем содержимое ipp,
*ipp
мы получим адрес переменной указателяip1
. Написав,*ipp=ip2
мы копируем ip2 в ip1, это эквивалентноip1=ip2
. В любом случае мы бы получили(Эти примеры были приведены для процессора с прямым порядком байтов)
источник
location, value, variable
местоположения1,2,3,4,5
и значенияA,1,B,C,3
, соответствующая идея указателей могла бы быть легко объяснена без использования стрелок, которые по своей сути сбивают с толку. В любой выбранной реализации значение существует в некотором месте, и это часть головоломки, которая становится запутанной при моделировании со стрелками.&
Оператор переменной дает монету, представляющую эту переменную.*
Оператор на этой монете дает вам обратно переменную. Стрелки не требуются!Обратите внимание на назначения:
результаты,
ipp
чтобы указатьip1
.поэтому,
ipp
чтобы указать наip2
, мы должны изменить таким же образом,что мы явно не делаем. Вместо этого мы меняем значение по адресу, указанному
ipp
.Делая следующее
мы просто заменяем значение, хранящееся в
ip1
.ipp = &ip1
значит*ipp = ip1 = &i
,сейчас
*ipp = ip2 = &j
.Итак,
*ipp = ip2
по сути то же самое, что иip1 = ip2
.источник
Никакое последующее назначение не изменило значение
ipp
. Вот почему это все еще указывает наip1
.То, что вы делаете с
*ipp
, то есть сip1
, не меняет того факта, на которыйipp
указываетip1
.источник
Вы разместили красивые картинки, я постараюсь сделать хорошее ascii art:
Как сказал @ Robert-S-Barnes в своем ответе: забудьте про указатели и что на что указывает, но подумайте с точки зрения памяти. По сути,
int*
означает, что он содержит адрес переменной иint**
содержит адрес переменной, которая содержит адрес переменной. Затем вы можете использовать алгебру указателя для доступа к значениям или адресам:&foo
средстваaddress of foo
и*foo
средстваvalue of the address contained in foo
.Итак, поскольку указатели связаны с памятью, лучший способ сделать это «осязаемым» - показать, что алгебра указателей делает с памятью.
Итак, вот память вашей программы (упрощенная для целей примера):
когда вы делаете свой начальный код:
вот как выглядит ваша память:
там вы можете увидеть
ip1
иip2
получает адресаi
иj
и доipp
сих пор не существует. Не забывайте, что адреса - это просто целые числа, хранящиеся в специальном типе.Затем вы объявляете и определяете
ipp
такие как:так вот твоя память:
и затем вы меняете значение, указанное адресом
ipp
, который хранится в нем , который является адресом, хранящимся вip1
:память программы
NB: так как
int*
это особый тип, я предпочитаю всегда избегать объявления нескольких указателей на одной строке, так как я думаю, что записьint *x;
илиint *x, *y;
запись может вводить в заблуждение. Я предпочитаю писатьint* x; int* y;
НТН
источник
ip2
должно быть3
не4
.Потому что, когда вы говорите
Вы говорите «объект, на который указывает
ipp
», чтобы указать направление памяти,ip2
которое указывает.Вы не говорите,
ipp
чтобы указатьip2
.источник
Если вы добавите оператор разыменования
*
к указателю, вы перенаправите указатель на объект, на который указывает указатель.Примеры:
Следовательно:
источник
Если вы хотите
ipp
указать наip2
, вы должны сказатьipp = &ip2;
. Тем не менее, это оставило быip1
все еще указывая наi
.источник
В самом начале вы установили,
Теперь разыщите это как,
источник
Рассмотрим каждую переменную, представленную следующим образом:
поэтому ваши переменные должны быть представлены так
Как значение
ipp
это&ip1
так inctruction:изменяет значение в адресе
&ip1
на значениеip2
, что означаетip1
изменение:Но
ipp
все же:Таким образом, значение по-
ipp
прежнему&ip1
означает, что это все еще указывает наip1
.источник
Потому что вы меняете указатель
*ipp
. Это значитipp
(изменяемое имя) ---- зайти внутрь.ipp
адресip1
.*ipp
так иди (адрес изнутри)ip1
.Теперь мы находимся на
ip1
.*ipp
(т.е.ip1
) =ip
2.ip2
содержит адресj
.soip1
содержимое будет заменено содержимым ip2 (то есть адресом j), МЫ НЕ ИЗМЕНИМ КОНТЕНТipp
. ЭТО ОНО.источник
*ipp = ip2;
предполагает:Присвойте
ip2
переменной, на которую указываетipp
. Так что это эквивалентно:Если вы хотите, чтобы адрес
ip2
был сохраненipp
, просто выполните:Теперь
ipp
указывает наip2
.источник
ipp
может содержать значение (т.е. указывать на) указатель на объект типа указателя . Когда вы делаетезатем
ipp
содержит адрес переменной (указатель)ip2
, который (&ip2
) типа указатель на указатель . Теперь стрелкаipp
на втором рисунке будет указывать наip2
.Вики говорят: оператор является оператор разыменовать работает на переменном указатель, и возвращает л-значение (переменный) эквивалентно значение в указателе адрес. Это называется разыменованием указателя.
*
Применение
*
оператора поipp
разыменованию его к l-значению указателя наint
тип. Разыменованное l-значение*ipp
имеет указатель наint
тип , оно может содержать адресint
типа данных. После утвержденияipp
держит адресip1
и*ipp
держит адрес (указывая на)i
. Вы можете сказать, что*ipp
это псевдонимip1
. Оба**ipp
и*ip1
являются псевдонимами дляi
.При выполнении
*ipp
иip2
оба указывают на то же место, ноipp
все еще указывают наip1
.Что на
*ipp = ip2;
самом деле заключается в том, что он копирует содержимоеip2
(адресj
) вip1
(как*ipp
псевдонимip1
), фактически делая оба указателяip1
иip2
указывая на один и тот же объект (j
).Итак, на втором рисунке стрелка
ip1
иip2
указывает наj
whileipp
, все еще указывает на то,ip1
что никакие модификации для изменения значения не выполняютсяipp
.источник