Указатель на переменную указателя, означающую C [duplicate]

Это именно то место, где вы должны использовать eval, или вам придется перебирать строку и генерировать числа. Вам нужно будет использовать метод isNaN.

138
задан 8 revs, 7 users 49% 8 February 2014 в 00:17
поделиться

16 ответов

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

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

140
ответ дан gehho 17 August 2018 в 08:52
поделиться
  • 1
    & Амп; и * никогда не были такими легкими – Ray Koopa 7 February 2014 в 18:12
  • 2
    Я считаю, что основным источником путаницы является двусмысленность оператора *, который во время объявления переменной используется для указания того, что переменная, по сути, является указателем на определенный тип данных. Но, с другой стороны, он также используется в операторах для доступа к содержимому переменной, на которую указывает указатель (оператор разыменования). – Lucas A. 11 February 2014 в 22:33

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

  1. ipp (varayable name) ---- внутрь.
  2. внутри ipp является адресом ip1.
  3. теперь *ipp, поэтому перейдите к (адрес внутри) ip1.

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

1
ответ дан 2 revs, 2 users 59% 17 August 2018 в 08:52
поделиться

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

Как и все остальное на вашем компьютере, указатели - 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

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

12
ответ дан Akshay Immanuel D 17 August 2018 в 08:52
поделиться
  • 1
    Хотя я считаю, что есть смысл думать о указателях как абстрактных, таинственных сущностях. Любая конкретная реализация указателей - это просто числа, но стратегия реализации, которую вы набросаете, не является требованием реализации, это всего лишь общая стратегия. Указатели не должны быть того же размера, что и int, указатели не обязательно должны быть адресами в модели с плоской виртуальной памятью и т. Д .; это всего лишь детали реализации. – Eric Lippert 6 February 2014 в 17:56
  • 2
    @EricLippert Я думаю, что можно сделать этот пример более абстрактным, не используя фактические адреса памяти или блоки данных. Если это таблица, в которой указано что-то вроде location, value, variable, где было 1,2,3,4,5, а значение было A,1,B,C,3, соответствующее представление указателей можно было бы легко объяснить без использования стрелок, которые по своей сути запутывают. При любой реализации, которую вы выбираете, значение существует в каком-то месте, и это часть головоломки, которая становится запутанной при моделировании со стрелками. – MirroredFate 6 February 2014 в 21:02
  • 3
    @EricLippert По моему опыту, большинство потенциальных программистов на C, у которых проблемы с пониманием указателей, - это те, кто кормили абстрактными, искусственными моделями. Абстракция not полезна, потому что вся цель языка C сегодня заключается в том, что она близка к оборудованию. Если вы изучаете C, но не собираетесь писать код рядом с оборудованием, вы тратите свое время . Java и т. Д. - гораздо лучший выбор, если вы не хотите знать, как работают компьютеры, но просто выполняют программирование на высоком уровне. – Lundin 7 February 2014 в 09:22
  • 4
    @EricLippert И да, могут существовать различные неясные реализации указателей, где указатели не обязательно соответствуют адресам. Но стрелки рисования не помогут вам понять, как они работают. В какой-то момент вам нужно оставить абстрактное мышление и перейти на аппаратный уровень, иначе вы не должны использовать C. Есть гораздо более подходящие современные языки, предназначенные для чисто абстрактного программирования на высоком уровне. – Lundin 7 February 2014 в 09:27
  • 5
    @Lundin: Я тоже не большой поклонник стрелочных диаграмм; понятие стрелки в качестве данных является сложным. Я предпочитаю думать об этом абстрактно, но без стрел. Оператор & в переменной дает вам монету, которая представляет эту переменную. Оператор * на этой монете возвращает вам переменную. Никаких стрелок не требуется! – Eric Lippert 7 February 2014 в 16:44

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

3
ответ дан Andrejovich 17 August 2018 в 08:52
поделиться
ipp = &ip1;

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

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

5
ответ дан Daniel Daranas 17 August 2018 в 08:52
поделиться

Обратите внимание на назначение:

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.

8
ответ дан Dipto 17 August 2018 в 08:52
поделиться

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

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

(Технически я должен сказать «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;

например.

20
ответ дан Eric Lippert 17 August 2018 в 08:52
поделиться

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

*ipp = ip2

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

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

43
ответ дан Grijesh Chauhan 17 August 2018 в 08:52
поделиться
  • 1
    Может быть, стоит отметить разницу между 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.

0
ответ дан haccks 17 August 2018 в 08:52
поделиться

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

#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 [/g0]

21
ответ дан michaeltang 17 August 2018 в 08:52
поделиться

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

Примеры:

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.
4
ответ дан moooeeeep 17 August 2018 в 08:52
поделиться

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

Присвоить ip2 переменной, на которую указывает ipp. Это эквивалентно:

ip1 = ip2;

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

ipp = &ip2;

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

1
ответ дан Rikayan Bandyopadhyay 17 August 2018 в 08:52
поделиться

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

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.

3
ответ дан rullof 17 August 2018 в 08:52
поделиться

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

ipp = &ip1;

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

*ipp = *&ip1 // Here *& becomes 1  
*ipp = ip1   // Hence proved 
3
ответ дан Sunil Bojanapally 17 August 2018 в 08:52
поделиться

Мой вопрос: почему во втором изображении 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;

памяти программы 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

5
ответ дан zmo 17 August 2018 в 08:52
поделиться
  • 1
    с вашим примером, начальное значение ip2 должно быть 3 не 4. – Dipto 6 February 2014 в 16:15
  • 2
    о, я просто изменил память, чтобы она соответствовала порядку объявления. Наверное, я решил, что это так? – zmo 6 February 2014 в 16:18

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

Это:

*ipp = ip2;

совпадает с:

ip1 = ip2;
43
ответ дан Grijesh Chauhan 17 August 2018 в 08:52
поделиться
  • 1
    Может быть, стоит отметить разницу между int *ip1 = &i и *ipp = ip2;, то есть если вы удалите int из первого утверждения, то присваивания выглядят очень схожими, но * делает что-то совсем другое в обоих случаях. – Paul Griffiths 6 February 2014 в 16:04
Другие вопросы по тегам:

Похожие вопросы: