Почему указатели являются таким ведущим фактором путаницы для многих новых и даже старых студентов уровня колледжа в C или C ++? Существуют ли какие-либо инструменты или мыслительные процессы, которые помогли вам понять, как работают указатели на уровне переменной, функции и за ее пределами?
Какие хорошие практики можно сделать, чтобы привести кого-то к уровню «Ах, я понял», не увязнув в общей концепции? В основном, тренировка как сценарии.
Ответы:
Указатели - это концепция, которая поначалу многих может сбить с толку, особенно когда речь идет о копировании значений указателей и сохранении ссылок на один и тот же блок памяти.
Я обнаружил, что лучшая аналогия - рассматривать указатель как лист бумаги с адресом дома и блоком памяти, на который он ссылается как на настоящий дом. Таким образом, все виды операций можно легко объяснить.
Я добавил немного кода Delphi внизу и несколько комментариев, где это уместно. Я выбрал Delphi, поскольку мой другой основной язык программирования, C #, не демонстрирует такие вещи, как утечки памяти, таким же образом.
Если вы хотите узнать только концепцию указателей высокого уровня, вам следует игнорировать части, помеченные как «Расположение памяти» в приведенном ниже объяснении. Они предназначены для того, чтобы привести примеры того, как память может выглядеть после операций, но они носят более низкоуровневый характер. Однако, чтобы точно объяснить, как на самом деле работают переполнения буфера, было важно, чтобы я добавил эти диаграммы.
Отказ от ответственности: для всех намерений и целей, это объяснение и примеры макетов памяти значительно упрощены. Там больше накладных расходов и гораздо больше деталей, которые вам нужно знать, если вам нужно иметь дело с памятью на низкоуровневой основе. Тем не менее, для целей объяснения памяти и указателей, это достаточно точно.
Давайте предположим, что используемый ниже класс THouse выглядит следующим образом:
Когда вы инициализируете объект дома, имя, данное конструктору, копируется в приватное поле FName. Есть причина, по которой он определяется как массив фиксированного размера.
В памяти будут некоторые накладные расходы, связанные с распределением домов, я проиллюстрирую это ниже следующим образом:
Область "tttt" является дополнительной, обычно ее будет больше для различных типов сред выполнения и языков, таких как 8 или 12 байтов. Крайне важно, чтобы любые значения, хранящиеся в этой области, никогда не изменялись ничем, кроме распределителя памяти или подпрограмм основной системы, иначе вы рискуете аварийно завершить работу программы.
Выделить память
Заставьте предпринимателя построить свой дом и укажите адрес дома. В отличие от реального мира, распределение памяти не может быть определено, где выделить, но найдет подходящее место с достаточным пространством и сообщит адрес в выделенную память.
Другими словами, предприниматель выберет место.
Расположение памяти:
Держите переменную с адресом
Запишите адрес вашего нового дома на листе бумаги. Этот документ послужит вашей ссылкой на ваш дом. Без этого клочка бумаги вы потерялись и не можете найти дом, если уже не в нем.
Расположение памяти:
Скопировать значение указателя
Просто напишите адрес на новом листе бумаги. Теперь у вас есть два листа бумаги, которые доставят вас в один дом, а не два отдельных дома. Любые попытки перейти по адресу из одной бумаги и переставить мебель в этом доме создадут впечатление, что другой дом был изменен таким же образом, если только вы не можете явно определить, что это на самом деле только один дом.
Примечание. Обычно это та концепция, которую я больше всего объясняю людям: два указателя не означают два объекта или блока памяти.
Освобождая память
Снести дом Затем вы можете позже использовать бумагу для нового адреса, если хотите, или очистить ее, чтобы забыть адрес дома, который больше не существует.
Здесь я сначала строю дом, и получаю его адрес. Затем я что-то делаю с домом (использую его, код, оставленный в качестве упражнения для читателя), а затем освобождаю это. Наконец я очищаю адрес из моей переменной.
Расположение памяти:
Висячие указатели
Вы говорите своему предпринимателю разрушить дом, но вы забыли стереть адрес с вашего листа бумаги. Когда позже вы посмотрите на лист бумаги, вы забыли, что дома больше нет, и отправляетесь навестить его с ошибочными результатами (см. Также часть о недействительной ссылке ниже).
Использование
h
после вызова.Free
может сработать, но это просто удача. Скорее всего, он потерпит неудачу на месте клиента в середине критической операции.Как вы можете видеть, h по-прежнему указывает на остатки данных в памяти, но, поскольку они могут быть неполными, использование их, как и раньше, может привести к сбою.
Утечка памяти
Вы теряете лист бумаги и не можете найти дом. Хотя дом все еще где-то стоит, и когда вы позже захотите построить новый дом, вы не сможете использовать это место повторно.
Здесь мы переписали содержимое
h
переменной с адресом нового дома, но старый все еще стоит ... где-то. После этого кода нет возможности добраться до этого дома, и он останется стоять. Другими словами, выделенная память будет оставаться выделенной до тех пор, пока приложение не закроется, после чего операционная система отключит ее.Расположение памяти после первого выделения:
Расположение памяти после второго выделения:
Более распространенный способ получить этот метод - просто забыть освободить что-то, а не перезаписать это, как указано выше. В терминах Delphi это произойдет с помощью следующего метода:
После выполнения этого метода в наших переменных не будет места, где указан адрес дома, но дом все еще там.
Расположение памяти:
Как видите, старые данные остаются нетронутыми в памяти и не будут повторно использоваться распределителем памяти. Распределитель отслеживает, какие области памяти были использованы, и не будет использовать их повторно, пока вы не освободите его.
Освобождение памяти, но сохранение (теперь недействительной) ссылки
Снесите дом, сотрите один из кусочков бумаги, но у вас также есть другой листок бумаги со старым адресом на нем, когда вы идете по адресу, вы не найдете дом, но вы можете найти что-то, что напоминает руины одного.
Возможно, вы даже найдете дом, но это не тот дом, которому вы изначально дали адрес, и поэтому любые попытки использовать его так, как будто он принадлежит вам, могут ужасно потерпеть неудачу.
Иногда вы можете даже обнаружить, что на соседнем адресе настроен довольно большой дом, который занимает три адреса (главная улица 1-3), и ваш адрес идет в середину дома. Любые попытки трактовать эту часть большого трехадресного дома как один маленький дом также могут оказаться ужасными.
Здесь дом был снесен, по ссылке
h1
, и, хотя онh1
был очищен,h2
все еще имеет старый, устаревший адрес. Доступ к дому, который больше не стоит, может или не может работать.Это вариант висящего указателя выше. Смотрите его расположение памяти.
Переполнение буфера
Вы перемещаете в дом больше вещей, чем можете, проливая в соседний дом или во двор. Когда владелец этого соседнего дома позже придет домой, он найдет все виды вещей, которые он считает своими собственными.
По этой причине я выбрал массив фиксированного размера. Чтобы установить сцену, предположим, что второй дом, который мы выделяем, по какой-то причине будет помещен перед первым в памяти. Другими словами, у второго дома будет более низкий адрес, чем у первого. Кроме того, они расположены рядом друг с другом.
Таким образом, этот код:
Расположение памяти после первого выделения:
Расположение памяти после второго выделения:
Часть, которая чаще всего вызывает сбой, - это когда вы перезаписываете важные части данных, которые вы сохранили, которые действительно не должны изменяться случайным образом. Например, может не быть проблемой, что части имени h1-house были изменены с точки зрения сбоя программы, но перезапись служебных данных объекта, скорее всего, приведет к сбою при попытке использовать сломанный объект, так как перезаписывать ссылки, которые хранятся на других объектах в объекте.
Связанные списки
Когда вы следуете по адресу на листе бумаги, вы попадаете в дом, и в этом доме есть еще один листок бумаги с новым адресом для следующего дома в цепочке и так далее.
Здесь мы создаем ссылку от нашего дома до нашей каюты. Мы можем следовать по цепочке до тех пор, пока дом не будет
NextHouse
упоминаться, что означает, что он последний. Чтобы посетить все наши дома, мы могли бы использовать следующий код:Расположение памяти (добавлено NextHouse как ссылка в объекте, отмеченное четырьмя LLLL на диаграмме ниже):
В общих чертах, что такое адрес памяти?
Адрес памяти в основных терминах просто число. Если вы думаете о памяти как о большом массиве байтов, самый первый байт имеет адрес 0, следующий - адрес 1 и так далее. Это упрощено, но достаточно хорошо.
Итак, это расположение памяти:
Может иметь эти два адреса (самый левый - это адрес 0):
Это означает, что наш приведенный выше список может выглядеть так:
Обычно адрес, который нигде не указывает, является нулевым адресом.
В общих чертах, что такое указатель?
Указатель - это просто переменная, содержащая адрес памяти. Обычно вы можете попросить язык программирования дать вам его номер, но большинство языков программирования и сред выполнения стараются скрыть тот факт, что число находится под ним, просто потому, что само число на самом деле не имеет никакого значения для вас. Лучше всего рассматривать указатель как черный ящик, т.е. вы на самом деле не знаете или не заботитесь о том, как это на самом деле реализовано, до тех пор, пока оно работает.
источник
В моем первом классе Comp Sci мы выполнили следующее упражнение. Конечно, это был лекционный зал с примерно 200 студентами ...
Профессор пишет на доске:
int john;
Джон встает
Профессор пишет:
int *sally = &john;
Салли встает, указывает на Джона
Профессор:
int *bill = sally;
Билл встает, указывает на Джона
Профессор:
int sam;
Сэм встает
Профессор:
bill = &sam;
Билл теперь указывает на Сэма.
Я думаю, вы поняли идею. Я думаю, что мы потратили на это около часа, пока не рассмотрели основы назначения указателей.
источник
Аналогия, которую я нашел полезной для объяснения указателей, - это гиперссылки. Большинство людей могут понять, что ссылка на веб-странице «указывает» на другую страницу в Интернете, и если вы сможете скопировать и вставить эту гиперссылку, они оба будут указывать на одну и ту же исходную веб-страницу. Если вы перейдете и отредактируете эту исходную страницу, а затем перейдите по любой из этих ссылок (указателей), вы получите эту новую обновленную страницу.
источник
int *a = b
не делает две копии*b
).Похоже, причина, по которой указатели смущают многих людей, заключается в том, что они в основном не имеют никакого опыта в компьютерной архитектуре или вообще не имеют его. Поскольку многие, похоже, не имеют представления о том, как на самом деле реализованы компьютеры (машины), работа в C / C ++ кажется чуждой.
Задача состоит в том, чтобы попросить их внедрить простую виртуальную машину на основе байт-кода (на любом языке, который они выбрали, для этого прекрасно работает python) с набором инструкций, ориентированных на операции с указателями (загрузка, сохранение, прямая / косвенная адресация). Затем попросите их написать простые программы для этого набора инструкций.
Все, что требует чуть более простого дополнения, будет включать указатели, и они обязательно получат это.
источник
Концепция заполнителя для значения - переменных - отображает то, чему нас учат в школе - алгебру. Не существует существующей параллели, которую вы можете провести, не понимая, как физически распределена память внутри компьютера, и никто не думает об этом, пока они не имеют дело с вещами низкого уровня - на уровне связи C / C ++ / byte. ,
Адреса ящиков. Я помню, когда я учился программировать BASIC в микрокомпьютерах, там были эти красивые книги с играми, и иногда вам приходилось вставлять значения в конкретные адреса. У них было изображение группы коробок, постепенно помеченных 0, 1, 2 ... и было объяснено, что только одна маленькая вещь (байт) могла поместиться в этих коробках, и их было много - некоторые компьютеры было целых 65535! Они были рядом друг с другом, и у них всех был адрес.
Для дрели? Создайте структуру:
Тот же пример, что и выше, за исключением C:
Вывод:
Возможно, это объясняет некоторые из основ на примере?
источник
Причина, по которой я с трудом разбираюсь в указателях, заключается в том, что во многих объяснениях содержится много дерьма о передаче по ссылке. Все это путает проблему. Когда вы используете параметр указателя, вы все равно передаете по значению; но значение оказывается адресом, а не, скажем, int.
Кто-то уже связался с этим учебником, но я могу выделить момент, когда я начал понимать указатели:
Учебник по указателям и массивам в C: Глава 3 - Указатели и строки
В тот момент, когда я прочитал эти слова, облака разошлись, и луч солнца окутал меня пониманием указателя.
Даже если вы являетесь разработчиком VB .NET или C # (как и я) и никогда не используете небезопасный код, все равно стоит понять, как работают указатели, иначе вы не поймете, как работают ссылки на объекты. Тогда у вас будет распространенное, но ошибочное мнение, что передача ссылки на объект в метод копирует объект.
источник
«Учебник Теда Дженсена по указателям и массивам в Си» я нашел отличным ресурсом для изучения указателей. Он разделен на 10 уроков, начиная с объяснения того, что такое указатели (и для чего они нужны) и заканчивая указателями на функции. http://home.netcom.com/~tjensen/ptr/cpoint.htm
Далее Beej's Guide по сетевому программированию обучает API сокетов Unix, из которого вы можете начать делать действительно забавные вещи. http://beej.us/guide/bgnet/
источник
Сложности указателей выходят за рамки того, что мы можем легко научить. Заставить студентов указывать друг на друга и использовать листы бумаги с домашними адресами - оба являются отличными инструментами обучения. Они делают большую работу по представлению основных понятий. Действительно, изучение основных понятий жизненно важно для успешного использования указателей. Тем не менее, в рабочем коде часто встречаются гораздо более сложные сценарии, чем эти простые демонстрации.
Я был связан с системами, где у нас были структуры, указывающие на другие структуры, указывающие на другие структуры. Некоторые из этих структур также содержали встроенные структуры (а не указатели на дополнительные структуры). Это где указатели становятся действительно запутанными. Если у вас есть несколько уровней косвенности, и вы начинаете заканчивать с кода, как это:
это может очень быстро запутать (представьте себе гораздо больше строк и, возможно, больше уровней). Добавьте массивы указателей и указатели от узла к узлу (деревья, связанные списки), и это еще хуже. Я видел, как некоторые действительно хорошие разработчики терялись, когда начали работать над такими системами, даже разработчики, которые действительно хорошо понимали основы.
Сложные структуры указателей также не обязательно указывают на плохое кодирование (хотя они могут). Композиция является важной частью хорошего объектно-ориентированного программирования, а в языках с необработанными указателями она неизбежно приведет к многоуровневой косвенности. Кроме того, системы часто должны использовать сторонние библиотеки со структурами, которые не соответствуют друг другу по стилю или технике. В подобных ситуациях сложность естественным образом возникает (хотя, конечно, мы должны бороться с ней как можно больше).
Я думаю, что лучшее, что могут сделать колледжи, чтобы помочь студентам выучить указатели, - это использовать хорошие демонстрации в сочетании с проектами, которые требуют использования указателей. Один сложный проект сделает больше для понимания указателя, чем тысяча демонстраций. Демонстрации могут дать вам поверхностное понимание, но чтобы глубоко понять указатели, вы должны действительно использовать их.
источник
Я решил добавить аналогию к этому списку, которая показалась мне очень полезной при объяснении указателей (в прошлом) в качестве преподавателя информатики; во-первых, давайте:
Установите сцену :
Рассмотрим парковку с 3-мя местами, эти номера пронумерованы:
В некотором смысле это похоже на ячейки памяти, они последовательные и смежные ... вроде как массив. Сейчас в них нет машин, так что это как пустой массив (
parking_lot[3] = {0}
).Добавить данные
Стоянка никогда не останется пустой на долгое время ... если бы это было так, это было бы бессмысленно, и никто бы не стал строить. Итак, скажем, по мере того, как день движется, участок наполняется 3 автомобилями, синим, красным и зеленым:
Эти машины все тот же тип (автомобиль) , так что один способ думать о том , что наши машины какие - то данные (скажем
int
) , но они имеют разные значения (blue
,red
,green
, которые могут быть цветаenum
)Введите указатель
Теперь, если я отвезу вас на эту парковку и попрослю вас найти мне синюю машину, вы вытяните один палец и при помощи него укажите на синюю машину в месте 1. Это все равно, что взять указатель и присвоить его адресу памяти. (
int *finger = parking_lot
)Ваш палец (указатель) не является ответом на мой вопрос. Взгляд на ваш палец мне ничего не говорит, но если я посмотрю, куда указывает ваш палец (разыменовывает указатель), я могу найти машину (данные), которую я искал.
Переназначение указателя
Теперь я могу попросить вас найти красную машину, а вы можете перенаправить палец на новую машину. Теперь ваш указатель (такой же, как и раньше) показывает мне новые данные (место для парковки, где находится красная машина) того же типа (автомобиль).
Указатель физически не изменился, это все еще ваш палец, изменились только те данные, которые он мне показывал. (адрес "места для парковки")
Двойные указатели (или указатель на указатель)
Это работает с более чем одним указателем. Я могу спросить, где находится указатель, указывающий на красную машину, и вы можете использовать другую руку и указать пальцем на первый палец. (это как
int **finger_two = &finger
)Теперь, если я хочу знать, где находится синяя машина, я могу следовать по направлению первого пальца ко второму пальцу, к машине (данные).
Свисающий указатель
Теперь предположим, что вы очень похожи на статую и хотите, чтобы ваша рука указывала на красную машину бесконечно. Что если эта красная машина уедет?
Ваш указатель все еще указывает на то, где находилась красная машина, но ее больше нет. Скажем, новая машина останавливается там ... оранжевая машина. Теперь, если я снова спрошу вас «где красная машина», вы все еще указываете туда, но теперь вы ошибаетесь. Это не красная машина, это оранжевая.
Арифметика указателей
Итак, вы все еще указываете на второе место для парковки (сейчас занято автомобилем Orange)
Ну, у меня теперь новый вопрос ... Я хочу знать цвет машины на следующем месте парковки. Вы можете видеть, что вы указываете на точку 2, поэтому вы просто добавляете 1 и указываете на следующую точку. (
finger+1
), теперь, так как я хотел знать, какие данные были там, вы должны проверить это место (не только палец), чтобы вы могли отложить указатель (*(finger+1)
), чтобы увидеть, что там есть зеленая машина (данные в этом месте )источник
"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"
- вы были бы очень удивлены, как много людей не понимают указателей даже до этого уровняЯ не думаю, что указатели как концепция особенно хитры - ментальные модели большинства учеников соответствуют чему-то подобному, и некоторые быстрые наброски могут помочь.
Трудность, по крайней мере та, с которой я сталкивался в прошлом и с которой сталкивались другие, заключается в том, что управление указателями в C / C ++ может быть излишне запутанным.
источник
Пример учебника с хорошим набором диаграмм очень помогает в понимании указателей .
Джоэл Спольски делает несколько хороших замечаний о понимании указателей в своей статье « Партизанское руководство по интервью» :
источник
Проблема с указателями не в концепции. Это исполнение и язык вовлечены. Дополнительная путаница приводит к тому, что учителя предполагают, что это КОНЦЕПЦИЯ указателей труднее, а не жаргон или запутанный беспорядок C и C ++ делает концепцию. Огромные усилия прилагаются для объяснения концепции (как в принятом ответе на этот вопрос), и она в значительной степени просто напрасна на кого-то вроде меня, потому что я уже все это понимаю. Это просто объясняет не ту часть проблемы.
Чтобы дать вам представление о том, откуда я родом, я тот, кто прекрасно понимает указатели, и я могу грамотно использовать их на языке ассемблера. Потому что на языке ассемблера они не называются указателями. Они называются адресами. Когда дело доходит до программирования и использования указателей в C, я допускаю много ошибок и очень запутываюсь. Я до сих пор не разобрался в этом. Позвольте привести пример.
Когда API говорит:
чего он хочет?
это могло бы хотеть:
число, представляющее адрес буфера
(Чтобы дать это, я говорю
doIt(mybuffer)
, илиdoIt(*myBuffer)
?)число, представляющее адрес к адресу в буфере
(это
doIt(&mybuffer)
илиdoIt(mybuffer)
илиdoIt(*mybuffer)
?)число, представляющее адрес по адресу к адресу в буфере
(может быть
doIt(&mybuffer)
. или этоdoIt(&&mybuffer)
? или дажеdoIt(&&&mybuffer)
)и так далее, и используемый язык не делает это настолько ясным, потому что он включает в себя слова «указатель» и «ссылка», которые не имеют такого большого значения и ясности для меня, как «x содержит адрес для y» и « эта функция требует адреса к y ". Ответ дополнительно зависит от того, с какого черта «mybuffer» должен начинаться, и что он намерен делать с ним. Язык не поддерживает уровни вложенности, которые встречаются на практике. Например, когда мне нужно передать «указатель» на функцию, которая создает новый буфер, и он изменяет указатель так, чтобы он указывал на новое местоположение буфера. Действительно ли он хочет указатель или указатель на указатель, чтобы он знал, куда идти, чтобы изменить содержимое указателя. Большую часть времени я просто должен догадаться, что подразумевается под "
«Указатель» просто перегружен. Указатель является адресом для значения? или это переменная, которая содержит адрес значения. Когда функция хочет указатель, она хочет адрес, который содержит переменная указателя, или она хочет адрес переменной указателя? Я смущен.
источник
double *(*(*fn)(int))(char)
, то результатом оценки*(*(*fn)(42))('x')
будет adouble
. Вы можете снять уровни оценки, чтобы понять, какими должны быть промежуточные типы.(*(*fn)(42))('x')
тогда?x
), где, если вы оцениваете*x
, вы получаете двойную.fn
есть, и больше с точки зрения того, что вы можете сделать сfn
Я думаю, что основным препятствием для понимания указателей являются плохие учителя.
Почти всех учат лжи об указателях: они являются не чем иным, как адресами памяти , или что они позволяют вам указывать на произвольные места .
И конечно, что они трудны для понимания, опасны и полумагичны.
Ничего из этого не является правдой. Указатели на самом деле довольно простые понятия, если вы придерживаетесь того, что говорит о них язык C ++, и не наделяете их атрибутами, которые «обычно» работают на практике, но тем не менее не гарантируются языком и так не являются частью реальной концепции указателя.
Я попытался написать объяснение этому несколько месяцев назад в этом посте - надеюсь, это кому-нибудь поможет.
(Обратите внимание, что до того, как кто-нибудь станет со мной педантичен, да, стандарт C ++ говорит, что указатели представляют адреса памяти. Но он не говорит, что «указатели являются адресами памяти и ничем иным, как адресами памяти, и могут использоваться или считаться взаимозаменяемыми с памятью». адреса ». Различие важно)
источник
Я думаю, что сделать указатели сложнее в изучении, так это то, что до указателей вас устраивает мысль о том, что «в этой ячейке памяти находится набор битов, представляющих целое число, двойное число, символ, что угодно».
Когда вы впервые видите указатель, вы на самом деле не получаете то, что находится в этом месте памяти. "Что вы имеете в виду, он имеет адрес ?"
Я не согласен с тем, что «вы либо получаете их, либо не получаете».
Их становится легче понять, когда вы начинаете находить для них реальное применение (например, не передавать большие структуры в функции).
источник
Причина, по которой это так трудно понять, не в том, что это сложная концепция, а в том, что синтаксис противоречив .
Сначала вы узнали, что крайняя левая часть создания переменной определяет тип переменной. Объявление указателя не работает так в C и C ++. Вместо этого они говорят, что переменная указывает на тип слева. В этом случае:
*
mypointer указывает на int.Я не полностью понял указатели, пока не попробую использовать их в C # (с небезопасным), они работают точно так же, но с логическим и последовательным синтаксисом. Указатель сам по себе является типом. Здесь mypointer является указателем на int.
Даже не заводите меня на указатели функций ...
источник
int *p;
имеет простое значение:*p
является целым числом.int *p, **pp
означает:*p
и**pp
являются целыми числами.*p
и не**pp
являются целыми числами, потому что вы никогда не инициализировали или или указывать на что-либо. Я понимаю, почему некоторые люди предпочитают придерживаться этой грамматики, особенно потому, что некоторые крайние случаи и сложные случаи требуют от вас (хотя, тем не менее, вы можете тривиально обойти это во всех случаях, о которых я знаю) ... но я не думаю, что эти случаи более важны, чем тот факт, что обучение правильному выравниванию вводит в заблуждение новичков. Не говоря уже о некрасивом! :)p
pp
*pp
Я мог работать с указателями, когда знал только C ++. Я вроде знал, что делать в некоторых случаях, а что не делать методом проб / ошибок. Но то, что дало мне полное понимание, это язык ассемблера. Если вы выполняете серьезную отладку на уровне инструкций с помощью написанной вами программы на языке ассемблера, вы должны понимать многие вещи.
источник
Мне нравится аналогия с домашним адресом, но я всегда думал, что адрес относится к самому почтовому ящику. Таким образом, вы можете визуализировать концепцию разыменования указателя (открытия почтового ящика).
Например, следуя связанному списку: 1) начните со своей бумаги с адресом 2) перейдите по адресу на листе 3) откройте почтовый ящик, чтобы найти новый лист бумаги со следующим адресом на нем
В линейном связанном списке в последнем почтовом ящике ничего нет (конец списка). В круговом связанном списке последний почтовый ящик содержит адрес первого почтового ящика.
Обратите внимание, что на шаге 3 происходит разыменование, и вы можете потерпеть крах или ошибиться, если адрес недействителен. Предполагая, что вы можете подойти к почтовому ящику с неверным адресом, представьте, что там есть черная дыра или что-то еще, что выворачивает мир наизнанку :)
источник
Я думаю, что главная причина, по которой у людей возникают проблемы, заключается в том, что, как правило, этому не учат интересно и увлекательно. Я хотел бы, чтобы лектор собрал 10 добровольцев из толпы и дал им по 1 метру каждого, чтобы они стояли в определенной конфигурации и использовали линейки, чтобы указывать друг на друга. Затем покажите арифметику указателей, перемещая людей (и куда они указывают своих правителей). Это был бы простой, но эффективный (и прежде всего запоминающийся) способ показать концепции, не слишком увязая в механике.
Как только вы попадаете на C и C ++, некоторым людям становится все труднее. Я не уверен, что это потому, что они, наконец, выдвигают теорию о том, что они не понимают должным образом на практике, или потому, что манипулирование указателями по сути сложнее в этих языках. Я не очень хорошо помню свой собственный переход, но я знал указатели на Паскале, а затем перешел на Си и совершенно потерян.
источник
Я не думаю, что сами указатели сбивают с толку. Большинство людей могут понять концепцию. Теперь, сколько указателей вы можете думать или сколько уровней косвенности вам удобно. Не нужно слишком много, чтобы поставить людей на край. Тот факт, что они могут быть случайно изменены из-за ошибок в вашей программе, также может сильно затруднить их отладку, когда в вашем коде что-то не так.
источник
Я думаю, что это может быть проблема синтаксиса. Синтаксис C / C ++ для указателей кажется непоследовательным и более сложным, чем необходимо.
По иронии судьбы, то, что на самом деле помогло мне понять указатели, это столкновение с концепцией итератора в стандартной библиотеке шаблонов c ++ . Это иронично, потому что я могу только предположить, что итераторы были задуманы как обобщение указателя.
Иногда вы просто не можете видеть лес, пока не научитесь игнорировать деревья.
источник
(*p)
бы это было так(p->)
, и поэтому мы имели быp->->x
вместо двусмысленного*p->x
a->b
просто значит(*a).b
.* p->x
средства , в* ((*a).b)
то время как*p -> x
средства(*(*p)) -> x
. Смешивание префиксных и постфиксных операторов приводит к неоднозначному анализу.1+2 * 3
должно быть 9.Путаница возникает из-за нескольких уровней абстракции, смешанных вместе в концепции «указатель». Программистов не смущают обычные ссылки в Java / Python, но указатели отличаются тем, что они раскрывают характеристики базовой архитектуры памяти.
Хорошим принципом является чистое разделение слоев абстракции, а указатели этого не делают.
источник
foo[i]
означает идти в определенное место, двигаться вперед на определенное расстояние и видеть, что там. Что усложняет вещи, так это гораздо более сложный дополнительный уровень абстракции, который был добавлен стандартом исключительно для выгоды компилятора, но моделирует вещи таким образом, который плохо подходит для нужд программиста и компилятора.Мне нравилось объяснять это с точки зрения массивов и индексов - люди могут быть не знакомы с указателями, но они обычно знают, что такое индекс.
Итак, я говорю, представьте, что ОЗУ - это массив (а у вас всего 10 байтов ОЗУ):
Тогда указатель на переменную на самом деле является просто индексом (первого байта) этой переменной в ОЗУ.
Таким образом, если у вас есть указатель / индекс
unsigned char index = 2
, то значение, очевидно, является третьим элементом или числом 4. Указатель на указатель - это то место, где вы берете это число и используете его как сам индекс, напримерRAM[RAM[index]]
.Я бы нарисовал массив на листе бумаги и просто использовал его, чтобы показать такие вещи, как множество указателей, указывающих на одну и ту же память, арифметику указателей, указатель на указатель и так далее.
источник
Номер почтового ящика.
Это часть информации, которая позволяет вам получить доступ к чему-то еще.
(И если вы выполняете арифметику с номерами почтовых ящиков, у вас могут возникнуть проблемы, потому что письмо идет не в тот ящик. А если кто-то переходит в другое состояние - без адреса переадресации - тогда у вас есть висячий указатель. с другой стороны - если почтовое отделение пересылает почту, то у вас есть указатель на указатель.)
источник
Неплохой способ понять это с помощью итераторов ... но продолжайте искать, вы увидите, как Александреску начнет жаловаться на них.
Многие бывшие разработчики C-C ++ (которые никогда не понимали, что итераторы являются современным указателем до создания дампов языка) переходят на C # и все еще верят, что у них есть достойные итераторы.
Хм, проблема в том, что все, что делают итераторы, в полной мере не соответствует тому, чего стремятся достичь платформы времени исполнения (Java / CLR): новое, простое использование «все - как разработчик». Что может быть хорошо, но они сказали это однажды в фиолетовой книге, и они сказали это даже до и до C:
Косвенность.
Очень мощная концепция, но никогда, если вы делаете это все время. Итераторы полезны, поскольку они помогают с абстракцией алгоритмов, еще один пример. А время компиляции - это место для алгоритма, очень простого. Вы знаете код + данные или на другом языке C #:
IEnumerable + LINQ + Massive Framework = 300 МБ штрафных санкций за время выполнения при паршивом перетаскивании приложений через кучи экземпляров ссылочных типов.
«Le Pointer - это дешево».
источник
В некоторых ответах выше утверждалось, что «указатели не очень сложны», но они не обращались непосредственно к тому месту, где «указатель сложен!» происходит от. Несколько лет назад я обучал студентов-первокурсников CS (всего один год, так как я явно сосал их), и мне было ясно, что идея указателя не сложна. Трудно понять, почему и когда вам нужен указатель .
Я не думаю, что вы можете отделить этот вопрос - почему и когда использовать указатель - от объяснения более широких проблем разработки программного обеспечения. Почему каждая переменная не должна быть глобальной переменной, и почему нужно выделять похожий код в функции (чтобы получить это, используйте указатели, чтобы специализировать их поведение на своем сайте вызова).
источник
Я не вижу, что так смущает указатели. Они указывают на место в памяти, то есть оно хранит адрес памяти. В C / C ++ вы можете указать тип, на который указывает указатель. Например:
Говорит, что my_int_pointer содержит адрес для местоположения, которое содержит int.
Проблема с указателями заключается в том, что они указывают на место в памяти, поэтому легко отследить место, в котором вы не должны находиться. В качестве доказательства посмотрите на многочисленные дыры в безопасности в приложениях C / C ++ от переполнения буфера (увеличение указателя) мимо выделенной границы).
источник
Просто чтобы еще больше запутать, иногда приходится работать с указателями, а не с указателями. Дескрипторы являются указателями на указатели, так что серверная часть может перемещать объекты в памяти, чтобы дефрагментировать кучу. Если указатель изменяется в середине процедуры, результаты непредсказуемы, поэтому сначала нужно заблокировать дескриптор, чтобы убедиться, что ничего никуда не денется.
http://arjay.bc.ca/Modula-2/Text/Ch15/Ch15.8.html#15.8.5 говорит об этом немного более связно, чем я. :-)
источник
У каждого новичка C / C ++ есть та же самая проблема, и эта проблема возникает не потому, что «указатели трудно выучить», а «кто и как это объясняет». Некоторые учащиеся собирают это в устной форме, а некоторые визуально, и лучший способ объяснить это - использовать пример «поезд» (подходит для словесного и визуального примера).
Где «локомотив» - это указатель, который не может ничего удержать, а «вагон» - это то, что «локомотив» пытается тянуть (или указывать). После этого вы можете классифицировать сам «вагон», может ли он содержать животных, растения или людей (или их комбинацию).
источник