Это именно то место, где вы должны использовать eval, или вам придется перебирать строку и генерировать числа. Вам нужно будет использовать метод isNaN.
Забудьте о секунде о направленной аналогии. То, что действительно содержит указатель, - это адрес памяти. &
- это «адрес» оператора, то есть он возвращает адрес в памяти объекта. Оператор *
дает вам объект, на который ссылается указатель, то есть с указателем, содержащим адрес, он возвращает объект по этому адресу памяти. Итак, когда вы делаете *ipp = ip2
, то, что вы делаете, *ipp
, получает объект по адресу, хранящемуся в ipp
, который является ip1
, а затем присваивает ip1
значение, сохраненное в ip2
, которое является адрес j
.
Просто &
-> Адрес *
-> Значение в
Поскольку вы меняете указатель на *ipp
. Это означает
ipp
(varayable name) ---- внутрь. ipp
является адресом ip1
. *ipp
, поэтому перейдите к (адрес внутри) ip1
. Теперь мы находимся в ip1
. *ipp
(т. е. ip1
) = ip
2. ip2
содержат адрес j
.so ip1
содержимое будет заменено содержимым ip2 (то есть адресом j), МЫ НЕ ИЗМЕНИТЬ ipp
СОДЕРЖАНИЕ. ЭТО ОНО.
Мое личное мнение заключается в том, что фотографии со стрелками, указывающими этот путь, или которые затрудняют понимание указателей. Это заставляет их казаться абстрактными, таинственными сущностями. Они не.
Как и все остальное на вашем компьютере, указатели - numbers . Имя «указатель» - просто причудливый способ сказать «переменная, содержащая адрес».
Поэтому позвольте мне разобраться, объясняя, как работает компьютер.
Мы имеют 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;
(«копировать адрес 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
(Эти примеры были приведены для большого центрального процессора)
location, value, variable
, где было 1,2,3,4,5
, а значение было A,1,B,C,3
, соответствующее представление указателей можно было бы легко объяснить без использования стрелок, которые по своей сути запутывают. При любой реализации, которую вы выбираете, значение существует в каком-то месте, и это часть головоломки, которая становится запутанной при моделировании со стрелками.
– MirroredFate
6 February 2014 в 21:02
&
в переменной дает вам монету, которая представляет эту переменную. Оператор *
на этой монете возвращает вам переменную. Никаких стрелок не требуется!
– Eric Lippert
7 February 2014 в 16:44
Если вы хотите, чтобы ipp
указывал на ip2
, вам нужно сказать ipp = &ip2;
. Однако это оставило бы ip1
все еще указывая на i
.
ipp = &ip1;
Позднее назначение изменило значение ipp
. Вот почему он все еще указывает на ip1
.
Что вы делаете с *ipp
, т. Е. С ip1
, не меняет того факта, что ipp
указывает на ip1
.
Обратите внимание на назначение:
ipp = &ip1;
результатов ipp
, чтобы указать на ip1
.
, поэтому для ipp
, чтобы указать на ip2
, мы должны изменить аналогичным образом
ipp = &ip2;
, чего мы явно не делаем. Вместо этого мы меняем значение по адресу, указанному в ipp
. Выполняя следующее
*ipp = ip2;
, мы просто заменяем значение, сохраненное в ip1
.
ipp = &ip1
означает *ipp = ip1 = &i
, Now, *ipp = ip2 = &j
. Таким образом, *ipp = ip2
по существу такой же, как ip1 = ip2
.
Как и большинство новичков в теге 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
"Почему
blockquote>ipp
все еще указывает наip1
, а неip2
?Изменяется переменная, когда вы назначаете ему. Подсчитайте назначения: переменные не могут быть больше, чем есть назначения! Вы начинаете с назначения
i
,j
,ip1
,ip2
иipp
. Затем вы назначаете*ipp
, который, как мы видели, означает то же самое, что «присваиватьip1
». Поскольку вы не назначалиipp
во второй раз, это не изменилось!Если вы хотите изменить
ipp
, вам действительно нужно назначитьipp
:ipp = &ip2;
например.
Потому что, когда вы говорите
*ipp = ip2
, вы говорите, что объект, на который указывает ipp
', указывает направление памяти, которое указывает ip2
.
Вы не говорите ipp
, чтобы указать ip2
.
int *ip1 = &i
и *ipp = ip2;
, то есть если вы удалите int
из первого утверждения, то присваивания выглядят очень схожими, но *
делает что-то совсем другое в обоих случаях.
– Paul Griffiths
6 February 2014 в 16:04
ipp
может содержать значение (например, to) указателя для объекта типа указателя . Когда вы выполняете
ipp = &ip2;
, тогда ipp
содержит адрес переменной (указатель) ip2
, которая является (&ip2
) указателя типа указателю , Теперь стрелка ipp
во втором pic укажет на ip2
.
Wiki говорит: Оператор *
является оператором разыменования, который работает с переменной указателя и возвращает значение l-значение (переменная), эквивалентное значение по адресу указателя. Это называется разыменование указателя.
Применяя оператор *
к 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
.
надеюсь, что эта часть кода может помочь.
#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);
}
выводит:
[/g0]
Если вы добавите оператор указателя *
к указателю, вы перенаправляете его с указателя на объект, на который указывает.
Примеры:
int i = 0;
int *p = &i; // <-- N.B. the pointer declaration also uses the `*`
// it's 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.
*ipp = ip2;
подразумевает:
Присвоить ip2
переменной, на которую указывает ipp
. Это эквивалентно:
ip1 = ip2;
Если вы хотите сохранить адрес ip2
в ipp
, просто выполните:
ipp = &ip2;
Теперь ipp
указывает на ip2
.
Объясните каждую переменную, представленную следующим образом:
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
, так inctruction:
*ipp = ip2;
изменяет значение на добавке &ip1
на значение ip2
, что означает ip1
:
(ip1, &ip1, &i) -> (ip1, &ip1, &j)
Но ipp
еще:
(ipp, &ipp, &ip1)
Таким образом, значение ipp
все еще &ip1
, что означает, что оно все еще указывает на ip1
.
Очень скоро вы установите
ipp = &ip1;
Теперь разыщите его как [, g1]
*ipp = *&ip1 // Here *& becomes 1
*ipp = ip1 // Hence proved
Мой вопрос: почему во втором изображении ipp все еще указывает на ip1, но не ip2?
blockquote>вы разместили красивые картинки, я попытаюсь сделать Хорошее искусство 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;
памяти программы is
name: i j ip1 ip2 ipp addr: 0 1 2 3 4 mem : [ 5| 6| 1| 1| 2]
NB: поскольку
int*
является специальным типом, я предпочитаю всегда избегать объявления нескольких указателей в одной строке, так как я думаю, что нотацияint *x;
илиint *x, *y;
может вводить в заблуждение , Я предпочитаю писатьint* x; int* y;
HTH
Поскольку вы изменили значение, на которое указывает ipp
, а не значение ipp
. Таким образом, ipp
все еще указывает на ip1
(значение ipp
), значение ip1
теперь совпадает с значением ip2
, поэтому оба они указывают на j
.
Это:
*ipp = ip2;
совпадает с:
ip1 = ip2;
int *ip1 = &i
и *ipp = ip2;
, то есть если вы удалите int
из первого утверждения, то присваивания выглядят очень схожими, но *
делает что-то совсем другое в обоих случаях.
– Paul Griffiths
6 February 2014 в 16:04