Каковы барьеры для понимания указателей и что можно сделать для их преодоления? [закрыто]

Почему указатели являются таким ведущим фактором путаницы для многих новых и даже старых студентов уровня колледжа в C или C ++? Существуют ли какие-либо инструменты или мыслительные процессы, которые помогли вам понять, как работают указатели на уровне переменной, функции и за ее пределами?

Какие хорошие практики можно сделать, чтобы привести кого-то к уровню «Ах, я понял», не увязнув в общей концепции? В основном, бурильные сценарии.

8.08.2008 06:17:51
Тезис этого вопроса заключается в том, что указатели трудно понять. Этот вопрос не дает никаких доказательств того, что указатели сложнее понять, чем что-либо еще.
bmargulies 11.05.2010 12:46:57
Может быть, я что-то упускаю (потому что я кодирую на языках GCC), но я всегда думал, что указатели в памяти - это структура Key-> Value. Поскольку передача больших объемов данных в программе обходится дорого, вы создаете структуру (значение) и передаете ее указатель / ссылку (ключ), поскольку ключ представляет собой гораздо меньшее представление более крупной структуры. Сложная часть - это когда вам нужно сравнить два указателя / ссылки (сравниваете ли вы ключи или значения), что требует больше работы, чтобы разбить данные, содержащиеся в структуре (значении).
Evan Plaice 14.02.2011 11:17:50
@ Wolfpack'08 "Мне кажется, что память в адресе всегда будет int." - Тогда вам должно показаться, что ничто не имеет типа, так как все они - просто биты в памяти. «На самом деле тип указателя - это тип переменной, на которую указывает указатель» - Нет, тип указателя - это указатель на тип переменной, на которую указывает указатель - что является естественным и должно быть очевидным.
Jim Balter 9.04.2013 09:57:00
Мне всегда было интересно, что так трудно понять в том факте, что переменные (и функции) - это просто блоки памяти, а указатели - это переменные, хранящие адреса памяти. Эта, возможно, слишком практичная модель мышления может не впечатлить всех поклонников абстрактных концепций, но она прекрасно помогает понять, как работают указатели.
Chris says Reinstate Monica 13.05.2013 08:45:27
Короче говоря, учащиеся, вероятно, не понимают, потому что они не понимают правильно или вообще не понимают, как работает память компьютера в целом и, в частности, C-модель памяти . Эта книга « Программирование с нуля» дает очень хороший урок на эти темы.
Abbafei 22.05.2013 11:22:20
28 ОТВЕТОВ
РЕШЕНИЕ

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

Я обнаружил, что лучшая аналогия - рассматривать указатель как лист бумаги с адресом дома и блоком памяти, на который он ссылается как на настоящий дом. Таким образом, все виды операций могут быть легко объяснены.

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

Если вы хотите узнать только концепцию указателей высокого уровня, вам следует игнорировать части, помеченные как «Расположение памяти» в приведенном ниже объяснении. Они предназначены для того, чтобы привести примеры того, как память может выглядеть после операций, но они имеют более низкоуровневый характер. Однако, чтобы точно объяснить, как на самом деле работают переполнения буфера, было важно, чтобы я добавил эти диаграммы.

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


Давайте предположим, что используемый ниже класс THouse выглядит следующим образом:

type
    THouse = class
    private
        FName : array[0..9] of Char;
    public
        constructor Create(name: PChar);
    end;

Когда вы инициализируете объект дома, имя, данное конструктору, копируется в приватное поле FName. Есть причина, по которой он определяется как массив фиксированного размера.

В памяти будут некоторые накладные расходы, связанные с распределением домов, я проиллюстрирую это ниже следующим образом:

--- [ttttNNNNNNNNNN] ---
     ^ ^
     | |
     | + - массив FName
     |
     + - накладные расходы

Область "tttt" является дополнительной, обычно ее будет больше для различных типов сред выполнения и языков, таких как 8 или 12 байтов. Крайне важно, чтобы любые значения, хранящиеся в этой области, никогда не изменялись ничем, кроме распределителя памяти или процедур основной системы, иначе вы рискуете аварийно завершить работу программы.


Выделить память

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

Другими словами, предприниматель выберет место.

THouse.Create('My house');

Расположение памяти:

--- [ttttNNNNNNNNNN] ---
    1234Мой дом

Держите переменную с адресом

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

var
    h: THouse;
begin
    h := THouse.Create('My house');
    ...

Расположение памяти:

    час
    v
--- [ttttNNNNNNNNNN] ---
    1234Мой дом

Скопировать значение указателя

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

Примечание. Обычно это та концепция, которую я больше всего объясняю людям: два указателя не означают два объекта или блока памяти.

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('My house');
    h2 := h1; // copies the address, not the house
    ...
    h1
    v
--- [ttttNNNNNNNNNN] ---
    1234Мой дом
    ^
    h2

Освобождая память

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

var
    h: THouse;
begin
    h := THouse.Create('My house');
    ...
    h.Free;
    h := nil;

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

Расположение памяти:

    ч <- +
    v + - перед свободным
--- [ttttNNNNNNNNNN] --- |
    1234Мой дом <- +

    h (теперь нигде не указывает) <- +
                                + - после бесплатно
---------------------- | (обратите внимание, память может еще
    xx34Мой дом <- + содержит некоторые данные)

Висячие указатели

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

var
    h: THouse;
begin
    h := THouse.Create('My house');
    ...
    h.Free;
    ... // forgot to clear h here
    h.OpenFrontDoor; // will most likely fail

Использование hпосле вызова .Free может сработать, но это просто удача. Скорее всего, он потерпит неудачу на месте клиента в середине критической операции.

    ч <- +
    v + - перед свободным
--- [ttttNNNNNNNNNN] --- |
    1234Мой дом <- +

    ч <- +
    v + - после бесплатно
---------------------- |
    xx34Мой дом <- +

Как вы можете видеть, h по-прежнему указывает на остатки данных в памяти, но, поскольку они могут быть неполными, использование их, как и раньше, может привести к сбою.


Утечка памяти

Вы теряете лист бумаги и не можете найти дом. Хотя дом все еще где-то стоит, и когда вы позже захотите построить новый дом, вы не сможете использовать это место повторно.

var
    h: THouse;
begin
    h := THouse.Create('My house');
    h := THouse.Create('My house'); // uh-oh, what happened to our first house?
    ...
    h.Free;
    h := nil;

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

Расположение памяти после первого выделения:

    час
    v
--- [ttttNNNNNNNNNN] ---
    1234Мой дом

Расположение памяти после второго выделения:

                       час
                       v
--- [ttttNNNNNNNNNN] --- [ttttNNNNNNNNNN]
    1234Мой дом 5678Мой дом

Более распространенный способ получить этот метод - просто забыть освободить что-то, а не перезаписать это, как указано выше. В терминах Delphi это произойдет с помощью следующего метода:

procedure OpenTheFrontDoorOfANewHouse;
var
    h: THouse;
begin
    h := THouse.Create('My house');
    h.OpenFrontDoor;
    // uh-oh, no .Free here, where does the address go?
end;

После того, как этот метод был выполнен, в наших переменных не осталось места для адреса дома, но дом все еще там.

Расположение памяти:

    ч <- +
    v + - до потери указателя
--- [ttttNNNNNNNNNN] --- |
    1234Мой дом <- +

    h (теперь нигде не указывает) <- +
                                + - после потери указателя
--- [ttttNNNNNNNNNN] --- |
    1234Мой дом <- +

Как видите, старые данные остаются нетронутыми в памяти и не будут повторно использоваться распределителем памяти. Распределитель отслеживает, какие области памяти были использованы, и не будет использовать их повторно, пока вы не освободите его.


Освобождение памяти, но сохранение (теперь недействительной) ссылки

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

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

Иногда вы можете даже обнаружить, что на соседнем адресе настроен довольно большой дом, который занимает три адреса (главная улица 1-3), и ваш адрес идет в середину дома. Любые попытки трактовать эту часть большого трехадресного дома как один маленький дом также могут оказаться ужасными.

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('My house');
    h2 := h1; // copies the address, not the house
    ...
    h1.Free;
    h1 := nil;
    h2.OpenFrontDoor; // uh-oh, what happened to our house?

Здесь дом был снесен, по ссылке h1, и, хотя он h1был очищен, h2все еще имеет старый, устаревший адрес. Доступ к дому, который больше не стоит, может или не может работать.

Это вариант висящего указателя выше. Смотрите его расположение памяти.


Переполнение буфера

Вы перемещаете больше вещей в дом, чем вы можете вместить, проливая в соседний дом или двор. Когда владелец этого соседнего дома позже придет домой, он найдет все, что посчитает своими.

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

Таким образом, этот код:

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('My house');
    h2 := THouse.Create('My other house somewhere');
                         ^-----------------------^
                          longer than 10 characters
                         0123456789 <-- 10 characters

Расположение памяти после первого выделения:

                        h1
                        v
----------------------- [ttttNNNNNNNNNN]
                        5678Мой дом

Расположение памяти после второго выделения:

    h2 h1
    ст
--- [ttttNNNNNNNNNN] ---- [ttttNNNNNNNNNN]
    1234Мой другой дом где-нибудь
                        ^ --- + - ^
                            |
                            + - перезаписано

Часть, которая чаще всего вызывает сбой, - это когда вы перезаписываете важные части данных, которые вы сохранили, которые действительно не должны изменяться случайным образом. Например, может не быть проблемой, что части имени h1-house были изменены с точки зрения сбоя программы, но перезапись служебных данных объекта, скорее всего, приведет к сбою при попытке использовать сломанный объект, так как перезаписывать ссылки, которые хранятся на других объектах в объекте.


Связанные списки

Когда вы следуете по адресу на листе бумаги, вы попадаете в дом, и в этом доме есть еще один листок бумаги с новым адресом для следующего дома в цепочке и так далее.

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('Home');
    h2 := THouse.Create('Cabin');
    h1.NextHouse := h2;

Здесь мы создаем ссылку от нашего дома до нашей каюты. Мы можем следовать по цепочке до тех пор, пока дом не будет NextHouseупоминаться, что означает, что он последний. Чтобы посетить все наши дома, мы могли бы использовать следующий код:

var
    h1, h2: THouse;
    h: THouse;
begin
    h1 := THouse.Create('Home');
    h2 := THouse.Create('Cabin');
    h1.NextHouse := h2;
    ...
    h := h1;
    while h <> nil do
    begin
        h.LockAllDoors;
        h.CloseAllWindows;
        h := h.NextHouse;
    end;

Расположение памяти (добавлено NextHouse как ссылка в объекте, отмеченное четырьмя LLLL на диаграмме ниже):

    h1 h2
    ст
--- [ttttNNNNNNNNNNLLLL] ---- [ttttNNNNNNNNNNLLLL]
    1234Дом + 5678Кабина +
                   | ^ |
                   + -------- + * (без ссылки)

В общих чертах, что такое адрес памяти?

Адрес памяти в основных терминах просто число. Если вы думаете о памяти как о большом массиве байтов, самый первый байт имеет адрес 0, следующий - адрес 1 и т. Д. Вверх. Это упрощено, но достаточно хорошо.

Итак, это макет памяти:

    h1 h2
    ст
--- [ttttNNNNNNNNNN] --- [ttttNNNNNNNNNN]
    1234Мой дом 5678Мой дом

Может иметь эти два адреса (самый левый - это адрес 0):

  • h1 = 4
  • h2 = 23

Это означает, что наш приведенный выше список может выглядеть так:

    h1 (= 4) h2 (= 28)
    ст
--- [ttttNNNNNNNNNNLLLL] ---- [ttttNNNNNNNNNNLLLL]
    1234Дом 0028 5678Кабина 0000
                   | ^ |
                   + -------- + * (без ссылки)

Обычно адрес, который «нигде не указывает», является нулевым адресом.


В общих чертах, что такое указатель?

Указатель - это просто переменная, содержащая адрес памяти. Обычно вы можете попросить язык программирования указать свой номер, но большинство языков программирования и сред выполнения стараются скрыть тот факт, что число находится под ним, просто потому, что само число на самом деле не имеет никакого значения для вас. Лучше всего рассматривать указатель как черный ящик, т.е. Вы действительно не знаете или не заботитесь о том, как это на самом деле реализовано, до тех пор, пока оно работает.

745
29.06.2010 08:11:15
Переполнение буфера смешно. «Сосед приходит домой, раскрывает свой череп, скользит по твоему барахлу и судится с тобой в забвении».
gtd 16.04.2009 23:32:03
Это хорошее объяснение концепции, конечно. Концепция НЕ является вещью, которую я нахожу запутывающей относительно указателей, так что вся эта статья была немного потрачена впустую.
Breton 17.04.2009 00:21:11
Но просто чтобы спросить, что вас смущает в отношении указателей?
Lasse V. Karlsen 17.04.2009 06:57:32
Я повторно посетил этот пост с тех пор, как вы написали ответ. Ваше разъяснение с кодом превосходно, и я благодарен вам за то, что вы вернулись к нему, чтобы добавить / уточнить другие мысли. Браво Лассе!
David McGraw 4.06.2009 16:45:35
Невозможно, чтобы одна страница текста (независимо от того, сколько она длилась) могла бы суммировать все нюансы управления памятью, ссылок, указателей и т. Д. Поскольку 465 человек проголосовали за это, я бы сказал, что это достаточно хорошо стартовая страница на информацию. Есть еще что-то, чтобы учиться? Конечно, когда это не так?
Lasse V. Karlsen 26.07.2013 17:33:28

Почему указатели являются таким ведущим фактором путаницы для многих новых и даже старых студентов уровня колледжа на языке C / C ++?

Концепция заполнителя для значения - переменных - отображает то, чему нас учат в школе - алгебру. Не существует существующей параллели, которую вы можете провести, не понимая, как физически распределена память в компьютере, и никто не думает об этом, пока они не имеют дело с вещами низкого уровня - на уровне связи C / C ++ / byte. ,

Существуют ли какие-либо инструменты или мыслительные процессы, которые помогли вам понять, как работают указатели на уровне переменной, функции и за ее пределами?

Адреса ящиков. Я помню, когда я учился программировать BASIC на микрокомпьютеры, там были эти красивые книги с играми, и иногда вам приходилось вставлять значения в определенные адреса. У них было изображение группы коробок, постепенно помеченных 0, 1, 2 ... и было объяснено, что только одна маленькая вещь (байт) могла поместиться в этих коробках, и их было много - некоторые компьютеры было целых 65535! Они были рядом друг с другом, и у них всех был адрес.

Какие хорошие практики можно сделать, чтобы привести кого-то к уровню «Ах, я понял», не увязнув в общей концепции? В основном, бурильные сценарии.

Для дрели? Создайте структуру:

struct {
char a;
char b;
char c;
char d;
} mystruct;
mystruct.a = 'r';
mystruct.b = 's';
mystruct.c = 't';
mystruct.d = 'u';

char* my_pointer;
my_pointer = &mystruct.b;
cout << 'Start: my_pointer = ' << *my_pointer << endl;
my_pointer++;
cout << 'After: my_pointer = ' << *my_pointer << endl;
my_pointer = &mystruct.a;
cout << 'Then: my_pointer = ' << *my_pointer << endl;
my_pointer = my_pointer + 3;
cout << 'End: my_pointer = ' << *my_pointer << endl;

Тот же пример, что и выше, за исключением C:

// Same example as above, except in C:
struct {
    char a;
    char b;
    char c;
    char d;
} mystruct;

mystruct.a = 'r';
mystruct.b = 's';
mystruct.c = 't';
mystruct.d = 'u';

char* my_pointer;
my_pointer = &mystruct.b;

printf("Start: my_pointer = %c\n", *my_pointer);
my_pointer++;
printf("After: my_pointer = %c\n", *my_pointer);
my_pointer = &mystruct.a;
printf("Then: my_pointer = %c\n", *my_pointer);
my_pointer = my_pointer + 3;
printf("End: my_pointer = %c\n", *my_pointer);

Вывод:

Start: my_pointer = s
After: my_pointer = t
Then: my_pointer = r
End: my_pointer = u

Возможно, это объясняет некоторые из основ на примере?

27
25.02.2015 17:04:17
+1 за "не понимая, как физически заложена память". Я пришел к Си из языка ассемблера, и концепция указателей была очень естественной и простой; и я видел людей с языковым прошлым более высокого уровня, которые пытаются это понять. Что еще хуже, синтаксис вводит в заблуждение (указатели функций!), Поэтому одновременное изучение концепции и синтаксиса является причиной неприятностей.
Brendan 28.04.2013 19:08:37
Я знаю это старый пост, но было бы здорово, если бы к сообщению был добавлен вывод предоставленного кода.
Josh 7.11.2013 15:05:59
Да, это похоже на алгебру (хотя алгебра обладает дополнительным пониманием того, что их «переменные» неизменны). Но примерно половина людей, которых я знаю, на практике не разбираются в алгебре. Это просто не для них. Они знают все эти «уравнения» и предписания, чтобы получить результат, но применяют их несколько случайно и неуклюже. И они не могут расширить их для своих собственных целей - для них это просто какой-то неизменный, несложный черный ящик. Если вы понимаете алгебру и умеете эффективно ее использовать, вы уже намного впереди - даже среди программистов.
Luaan 26.05.2015 14:16:15

Я не думаю, что указатели как концепция особенно хитры - ментальные модели большинства учеников соответствуют чему-то подобному, и некоторые быстрые наброски могут помочь.

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

9
8.08.2008 06:35:42

Я не вижу, что так смущает указатели. Они указывают на место в памяти, то есть оно хранит адрес памяти. В C / C ++ вы можете указать тип, на который указывает указатель. Например:

int* my_int_pointer;

Говорит, что my_int_pointer содержит адрес для местоположения, которое содержит int.

Проблема с указателями заключается в том, что они указывают на место в памяти, поэтому легко отследить место, в котором вы не должны находиться. В качестве доказательства посмотрите на многочисленные дыры в безопасности приложений C / C ++ от переполнения буфера (увеличение указателя) мимо выделенной границы).

0
8.08.2008 06:28:48

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

Задача состоит в том, чтобы попросить их реализовать простую виртуальную машину на основе байт-кода (на любом языке, который они выбрали, для этого отлично работает python) с набором инструкций, ориентированных на операции с указателями (загрузка, сохранение, прямая / косвенная адресация). Затем попросите их написать простые программы для этого набора инструкций.

Все, что требует чуть более простого дополнения, будет включать указатели, и они обязательно получат это.

48
8.08.2008 06:29:59
Интересно. Не знаю, как начать идти об этом, хотя. Любые ресурсы, чтобы поделиться?
Karolis 4.05.2009 10:13:15
Я согласен. Например, я научился программировать на ассемблере до C, и, зная, как работают регистры, учить указатели было легко. На самом деле, не было много обучения, все это было очень естественно.
Milan Babuškov 1.06.2009 23:07:53
Возьмите базовый процессор, скажите что-нибудь, что работает на газонокосилках или посудомоечных машинах, и реализуйте его. Или очень очень базовое подмножество ARM или MIPS. Оба из них имеют очень простой ISA.
Daniel Goldberg 12.12.2009 11:29:32
Возможно, стоит отметить, что этот образовательный подход защищал / практиковал сам Дональд Кнут. Искусство компьютерного программирования Кнута описывает простую гипотетическую архитектуру и просит студентов реализовать решения для практических задач на гипотетическом языке ассемблера для этой архитектуры. Как только это стало практически осуществимым, некоторые студенты, читающие книги Кнута, фактически реализуют его архитектуру как виртуальную машину (или используют существующую реализацию) и фактически запускают свои решения. ИМО это отличный способ учиться, если у вас есть время.
WeirdlyCheezy 2.11.2012 20:35:44
@ Люк, я не думаю, что так легко понять людей, которые просто не могут понять указатели (или, если быть более точным, косвенность вообще). Вы в основном предполагаете, что люди, которые не понимают указатели в C, смогут начать изучать ассемблер, понять основную архитектуру компьютера и вернуться к C с пониманием указателей. Это может быть правдой для многих, но согласно некоторым исследованиям, кажется, что некоторые люди по своей сути не могут понять косвенность, даже в принципе (мне все еще трудно в это поверить, но, возможно, мне просто повезло с моими "учениками" «).
Luaan 26.05.2015 14:11:23

Я думаю, что главная причина, по которой у людей возникают проблемы, заключается в том, что, как правило, этому не учат интересно и увлекательно. Я хотел бы, чтобы лектор собрал 10 добровольцев из толпы и дал им по 1 метру каждого, чтобы они стояли в определенной конфигурации и использовали линейки, чтобы указывать друг на друга. Затем покажите арифметику указателей, перемещая людей (и куда они указывают своих правителей). Это был бы простой, но эффективный (и прежде всего запоминающийся) способ показать концепции, не слишком увязая в механике.

Как только вы попадаете на C и C ++, некоторым людям становится все труднее. Я не уверен, что это потому, что они, наконец, выдвигают теорию о том, что они не понимают должным образом на практике, или потому, что манипулирование указателями по сути сложнее в этих языках. Я не очень хорошо помню свой собственный переход, но я знал указатели на Паскале, а затем перешел на Си и совершенно потерян.

3
11.11.2010 13:57:27

Пример учебника с хорошим набором диаграмм очень помогает в понимании указателей .

Джоэл Спольски делает несколько хороших замечаний о понимании указателей в своей статье « Партизанское руководство по интервью» :

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

9
20.05.2012 17:38:38

В моем первом классе Comp Sci мы выполнили следующее упражнение. Конечно, это был лекционный зал с примерно 200 студентами ...

Профессор пишет на доске: int john;

Джон встает

Профессор пишет: int *sally = &john;

Салли встает, указывает на Джона

Профессор: int *bill = sally;

Билл встает, указывает на Джона

Профессор: int sam;

Сэм встает

Профессор: bill = &sam;

Билл теперь указывает на Сэма.

Я думаю, вы поняли идею. Я думаю, что мы потратили на это около часа, пока не рассмотрели основы назначения указателей.

152
21.04.2016 20:41:01
Я не думаю, что я неправильно понял. Мое намерение состояло в том, чтобы изменить значение указываемой переменной с Джона на Сэма. Представить людей немного сложнее, потому что похоже, что вы меняете значение обоих указателей.
Tryke 2.10.2008 18:05:02
Но причина, по которой это сбивает с толку, заключается в том, что Джон не встал со своего места, а Сэм сел, как мы могли себе представить. Это больше похоже на то, как Сэм подошел, сунул руку в Джона и клонировал программирование Сэма в тело Джона, как переплетение Гюго в перезагруженной матрице.
Breton 17.04.2009 00:31:44
Больше похоже на то, как Сэм садится на место Джона, а Джон плывет по комнате, пока не наталкивается на что-то критическое и вызывает сегфо
just_wes 31.01.2010 04:36:05
Я лично считаю этот пример излишне сложным. Мой профессор велел мне указать на свет и сказал: «Твоя рука - указатель на световой объект».
Celeritas 15.03.2013 22:03:26
Проблема с примерами такого типа заключается в том, что указатель на X и X не совпадает. И это не изображается с людьми.
Isaac Remuant 19.04.2013 18:19:48

Сложности указателей выходят за рамки того, что мы можем легко научить. Заставить студентов указывать друг на друга и использовать листы бумаги с домашними адресами - оба являются отличными инструментами обучения Они делают большую работу по представлению основных понятий. Действительно, изучение основных понятий жизненно важно для успешного использования указателей. Тем не менее, в рабочем коде часто встречаются гораздо более сложные сценарии, чем эти простые демонстрации.

Я был связан с системами, где у нас были структуры, указывающие на другие структуры, указывающие на другие структуры. Некоторые из этих структур также содержали встроенные структуры (а не указатели на дополнительные структуры). Это где указатели становятся действительно запутанными. Если у вас есть несколько уровней косвенности, и вы начинаете заканчивать с кода, как это:

widget->wazzle.fizzle = fazzle.foozle->wazzle;

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

Сложные структуры указателей также не обязательно указывают на плохое кодирование (хотя они могут). Композиция является жизненно важной частью хорошего объектно-ориентированного программирования, и в языках с необработанными указателями она неизбежно приведет к многоуровневой косвенности. Кроме того, системы часто должны использовать сторонние библиотеки со структурами, которые не соответствуют друг другу по стилю или технике. В подобных ситуациях сложность естественным образом возникает (хотя, конечно, мы должны бороться с ней как можно больше).

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

12
10.08.2008 18:09:16

Я не думаю, что сами указатели сбивают с толку. Большинство людей могут понять концепцию. Теперь, сколько указателей вы можете думать или сколько уровней косвенности вам удобно. Не нужно слишком много, чтобы поставить людей на край. Тот факт, что они могут быть случайно изменены из-за ошибок в вашей программе, также может сильно затруднить их отладку, когда в вашем коде что-то не так.

2
10.08.2008 18:39:07

«Учебник Теда Дженсена по указателям и массивам в Си» я нашел отличным ресурсом для изучения указателей. Он разделен на 10 уроков, начиная с объяснения того, что такое указатели (и для чего они нужны) и заканчивая указателями на функции. http://home.netcom.com/~tjensen/ptr/cpoint.htm

Далее Beej's Guide по сетевому программированию обучает API сокетов Unix, из которого вы можете начать делать действительно забавные вещи. http://beej.us/guide/bgnet/

19
11.08.2008 04:21:07
Я второй учебник Теда Дженсена. Он разбивает указатели на уровень детализации, это не слишком подробно, ни одна книга, которую я читал, не делает. Очень полезно! :)
Dave Gallagher 13.09.2010 18:39:20

Мне нравится аналогия с домашним адресом, но я всегда думал, что адрес относится к самому почтовому ящику. Таким образом, вы можете визуализировать концепцию разыменования указателя (открытия почтового ящика).

Например, следуя связанному списку: 1) начните со своей бумаги с адресом 2) перейдите по адресу на бумаге 3) откройте почтовый ящик, чтобы найти новый лист бумаги со следующим адресом на нем

В линейном связанном списке в последнем почтовом ящике ничего нет (конец списка). В круговом связанном списке последний почтовый ящик содержит адрес первого почтового ящика.

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

4
16.08.2008 03:28:13
Противоречие с аналогией номера почтового ящика состоит в том, что, хотя язык, изобретенный Деннисом Ритчи, определяет поведение в терминах адресов байтов и значений, хранящихся в этих байтах, язык, определенный стандартом C, предлагает «оптимизировать» реализации для использования поведенческого поведения. модель, которая является более сложной, но определяет различные аспекты модели способами, которые неоднозначны, противоречивы и неполны.
supercat 10.07.2018 22:40:21

Аналогия, которую я нашел полезной для объяснения указателей, - это гиперссылки. Большинство людей могут понять, что ссылка на веб-странице «указывает» на другую страницу в Интернете, и если вы можете скопировать и вставить эту гиперссылку, они оба будут указывать на одну и ту же исходную веб-страницу. Если вы зайдете и отредактируете эту исходную страницу, затем перейдите по любой из этих ссылок (указателей), и вы получите эту новую обновленную страницу.

124
26.07.2013 15:56:33
Мне действительно это нравится. Нетрудно видеть, что двойная запись гиперссылки не приводит к появлению двух веб-сайтов (точно так же, как int *a = bне делает две копии *b).
detly 29.06.2010 08:39:16
Это на самом деле очень интуитивно понятно, и каждый должен иметь к этому отношение. Хотя есть много сценариев, где эта аналогия разваливается. Отлично подходит для быстрого ознакомления. +1
Brian Wigginton 10.10.2010 19:11:18
Ссылка на страницу, которая открывается дважды, обычно создает два почти полностью независимых экземпляра этой веб-страницы. Я думаю, что гиперссылка может быть хорошей аналогией с конструктором, но не с указателем.
ThoAppelsin 11.08.2014 02:21:00
@ThoAppelsin Не обязательно верно, если вы, например, обращаетесь к статической html-странице, вы обращаетесь к одному файлу на сервере.
dramzy 10.01.2015 17:47:42
Вы переосмысливаете это. Гиперссылки указывают на файлы на сервере, это аналогия.
dramzy 11.01.2015 02:11:41

Просто чтобы немного запутать вещи, иногда приходится работать с ручками, а не с указателями. Дескрипторы - это указатели на указатели, так что серверная часть может перемещать объекты в памяти для дефрагментации кучи. Если указатель изменяется в середине процедуры, результаты непредсказуемы, поэтому сначала нужно заблокировать дескриптор, чтобы убедиться, что ничего никуда не денется.

http://arjay.bc.ca/Modula-2/Text/Ch15/Ch15.8.html#15.8.5 говорит об этом немного более связно, чем я. :-)

0
22.08.2008 18:25:52
-1: дескрипторы не являются указателями на указатели; они не являются указателями в любом смысле. Не путай их.
Ian Goldby 10.04.2012 10:59:43
«Они не являются указателями в любом смысле», - умоляю я отличаться.
SarekOfVulcan 11.04.2012 14:15:50
Указатель - это область памяти. Дескриптор - это любой уникальный идентификатор. Это может быть указатель, но это может быть также индекс в массиве или что-то еще в этом отношении. Ссылка, которую вы дали, представляет собой особый случай, когда дескриптор является указателем, но это не обязательно. См. Также parashift.com/c++-faq-lite/references.html#faq-8.8
Ian Goldby 12.04.2012 15:15:05
Эта ссылка не поддерживает ваше утверждение о том, что они не являются указателями в каком-либо смысле - «Например, дескрипторы могут быть Fred **, где указатели на Fred * указывают ...» Я не думаю, что -1 был справедливым.
SarekOfVulcan 13.04.2012 16:33:20

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

Когда вы впервые видите указатель, вы на самом деле не получаете то, что находится в этом месте памяти. "Что вы имеете в виду, он имеет адрес ?"

Я не согласен с тем, что «вы либо получаете их, либо не получаете».

Их становится легче понять, когда вы начинаете находить для них реальное применение (например, не передавать большие структуры в функции).

5
22.08.2008 19:19:11

Номер почтового ящика.

Это часть информации, которая позволяет вам получить доступ к чему-то еще.

(И если вы выполняете арифметику с номерами почтовых ящиков, у вас могут возникнуть проблемы, потому что письмо идет в неправильном ящике. А если кто-то переходит в другое состояние - без адреса пересылки - тогда у вас есть свисающий указатель. с другой стороны - если почтовое отделение пересылает почту, то у вас есть указатель на указатель.)

1
29.08.2008 18:47:30

Проблема с указателями не в концепции. Это исполнение и язык. Дополнительная путаница приводит к тому, что учителя предполагают, что это КОНЦЕПЦИЯ указателей труднее, а не жаргон или запутанная путаница C и C ++ делает концепцию. Огромные усилия прилагаются для объяснения концепции (как в принятом ответе на этот вопрос), и это в значительной степени просто напрасно тратится на кого-то вроде меня, потому что я уже все это понимаю. Это просто объясняет не ту часть проблемы.

Чтобы дать вам представление о том, откуда я родом, я тот, кто прекрасно понимает указатели, и я могу грамотно использовать их на языке ассемблера. Потому что на языке ассемблера они не называются указателями. Они называются адресами. Когда дело доходит до программирования и использования указателей на C, я допускаю много ошибок и очень запутываюсь. Я до сих пор не разобрался в этом. Позвольте привести пример.

Когда API говорит:

int doIt(char *buffer )
//*buffer is a pointer to the buffer

чего он хочет?

это может хотеть:

число, представляющее адрес буфера

(Чтобы дать это, я говорю doIt(mybuffer), или doIt(*myBuffer)?)

число, представляющее адрес к адресу в буфере

(это doIt(&mybuffer)или doIt(mybuffer)или doIt(*mybuffer)?)

число, представляющее адрес по адресу к адресу в буфере

(может быть doIt(&mybuffer). или это doIt(&&mybuffer)? или даже doIt(&&&mybuffer))

и так далее, и используемый язык не делает это настолько ясным, потому что он включает в себя слова «указатель» и «ссылка», которые не имеют для меня такого значения и ясности, как «x содержит адрес для y» и « эта функция требует адреса к y ". Ответ дополнительно зависит от того, с какого черта «mybuffer» должен начинаться, и что он намерен делать с ним. Язык не поддерживает уровни вложенности, которые встречаются на практике. Например, когда мне нужно передать «указатель» на функцию, которая создает новый буфер, и он изменяет указатель так, чтобы он указывал на новое местоположение буфера. Действительно ли он хочет указатель или указатель на указатель, чтобы он знал, куда идти, чтобы изменить содержимое указателя. Большую часть времени я просто должен догадаться, что подразумевается под "

«Указатель» просто перегружен. Указатель является адресом для значения? или это переменная, которая содержит адрес значения. Когда функция хочет указатель, она хочет адрес, который содержит переменная указателя, или она хочет адрес переменной указателя? Я запутался.

8
17.04.2009 00:17:45
Я видел, что это объясняется так: если вы видите объявление указателя как double *(*(*fn)(int))(char), то результатом оценки *(*(*fn)(42))('x')будет a double. Вы можете снять уровни оценки, чтобы понять, какими должны быть промежуточные типы.
Bernd Jendrissek 28.04.2013 15:46:08
@BerndJendrissek Не уверен, что я следую. Каков результат оценки (*(*fn)(42))('x') тогда?
Breton 19.08.2013 04:51:26
Вы получаете вещь (давайте назовем это x), где, если вы оцениваете *x, вы получаете двойную.
Bernd Jendrissek 19.08.2013 04:54:50
@BerndJendrissek Это должно объяснить что-то о указателях? Я не понимаю В чем ваша точка зрения? Я снял слой и не получил никакой новой информации о каких-либо промежуточных типах. Что это объясняет о том, что определенная функция собирается принять? Какое это имеет отношение к чему-либо?
Breton 19.08.2013 08:33:42
Может быть, сообщение в этом объяснении (и это не мое, если бы я мог найти , где я впервые увидел это), чтобы думать об этом меньше в плане того , что fn есть и более с точки зрения того, что вы можете сделать сfn
Bernd Jendrissek 20.08.2013 09:26:33

Я думаю, что это может быть проблема синтаксиса. Синтаксис C / C ++ для указателей кажется непоследовательным и более сложным, чем необходимо.

По иронии судьбы, то, что на самом деле помогло мне понять указатели, было столкновение с концепцией итератора в стандартной библиотеке шаблонов c ++ . Это иронично, потому что я могу только предположить, что итераторы были задуманы как обобщение указателя.

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

2
17.04.2009 16:23:51
Проблема в основном в синтаксисе объявления C. Но использование указателя было бы легче, если (*p)бы это было так (p->), и поэтому мы имели бы p->->xвместо двусмысленного*p->x
MSalters 13.01.2010 14:08:27
@MSalters Боже мой, ты шутишь, верно? Там нет никаких несоответствий там. a->bпросто значит (*a).b.
Miles Rout 28.06.2014 00:00:18
@Miles: Действительно, и по этой логике * p->xсредства , в * ((*a).b)то время как *p -> xсредства (*(*p)) -> x. Смешивание префиксных и постфиксных операторов приводит к неоднозначному анализу.
MSalters 28.06.2014 15:27:39
@MSalters нет, потому что пробел не имеет значения. Это все равно, что сказать, что 1+2 * 3должно быть 9.
Miles Rout 1.07.2014 15:35:30

Неплохой способ понять это с помощью итераторов ... но продолжайте искать, вы увидите, как Александреску начнет жаловаться на них.

Многие бывшие разработчики на C ++ (которые никогда не понимали, что итераторы являются современным указателем до создания дампов языка) переходят на C # и все еще верят, что у них есть достойные итераторы.

Хм, проблема в том, что все, что делают итераторы, в полной мере не согласны с тем, чего стремятся достичь платформы времени исполнения (Java / CLR): новое, простое использование «все - как разработчик». Что может быть хорошо, но они сказали это однажды в фиолетовой книге, и они сказали это даже до и до C:

Косвенность.

Очень мощная концепция, но никогда, если вы делаете это все время. Итераторы полезны, поскольку они помогают с абстракцией алгоритмов, еще один пример. А время компиляции - это место для алгоритма, очень простого. Вы знаете код + данные или другой язык C #:

IEnumerable + LINQ + Massive Framework = 300 МБ штрафных санкций за время выполнения при паршивом перетаскивании приложений через кучи экземпляров ссылочных типов.

«Le Pointer - это дешево».

1
17.04.2009 00:34:27
Какое это имеет отношение к чему-либо?
Neil Williams 17.04.2009 17:03:40
... что вы пытаетесь сказать, кроме того, что "статическое связывание - лучшая вещь когда-либо" и "я не понимаю, как работает нечто отличное от того, что я изучил ранее"?
Luaan 29.06.2016 14:06:07
Луаан, ты не мог знать, что можно узнать, разобрав JIT в 2000 году, не так ли? То, что он заканчивается таблицей переходов, из таблицы указателей, как показано в 2000 году в Интернете в ASM, поэтому непонимание чего-либо другого может иметь другое значение: внимательное чтение является важным навыком, попробуйте еще раз.
rama-jka toti 31.07.2016 13:42:50

Причина, по которой это так трудно понять, не в том, что это сложная концепция, а в том, что синтаксис противоречив .

   int *mypointer;

Сначала вы узнали, что крайняя левая часть создания переменной определяет тип переменной. Объявление указателя не работает так в C и C ++. Вместо этого они говорят, что переменная указывает на тип слева. В этом случае: *mypointer указывает на int.

Я не полностью понял указатели, пока не попробую использовать их в C # (с небезопасным), они работают точно так же, но с логическим и последовательным синтаксисом. Указатель сам по себе является типом. Здесь mypointer является указателем на int.

  int* mypointer;

Даже не заводите меня на указатели функций ...

5
17.04.2009 16:51:08
На самом деле, оба ваших фрагмента являются действительными C. В течение многих лет стиль C является наиболее распространенным. Например, второе немного более распространено в C ++.
RBerteig 18.04.2009 08:36:42
Второй фрагмент не очень хорошо работает с более сложными объявлениями. И синтаксис не будет таким «непоследовательным», как только вы поймете, что правая часть объявления указателя показывает вам, что вы должны сделать с указателем, чтобы получить что-то, чей тип является атомарным спецификатором типа слева.
Bernd Jendrissek 28.04.2013 15:50:02
int *p;имеет простое значение: *pявляется целым числом. int *p, **ppозначает: *pи **ppявляются целыми числами.
Miles Rout 28.06.2014 00:02:11
@MilesRout: Но это именно проблема. *pи не**pp являются целыми числами, потому что вы никогда не инициализировали или или указывать на что-либо. Я понимаю, почему некоторые люди предпочитают придерживаться этой грамматики, особенно потому, что некоторые крайние случаи и сложные случаи требуют от вас (хотя, тем не менее, вы можете обойти это во всех случаях, о которых я знаю) ... но я не думаю, что эти случаи более важны, чем тот факт, что обучение правильному выравниванию вводит в заблуждение новичков. Не говоря уже о некрасивом! :)ppp*pp
Lightness Races in Orbit 1.12.2015 20:18:19
@LightnessRacesinOrbit Обучение правильному выравниванию далеко не вводит в заблуждение. Это единственный правильный способ научить этому. НЕ учить это вводит в заблуждение.
Miles Rout 2.12.2015 00:12:32

Я мог работать с указателями, когда знал только C ++. Я как бы знал, что делать в некоторых случаях, а что не делать методом проб / ошибок. Но то, что дало мне полное понимание, это язык ассемблера. Если вы выполняете серьезную отладку на уровне инструкций с помощью написанной вами программы на языке ассемблера, вы должны понимать многие вещи.

5
29.04.2009 02:18:09

Путаница возникает из-за нескольких уровней абстракции, смешанных вместе в концепции «указатель». Программистов не смущают обычные ссылки в Java / Python, но указатели отличаются тем, что они раскрывают характеристики базовой архитектуры памяти.

Хорошим принципом является чистое разделение слоев абстракции, а указатели этого не делают.

2
4.05.2009 08:53:22
Интересно то, что указатели C на самом деле не раскрывают какой-либо особенности базовой архитектуры памяти. Единственные различия между ссылками на Java и указателями на Си состоят в том, что у вас могут быть сложные типы, включающие указатели (например, int *** или char * ( ) (void * )), существует арифметика указателей для массивов и указателей на структуру членов, наличие пустоты * и двойственность массива / указателя. Кроме того, они работают точно так же.
jpalecek 19.06.2009 17:08:45
Хорошая точка зрения. Это - арифметика указателя и возможность переполнения буфера - вырывание абстракции из выпадающей из текущей области памяти - это делает это.
Joshua Fox 23.06.2009 10:12:21
@jpalecek: Довольно легко понять, как работают указатели в реализациях, которые документируют их поведение с точки зрения базовой архитектуры. Говорить foo[i]означает идти в определенное место, двигаться вперед на определенное расстояние и смотреть, что там. Что усложняет вещи, так это гораздо более сложный дополнительный уровень абстракции, который был добавлен стандартом исключительно для выгоды компилятора, но моделирует вещи таким образом, который плохо подходит для нужд программиста и компилятора.
supercat 9.08.2018 20:02:42

Причина, по которой я с трудом разбираюсь в указателях, заключается в том, что во многих объяснениях содержится много дерьма о передаче по ссылке. Все это путает проблему. Когда вы используете параметр указателя, вы все равно передаете по значению; но значение оказывается адресом, а не, скажем, int.

Кто-то уже связался с этим учебником, но я могу выделить момент, когда я начал понимать указатели:

Учебник по указателям и массивам в C: Глава 3 - Указатели и строки

int puts(const char *s);

На данный момент игнорируем const.параметр, на который передается параметр puts(), это указатель, то есть значение указателя (поскольку все параметры в C передаются по значению), а значение указателя является адресом, на который он указывает, или, просто , адрес. Таким образом, когда мы пишем, puts(strA);как мы видели, мы передаем адрес strA [0].

В тот момент, когда я прочитал эти слова, облака разошлись, и луч солнца окутал меня пониманием указателя.

Даже если вы являетесь разработчиком VB .NET или C # (как и я) и никогда не используете небезопасный код, все равно стоит понять, как работают указатели, иначе вы не поймете, как работают ссылки на объекты. Тогда у вас будет распространенное, но ошибочное мнение, что передача ссылки на объект в метод копирует объект.

24
12.09.2012 17:07:42
Оставляет меня интересно, какой смысл иметь указатель. В большинстве блоков кода, с которыми я сталкиваюсь, указатель виден только в его объявлении.
Wolfpack'08 6.12.2012 13:17:43
@ Wolfpack'08 ... что ?? Какой код вы смотрите ??
Kyle Strand 11.11.2015 22:31:01
@KyleStrand Позвольте мне взглянуть.
Wolfpack'08 13.11.2015 09:00:36

Я думаю, что основным препятствием для понимания указателей являются плохие учителя.

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

И конечно, что они трудны для понимания, опасны и полумагичны.

Ничего из этого не является правдой. Указатели на самом деле довольно простые понятия, если вы придерживаетесь того, что говорит о них язык C ++, и не наделяете их атрибутами, которые «обычно» работают на практике, но тем не менее не гарантируются языком и так не являются частью реальной концепции указателя.

Я попытался написать объяснение этому несколько месяцев назад в этом посте - надеюсь, это кому-нибудь поможет.

(Обратите внимание, что до того, как кто-то станет меня педантичен, да, стандарт C ++ говорит, что указатели представляют адреса памяти. Но он не говорит, что «указатели являются адресами памяти и ничем иным, как адресами памяти, и могут использоваться или считаться взаимозаменяемыми с памятью». адреса ». Различие важно)

8
17.03.2018 03:43:58
В конце концов, нулевой указатель не указывает на нулевой адрес в памяти, хотя его «значение» C равно нулю. Это совершенно отдельная концепция, и если вы справитесь с ней неправильно, вы можете в конечном итоге обратиться (и разыменовать) к чему-то, чего вы не ожидали. В некоторых случаях это может быть даже нулевой адрес в памяти (особенно теперь, когда адресное пространство обычно плоское), но в других оно может быть опущено оптимизирующим компилятором как неопределенное поведение, или доступ к какой-либо другой части памяти, которая связана с "нулем" для данного типа указателя. Веселье наступает.
Luaan 29.06.2016 13:59:51
Не обязательно. Вы должны быть в состоянии смоделировать компьютер в своей голове, чтобы указатели имели смысл (а также для отладки других программ). Не каждый может это сделать.
Thorbjørn Ravn Andersen 27.02.2018 22:32:25

У каждого новичка C / C ++ есть та же самая проблема, и эта проблема возникает не потому, что «указатели трудно выучить», а «кто и как это объясняет». Некоторые учащиеся собирают его в устной форме, а некоторые визуально, и лучший способ объяснить это - использовать пример «тренировка» (подходит для словесного и визуального примера).

Где «локомотив» - указатель, который не может ничего удержать, а «вагон» - это то, что «локомотив» пытается тянуть (или указывать). После этого вы можете классифицировать сам «вагон», может ли он содержать животных, растения или людей (или их комбинацию).

0
9.12.2012 19:26:57

Я решил добавить аналогию к этому списку, которая показалась мне очень полезной при объяснении указателей (в прошлом) в качестве преподавателя информатики; во-первых, давайте:


Установите сцену :

Рассмотрим парковку с 3-мя местами, эти номера пронумерованы:

-------------------
|     |     |     |
|  1  |  2  |  3  |
|     |     |     |

В некотором смысле это похоже на ячейки памяти, они последовательные и смежные ... вроде как массив. Сейчас в них нет машин, так что это как пустой массив ( parking_lot[3] = {0}).


Добавить данные

Стоянка никогда не останется пустой на долгое время ... если бы это было так, это было бы бессмысленно, и никто бы не стал ее строить. Скажем, по мере того, как день движется, весь участок заполнен 3 автомобилями, синим, красным и зеленым:

   1     2     3
-------------------
| o=o | o=o | o=o |
| |B| | |R| | |G| |
| o-o | o-o | o-o |

Эти машины все тот же тип (автомобиль) , так что один способ думать о том , что наши машины какие - то данные (скажем int) , но они имеют разные значения ( blue, red, green, которые могут быть цвета enum)


Введите указатель

Теперь, если я отвезу вас на эту парковку и попрослю вас найти мне синюю машину, вы вытяните один палец и при помощи него сможете указать на синюю машину в месте 1. Это все равно, что взять указатель и присвоить его адресу памяти. ( int *finger = parking_lot)

Ваш палец (указатель) не является ответом на мой вопрос. Взгляд на ваш палец ничего не говорит мне, но если я посмотрю, куда указывает ваш палец (разыменовывает указатель), я могу найти машину (данные), которую я искал.


Переназначение указателя

Теперь я могу попросить вас найти красную машину, а вы можете перенаправить палец на новую машину. Теперь ваш указатель (такой же, как и раньше) показывает мне новые данные (место для парковки, где находится красная машина) того же типа (автомобиль).

Указатель физически не изменился, это все еще ваш палец, изменились только данные, которые он мне показывал. (адрес "места для парковки")


Двойные указатели (или указатель на указатель)

Это работает с более чем одним указателем. Я могу спросить, где находится указатель, указывающий на красную машину, и вы можете использовать другую руку и указать пальцем на первый палец. (это как int **finger_two = &finger)

Теперь, если я хочу узнать, где находится синяя машина, я могу следовать по направлению первого пальца ко второму пальцу, к машине (данные).


Свисающий указатель

Теперь предположим, что вы очень похожи на статую и хотите, чтобы ваша рука указывала на красную машину бесконечно. Что если эта красная машина уедет?

   1     2     3
-------------------
| o=o |     | o=o |
| |B| |     | |G| |
| o-o |     | o-o |

Ваш указатель все еще указывает на то, где находилась красная машина, но ее больше нет. Скажем, новая машина останавливается там ... оранжевая машина. Теперь, если я спрошу вас снова, «где красная машина», вы все еще указываете туда, но теперь вы ошибаетесь. Это не красная машина, это оранжевая.


Арифметика указателей

Итак, вы все еще указываете на второе место для парковки (сейчас занято автомобилем Orange)

   1     2     3
-------------------
| o=o | o=o | o=o |
| |B| | |O| | |G| |
| o-o | o-o | o-o |

Ну, теперь у меня новый вопрос ... Я хочу знать цвет машины на следующем месте парковки. Вы можете видеть, что вы указываете на точку 2, поэтому вы просто добавляете 1 и указываете на следующую точку. ( finger+1), теперь, так как я хотел знать, какие данные были там, вы должны проверить это место (а не только палец), чтобы вы могли отложить указатель ( *(finger+1)), чтобы увидеть, что там присутствует зеленая машина (данные в этом месте )

10
5.04.2013 19:55:49
Только не используйте слово «двойной указатель». Указатели могут указывать на что угодно, поэтому очевидно, что у вас могут быть указатели, указывающие на другие указатели. Они не являются двойными указателями.
gnasher729 22.03.2014 14:45:09
Я думаю, что здесь не хватает того, что сами «пальцы», продолжая вашу аналогию, каждый «занимают парковочное место». Я не уверен, что людям трудно понять указатели на высоком уровне абстракции вашей аналогии, это понимание того, что указатели - это изменчивые вещи, которые занимают места в памяти, и насколько это полезно, что, кажется, уклоняется от людей.
Emmet 2.08.2014 10:14:55
@Emmet - я не согласен с тем, что можно гораздо больше использовать указатели WRT, но я читаю вопрос: "without getting them bogged down in the overall concept"как понимание высокого уровня. И к вашему мнению: "I'm not sure that people have any difficulty understanding pointers at the high level of abstraction"- вы были бы очень удивлены, как много людей не понимают указателей даже до этого уровня
Mike 2.08.2014 13:07:45
Есть ли смысл в том, чтобы провести аналогию «автомобильный палец» с человеком (одним или несколькими пальцами) и генетической аномалией, которая может позволить каждому из них указывать в любом направлении!), Сидевшей в одной из машин, указывающей на другую машину (или наклоняется, указывая на пустошь рядом с участком как «неинициализированный указатель», или целую руку, указывающую на ряд пробелов как «массив указателей фиксированного размера [5]», или свернутую в ладонь «нулевой указатель» это указывает куда-то, где известно, что НИКОГДА нет машины) ... 8-)
SlySven 5.08.2016 00:14:35
Ваше объяснение было заметным и хорошим для начинающего.
Yatendra Rathore 27.07.2017 08:31:47

В некоторых ответах выше утверждалось, что «указатели не очень сложны», но они не обращались непосредственно к тому, где «указатель сложен!» происходит от. Несколько лет назад я обучал студентов первого курса CS (всего один год, так как я явно сосал их), и мне было ясно, что идея указателя не сложна. Трудно понять, почему и когда вам нужен указатель .

Я не думаю, что вы можете отделить этот вопрос - почему и когда использовать указатель - от объяснения более широких проблем разработки программного обеспечения. Почему каждая переменная не должна быть глобальной переменной и почему нужно выделять похожий код в функции (чтобы получить это, используйте указатели, чтобы специализировать их поведение на своем сайте вызова).

1
28.04.2013 16:04:32

Мне нравилось объяснять это с точки зрения массивов и индексов - люди могут быть не знакомы с указателями, но они обычно знают, что такое индекс.

Итак, я говорю, представьте, что ОЗУ - это массив (а у вас всего 10 байтов ОЗУ):

unsigned char RAM[10] = { 10, 14, 4, 3, 2, 1, 20, 19, 50, 9 };

Тогда указатель на переменную на самом деле является просто индексом (первого байта) этой переменной в ОЗУ.

Таким образом, если у вас есть указатель / индекс unsigned char index = 2, то значение, очевидно, является третьим элементом или числом 4. Указатель на указатель - это то место, где вы берете это число и используете его как сам индекс, как RAM[RAM[index]].

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

2
22.05.2013 14:42:08