Указатель на разъяснение указателя

Я следовал этому учебнику о том, как работает указатель на указатель.

Позвольте мне привести соответствующий отрывок:


    int i = 5, j = 6, k = 7;
    int *ip1 = &i, *ip2 = &j;

Теперь мы можем установить

    int **ipp = &ip1;

и ipp указывает на ip1, который указывает на i. *ipp - ip1, а **ipp - i или 5. Мы можем проиллюстрировать ситуацию с помощью нашей знакомой нотации в виде ящика и стрелки, например:

enter image description here

Если тогда мы скажем

    *ipp = ip2;

мы изменили указатель, на который указывает ipp (т.е. ip1), чтобы содержать копию ip2, так что она (ip1) теперь указывает на j:

enter image description here


Мой вопрос: почему во втором изображении ipp все еще указывает на ip1, но не ip2?

Ответ 1

Забудьте о секунде о направленной аналогии. То, что действительно содержит указатель, - это адрес памяти. & - это "адрес оператора", то есть он возвращает адрес в памяти объекта. Оператор * дает вам объект, на который ссылается указатель, то есть с указателем, содержащим адрес, он возвращает объект по этому адресу памяти. Поэтому, когда вы выполняете *ipp = ip2, то, что вы делаете, это *ipp получить объект по адресу, содержащемуся в ipp, который равен ip1, а затем назначить ip1 значение, хранящееся в ip2, которое является адрес j.

Просто
& → Адрес
* → Значение в

Ответ 2

Поскольку вы изменили значение, на которое указывает ipp, а не значение ipp. Таким образом, ipp по-прежнему указывает на ip1 (значение ipp), значение ip1 теперь совпадает с значением ip2, поэтому они оба указывают на j.

Это:

*ipp = ip2;

совпадает с:

ip1 = ip2;

Ответ 3

надеюсь, что этот фрагмент кода может помочь.

#include <iostream>
#include <stdio.h>
using namespace std;

int main()
{
    int i = 5, j = 6, k = 7;
    int *ip1 = &i, *ip2 = &j;
    int** ipp = &ip1;
    printf("address of value i: %p\n", &i);
    printf("address of value j: %p\n", &j);
    printf("value ip1: %p\n", ip1);
    printf("value ip2: %p\n", ip2);
    printf("value ipp: %p\n", ipp);
    printf("address value of ipp: %p\n", *ipp);
    printf("value of address value of ipp: %d\n", **ipp);
    *ipp = ip2;
    printf("value ipp: %p\n", ipp);
    printf("address value of ipp: %p\n", *ipp);
    printf("value of address value of ipp: %d\n", **ipp);
}

выводится:

enter image description here

Ответ 4

Как и большинство начинающих вопросов в теге C, на этот вопрос можно ответить, вернувшись к первым принципам:

  • Указатель - это своего рода значение.
  • Переменная содержит значение.
  • Оператор & превращает переменную в указатель.
  • Оператор * превращает указатель в переменную.

(Технически я должен сказать "lvalue" вместо "variable", но я считаю более понятным описать изменяемые места хранения как "переменные".)

Итак, мы имеем переменные:

int i = 5, j = 6;
int *ip1 = &i, *ip2 = &j;

Переменная ip1 содержит указатель. Оператор & превращает i в указатель и значение указателя присваивается ip1. Поэтому ip1 содержит указатель на i.

Переменная ip2 содержит указатель. Оператор & превращает j в указатель, а указатель присваивается ip2. Поэтому ip2 содержит указатель на j.

int **ipp = &ip1;

Переменная ipp содержит указатель. Оператор & превращает переменную ip1 в указатель и значение указателя присваивается ipp. Поэтому ipp содержит указатель на ip1.

Подведем итог истории:

  • i содержит 5
  • j содержит 6
  • ip1 содержит "указатель на i"
  • ip2 содержит "указатель на j"
  • ipp содержит "указатель на ip1"

Теперь мы говорим

*ipp = ip2;

Оператор * возвращает указатель обратно в переменную. Мы получаем значение ipp, которое является "указателем на ip1 и превращает его в переменную. Какую переменную? ip1, конечно!

Поэтому это просто еще один способ сказать

ip1 = ip2;

Итак, мы получаем значение ip2. Что это? "указатель на j". Мы присваиваем этому указателю значение ip1, поэтому ip1 теперь является "указателем на j"

Мы изменили только одно: значение ip1:

  • i содержит 5
  • j содержит 6
  • ip1 содержит "указатель на j"
  • ip2 содержит "указатель на j"
  • ipp содержит "указатель на ip1"

Почему ipp все еще указывает на ip1, а не ip2?

При назначении ему изменяется переменная. Подсчитайте задания; переменные не могут быть больше, чем есть назначения! Вы начинаете с назначения i, j, ip1, ip2 и ipp. Затем вы назначаете *ipp, который, как мы видели, означает то же самое, что "присваивать ip1". Поскольку вы не назначали ipp второй раз, это не менялось!

Если вы хотите изменить ipp, вам нужно будет фактически назначить ipp:

ipp = &ip2;

например.

Ответ 5

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

Как и все остальное на вашем компьютере, указатели - это числа. Имя "указатель" - просто причудливый способ сказать "переменная, содержащая адрес".

Поэтому позвольте мне разобраться, объясняя, как работает компьютер.

У нас есть int, он имеет имя i и значение 5. Это сохраняется в памяти. Как и все, что хранится в памяти, ему нужен адрес, или мы не сможем его найти. Допустим, что i заканчивается по адресу 0x12345678, а его приятель j со значением 6 заканчивается сразу после него. Предположим, что 32-разрядный процессор, где int составляет 4 байта, а указатели - 4 байта, тогда переменные хранятся в физической памяти следующим образом:

Address     Data           Meaning
0x12345678  00 00 00 05    // The variable i
0x1234567C  00 00 00 06    // The variable j

Теперь мы хотим указать на эти переменные. Мы создаем один указатель на int, int* ip1 и один int* ip2. Как и все в компьютере, эти переменные указателя получают и в памяти. Предположим, что они заканчиваются на соседних адресах в памяти сразу после j. Мы указываем, что указатели содержат адреса ранее выделенных переменных: ip1=&i; ( "копировать адрес я в ip1" ) и ip2=&j. Что происходит между линиями:

Address     Data           Meaning
0x12345680  12 34 56 78    // The variable ip1(equal to address of i)
0x12345684  12 34 56 7C    // The variable ip2(equal to address of j)

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

Фактически, просто глядя на дамп памяти, мы не можем определить, содержит ли адрес 0x12345680 int или int*. Разница заключается в том, как наша программа выбирает использование содержимого, хранящегося по этому адресу. (Задача нашей программы состоит в том, чтобы просто сказать CPU, что делать с этими числами.)

Затем добавим еще один уровень косвенности с int** ipp = &ip1;. Опять же, мы просто получаем кусок памяти:

Address     Data           Meaning
0x12345688  12 34 56 80    // The variable ipp

Образец кажется знакомым. Еще один фрагмент из 4 байтов, содержащий число.

Теперь, если бы у нас был дамп памяти вышеупомянутой вымышленной маленькой ОЗУ, мы могли бы вручную проверить, на что указывают эти указатели. Мы заглянем в то, что хранится по адресу переменной ipp, и найдите содержимое 0x12345680. Это, конечно, адрес, в котором хранится ip1. Мы можем пойти по этому адресу, проверить содержимое там и найти адрес i, а затем, наконец, мы сможем перейти на этот адрес и найти номер 5.

Итак, если мы возьмем содержимое ipp, *ipp, мы получим адрес переменной указателя ip1. Написав *ipp=ip2, мы копируем ip2 в ip1, это эквивалентно ip1=ip2. В любом случае мы получим

Address     Data           Meaning
0x12345680  12 34 56 7C    // The variable ip1
0x12345684  12 34 56 7C    // The variable ip2

(Эти примеры были приведены для большого центрального процессора)

Ответ 6

Обратите внимание на присваивания:

ipp = &ip1;

результат ipp, чтобы указать на ip1.

поэтому для ipp, чтобы указать на ip2, мы должны измениться аналогичным образом,

ipp = &ip2;

которые мы явно не делаем. Вместо этого мы меняем значение по адресу, на которое указывает ipp.
Сделав следующее

*ipp = ip2;

мы просто заменяем значение, хранящееся в ip1.

ipp = &ip1, означает *ipp = ip1 = &i,
Теперь *ipp = ip2 = &j.
Итак, *ipp = ip2 по существу совпадает с ip1 = ip2.

Ответ 7

ipp = &ip1;

Никакое последующее назначение не изменило значение ipp. Вот почему он все еще указывает на ip1.

Что вы делаете с *ipp, т.е. с ip1, не меняет того факта, что ipp указывает на ip1.

Ответ 8

Потому что, когда вы говорите

*ipp = ip2

вы говорите, что объект, на который указывает ipp ', указывает направление памяти, на которое указывает ip2.

Вы не говорите ipp, чтобы указать ip2.

Ответ 9

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

Примеры:

int i = 0;
int *p = &i; // <-- N.B. the pointer declaration also uses the `*`
             //     it not the dereference operator in this context
*p;          // <-- this expression uses the pointed-to object, that is `i`
p;           // <-- this expression uses the pointer object itself, that is `p`

Таким образом:

*ipp = ip2; // <-- you change the pointer `ipp` points to, not `ipp` itself
            //     therefore, `ipp` still points to `ip1` afterwards.

Ответ 10

Мой вопрос: почему во втором изображении ipp все еще указывает на ip1, но не ip2?

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

Подобно @Robert-S-Barnes сказал в своем ответе: забудьте о указателях, и что указывает на что, но подумайте с точки зрения памяти. В принципе, int* означает, что он содержит адрес переменной, а int** содержит адрес переменной, который содержит адрес переменной. Затем вы можете использовать алгебру указателя для доступа к значениям или адресам: &foo означает address of foo, а *foo означает value of the address contained in foo.

Итак, поскольку указатели касаются памяти, лучший способ сделать это "осязаемым" - показать, что делает алгебра указателей в памяти.

Итак, вот ваша программная память (упрощенная для примера):

name:    i   j ip1 ip2 ipp
addr:    0   1   2   3   4
mem : [   |   |   |   |   ]

когда вы выполняете свой первоначальный код:

int i = 5, j = 6;
int *ip1 = &i, *ip2 = &j;

вот как выглядит ваша память:

name:    i   j ip1 ip2
addr:    0   1   2   3
mem : [  5|  6|  0|  1]

там вы можете видеть, что ip1 и ip2 получает адреса i и j и ipp все еще не существует. Не забывайте, что адреса - это просто целые числа, хранящиеся в специальном типе.

Затем вы объявляете и определяете ipp, например:

int **ipp = &ip1;

так вот ваша память:

name:    i   j ip1 ip2 ipp
addr:    0   1   2   3   4
mem : [  5|  6|  0|  1|  2]

а затем вы меняете значение, указанное адресом, хранящимся в ipp, который адрес, сохраненный в ip1:

*ipp = ip2;

память программы

name:    i   j ip1 ip2 ipp
addr:    0   1   2   3   4
mem : [  5|  6|  1|  1|  2]

N.B.: as int* является специальным типом, я предпочитаю всегда избегать объявления нескольких указателей в одной строке, так как я думаю, что нотация int *x; или int *x, *y; может вводить в заблуждение. Я предпочитаю писать int* x; int* y;

НТН

Ответ 11

Если вы хотите, чтобы ipp указывал на ip2, вам нужно сказать ipp = &ip2;. Однако это оставило бы ip1 все еще указывая на i.

Ответ 12

Очень скоро вы установите

ipp = &ip1;

Теперь разыщите его так, как

*ipp = *&ip1 // Here *& becomes 1  
*ipp = ip1   // Hence proved 

Ответ 13

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

type  : (name, adress, value)

поэтому ваши переменные должны быть представлены следующим образом

int   : ( i ,  &i , 5 ); ( j ,  &j ,  6); ( k ,  &k , 5 )

int*  : (ip1, &ip1, &i); (ip1, &ip1, &j)

int** : (ipp, &ipp, &ip1)

Поскольку значение ipp равно &ip1, так что вложение:

*ipp = ip2;

изменяет значение в addess &ip1 на значение ip2, что означает ip1:

(ip1, &ip1, &i) -> (ip1, &ip1, &j)

Но ipp еще:

(ipp, &ipp, &ip1)

Итак, значение ipp еще &ip1, что означает, что оно все еще указывает на ip1.

Ответ 14

Потому что вы меняете указатель *ipp. Это означает

  • ipp (varaiable name) ---- входите внутрь.
  • внутри ipp - адрес ip1.
  • теперь *ipp, поэтому перейдите к (адрес внутри) ip1.

Теперь мы находимся в ip1. *ipp (т.е. ip1) = ip 2.
ip2 содержать адрес j.so ip1 содержимое будет заменено на содержимое ip2 (то есть адрес j), МЫ НЕ ИЗМЕНИТЬ ipp СОДЕРЖАНИЕ. ЭТО.

Ответ 15

*ipp = ip2; подразумевает:

Назначьте ip2 переменной, на которую указывает ipp. Таким образом, это эквивалентно:

ip1 = ip2;

Если вы хотите сохранить адрес ip2 в ipp, просто выполните:

ipp = &ip2;

Теперь ipp указывает на ip2.

Ответ 16

ipp может содержать значение (например, указатель) указателя на объект типа указателя. Когда вы делаете

ipp = &ip2;  

то ipp содержит адрес переменной (указатель) ip2, которая является (&ip2) указателем типа на указатель. Теперь стрелка ipp во втором pic укажет на ip2.

Wiki говорит:
Оператор * является оператором разыменования, который работает с переменной указателя и возвращает l-value (переменный) эквивалент на значение по адресу указателя. Это называется разыменование указателя.

Применяя оператор * на ipp, отмените его до l-значения указателя на тип int. Разделяемое l-значение *ipp имеет указатель типа int, он может содержать адрес данных типа int. После утверждения

ipp = &ip1;

ipp удерживает адрес ip1, а *ipp удерживает адрес (указывающий) i. Вы можете сказать, что *ipp является псевдонимом ip1. Оба **ipp и *ip1 являются псевдонимами для i.
Выполняя

 *ipp = ip2;  

*ipp и ip2 обе точки в одном месте, но ipp все еще указывает на ip1.

То, что *ipp = ip2; действительно состоит в том, что он копирует содержимое ip2 (адрес j) в ip1 (поскольку *ipp является псевдонимом для ip1), фактически создавая оба указателя ip1 и ip2, указывающие на один и тот же объект (j).
Итак, во втором рисунке стрелка ip1 и ip2 указывает на j, а ipp по-прежнему указывает на ip1, поскольку никакая модификация не выполняется для изменения значения ipp.