Почему указатели являются таким главным фактором путаницы для многих новых и даже старых студентов, изучающих C или C++? Существуют ли какие-либо инструменты или мыслительные процессы, которые помогли вам понять, как указатели работают на уровне переменных, функций и других уровней?
Какие хорошие практические приемы можно сделать, чтобы довести кого-нибудь до уровня «Ага, я понял», не увязнув в общей концепции? По сути, тренируйте похожие сценарии.
Тезис этого вопроса заключается в том, что указатели трудно понять. Этот вопрос не свидетельствует о том, что указатели труднее понять, чем что-либо еще.
Возможно, мне что-то не хватает (потому что я кодирую на языках GCC), но я всегда думал, что указатели в памяти как структура Key-> Value. Поскольку передавать большие объемы данных в программе дорого, вы создаете структуру (значение) и передаете ее указатель / ссылку (ключ), потому что ключ является гораздо меньшим представлением более крупной структуры. Сложная часть - это когда вам нужно сравнить два указателя / ссылки (сравниваете ли вы ключи или значения), что требует дополнительной работы для взлома данных, содержащихся в структуре (значении).
Что меня сбивает с толку, так это то, что указатели имеют тип. Мне кажется, что память в адресе всегда будет int. Фактически, тип указателя - это тип переменной, на которую указывает указатель ... Логика, необходимая для переваривания такого блока текста, выходит за рамки того, что люди видят при обычном ежедневном чтении.
@ Wolfpack'08 "Мне кажется, что адрес памяти всегда будет целым." - Тогда вам должно показаться, что ничего имеет тип, так как все они просто биты в памяти. «Фактически, тип указателя - это тип переменной, на которую указывает указатель» - Нет, тип указателя - указатель на, тип переменной, на которую указывает указатель - что естественно и должно быть очевидным.
Мне всегда было интересно, что так сложно понять в том факте, что переменные (и функции) - это просто блоки памяти, а указатели - это переменные, хранящие адреса памяти. Эта, возможно, слишком практичная модель мышления может не впечатлить всех поклонников абстрактных концепций, но она отлично помогает понять, как работают указатели.
Короче говоря, студенты, вероятно, не понимают, потому что они не понимают правильно или вообще не понимают, как работает память компьютера в целом, и в частности C "модель памяти". Эта книга Программирование с нуля дает очень хороший урок по этим темам.





Why are pointers such a leading factor of confusion for many new, and even old, college level students in the C/C++ language?
Концепция заполнителя для значения - переменных - отображается на то, чему нас учат в школе - алгебру. Не существует параллели, которую вы могли бы провести, не понимая, как физически размещена память в компьютере, и никто не думает об этом, пока не будет иметь дело с вещами низкого уровня - на уровне связи C / C++ / байтов .
Are there any tools or thought processes that helped you understand how pointers work at the variable, function, and beyond level?
Адреса ящики. Я помню, когда я учился программировать BASIC на микрокомпьютерах, были эти милые книжки с играми, и иногда приходилось указывать значения по определенным адресам. У них было изображение группы коробок, постепенно помеченных цифрами 0, 1, 2 ... и было объяснено, что только одна маленькая вещь (байт) могла поместиться в эти коробки, а их было много - некоторые компьютеры было аж 65535! Они были рядом друг с другом, и у всех был адрес.
What are some good practice things that can be done to bring somebody to the level of, "Ah-hah, I got it," without getting them bogged down in the overall concept? Basically, drill like scenarios.
Для дрели? Сделайте структуру:
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
Возможно, это объясняет некоторые основы на примере?
+1 за "не понимая, как физически закладывается память". Я пришел к C из языка ассемблера, и концепция указателей была очень естественной и простой; и я видел, как люди с более высоким уровнем языкового фона пытались понять это. Что еще хуже, синтаксис сбивает с толку (указатели на функции!), Поэтому изучение концепции и синтаксиса одновременно - это рецепт неприятностей.
Я знаю, что это старый пост, но было бы здорово, если бы вывод предоставленного кода был добавлен в пост.
Да, это похоже на алгебру (хотя у алгебры есть дополнительный момент для понимания в том, что их «переменные» неизменны). Но примерно половина людей, которых я знаю, на практике не разбираются в алгебре. Для них это просто не подходит. Они знают все эти «уравнения» и рецепты, чтобы получить результат, но применяют их несколько случайно и неуклюже. И они не могут продлевать их для своих целей - для них это просто какой-то неизменный, неразборный черный ящик. Если вы разбираетесь в алгебре и умеете эффективно ее использовать, вы уже далеко впереди остальных - даже среди программистов.
Я не думаю, что указатели как концепция особенно сложны - ментальные модели большинства студентов сопоставляются с чем-то вроде этого, и некоторые быстрые наброски блоков могут помочь.
Трудность, по крайней мере та, с которой я сталкивался в прошлом и с которой сталкивались другие, состоит в том, что управление указателями в C / C++ может быть безнадежно запутанным.
Я не понимаю, что сбивает с толку в указателях. Они указывают на место в памяти, то есть хранят адрес памяти. В C / C++ вы можете указать тип, на который указывает указатель. Например:
int* my_int_pointer;
Говорит, что my_int_pointer содержит адрес места, содержащего int.
Проблема с указателями заключается в том, что они указывают на место в памяти, поэтому легко уйти в какое-то место, где вы не должны находиться. В качестве доказательства посмотрите на многочисленные дыры в безопасности в приложениях C / C++ от переполнения буфера (увеличение указателя за выделенную границу).
Причина, по которой указатели, кажется, сбивают с толку многих людей, заключается в том, что они в основном не имеют или почти не имеют знаний в области компьютерной архитектуры. Поскольку многие, похоже, не имеют представления о том, как на самом деле реализованы компьютеры (машина), работа на C / C++ кажется чуждой.
Упражнение состоит в том, чтобы попросить их реализовать простую виртуальную машину на основе байт-кода (на любом языке, который они выберут, python отлично подходит для этого) с набором инструкций, ориентированным на операции с указателями (загрузка, сохранение, прямая / косвенная адресация). Затем попросите их написать простые программы для этого набора инструкций.
Все, что требует немного большего, чем простое добавление, будет включать указатели, и они обязательно их получат.
Интересно. Не знаю, с чего начать. Есть ли ресурсы, которыми можно поделиться?
Я согласен. Например, я научился программировать на ассемблере до C и, зная, как работают регистры, изучать указатели было легко. На самом деле, обучения было немного, все шло очень естественно.
Возьмите базовый процессор, скажите что-нибудь, на котором работают газонокосилки или посудомоечные машины, и примените его. Или очень простое подмножество ARM или MIPS. У обоих очень простая ISA.
Возможно, стоит отметить, что этот образовательный подход пропагандировался / практиковался самим Дональдом Кнутом. Книга Кнута «Искусство компьютерного программирования» описывает простую гипотетическую архитектуру и просит студентов реализовать решения практических задач на гипотетическом языке ассемблера для этой архитектуры. Когда это стало практически осуществимым, некоторые студенты, читающие книги Кнута, фактически реализуют его архитектуру как виртуальную машину (или используют существующую реализацию) и фактически запускают свои решения. ИМО, это отличный способ научиться, если у вас есть время.
Я всегда соглашался с этим, и я считаю, что именно поэтому мне не составляло труда разбираться в указателях; так как я научился программировать с помощью ассемблера, прежде чем изучал указатели C.
@Luke Я не думаю, что это так легко понять людей, которые просто не понимают указатели (или, точнее, косвенность в целом). Вы в основном предполагаете, что люди, которые не понимают указатели в C, смогут начать изучение сборки, понять базовую архитектуру компьютера и вернуться к C с пониманием указателей. Это может быть правдой для многих, но, согласно некоторым исследованиям, кажется, что некоторые люди по своей природе не могут понять косвенное обращение, даже в принципе (мне все еще очень трудно в это поверить, но, возможно, мне просто повезло с моими "учениками" ").
@Luaan С таким количеством людей, с которыми я столкнулся, которые просто не могут понять абстракцию, будь то в программировании или даже в реальной жизни, меня совсем не удивляет, что так много людей борются с понятием косвенности! В конце концов, они не совсем разные.
Я думаю, что основная причина, по которой у людей возникают проблемы с этим, заключается в том, что этому обычно не преподают интересно и увлекательно. Я бы хотел, чтобы лектор набрал 10 добровольцев из толпы и дал им линейку длиной 1 метр, заставил их встать в определенной конфигурации и использовать линейки, чтобы указывать друг на друга. Затем покажите арифметику указателя, перемещая людей (и куда они указывают свои линейки). Это был бы простой, но эффективный (и, прежде всего, запоминающийся) способ продемонстрировать концепции, не слишком увязнув в механике.
Как только вы дойдете до C и C++, некоторым людям станет труднее. Я не уверен, что это потому, что они, наконец, применяют теорию, которую они не понимают должным образом, или потому, что манипуляции с указателями по своей сути сложнее в этих языках. Я не могу вспомнить свой собственный переход так хорошо, но я знал указатели в Паскале, а затем перешел на C и полностью потерялся.
Пример учебника с хорошим набором диаграмм очень помогает в понимании указателей..
Джоэл Спольски делает несколько хороших замечаний по поводу понимания указателей в своей статье Guerrilla Руководство по собеседованию:
For some reason most people seem to be born without the part of the brain that understands pointers. This is an aptitude thing, not a skill thing – it requires a complex form of doubly-indirected thinking that some people just can't do.
На моем первом занятии по компьютерным наукам мы сделали следующее упражнение. Конечно, это был лекционный зал, в котором было около 200 студентов ...
Профессор пишет на доске: int john;.
Джон встает
Профессор пишет: int *sally = &john;.
Салли встает, показывает на Джона.
Профессор: int *bill = sally;
Билл встает, указывает на Джона
Профессор: int sam;
Сэм встает
Профессор: bill = &sam;
Билл теперь указывает на Сэма.
Думаю, вы поняли. Думаю, мы потратили на это около часа, пока не познакомились с основами присвоения указателей.
Не думаю, что я ошибся. Я намеревался изменить значение указанной переменной с Джона на Сэма. Это немного сложнее представить людям, потому что похоже, что вы меняете значение обоих указателей.
Но причина, по которой это сбивает с толку, заключается в том, что Джон не встал со своего места, а затем сел Сэм, как мы могли бы представить. Это больше похоже на то, как Сэм подошел и сунул руку в Джона и клонировал программу Сэма в тело Джона, как Хьюго Уивинг в перезагрузке матрицы.
Скорее Сэм садится на место Джона, а Джон парит по комнате, пока не натолкнется на что-то критическое и не вызовет ошибку.
Я лично считаю этот пример излишне сложным. Мой профессор сказал мне указать на источник света и сказал: «Ваша рука - указатель на объект света».
Проблема с примерами этого типа заключается в том, что указатели на X и X не совпадают. И это не изображается с людьми.
это было действительно замечательно ... мне это нравится ... но меня беспокоит необходимость оптимизации самих указателей, поскольку они фактически занимают блок памяти для хранения адреса ...
@armin John не является целым числом представлять; тогда он будет указателем. Вместо этого он является целое число.
@ LegionMammal978 Если быть более точным: Джон и Сэм - личности, но здесь - в этом упражнении - они пытаются представить что-то еще. И если мы посмотрим на код, мы могли бы назвать John означает неинициализированной переменной «John» типа «int». Или, если мы думаем о времени выполнения, мы могли бы сказать, что он означает определенное пространство для хранения, полное мусора. Дело в том, что все это упражнение не помогает людям понять, что на самом деле происходит. Вы только посмотрите на разные интерпретации.
Я не понимаю, почему «Салли не должна указывать на Джона после эпизода». Потому что bill и sally - два разных указателя. если значение bill изменится, почему Sally должен измениться?
Сложности указателей выходят за рамки того, чему мы можем легко научить. Когда учащиеся указывают друг на друга, и использование листов бумаги с адресами домов - это отличные инструменты обучения. Они отлично справляются с введением основных понятий. Действительно, изучение основных понятий - это жизненно важный для успешного использования указателей. Однако в производственном коде часто встречаются гораздо более сложные сценарии, чем могут инкапсулировать эти простые демонстрации.
Я был связан с системами, в которых у нас были структуры, указывающие на другие структуры, указывающие на другие структуры. Некоторые из этих структур также содержат встроенные структуры (а не указатели на дополнительные структуры). Вот где указатели действительно сбивают с толку. Если у вас есть несколько уровней косвенного обращения, и вы начинаете с такого кода:
widget->wazzle.fizzle = fazzle.foozle->wazzle;
он может очень быстро запутаться (представьте намного больше линий и, возможно, больше уровней). Добавьте массивы указателей и указатели от узла к узлу (деревья, связанные списки), и это станет еще хуже. Я видел, как некоторые действительно хорошие разработчики терялись, когда начинали работать над такими системами, даже те разработчики, которые действительно хорошо понимали основы.
Сложные структуры указателей также не обязательно указывают на плохое кодирование (хотя могут). Композиция - жизненно важная часть хорошего объектно-ориентированного программирования, и в языках с необработанными указателями она неизбежно приведет к многоуровневому косвенному обращению. Кроме того, системам часто необходимо использовать сторонние библиотеки со структурами, которые не соответствуют друг другу по стилю или технике. В таких ситуациях, естественно, возникают сложности (хотя, конечно, мы должны бороться с этим как можно больше).
Я думаю, что лучшее, что колледжи могут сделать, чтобы помочь студентам изучить указатели, - это использовать хорошие демонстрации в сочетании с проектами, требующими использования указателей. Один сложный проект сделает больше для понимания указателя, чем тысяча демонстраций. Демонстрации могут дать вам поверхностное понимание, но чтобы глубоко усвоить указатели, вы должны действительно их использовать.
Не думаю, что сами указатели сбивают с толку. Большинство людей могут понять эту концепцию. Теперь о том, сколько указателей вы можете придумать или сколько уровней косвенности вам удобно. Не нужно слишком много людей, чтобы довести людей до крайности. Тот факт, что они могут быть случайно изменены из-за ошибок в вашей программе, также может затруднить их отладку, когда что-то идет не так в вашем коде.
Я нашел "Учебник по указателям и массивам в C" Теда Дженсена отличным ресурсом для изучения указателей. Он разделен на 10 уроков, начиная с объяснения того, что такое указатели (и для чего они нужны), и заканчивая указателями на функции. http://web.archive.org/web/20181011221220/http://home.netcom.com:80/~tjensen/ptr/cpoint.htm
Двигаясь дальше, Beej's Guide to Network Programming учит API сокетов Unix, из которого вы можете начать делать действительно забавные вещи. http://beej.us/guide/bgnet/
Я второй учебник Теда Дженсена. Он разбивает указатели до уровня детализации, который не является чрезмерно подробным, как ни в одной книге, которую я читал. Очень полезно! :)
Мне нравится аналогия с домашним адресом, но я всегда думал об адресе самого почтового ящика. Таким образом вы можете визуализировать концепцию разыменования указателя (открытие почтового ящика).
Например, следуя связному списку: 1) начните с вашей статьи с адреса 2) Перейдите по адресу, указанному на бумаге 3) Откройте почтовый ящик, чтобы найти новый лист бумаги со следующим адресом.
В линейно связанном списке в последнем почтовом ящике ничего нет (конец списка). В круговом связном списке последний почтовый ящик имеет адрес первого почтового ящика в нем.
Обратите внимание, что на шаге 3 происходит разыменование и когда вы ошибаетесь или ошибаетесь, если адрес недействителен. Предполагая, что вы можете подойти к почтовому ящику с недействительным адресом, представьте, что там есть черная дыра или что-то такое, что выворачивает мир наизнанку :)
Ужасная сложность с аналогией с номером почтового ящика заключается в том, что, хотя язык, изобретенный Деннисом Ричи, определяет поведение в терминах адресов байтов и значений, хранящихся в этих байтах, язык, определенный стандартом C, предлагает «оптимизирующие» реализации для использования поведенческого модель, которая является более сложной, но определяет различные аспекты модели неоднозначно, противоречиво и неполно.
Аналогия, которую я нашел полезной для объяснения указателей, - это гиперссылки. Большинство людей понимают, что ссылка на веб-странице «указывает» на другую страницу в Интернете, и если вы можете скопировать и вставить эту гиперссылку, они обе будут указывать на одну и ту же исходную веб-страницу. Если вы перейдете и отредактируете исходную страницу, а затем перейдите по любой из этих ссылок (указателей), вы получите новую обновленную страницу.
Мне действительно это нравится. Нетрудно заметить, что двойное написание гиперссылки не приводит к появлению двух веб-сайтов (точно так же, как int *a = b не создает две копии *b).
На самом деле это очень интуитивно понятно, и каждый должен понять это. Хотя есть много сценариев, в которых эта аналогия разваливается. Тем не менее, отлично подходит для быстрого знакомства. +1
Ссылка на дважды открываемую страницу обычно создает два почти полностью независимых экземпляра этой веб-страницы. Я думаю, что гиперссылка могла бы быть хорошей аналогией конструктора, но не указателя.
@ThoAppelsin Не обязательно верно, например, если вы обращаетесь к статической веб-странице html, вы обращаетесь к одному файлу на сервере.
@RespectMyAuthoritah По-прежнему верно. Ваш компьютер загружает информацию, необходимую для создания этой веб-страницы, со своего сервера, а затем ваш браузер творит чудеса. Вы получите полную копию всего необходимого. Подумайте об этом так: даже если вы потеряете соединение, как только вы откроете эту веб-страницу, вы не потеряете ее, пока не избавитесь от нее. Эта аналогия почти полностью ложна; и это скорее хорошая аналогия того, как, например, когда сама структура передается в качестве аргумента функции, функция получает полную копию в свой переменный параметр.
Вы слишком много думаете об этом. Гиперссылки указывают на файлы на сервере, в этом и заключается аналогия.
Чтобы еще больше запутать ситуацию, иногда приходится работать с дескрипторами вместо указателей. Дескрипторы - это указатели на указатели, так что серверная часть может перемещать объекты в памяти для дефрагментации кучи. Если указатель изменяется в середине процедуры, результаты непредсказуемы, поэтому сначала нужно заблокировать дескриптор, чтобы убедиться, что ничего никуда не денется.
http://arjay.bc.ca/Modula-2/Text/Ch15/Ch15.8.html#15.8.5 говорит об этом немного более связно, чем я. :-)
-1: дескрипторы не являются указателями на указатели; они не являются указателями ни в каком смысле. Не путайте их.
«Они ни в коем случае не указатели» - хм, я позволю себе не согласиться.
Указатель - это ячейка памяти. Дескриптор - это любой уникальный идентификатор. Это может быть указатель, но с тем же успехом это может быть индекс в массиве или что-то еще в этом отношении. Ссылка, которую вы дали, - это просто особый случай, когда дескриптор является указателем, но это не обязательно. См. Также parashift.com/c++-faq-lite/references.html#faq-8.8
Эта ссылка также не поддерживает ваше утверждение о том, что они не являются указателями ни в каком смысле: «Например, указатели могут быть Fred **, а указатели Fred *, указывающие на ...» Я не думаю -1 был справедливым.
Я думаю, что то, что делает указатели сложными для изучения, заключается в том, что до указателей вы будете довольны идеей, что «в этой ячейке памяти находится набор битов, представляющих int, double, символ, что угодно».
Когда вы впервые видите указатель, вы действительно не понимаете, что находится в этой ячейке памяти. "Что ты имеешь в виду, у него адрес?"
Я не согласен с утверждением, что «вы их либо получаете, либо нет».
Их становится легче понять, когда вы начинаете находить для них реальное применение (например, не передавать большие структуры в функции).
Номер почтового ящика.
Это часть информации, которая позволяет вам получить доступ к чему-то еще.
(И если вы выполняете арифметические действия с номерами почтовых ящиков, у вас может возникнуть проблема, потому что буква попадает в неправильный ящик. А если кто-то переходит в другое состояние - без адреса пересылки - то у вас есть свисающий указатель. с другой стороны - если почта пересылает почту, значит, у вас есть указатель на указатель.)
Проблема с указателями не в концепции. Дело в исполнении и языке. Дополнительная путаница возникает, когда учителя полагают, что сложна КОНЦЕПЦИЯ указателей, а не жаргон или запутанный беспорядок, который 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" вначале и что doI собирается с ним делать. Язык не поддерживает уровни вложенности, встречающиеся на практике. Например, когда мне нужно передать «указатель» на функцию, которая создает новый буфер, и она изменяет указатель так, чтобы он указывал на новое местоположение буфера. Действительно ли ему нужен указатель или указатель на указатель, чтобы он знал, куда идти, чтобы изменить содержимое указателя. В большинстве случаев мне просто нужно угадать, что имеется в виду под «указателем», и в большинстве случаев я ошибаюсь, независимо от того, сколько опыта я имею в этом.
"Указатель" слишком перегружен. Указатель - это адрес значения? или это переменная, которая содержит адрес значения. Когда функции нужен указатель, нужен ли ей адрес, который хранится в переменной-указателе, или ей нужен адрес переменной-указателя? Я запутался.
Я видел, как это объяснялось следующим образом: если вы видите объявление указателя вроде double *(*(*fn)(int))(char), то результатом оценки *(*(*fn)(42))('x') будет double. Вы можете убрать слои оценки, чтобы понять, какими должны быть промежуточные типы.
@BerndJendrissek Не уверен, что понимаю. Каков тогда результат оценки (*(*fn)(42))('x')?
вы получаете вещь (назовем ее x), где, если вы оцениваете *x, вы получаете двойную.
@BerndJendrissek Это должно что-то объяснить об указателях? Я не понимаю. В чем твоя точка зрения? Я удалил слой и не получил новой информации о каких-либо промежуточных типах. Что в нем объясняется, что будет принимать конкретная функция? При чем тут все?
Возможно, сообщение в этом объяснении (и это не мое, я бы хотел найти то место, где я его впервые увидел) состоит в том, чтобы думать об этом меньше с точки зрения того, что fnявляется, и больше с точки зрения того, что вы можете делать с fn
чистый как грязь. Какое отношение имеет fn или что-то еще к моему исходному ответу? Пожалуйста, поверьте мне, когда я говорю, что даже немного не понимаю, что вы имеете в виду или к чему это относится, кроме как с указателями. Может быть, вы пытаетесь что-то уточнить или объяснить, я просто не могу понять, что именно. Насколько я могу понять, такая аннотация типа, как "double", означает тип своего аргумента. Но это типы. Когда вас спросили, какое отношение имеет аннотация типа к указатели, вы обрадовались. Вам придется вернуться к началу. Предположим, я ничего не знаю.
Я думаю, это может быть проблема с синтаксисом. Синтаксис C / C++ для указателей кажется непоследовательным и более сложным, чем должен быть.
По иронии судьбы, что действительно помогло мне понять указатели, так это то, что я столкнулся с концепцией итератора в C++ Стандартная библиотека шаблонов. Это иронично, потому что я могу только предположить, что итераторы были задуманы как обобщение указателя.
Иногда вы просто не можете видеть лес, пока не научитесь игнорировать деревья.
Проблема в основном в синтаксисе объявления C. Но использование указателя было бы проще, если бы (*p) был (p->), и, таким образом, у нас был бы p->->x вместо неоднозначного *p->x.
@MSalters Боже мой, ты шутишь, да? Здесь нет никаких противоречий. a->b просто означает (*a).b.
@Miles: Действительно, и по этой логике * p->x означает * ((*a).b), тогда как *p -> x означает (*(*p)) -> x. Смешивание префиксных и постфиксных операторов приводит к неоднозначному синтаксическому анализу.
@MSalters нет, потому что пробелы не имеют значения. Это все равно что сказать, что 1+2 * 3 должен быть 9.
Неплохой способ понять это с помощью итераторов ... но продолжайте искать, и вы увидите, что Александреску начнет жаловаться на них.
Многие бывшие разработчики C++ (которые никогда не понимали, что итераторы - это современные указатели до того, как сбросить код языка) переходят на C# и все еще считают, что у них есть достойные итераторы.
Хм, проблема в том, что все, что есть итераторы, совершенно не соответствуют тому, что платформы времени выполнения (Java / CLR) пытаются достичь: новое, простое использование, каждый - разработчик. Это может быть хорошо, но они сказали это однажды в пурпурной книге, и они сказали это даже до и перед C:
Косвенность.
Очень мощная концепция, но никогда не будет так, если вы будете делать это полностью. Итераторы полезны, поскольку они помогают с абстракцией алгоритмов, еще один пример. И время компиляции - это место для алгоритма, очень простого. Вы знаете код + данные или на другом языке C#:
IEnumerable + LINQ + Massive Framework = штраф во время выполнения 300 МБ за отвратительное перетаскивание приложений через кучу экземпляров ссылочных типов ..
«Ле Пойнтер дешевый».
При чем тут все?
... что вы пытаетесь сказать, кроме «статическое связывание - лучшее, что когда-либо было» и «я не понимаю, как работает что-то отличное от того, что я узнал ранее»?
Луаан, вы не могли знать, что можно узнать, разобрав JIT в 2000 году, не так ли? То, что он заканчивается в таблице переходов из таблицы указателей, как показано в 2000 году онлайн в ASM, поэтому непонимание чего-либо другого может иметь другое значение: внимательное чтение - важный навык, попробуйте еще раз.
Причина, по которой это так сложно понять, заключается не в том, что это сложная концепция, а в том, что синтаксис непоследователен.
int *mypointer;
Сначала вы узнали, что самая левая часть создания переменной определяет тип переменной. Объявление указателя работает иначе в C и C++. Вместо этого они говорят, что переменная указывает на тип слева. В этом случае: *mypointer указывает на int.
Я не полностью понимал указатели, пока не попытался использовать их в C# (с небезопасным), они работают точно так же, но с логичным и последовательным синтаксисом. Указатель - это сам тип. Здесь mypointer является указатель на int.
int* mypointer;
Даже не заставляйте меня начинать с указателей функций ...
На самом деле, оба ваших фрагмента действительны C. Это вопрос стилей C, который уже много лет используется, что первый более распространен. Второй вариант, например, немного чаще встречается в C++.
Второй фрагмент не очень хорошо работает с более сложными объявлениями. И синтаксис становится не таким «непоследовательным», как только вы понимаете, что правая часть объявления указателя показывает вам, что вы должны сделать с указателем, чтобы получить что-то, чей тип является спецификатором атомарного типа слева.
int *p; имеет простое значение: *p - целое число. int *p, **pp означает: *p и **pp - целые числа.
@MilesRout: Но проблема именно в этом. *p и **pp являются целыми числами нет, потому что вы никогда не инициализировали p, pp или *pp для указания на что-либо. Я понимаю, почему некоторые люди предпочитают придерживаться грамматики в этом, особенно потому, что некоторые крайние случаи и сложные случаи требуют от вас этого (хотя, тем не менее, вы можете тривиально обойти это во всех случаях, о которых я знаю) ... но я не думаю, что эти случаи более важны, чем тот факт, что обучение выравниванию по правому краю вводит в заблуждение новичков. Не говоря уже о том, что это некрасиво! :)
@LightnessRacesinOrbit Обучение выравниванию по правому краю далеко не вводит в заблуждение. Это единственно правильный способ обучения этому. НЕ учить этому - вводит в заблуждение.
@MilesRout: Мне жаль, что вы так думаете. По крайней мере, теперь я обнаружил, что люди на самом деле активно и гордо учат этому бедствию, а не просто то, что новички извлекают из плохо написанных «тусовщиков». Так вот что!
@LightnessRacesinOrbit Это не беда, а логично как работает! Это имеет смысл! int *foo означает, что *foo является int. int (*foo)(int, int) означает, что (*foo)(1, 2) является int.
@LightnessRacesinOrbit Точка «не целые числа, потому что вы никогда не инициализировали их» - это глупость, мы говорим об этом на уровне системы типов.
@MilesRout: Это не «глупо»: это невероятно частый источник путаницы для миллионов начинающих программистов и приводит к невероятному количеству ошибок. p - это int*, точка. Это имеет смысл. И я прошу вас найти способ изложить свою точку зрения, не прибегая к уничижительным комментариям.
@MilesRout stackoverflow.com/questions/34091142/…
@LightnessRacesinOrbit Вы подкрепляете мою точку зрения - в этом примере было непонятно, как написано int* p, а не int *p.
@MilesRout: Э, нет, OP был сбит с толку, потому что он изначально читал const Stock &p и, как следствие, неправильно его понял. И только когда увидел const Stock& p, ОП дошла до истины после некоторой помощи в переходе. Не знаете, почему вы намеренно искажаете события и надеетесь, что я этого не заметлю ?!
Одна проблема с левым выравниванием * заключается в том, что несколько переменных объявляются в одной строке, что допустимо для C и C++: int* p, q, r; может означать для неосторожных, что p, q и r являются указателями все на целые значения, хотя на самом деле, конечно, только Во-первых, два других являются целыми числами, что визуально более очевидно с int *p, q, r;. На самом деле я бы предпочел int * p, q, r; или, что более вероятно, переместил объявление указателя в отдельную строку, но, возможно, это я нахожусь в середине пути ...
Кроме того, технически находятся указывает на целочисленный тип без знака, но НЕ рекомендуется слишком глубоко вникать в двоичное представление, если только вы не кодируете компилятор, в котором вы не можете позволить себе делать предположения об их содержании - возможно, после прочтения это Q&A читатель может увидеть почему указатели (адреса) следует рассматривать как отличные от других целочисленных типов данных, даже если есть некоторые аналогичные операции, которые могут выполняться с обоими! Это апельсины и яблоки.
Я мог работать с указателями, когда знал только C++. Я как бы знал, что делать в некоторых случаях, а чего не делать методом проб / ошибок. Но то, что дало мне полное понимание, - это ассемблер. Если вы выполняете серьезную отладку на уровне инструкций с помощью написанной вами программы на языке ассемблера, вы должны понимать многие вещи.
Путаница возникает из-за того, что несколько уровней абстракции смешиваются в концепции «указателя». Программистов не смущают обычные ссылки в Java / Python, но указатели отличаются тем, что раскрывают характеристики базовой архитектуры памяти.
Четкое разделение уровней абстракции - хороший принцип, а указатели этого не делают.
Интересно то, что указатели C на самом деле не раскрывают никаких характеристик базовой архитектуры памяти. Единственные различия между ссылками на Java и указателями C заключаются в том, что у вас могут быть сложные типы, включающие указатели (например, int *** или char * ()(пустота*)), есть арифметика указателей для массивов и указатели на элементы структуры, наличие void * и двойственность массива / указателя. В остальном они работают точно так же.
Хорошая точка зрения. Это арифметика указателя и возможность переполнения буфера - выход из абстракции за счет выхода из актуальной в данный момент области памяти - вот что делает это.
@jpalecek: Довольно легко понять, как указатели работают в реализациях, которые документируют их поведение с точки зрения базовой архитектуры. Сказать foo[i] означает пойти в определенное место, продвинуться на определенное расстояние и посмотреть, что там находится. Что усложняет, так это гораздо более сложный дополнительный уровень абстракции, который был добавлен стандартом исключительно для выгоды компилятора, но моделирует вещи таким образом, который плохо подходит как для нужд программиста, так и для нужд компилятора.
Причина, по которой мне поначалу было трудно понять указатели, заключалась в том, что многие объяснения содержат много чуши о передаче по ссылке. Все это только запутывает проблему. Когда вы используете параметр указателя, вы передаете Все еще по значению; но значение оказывается адресом, а не, скажем, int.
Кто-то уже связался с этим руководством, но я могу выделить момент, когда я начал понимать указатели:
Учебник по указателям и массивам в C: Глава 3 - Указатели и строки
int puts(const char *s);
For the moment, ignore the
const.The parameter passed toputs()is a pointer, that is the value of a pointer (since all parameters in C are passed by value), and the value of a pointer is the address to which it points, or, simply, an address. Thus when we writeputs(strA);as we have seen, we are passing the address of strA[0].
В тот момент, когда я прочитал эти слова, облака разошлись, и луч солнечного света окутал меня указательным пониманием.
Даже если вы разработчик VB .NET или C# (как и я) и никогда не используете небезопасный код, все равно стоит понимать, как работают указатели, иначе вы не поймете, как работают ссылки на объекты. Тогда у вас будет распространенное, но ошибочное представление о том, что передача ссылки на объект методу копирует объект.
Мне интересно, в чем смысл указателя. В большинстве блоков кода, с которыми я сталкиваюсь, указатель виден только в его объявлении.
@ Wolfpack'08 ... что ?? Какой код вы смотрите ??
@KyleStrand Дай мне взглянуть.
Я думаю, что основным препятствием для понимания указателей являются плохие учителя.
Почти всех учат лжи об указателях: что это не более чем адреса памяти или что они позволяют указывать на произвольные местоположения.
И конечно, что они трудные для понимания, опасные и полу-магические.
Ничего из этого не соответствует действительности. Указатели на самом деле являются довольно простыми концепциями, пока вы придерживаетесь того, что о них говорит язык C++, и не наделяют их атрибутами, которые «обычно» работают на практике, но, тем не менее, не гарантируются языком и поэтому не являются частью фактической концепции указателя. .
Я попытался написать объяснение этого несколько месяцев назад в это сообщение в блоге - надеюсь, это кому-то поможет.
(Обратите внимание, прежде чем кто-либо станет педантичным по отношению ко мне, да, стандарт C++ действительно говорит, что указатели представлять адресов памяти. Но он не говорит, что «указатели - это адреса памяти, и ничего, кроме адресов памяти, и могут использоваться или рассматриваться как взаимозаменяемые с памятью. адресов ". Различие важно)
В конце концов, нулевой указатель не указывает на нулевой адрес в памяти, даже если его «значение» C равно нулю. Это совершенно отдельная концепция, и если вы разберетесь с ней неправильно, вы можете в конечном итоге адресовать (и разыменовать) то, чего не ожидали. В некоторых случаях это может быть даже нулевой адрес в памяти (особенно теперь, когда адресное пространство обычно плоское), но в других он может быть опущен как неопределенное поведение оптимизирующим компилятором или доступ к какой-либо другой части памяти, которая связана с «нулем» для данного типа указателя. Веселье следует.
Не обязательно. Вы должны уметь моделировать компьютер в своей голове, чтобы указатели имели смысл (а также для отладки других программ). Не все могут это сделать.
Каждый новичок в C / C++ сталкивается с одной и той же проблемой, и эта проблема возникает не потому, что «указатели трудно выучить», а «кто и как это объясняет». Некоторые учащиеся собирают это словесно, некоторые визуально, и лучший способ объяснить это - использовать пример "поезда" (подходит для вербального и визуального примера).
Где "паровоз" - это указатель, который не можешь содержит что-либо, а "вагон" - это то, что «локомотив» пытается тянуть (или указывать). После этого вы можете классифицировать сам «вагон», может ли он содержать животных, растения или людей (или их комбинацию).
Я подумал, что добавлю аналогию к этому списку, который я нашел очень полезным при объяснении указателей (в свое время) в качестве репетитора по информатике; сначала давайте:
Подготовить почву:
Рассмотрим стоянку на 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 |
Ваш указатель все еще указывает на красный автомобиль был, но его уже нет. Допустим, туда подъезжает новая машина ... Оранжевая машина. Теперь, если я снова спрошу вас: «Где красная машина?», Вы все еще указываете туда, но теперь вы ошибаетесь. Это не красная машина, это оранжевая.
Указатель арифметики
Итак, вы все еще указываете на второе парковочное место (сейчас занято оранжевой машиной)
1 2 3
-------------------
| o=o | o=o | o=o |
| |B| | |O| | |G| |
| o-o | o-o | o-o |
Что ж, у меня теперь новый вопрос ... Я хочу знать цвет машины на парковочном месте следующий. Вы видите, что указываете на точку 2, поэтому вы просто добавляете 1, и вы указываете на следующую точку. (finger+1), теперь, поскольку я хотел знать, какие данные были там, вы должны проверить это место (а не только палец), чтобы вы могли почитать указатель (*(finger+1)), чтобы увидеть, что там присутствует зеленый автомобиль (данные на это место)
Только не используйте слово «двойной указатель». Указатели могут указывать на что угодно, поэтому очевидно, что у вас могут быть указатели, указывающие на другие указатели. Это не двойные указатели.
Я думаю, это упускает из виду то обстоятельство, что сами «пальцы», продолжая вашу аналогию, «занимают место для парковки». Я не уверен, что у людей есть какие-либо трудности с пониманием указателей на высоком уровне абстракции вашей аналогии, это понимание того, что указатели - это изменяемые вещи, которые занимают места в памяти, и насколько это полезно, это, кажется, ускользает от людей.
@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" - вы были бы очень удивлены, сколько людей не понимают указатели даже на этом уровне
Есть ли заслуга в распространении аналогии с автомобильным пальцем на человека (с одним или несколькими пальцами - и генетической аномалией, которая может позволить каждому из них указывать в любом направлении!), Сидевшего в одной из машин, указывая на другую машину (или наклонился, указывая на пустошь рядом с участком, как «неинициализированный указатель»; или целая рука развернулась, указывая на ряд пробелов как «массив указателей фиксированного размера [5]», или свернулась в ладони «нулевой указатель» что указывает на то место, где известно, что НИКОГДА нет машины) ... 8-)
Ваше объяснение было заметным и хорошим для новичка.
В некоторых ответах выше утверждалось, что «указатели на самом деле не сложны», но не обращались напрямую там, где «указатели жесткие!» происходит от. Несколько лет назад я обучал первокурсников CS (всего один год, так как я явно отстой), и мне было ясно, что идея указателя не сложно. Что сложно понять зачем и когда вам нужен указатель.
Я не думаю, что этот вопрос - почему и когда использовать указатель - можно отделить от объяснения более широких проблем разработки программного обеспечения. Почему каждая переменная должна быть глобальной переменной нет, и почему нужно выделить аналогичный код в функции (чтобы получить это, используйте указатели, чтобы привязать их поведение к месту их вызова).
Мне нравилось объяснять это в терминах массивов и индексов - люди могут быть не знакомы с указателями, но обычно они знают, что такое индекс.
Итак, я говорю: представьте, что ОЗУ - это массив (а у вас всего 10 байт ОЗУ):
unsigned char RAM[10] = { 10, 14, 4, 3, 2, 1, 20, 19, 50, 9 };
Тогда указатель на переменную на самом деле является просто индексом (первым байтом) этой переменной в ОЗУ.
Итак, если у вас есть указатель / индекс unsigned char index = 2, то значение, очевидно, является третьим элементом или числом 4. Указатель на указатель - это то место, где вы берете это число и используете его как сам индекс, например RAM[RAM[index]].
Я бы нарисовал массив в списке бумаги и просто использовал бы его, чтобы показать такие вещи, как множество указателей, указывающих на одну и ту же память, арифметику указателей, указатель на указатель и т. д.